O código a seguir deveria alocar setup entre as trocas de SKUS, porém, em alguns skus ele não coloca o setup e já segue com o próximo sku. Com a ajuda do GPT ele até arrumou o erro em algumas linhas de produção, mas duplicou em outras. E não consigo pegar onde está iterado errado. Como exemplo na imagem [linhas que não tem setups entre elas, estão na sequência em que aparecem na planilha original], o setup não acontece entre esses SKUS. O erro aparentemente ocorre quando a linha atual recebe um item que não é primário dela. Então quando ela se comporta como linha secundária para determinado item, o setup não ocorre. Decide compartilhar toda a lógica de alocação. Mas está identificado a parte da lógica de alocação de SETUP. Toda a ajuda é bem vida. # =========================================================== # 6. Lógica de Alocação de SKUs # =========================================================== def alocar_skus(): global producao_hora, ponteiro, ultimo_grupo, capacity_used, funcionarios_por_hora, setups_info production_summary = {} skus_nao_alocados = [] local_avisos = [] for idx, row in producao_planejada.iterrows(): sku = row["COD SAP"] # Monta a lista de linhas candidatas candidate_lines = [] if pd.notna(row["LINHA"]): candidate_lines.append(str(row["LINHA"]).strip()) if "LINHA 2" in producao_planejada.columns and pd.notna(row["LINHA 2"]): candidate_lines.append(str(row["LINHA 2"]).strip()) if "LINHA 3" in producao_planejada.columns and pd.notna(row["LINHA 3"]): candidate_lines.append(str(row["LINHA 3"]).strip()) if not candidate_lines: local_avisos.append(f"SKU {sku} sem linha definida. Ignorado.") continue candidate_lines = normalize_candidates(candidate_lines) primary_line = candidate_lines[0] secondary_lines = candidate_lines[1:] if len(candidate_lines) > 1 else [] if pd.isna(row["Tempo de Produção"]): local_avisos.append(f"SKU {sku} tem 'Tempo de Produção' ausente. SKU ignorado.") continue required_hours = int(row["Tempo de Produção"]) req_mod = float(row["QT. FUM"]) # Nova verificação: ignora SKU com volume zero prod_hora_value = float(row["PROD.HORA"]) if not pd.isna(row["PROD.HORA"]) else 0 if prod_hora_value * required_hours == 0: local_avisos.append(f"SKU {sku} tem volume zero para produzir. Ignorado.") continue grupo = row["Grupo de Setup"] if not pd.isna(row["Grupo de Setup"]) else None # ALOCAÇÃO DE SETUP setup_allocated = False if primary_line in linha_mapping: allowed = get_allowed_hours_for_machine(primary_line) try: if capacity_used[primary_line] > 0: if grupo is None or str(grupo).strip() == "": setup_time = 6 else: grupo = str(grupo).upper() if ultimo_grupo[primary_line] is None: setup_time = 6 elif grupo == ultimo_grupo[primary_line]: setup_time = 2 else: setup_time = 6 if primary_line not in setups_info: setups_info[primary_line] = {"6h_count": 0, "6h_time": 0, "2h_count": 0, "2h_time": 0} setup_done = False for _ in range(setup_time): try: data, hora = ponteiro[primary_line] while not (is_dia_util(data) and hora in allowed): data, hora = avancar_ponteiro_turnos(data, hora, allowed) except ValueError as ve: break col_index = calcular_indice_coluna_turnos(data, hora) producao_hora.iat[linha_mapping[primary_line], col_index] = "SETUP" capacity_used[primary_line] += 1 try: ponteiro[primary_line] = avancar_ponteiro_turnos(data, hora, allowed) except ValueError as ve: break setup_done = True if setup_done: if setup_time == 6: setups_info[primary_line]["6h_count"] += 1 setups_info[primary_line]["6h_time"] += 6 else: setups_info[primary_line]["2h_count"] += 1 setups_info[primary_line]["2h_time"] += 2 ultimo_grupo[primary_line] = grupo if grupo is not None else ultimo_grupo[primary_line] setup_allocated = True except ValueError as ve: local_avisos.append(f"Erro ao alocar setup na linha {primary_line} para SKU {sku}: {ve}") if not setup_allocated: for candidate in secondary_lines: if candidate in linha_mapping: allowed = get_allowed_hours_for_machine(candidate) try: if capacity_used[candidate] > 0: if grupo is None or str(grupo).strip() == "": setup_time = 6 else: grupo = str(grupo).upper() if ultimo_grupo[candidate] is None: setup_time = 6 elif grupo == ultimo_grupo[candidate]: setup_time = 2 else: setup_time = 6 if candidate not in setups_info: setups_info[candidate] = {"6h_count": 0, "6h_time": 0, "2h_count": 0, "2h_time": 0} setup_done = False for _ in range(setup_time): try: data, hora = ponteiro[candidate] while not (is_dia_util(data) and hora in allowed): data, hora = avancar_ponteiro_turnos(data, hora, allowed) except ValueError as ve: break col_index = calcular_indice_coluna_turnos(data, hora) producao_hora.iat[linha_mapping[candidate], col_index] = "SETUP" capacity_used[candidate] += 1 try: ponteiro[candidate] = avancar_ponteiro_turnos(data, hora, allowed) except ValueError as ve: break setup_done = True if setup_done: if setup_time == 6: setups_info[candidate]["6h_count"] += 1 setups_info[candidate]["6h_time"] += 6 else: setups_info[candidate]["2h_count"] += 1 setups_info[candidate]["2h_time"] += 2 ultimo_grupo[candidate] = grupo if grupo is not None else ultimo_grupo[candidate] setup_allocated = True break except ValueError as ve: local_avisos.append(f"Erro ao alocar setup na linha {candidate} para SKU {sku}: {ve}") continue if not setup_allocated: local_avisos.append(f"Não foi possível alocar setup para SKU {sku}; tentando produção mesmo assim.") # ALOCAÇÃO DE PRODUÇÃO remaining_hours = required_hours if primary_line in linha_mapping: allowed = get_allowed_hours_for_machine(primary_line) while remaining_hours > 0: try: data, hora = ponteiro[primary_line] while not (is_dia_util(data) and hora in allowed): data, hora = avancar_ponteiro_turnos(data, hora, allowed) except ValueError as ve: break key = (data, hora) factor = get_capacity_factor(hora) turno = get_shift(hora) available = shift_max[turno] * factor - funcionarios_por_hora.get(key, 0) if available >= req_mod: col_index = calcular_indice_coluna_turnos(data, hora) producao_hora.iat[linha_mapping[primary_line], col_index] = sku capacity_used[primary_line] += 1 funcionarios_por_hora[key] = funcionarios_por_hora.get(key, 0) + req_mod remaining_hours -= 1 prod_key = (primary_line, sku) if prod_key not in production_summary: production_summary[prod_key] = {"horas": 0, "quantidade": 0} production_summary[prod_key]["horas"] += 1 production_summary[prod_key]["quantidade"] += prod_hora_value try: ponteiro[primary_line] = avancar_ponteiro_turnos(data, hora, allowed) except ValueError as ve: break else: try: ponteiro[primary_line] = avancar_ponteiro_turnos(data, hora, allowed) except ValueError as ve: break for candidate in secondary_lines: if remaining_hours <= 0: break if candidate not in linha_mapping: continue allowed = get_allowed_hours_for_machine(candidate) while remaining_hours > 0: try: data, hora = ponteiro[candidate] while not (is_dia_util(data) and hora in allowed): data, hora = avancar_ponteiro_turnos(data, hora, allowed) except ValueError as ve: break key = (data, hora) factor = get_capacity_factor(hora) turno = get_shift(hora) available = shift_max[turno] * factor - funcionarios_por_hora.get(key, 0) if available >= req_mod: col_index = calcular_indice_coluna_turnos(data, hora) producao_hora.iat[linha_mapping[candidate], col_index] = sku capacity_used[candidate] += 1 funcionarios_por_hora[key] = funcionarios_por_hora.get(key, 0) + req_mod remaining_hours -= 1 prod_key = (candidate, sku) if prod_key not in production_summary: production_summary[prod_key] = {"horas": 0, "quantidade": 0} production_summary[prod_key]["horas"] += 1 production_summary[prod_key]["quantidade"] += prod_hora_value try: ponteiro[candidate] = avancar_ponteiro_turnos(data, hora, allowed) except ValueError as ve: break else: try: ponteiro[candidate] = avancar_ponteiro_turnos(data, hora, allowed) except ValueError as ve: break if capacity_used.get(candidate, 0) > 0 and grupo is not None: ultimo_grupo[candidate] = grupo if remaining_hours > 0: quant_programada = prod_hora_value * required_hours produced_qty = sum(production_summary[k]["quantidade"] for k in production_summary if k[1] == sku) skus_nao_alocados.append({ "Linha(s) Tentadas": ", ".join(candidate_lines), "SKU": sku, "Horas Requeridas": required_hours, "Horas Alocadas": required_hours - remaining_hours, "Quantidade Programada": quant_programada, "Quantidade Produzida": produced_qty, "Erro": "Capacidade insuficiente em todas as linhas alternativas." }) return producao_hora, production_summary, skus_nao_alocados, local_avisos producao_hora_atualizada, production_summary, skus_nao_alocados, avisos_ret = alocar_skus() for msg in avisos_ret: avisos_list.append(msg) # =========================================================== # Transferência: Reavaliar as linhas alternativas # =========================================================== def transfer_to_primary(): for idx, row in producao_planejada.iterrows(): sku = row["COD SAP"] if pd.isna(row["LINHA"]): continue primary_line = str(row["LINHA"]).strip() keys = [(line, s) for (line, s) in production_summary.keys() if s == sku and line != primary_line] for key in keys: allocated_hours = production_summary[key]["horas"] for _ in range(allocated_hours): available_space = line_capacity.get(primary_line, float('inf')) - capacity_used.get(primary_line, 0) if available_space <= 0: break allowed = get_allowed_hours_for_machine(primary_line) try: data, hora = ponteiro[primary_line] while not (is_dia_util(data) and hora in allowed): data, hora = avancar_ponteiro_turnos(data, hora, allowed) except ValueError: break key_slot = (data, hora) factor = get_capacity_factor(hora) turno = get_shift(hora) available = shift_max[turno] * factor - funcionarios_por_hora.get(key_slot, 0) req_mod = float(row["QT. FUM"]) if available >= req_mod: col_index = calcular_indice_coluna_turnos(data, hora) producao_hora.iat[linha_mapping[primary_line], col_index] = sku capacity_used[primary_line] += 1 funcionarios_por_hora[key_slot] = funcionarios_por_hora.get(key_slot, 0) + req_mod try: ponteiro[primary_line] = avancar_ponteiro_turnos(data, hora, allowed) except ValueError: pass production_summary.setdefault((primary_line, sku), {"horas": 0, "quantidade": 0}) production_summary[(primary_line, sku)]["horas"] += 1 production_summary[(primary_line, sku)]["quantidade"] += prod_hora_dict.get(sku, 0) production_summary[key]["horas"] -= 1 production_summary[key]["quantidade"] -= prod_hora_dict.get(sku, 0) else: break return transfer_to_primary() # =========================================================== # 7. Verificar restrição de capacidade por linha (opcional) # =========================================================== for linha, used in capacity_used.items(): if linha in line_capacity: if used > line_capacity[linha]: avisos_list.append(f"Aviso: Linha {linha} excedeu capacidade (Usado: {used} vs Cap: {line_capacity[linha]}).") Segue código do que o GPT me passou, mas ele acabou duplicando em outras linhas o setup: # ALOCAÇÃO DE SETUP setup_allocated = False setup_realizado = {} # Salva se o setup já foi feito na linha correta ultimo_sku_por_linha = {} # Salva o último SKU produzido em cada linha ultimo_sku_geral = None # Salva o último SKU globalmente para evitar setups desnecessários # Determina onde o SKU realmente será produzido final_line = None # Se a linha primária tem capacidade disponível, usa ela if primary_line in linha_mapping and capacity_used.get(primary_line, 0) > 0: final_line = primary_line # Caso contrário, usa a primeira linha secundária disponível for sec_line in secondary_lines: if sec_line in linha_mapping and capacity_used.get(sec_line, 0) > 0: final_line = sec_line break # Para no primeiro secundário disponível # Se nenhuma linha foi encontrada, não faz setup if not final_line: print(f"SKU {sku} não foi alocado em nenhuma linha válida.") else: line = final_line # Apenas a linha onde será produzido # ** Evitar setup duplicado entre primária e secundária** setup_needed = True if line in ultimo_sku_por_linha: ultimo_sku = ultimo_sku_por_linha[line] # Se o SKU anterior era o mesmo e só mudou de linha, evitar setup repetido if ultimo_sku == sku: setup_needed = False # Evitar setup duplicado em qualquer linha if setup_needed and not setup_realizado.get(line, False): allowed = get_allowed_hours_for_machine(line) try: if grupo is None or str(grupo).strip() == "": setup_time = 6 else: grupo = str(grupo).upper() ultimo_grupo_line = ultimo_grupo.get(line) # Regra de setup baseado no último grupo da linha if ultimo_grupo_line is None: setup_time = 6 elif grupo == ultimo_grupo_line: setup_time = 2 else: setup_time = 6 print(f"Setup time calculado para SKU {sku} na linha {line}: {setup_time} horas") # Inicializa informações de setup se necessário if line not in setups_info: setups_info[line] = {"6h_count": 0, "6h_time": 0, "2h_count": 0, "2h_time": 0} setup_done = False for _ in range(setup_time): try: data, hora = ponteiro.get(line, (None, None)) if data is None or hora is None: break while not (is_dia_util(data) and hora in allowed): data, hora = avancar_ponteiro_turnos(data, hora, allowed) except ValueError: break col_index = calcular_indice_coluna_turnos(data, hora) # Garante que não sobrescreva SETUP anterior e evita duplicação if producao_hora.iat[linha_mapping[line], col_index] != "SETUP": producao_hora.iat[linha_mapping[line], col_index] = "SETUP" capacity_used[line] += 1 setup_realizado[line] = True # Marca que essa linha já teve setup try: ponteiro[line] = avancar_ponteiro_turnos(data, hora, allowed) except ValueError: break setup_done = True # Atualiza contadores de setup if setup_done: if setup_time == 6: setups_info[line]["6h_count"] += 1 setups_info[line]["6h_time"] += 6 else: setups_info[line]["2h_count"] += 1 setups_info[line]["2h_time"] += 2 # Só atualiza o último grupo se o setup foi feito if setup_done: ultimo_grupo[line] = grupo setup_allocated = True except ValueError as ve: local_avisos.append(f"Erro ao alocar setup na linha {line} para SKU {sku}: {ve}") # Atualiza o último SKU rodado na linha para evitar setup duplicado ultimo_sku_por_linha[line] = sku ultimo_sku_geral = sku # Salva o último SKU globalmente print(f"SKU {sku} - Linha {line} - Grupo {grupo} - Último Grupo {ultimo_grupo.get(line)}") print(f"Capacidade Usada: {capacity_used.get(line, 0)}") Continue reading...