| |
| """Classificacao v4: media por dia ativo (ignora dias parados). |
| |
| Filosofia: classificar pelo que a escavadeira faz QUANDO esta operando. |
| - avg_dia_ativo = total_viagens / dias_com_alguma_viagem |
| - max_dia = melhor dia do periodo |
| - Producao = avg_dia_ativo >= 80 E max_dia >= 120 |
| - Marginal = avg_dia_ativo >= 40 E max_dia >= 60 |
| - Infra = resto |
| |
| Assim escavadeiras em corretiva longa nao sao penalizadas pela |
| quantidade de dias parados - o que importa e a performance quando |
| estao realmente produzindo. |
| """ |
| import duckdb, json |
| from datetime import datetime |
| from collections import defaultdict |
|
|
| DB = "/Users/gabrielsapucaia/Code/cco-apn/f2m_local.duckdb" |
| OUT = "/Users/gabrielsapucaia/Code/cco-apn/dashboard_apn/equipment_categories.json" |
|
|
| JANELA_DIAS = 21 |
| MIN_DIAS_ATIVOS = 2 |
| PROD_AVG_ATIVO = 80 |
| PROD_MAX_DIA = 120 |
| MARG_AVG_ATIVO = 40 |
| MARG_MAX_DIA = 60 |
|
|
| c = duckdb.connect(DB, read_only=True) |
|
|
| tables = [r[0] for r in c.execute("SHOW TABLES").fetchall()] |
| pivots = sorted([t for t in tables if t.startswith("pivot_transporte_") and "20" not in t])[-3:] |
| parts = (["current_transporte"] if "current_transporte" in tables else []) + pivots |
| union = " UNION ALL ".join(f'SELECT * FROM "{t}"' for t in parts) |
|
|
| rows = c.execute(f""" |
| SELECT TRIM(carga) as eq, |
| CAST(production_date AS VARCHAR)[:10] as dia, |
| COUNT(*) n |
| FROM ({union}) |
| WHERE carga IS NOT NULL |
| AND production_date >= CAST(NOW() - INTERVAL {JANELA_DIAS} DAY AS VARCHAR)[:10] |
| GROUP BY 1, 2 |
| """).fetchall() |
|
|
| by_eq = defaultdict(lambda: {"dias": 0, "total": 0, "max_dia": 0, "dias_50": 0, "dias_100": 0}) |
| for eq, dia, n in rows: |
| if not eq or eq == "-": |
| continue |
| by_eq[eq]["dias"] += 1 |
| by_eq[eq]["total"] += n |
| if n > by_eq[eq]["max_dia"]: |
| by_eq[eq]["max_dia"] = n |
| if n >= 50: |
| by_eq[eq]["dias_50"] += 1 |
| if n >= 100: |
| by_eq[eq]["dias_100"] += 1 |
|
|
| all_tipos = dict(c.execute(""" |
| SELECT DISTINCT equipamento, tipo_equipamento FROM current_registro_estados |
| WHERE equipamento IS NOT NULL |
| """).fetchall()) |
|
|
| candidatos = set(by_eq.keys()) |
| for eq, tipo in all_tipos.items(): |
| if tipo in ("Escavadeira", "Pá Carregadeira"): |
| candidatos.add(eq) |
|
|
| c.close() |
|
|
| producao = set() |
| producao_marginal = set() |
| infra = set() |
|
|
| for eq in candidatos: |
| s = by_eq.get(eq, {"dias": 0, "total": 0, "max_dia": 0, "dias_50": 0, "dias_100": 0}) |
| dias = s["dias"] |
| total = s["total"] |
| avg_ativo = total / dias if dias else 0 |
| max_dia = s["max_dia"] |
|
|
| if dias >= MIN_DIAS_ATIVOS and avg_ativo >= PROD_AVG_ATIVO and max_dia >= PROD_MAX_DIA: |
| producao.add(eq) |
| elif dias >= MIN_DIAS_ATIVOS and avg_ativo >= MARG_AVG_ATIVO and max_dia >= MARG_MAX_DIA: |
| producao_marginal.add(eq) |
| else: |
| infra.add(eq) |
|
|
| |
| detalhes = [] |
| for eq in candidatos: |
| s = by_eq.get(eq, {"dias": 0, "total": 0, "max_dia": 0, "dias_50": 0, "dias_100": 0}) |
| dias = s["dias"] |
| total = s["total"] |
| avg_ativo = round(total / dias, 1) if dias else 0 |
| detalhes.append({ |
| "equip": eq, |
| "tipo": all_tipos.get(eq, "?"), |
| "dias_ativos": dias, |
| "total": total, |
| "avg_dia_ativo": avg_ativo, |
| "max_dia": s["max_dia"], |
| "dias_com_50plus": s["dias_50"], |
| "dias_com_100plus": s["dias_100"], |
| "categoria": "producao" if eq in producao else ("producao_marginal" if eq in producao_marginal else "infra"), |
| }) |
| detalhes.sort(key=lambda x: (-x["avg_dia_ativo"], -x["total"])) |
|
|
| result = { |
| "_description": ( |
| f"Classificacao v4 (janela {JANELA_DIAS}d, media por dia ativo): " |
| f"producao = avg_dia_ativo >= {PROD_AVG_ATIVO} E max_dia >= {PROD_MAX_DIA}, " |
| f"marginal >= {MARG_AVG_ATIVO}/{MARG_MAX_DIA}. " |
| "Ignora dias parados - mede performance quando operando." |
| ), |
| "_thresholds": { |
| "janela_dias": JANELA_DIAS, |
| "min_dias_ativos": MIN_DIAS_ATIVOS, |
| "producao_avg_dia_ativo": PROD_AVG_ATIVO, |
| "producao_max_dia": PROD_MAX_DIA, |
| "marginal_avg_dia_ativo": MARG_AVG_ATIVO, |
| "marginal_max_dia": MARG_MAX_DIA, |
| }, |
| "_generated_at": datetime.now().isoformat(), |
| "producao": { |
| "escavadeiras": sorted(e for e in producao if all_tipos.get(e) == "Escavadeira"), |
| "carregadeiras": sorted(e for e in producao if all_tipos.get(e) == "Pá Carregadeira"), |
| "outros_producao": sorted(e for e in producao if all_tipos.get(e) not in ("Escavadeira", "Pá Carregadeira")), |
| }, |
| "producao_marginal": { |
| "escavadeiras": sorted(e for e in producao_marginal if all_tipos.get(e) == "Escavadeira"), |
| "carregadeiras": sorted(e for e in producao_marginal if all_tipos.get(e) == "Pá Carregadeira"), |
| }, |
| "infra": { |
| "escavadeiras": sorted(e for e in infra if all_tipos.get(e) == "Escavadeira"), |
| "carregadeiras": sorted(e for e in infra if all_tipos.get(e) == "Pá Carregadeira"), |
| }, |
| "_detalhes": detalhes[:25], |
| } |
|
|
| with open(OUT, "w") as f: |
| json.dump(result, f, indent=2, ensure_ascii=False) |
|
|
| print(f"=== Classificacao v4 (media por dia ATIVO, janela {JANELA_DIAS}d) ===\n") |
| print(f"PRODUCAO ({len(result['producao']['escavadeiras'])}): {result['producao']['escavadeiras']}") |
| print(f"MARGINAL ({len(result['producao_marginal']['escavadeiras'])}): {result['producao_marginal']['escavadeiras']}") |
| print(f"INFRA ({len(result['infra']['escavadeiras'])}): {result['infra']['escavadeiras']}") |
| print() |
| print("DETALHE (so escavadeiras):") |
| print(f" {'equip':<10} {'tipo':<16} {'dias_at':<8} {'total':<7} {'avg_at':<8} {'max':<5} {'50+':<4} {'100+':<5} categoria") |
| for d in detalhes: |
| if d["tipo"] == "Escavadeira" or d["total"] > 50: |
| print(f" {d['equip']:<10} {d['tipo'][:14]:<16} {d['dias_ativos']:<8} {d['total']:<7} {d['avg_dia_ativo']:<8} {d['max_dia']:<5} {d['dias_com_50plus']:<4} {d['dias_com_100plus']:<5} {d['categoria']}") |
|
|