From a366427dafff7f83b1e4acc4d32d578aab463e64 Mon Sep 17 00:00:00 2001 From: Admin MPCZ Date: Tue, 14 Apr 2026 19:05:08 +0200 Subject: [PATCH] Fix: iTop exporte 2 colonnes Etat (lifecycle+condition), prendre la 1ere Python csv.DictReader ne garde que le dernier quand 2 colonnes ont le meme nom: le Nouveau (condition) ecrasait le Production (lifecycle). Switch vers csv.reader + lecture par indice de colonne (1ere occurrence de Etat = lifecycle). --- tools/import_etat_itop.py | 28 +++++++++++++++++++++------- tools/list_csv_etats.py | 16 ++++++++++------ 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/tools/import_etat_itop.py b/tools/import_etat_itop.py index 893ab85..779253b 100644 --- a/tools/import_etat_itop.py +++ b/tools/import_etat_itop.py @@ -110,15 +110,29 @@ def main(): with open(csv_path, "r", encoding="utf-8-sig", newline="") as f: sample = f.read(4096); f.seek(0) delim = ";" if sample.count(";") > sample.count(",") else "," - rows = list(csv.DictReader(f, delimiter=delim)) - print(f"[INFO] {len(rows)} lignes (delim={delim!r})") - for r in rows: - hostname = (r.get("Nom") or r.get("Hostname") or "").strip() + reader = csv.reader(f, delimiter=delim) + header = next(reader) + # iTop exporte parfois 2 colonnes "Etat" (lifecycle + condition physique). + # On prend la PREMIERE occurrence = lifecycle. + idx_etat = next((i for i, h in enumerate(header) if h in ("Etat", "État")), -1) + idx_nom = next((i for i, h in enumerate(header) if h == "Nom"), -1) + idx_hostname = next((i for i, h in enumerate(header) if h == "Hostname"), -1) + rows = list(reader) + print(f"[INFO] {len(rows)} lignes (delim={delim!r}, idx_etat={idx_etat})") + if idx_etat == -1: + print("[WARN] Colonne Etat absente, skip.") + continue + for row in rows: + if idx_nom >= 0 and idx_nom < len(row): + hostname = row[idx_nom].strip() + elif idx_hostname >= 0 and idx_hostname < len(row): + hostname = row[idx_hostname].strip() + else: + hostname = "" if not hostname or not any(c.isalpha() for c in hostname): continue - # Etat d'abord (lifecycle VM). Si valeur "condition" -> NULL. - # Status ignore (toujours 'Production' cote iTop, pas pertinent). - raw = (r.get("Etat") or r.get("État") or "").strip() + # Lifecycle = 1ere colonne Etat (l'iTop export peut avoir 2 "Etat") + raw = row[idx_etat].strip() if idx_etat < len(row) else "" new_etat = norm_etat(raw) # Cas: valeur condition iTop (Nouveau, Recycle, ...) -> NULL (physique non deploye) is_condition = raw in ("Nouveau", "Recyclé", "A récupérer", "Cassé", diff --git a/tools/list_csv_etats.py b/tools/list_csv_etats.py index 1ae3841..b40c606 100644 --- a/tools/list_csv_etats.py +++ b/tools/list_csv_etats.py @@ -18,15 +18,19 @@ def main(): with open(path, "r", encoding="utf-8-sig", newline="") as f: sample = f.read(4096); f.seek(0) delim = ";" if sample.count(";") > sample.count(",") else "," - reader = csv.DictReader(f, delimiter=delim) + reader = csv.reader(f, delimiter=delim) + header = next(reader) rows = list(reader) print(f"{len(rows)} lignes (delim={delim!r})") - print(f"Colonnes: {list(rows[0].keys()) if rows else []}") - for col in ("Etat", "État", "Status", "Environnement"): - if rows and col in rows[0]: - c = Counter((r.get(col) or "").strip() for r in rows) - print(f"\n[{col}]") + # iTop peut avoir 2 colonnes "Etat" (lifecycle + condition) - les compter separement + cols_to_check = ["Etat", "État", "Status", "Environnement"] + for col in cols_to_check: + indices = [i for i, h in enumerate(header) if h == col] + for rank, idx in enumerate(indices, 1): + suffix = f" (#{rank})" if len(indices) > 1 else "" + c = Counter((row[idx].strip() if idx < len(row) else "") for row in rows) + print(f"\n[{col}{suffix} col index={idx}]") for v, n in c.most_common(): print(f" {v!r:30s} {n}")