Stéphan Peccini 6d2e877341
feat(audit): audit qualité complet — 907→0 erreurs ruff + fix multiselect labels
- Correction des 907 erreurs ruff (pathlib, imports, nommage, simplifications, docstrings)
- Fix déduplication labels dans multiselect nœuds d'arrivée (analyse)
- Expansion 1→N label→IDs pour le Sankey (Pays d'opération)
- Ajout CLAUDE.md et document de design de l'audit
- Mise à jour .gitignore (artefacts tests exploratoires)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 11:52:01 +01:00

105 lines
3.6 KiB
Python

# ics.py
import re
import textwrap
import unicodedata
import pandas as pd
import yaml
PAIR_RE = re.compile(r"```yaml[^\n]*\n(.*?)```", re.S | re.I)
def _normalize_unicode(text: str) -> str:
return unicodedata.normalize("NFKC", text)
def _pairs_dataframe(md: str) -> pd.DataFrame:
rows = []
for raw in PAIR_RE.findall(md):
bloc = yaml.safe_load(raw)
if isinstance(bloc, dict) and "pair" in bloc:
rows.append(bloc["pair"])
return pd.DataFrame(rows)
def _fill(segment: str, pair: dict) -> str:
segment = _normalize_unicode(segment)
for k, v in pair.items():
val = f"{v:.2f}" if isinstance(v, int | float) else str(v)
segment = re.sub(
rf"{{{{\s*{re.escape(k)}\s*}}}}",
val,
segment,
flags=re.I,
)
return re.sub(
r"ICS\s*=\s*[-+]?\d+(?:\.\d+)?",
f"ICS = {pair['ics']:.2f}",
segment,
count=1,
)
def _segments(md: str):
blocs = list(PAIR_RE.finditer(md))
for i, match in enumerate(blocs):
pair = yaml.safe_load(match.group(1))["pair"]
start = match.end()
end = blocs[i + 1].start() if i + 1 < len(blocs) else len(md)
segment = md[start:end]
yield pair, segment
def _pivot(df: pd.DataFrame) -> str:
out = []
for min_, g in df.groupby("minerai"):
out += [f"## {min_}",
"| Composant | ICS | Faisabilité technique | Délai d'implémentation | Impact économique |",
"| :-- | :--: | :--: | :--: | :--: |"]
for _, r in g.sort_values("ics", ascending=False).iterrows():
out += [f"| {r.composant} | {r.ics:.2f} | {r.f_tech:.2f} | "
f"{r.delai:.2f} | {r.cout:.2f} |"]
out.append("")
return "\n".join(out)
def _synth(df: pd.DataFrame) -> str:
lignes = ["| Composant | Minerai | ICS |", "| :-- | :-- | :--: |"]
for _, r in df.sort_values("ics", ascending=False).iterrows():
lignes.append(f"| {r.composant} | {r.minerai} | {r.ics:.2f} |")
return "\n".join(lignes)
def build_dynamic_sections(md_raw: str) -> str:
"""Procédure pour construire et remplacer les sections dynamiques dans les fiches d'analyse produit (ICS).
Cette fonction permet de :
1. Extraire les données structurées en YAML des blocs du markdown.
2. Générer un tableau pivotant les données sur la criticité et faisabilité technique.
3. Produire une synthèse finale avec l'analyse critique par composant.
Args:
md_raw (str): Contenu brut du fichier Markdown contenant les structures YAML à analyser.
Returns:
str: Le markdown enrichi des tableaux de donnée analysés, ou le contenu original inchangé si aucun bloc structuré n'est trouvé.
"""
md_raw = _normalize_unicode(md_raw)
df = _pairs_dataframe(md_raw)
if df.empty:
return md_raw
couples = ["# Criticité par couple Composant -> Minerai"]
for pair, seg in _segments(md_raw):
if pair:
couples.append(_fill(seg, pair))
couples_md = "\n".join(couples)
pivot_md = _pivot(df)
synth_md = _synth(df)
md = re.sub(r"#\s+Criticité par couple.*", couples_md, md_raw, flags=re.S | re.I)
md = re.sub(r"<!---- AUTO-BEGIN:PIVOT -->.*?<!---- AUTO-END:PIVOT -->",
f"<!---- AUTO-BEGIN:PIVOT -->\n{pivot_md}\n<!---- AUTO-END:PIVOT -->",
md, flags=re.S)
md = re.sub(r"<!---- AUTO-BEGIN:TABLEAU-FINAL -->.*?<!---- AUTO-END:TABLEAU-FINAL -->",
f"<!---- AUTO-BEGIN:TABLEAU-FINAL -->\n{synth_md}\n<!---- AUTO-END:TABLEAU-FINAL -->",
md, flags=re.S)
return textwrap.dedent(md)