106 lines
3.6 KiB
Python
106 lines
3.6 KiB
Python
# 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"<!---- 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)
|