# ics.py import re import yaml import pandas as pd import unicodedata import textwrap 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, ) segment = re.sub( r"ICS\s*=\s*[-+]?\d+(?:\.\d+)?", f"ICS = {pair['ics']:.2f}", segment, count=1, ) return segment 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 (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".*?", f"\n{pivot_md}\n", md, flags=re.S) md = re.sub(r".*?", f"\n{synth_md}\n", md, flags=re.S) return textwrap.dedent(md)