[Python] Erro ao alocar Setup [fechada]

Discussão em 'Python' iniciado por Stack, Março 17, 2025.

  1. Stack

    Stack Membro Participativo

    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...

Compartilhe esta Página