2025-06-11 14:57:53 +02:00

587 lines
25 KiB
Python

import streamlit as st
import re
import yaml
def _build_extraction_tableau(md: str, produit: str) -> str:
"""Génère le tableau d'extraction pour les fiches de minerai."""
# Identifier la section d'extraction
extraction_pattern = rf"Extraction_{re.escape(produit)}"
extraction_match = re.search(f"{extraction_pattern}:", md)
if not extraction_match:
return md # Pas de section d'extraction trouvée
# Récupérer les données structurées
extraction_data = {}
# Rechercher tous les pays et leurs acteurs
pays_pattern = rf"(\w+)_{extraction_pattern}:\s+nom_du_pays:\s+([^\n]+)\s+part_de_marche:\s+([^\n]+)"
for pays_match in re.finditer(pays_pattern, md):
code_pays, nom_pays, part_pays = pays_match.groups()
try:
part_marche_num = float(part_pays.strip().rstrip('%'))
except ValueError:
part_marche_num = 0
extraction_data[code_pays] = {
'nom': nom_pays.strip(),
'part_marche': part_pays.strip(),
'part_marche_num': part_marche_num,
'acteurs': []
}
# Rechercher tous les acteurs pour ce pays
acteur_pattern = rf"(\w+)_{code_pays}_{extraction_pattern}:\s+nom_de_l_acteur:\s+([^\n]+)\s+part_de_marche:\s+([^\n]+)\s+pays_d_origine:\s+([^\n]+)"
for acteur_match in re.finditer(acteur_pattern, md):
code_acteur, nom_acteur, part_acteur, pays_origine = acteur_match.groups()
try:
part_acteur_num = float(part_acteur.strip().rstrip('%'))
except ValueError:
part_acteur_num = 0
extraction_data[code_pays]['acteurs'].append({
'nom': nom_acteur.strip(),
'part_marche': part_acteur.strip(),
'part_marche_num': part_acteur_num,
'pays_origine': pays_origine.strip()
})
# Préparer les données pour l'affichage
pays_data = []
for code_pays, pays_info in extraction_data.items():
# Trier les acteurs par part de marché décroissante
acteurs_tries = sorted(pays_info['acteurs'], key=lambda x: x['part_marche_num'], reverse=True)
pays_data.append({
'nom': pays_info['nom'],
'part_marche': pays_info['part_marche'],
'part_marche_num': pays_info['part_marche_num'],
'acteurs': acteurs_tries
})
# Trier les pays par part de marché décroissante
pays_tries = sorted(pays_data, key=lambda x: x['part_marche_num'], reverse=True)
# Générer le tableau des producteurs
lignes_tableau = [
"| **Pays d'implantation** | **Entreprise** | **Pays d'origine** | **Part de marché** |",
"| :-- | :-- | :-- | :-- |"
]
for pays in pays_tries:
for acteur in pays['acteurs']:
# Formater la part de marché (ajouter un espace avant %)
part_marche_formattee = acteur['part_marche'].strip().replace('%', ' %')
lignes_tableau.append(
f"| {pays['nom']} | {acteur['nom']} | {acteur['pays_origine']} | {part_marche_formattee} |"
)
# Ajouter la ligne de total pour le pays (en gras)
part_marche_pays_formattee = pays['part_marche'].strip().replace('%', ' %')
lignes_tableau.append(
f"| **{pays['nom']}** | **Total** | **{pays['nom']}** | **{part_marche_pays_formattee}** |"
)
# Construire le tableau final
tableau_final = "\n".join(lignes_tableau)
# Remplacer la section du tableau dans la fiche
md_modifie = re.sub(
r"<!---- AUTO-BEGIN:TABLEAU-EXTRACTION -->.*?<!---- AUTO-END:TABLEAU-EXTRACTION -->",
f"<!---- AUTO-BEGIN:TABLEAU-EXTRACTION -->\n{tableau_final}\n<!---- AUTO-END:TABLEAU-EXTRACTION -->",
md,
flags=re.DOTALL
)
return md_modifie
def _build_traitement_tableau(md: str, produit: str) -> str:
"""Génère le tableau de traitement pour les fiches de minerai."""
# Identifier la section de traitement
traitement_pattern = rf"Traitement_{re.escape(produit)}"
traitement_match = re.search(f"{traitement_pattern}:", md)
if not traitement_match:
return md # Pas de section de traitement trouvée
# Récupérer les données structurées
traitement_data = {}
# Rechercher tous les pays et leurs acteurs
pays_pattern = rf"(\w+)_{traitement_pattern}:\s+nom_du_pays:\s+([^\n]+)\s+part_de_marche:\s+([^\n]+)"
for pays_match in re.finditer(pays_pattern, md):
code_pays, nom_pays, part_pays = pays_match.groups()
try:
part_marche_num = float(part_pays.strip().rstrip('%'))
except ValueError:
part_marche_num = 0
traitement_data[code_pays] = {
'nom': nom_pays.strip(),
'part_marche': part_pays.strip(),
'part_marche_num': part_marche_num,
'acteurs': []
}
# Rechercher tous les acteurs pour ce pays
acteur_pattern = rf"(\w+)_{code_pays}_{traitement_pattern}:"
for acteur_match in re.finditer(acteur_pattern, md):
code_acteur = acteur_match.group(1)
# Récupérer les informations de l'acteur
nom_acteur_match = re.search(rf"{code_acteur}_{code_pays}_{traitement_pattern}:\s+nom_de_l_acteur:\s+([^\n]+)", md)
part_acteur_match = re.search(rf"{code_acteur}_{code_pays}_{traitement_pattern}:.*?part_de_marche:\s+([^\n]+)", md, re.DOTALL)
pays_origine_match = re.search(rf"{code_acteur}_{code_pays}_{traitement_pattern}:.*?pays_d_origine:\s+([^\n]+)", md, re.DOTALL)
if nom_acteur_match and part_acteur_match and pays_origine_match:
nom_acteur = nom_acteur_match.group(1).strip()
part_acteur = part_acteur_match.group(1).strip()
pays_origine = pays_origine_match.group(1).strip()
try:
part_acteur_num = float(part_acteur.strip().rstrip('%'))
except ValueError:
part_acteur_num = 0
# Récupérer les origines du minerai
origines_minerai = []
for i in range(1, 10): # Vérifier jusqu'à 9 origines possibles
origine_key = "minerai_origine" if i == 1 else f"minerai_origine_{i}"
origine_pattern = rf"{code_acteur}_{code_pays}_{traitement_pattern}:.*?{origine_key}:\s+pays:\s+([^\n]+)\s+pourcentage:\s+([^\n]+)"
origine_match = re.search(origine_pattern, md, re.DOTALL)
if origine_match:
pays_origine_minerai = origine_match.group(1).strip()
pourcentage_origine = origine_match.group(2).strip()
origines_minerai.append(f"{pays_origine_minerai} ({pourcentage_origine})")
else:
break
origine_text = ", ".join(origines_minerai) if origines_minerai else ""
traitement_data[code_pays]['acteurs'].append({
'nom': nom_acteur,
'part_marche': part_acteur,
'part_marche_num': part_acteur_num,
'pays_origine': pays_origine,
'origine_minerai': origine_text
})
# Préparer les données pour l'affichage
pays_data = []
for code_pays, pays_info in traitement_data.items():
# Trier les acteurs par part de marché décroissante
acteurs_tries = sorted(pays_info['acteurs'], key=lambda x: x['part_marche_num'], reverse=True)
pays_data.append({
'nom': pays_info['nom'],
'part_marche': pays_info['part_marche'],
'part_marche_num': pays_info['part_marche_num'],
'acteurs': acteurs_tries
})
# Trier les pays par part de marché décroissante
pays_tries = sorted(pays_data, key=lambda x: x['part_marche_num'], reverse=True)
# Générer le tableau des producteurs
lignes_tableau = [
"| **Pays d'implantation** | **Entreprise** | **Pays d'origine** | **Origines du minerai** | **Part de marché** |",
"| :-- | :-- | :-- | :-- | :-- |"
]
for pays in pays_tries:
for acteur in pays['acteurs']:
# Formater la part de marché (ajouter un espace avant %)
part_marche_formattee = acteur['part_marche'].strip().replace('%', ' %')
lignes_tableau.append(
f"| {pays['nom']} | {acteur['nom']} | {acteur['pays_origine']} | {acteur['origine_minerai']} | {part_marche_formattee} |"
)
# Ajouter la ligne de total pour le pays (en gras)
part_marche_pays_formattee = pays['part_marche'].strip().replace('%', ' %')
lignes_tableau.append(
f"| **{pays['nom']}** | **Total** | **{pays['nom']}** | **-** | **{part_marche_pays_formattee}** |"
)
# Construire le tableau final
tableau_final = "\n".join(lignes_tableau)
# Remplacer la section du tableau dans la fiche
md_modifie = re.sub(
r"<!---- AUTO-BEGIN:TABLEAU-TRAITEMENT -->.*?<!---- AUTO-END:TABLEAU-TRAITEMENT -->",
f"<!---- AUTO-BEGIN:TABLEAU-TRAITEMENT -->\n{tableau_final}\n<!---- AUTO-END:TABLEAU-TRAITEMENT -->",
md,
flags=re.DOTALL
)
return md_modifie
def _build_reserves_tableau(md: str, produit: str) -> str:
"""Génère le tableau des réserves pour les fiches de minerai."""
# Identifier la section des réserves
reserves_pattern = rf"Reserves_{re.escape(produit)}"
reserves_match = re.search(f"{reserves_pattern}:", md)
if not reserves_match:
return md # Pas de section de réserves trouvée
# Récupérer les données structurées
reserves_data = []
# Rechercher tous les pays et leurs parts de marché
pays_pattern = rf"(\w+)_{reserves_pattern}:\s+nom_du_pays:\s+([^\n]+)\s+part_de_marche:\s+([^\n]+)"
for pays_match in re.finditer(pays_pattern, md):
code_pays, nom_pays, part_pays = pays_match.groups()
try:
part_marche_num = float(part_pays.strip().rstrip('%'))
except ValueError:
part_marche_num = 0
reserves_data.append({
'nom': nom_pays.strip(),
'part_marche': part_pays.strip(),
'part_marche_num': part_marche_num
})
# Trier les pays par part de marché décroissante
reserves_data_triees = sorted(reserves_data, key=lambda x: x['part_marche_num'], reverse=True)
# Générer le tableau des réserves
lignes_tableau = [
"| **Pays d'implantation** | **Part de marché** |",
"| :-- | :-- |"
]
for pays in reserves_data_triees:
# Formater la part de marché (ajouter un espace avant %)
part_marche_formattee = pays['part_marche'].strip().replace('%', ' %')
lignes_tableau.append(f"| {pays['nom']} | {part_marche_formattee} |")
# Construire le tableau final
tableau_final = "\n".join(lignes_tableau)
# Remplacer la section du tableau dans la fiche
md_modifie = re.sub(
r"<!---- AUTO-BEGIN:TABLEAU-RESERVES -->.*?<!---- AUTO-END:TABLEAU-RESERVES -->",
f"<!---- AUTO-BEGIN:TABLEAU-RESERVES -->\n{tableau_final}\n<!---- AUTO-END:TABLEAU-RESERVES -->",
md,
flags=re.DOTALL
)
return md_modifie
def build_minerai_ivc_section(md: str) -> str:
"""
Ajoute les informations IVC depuis la fiche technique IVC.md pour un minerai spécifique.
"""
# Extraire le type de fiche et le produit depuis l'en-tête YAML
type_fiche = None
produit = None
front_match = re.match(r"(?s)^---\n(.*?)\n---\n", md)
if front_match:
try:
front_matter = yaml.safe_load(front_match.group(1))
type_fiche = front_matter.get("type_fiche")
produit = front_matter.get("schema")
# Vérifier si c'est bien une fiche de minerai
if type_fiche != "minerai" or not produit:
return md
except Exception as e:
st.error(f"Erreur lors du chargement du front matter: {e}")
return md
# Injecter les informations IVC depuis la fiche technique
try:
# Charger le contenu de la fiche technique IVC
ivc_path = "Fiches/Criticités/Fiche technique IVC.md"
with open(ivc_path, "r", encoding="utf-8") as f:
ivc_content = f.read()
# Chercher la section correspondant au minerai
# Le pattern cherche un titre de niveau 2 commençant par le nom du produit
section_pattern = rf"## {produit} - (.*?)(?=\n###|$)"
section_match = re.search(section_pattern, ivc_content, re.DOTALL)
if section_match:
# Extraire la partie après le nom du minerai (ex: "IVC : 4 - Vulnérabilité: Faible")
ivc_value = section_match.group(1).strip()
# Extraire toutes les sous-sections
# Le pattern cherche depuis le titre du minerai jusqu'à la prochaine section de même niveau ou fin de fichier
full_section_pattern = rf"## {produit} - .*?\n([\s\S]*?)(?=\n## |$)"
full_section_match = re.search(full_section_pattern, ivc_content)
if full_section_match:
section_content = full_section_match.group(1).strip()
# Formater le contenu à insérer
ivc_content_formatted = f"```\n{ivc_value}\n```\n\n{section_content}"
# Remplacer la section IVC dans le markdown
md = re.sub(
r"<!---- AUTO-BEGIN:SECTION-IVC-MINERAI -->.*?<!---- AUTO-END:SECTION-IVC-MINERAI -->",
f"<!---- AUTO-BEGIN:SECTION-IVC-MINERAI -->\n{ivc_content_formatted}\n<!---- AUTO-END:SECTION-IVC-MINERAI -->",
md,
flags=re.DOTALL
)
except Exception as e:
st.error(f"Erreur lors de la génération de la section IVC: {e}")
return md
def build_minerai_ics_section(md: str) -> str:
"""
Ajoute les informations ICS depuis la fiche technique ICS.md pour un minerai spécifique.
"""
# Extraire le type de fiche et le produit depuis l'en-tête YAML
type_fiche = None
produit = None
front_match = re.match(r"(?s)^---\n(.*?)\n---\n", md)
if front_match:
try:
front_matter = yaml.safe_load(front_match.group(1))
type_fiche = front_matter.get("type_fiche")
produit = front_matter.get("schema")
# Vérifier si c'est bien une fiche de minerai
if type_fiche != "minerai" or not produit:
return md
except Exception as e:
st.error(f"Erreur lors du chargement du front matter: {e}")
return md
# Injecter les informations ICS depuis la fiche technique
try:
# Charger le contenu de la fiche technique ICS
ics_path = "Fiches/Criticités/Fiche technique ICS.md"
with open(ics_path, "r", encoding="utf-8") as f:
ics_content = f.read()
# Extraire la section ICS pour le minerai
# Dans le fichier ICS, on recherche dans la section "# Criticité par minerai"
# puis on cherche la sous-section correspondant au minerai
minerai_section_pattern = r"# Criticité par minerai\s*\n([\s\S]*?)(?=\n# |$)"
minerai_section_match = re.search(minerai_section_pattern, ics_content)
if minerai_section_match:
minerai_section = minerai_section_match.group(1)
# Chercher le minerai spécifique
# Rechercher un titre de niveau 2 correspondant exactement au produit
specific_minerai_pattern = rf"## {produit}\s*\n([\s\S]*?)(?=\n## |$)"
specific_minerai_match = re.search(specific_minerai_pattern, minerai_section)
if specific_minerai_match:
# Extraire le contenu de la section du minerai
minerai_content = specific_minerai_match.group(1).strip()
# Remplacer la section ICS dans le markdown
md = re.sub(
r"<!---- AUTO-BEGIN:SECTION-ICS-MINERAI -->.*?<!---- AUTO-END:SECTION-ICS-MINERAI -->",
f"<!---- AUTO-BEGIN:SECTION-ICS-MINERAI -->\n{minerai_content}\n<!---- AUTO-END:SECTION-ICS-MINERAI -->",
md,
flags=re.DOTALL
)
except Exception as e:
st.error(f"Erreur lors de la génération de la section ICS: {e}")
return md
def build_minerai_ics_composant_section(md: str) -> str:
"""
Ajoute les informations ICS pour tous les composants liés à un minerai spécifique
depuis la fiche technique ICS.md, en augmentant d'un niveau les titres.
"""
# Extraire le type de fiche et le produit depuis l'en-tête YAML
type_fiche = None
produit = None
front_match = re.match(r"(?s)^---\n(.*?)\n---\n", md)
if front_match:
try:
front_matter = yaml.safe_load(front_match.group(1))
type_fiche = front_matter.get("type_fiche")
produit = front_matter.get("schema")
# Vérifier si c'est bien une fiche de minerai
if type_fiche != "minerai" or not produit:
return md
except Exception as e:
st.error(f"Erreur lors du chargement du front matter: {e}")
return md
# Injecter les informations ICS depuis la fiche technique
try:
# Charger le contenu de la fiche technique ICS
ics_path = "Fiches/Criticités/Fiche technique ICS.md"
with open(ics_path, "r", encoding="utf-8") as f:
ics_content = f.read()
# Rechercher toutes les sections de composants liés au minerai
# Le pattern cherche les titres de niveau 2 de la forme "## * -> Minerai"
composant_sections_pattern = re.compile(
rf"^## ([^>]+) -> {re.escape(produit)} - .*?\n([\s\S]*?)(?=^## |\Z)",
re.MULTILINE | re.DOTALL
)
composant_sections = re.finditer(composant_sections_pattern, ics_content)
all_composant_content = []
for match in composant_sections:
composant = match.group(1).strip()
section_content = match.group(2).strip()
# Augmenter le niveau des titres d'un cran
# Titre de niveau 2 -> niveau 3
section_title = f"### {composant} -> {produit}{match.group(0).split(produit)[1].split('\n')[0]}"
# Augmenter les niveaux des sous-titres (### -> ####)
section_content = re.sub(r"### ", "#### ", section_content)
# Ajouter à la liste des contenus
all_composant_content.append(f"{section_title}\n{section_content}")
# Combiner tous les contenus
if all_composant_content:
combined_content = "\n\n".join(all_composant_content)
# Remplacer la section ICS dans le markdown
md = re.sub(
r"<!---- AUTO-BEGIN:SECTION-ICS-COMPOSANT-MINERAI -->.*?<!---- AUTO-END:SECTION-ICS-COMPOSANT-MINERAI -->",
f"<!---- AUTO-BEGIN:SECTION-ICS-COMPOSANT-MINERAI -->\n{combined_content}\n<!---- AUTO-END:SECTION-ICS-COMPOSANT-MINERAI -->",
md,
flags=re.DOTALL
)
except Exception as e:
st.error(f"Erreur lors de la génération de la section ICS pour les composants: {e}")
return md
def build_minerai_sections(md: str) -> str:
"""Traite les fiches de minerai et génère les tableaux des producteurs."""
# Extraire le type de fiche depuis l'en-tête YAML
type_fiche = None
produit = None
front_match = re.match(r"(?s)^---\n(.*?)\n---\n", md)
if front_match:
try:
front_matter = yaml.safe_load(front_match.group(1))
type_fiche = front_matter.get("type_fiche")
produit = front_matter.get("schema") # le produit à rechercher est schema pour faire le lien avec le graphe
# Vérifier si c'est bien une fiche de minerai
if type_fiche != "minerai" or not produit:
return md
except Exception as e:
st.error(f"Erreur lors du chargement du front matter: {e}")
return md
# Traiter le tableau d'Extraction
md = _build_extraction_tableau(md, produit)
# Traiter le tableau de Traitement
md = _build_traitement_tableau(md, produit)
# Traiter le tableau des Réserves
md = _build_reserves_tableau(md, produit)
# Supprimer les blocs YAML complets, y compris les délimiteurs ```yaml et ```
# Rechercher d'abord les blocs avec délimiteurs YAML
yaml_blocks = [
rf"```yaml\s*\n{re.escape(produit)}.*?```",
rf"```yaml\s*\nExtraction_{re.escape(produit)}.*?```",
rf"```yaml\s*\nReserves_{re.escape(produit)}.*?```",
rf"```yaml\s*\nTraitement_{re.escape(produit)}.*?```"
]
for pattern in yaml_blocks:
md = re.sub(pattern, "", md, flags=re.DOTALL)
# Supprimer également les blocs qui ne seraient pas entourés de délimiteurs
patterns_to_remove = [
rf"Extraction_{re.escape(produit)}:(?:.*?)(?=\n##|\Z)",
rf"Reserves_{re.escape(produit)}:(?:.*?)(?=\n##|\Z)",
rf"Traitement_{re.escape(produit)}:(?:.*?)(?=\n##|\Z)"
]
for pattern in patterns_to_remove:
md = re.sub(pattern, "", md, flags=re.DOTALL)
# Nettoyer les délimiteurs ```yaml et ``` qui pourraient rester
md = re.sub(r"```yaml\s*\n", "", md)
md = re.sub(r"```\s*\n", "", md)
# Injecter les sections IHH depuis la fiche technique
try:
# Charger le contenu de la fiche technique IHH
ihh_path = "Fiches/Criticités/Fiche technique IHH.md"
with open(ihh_path, "r", encoding="utf-8") as f:
ihh_content = f.read()
# D'abord, extraire toute la section concernant le produit
section_produit_pattern = rf"## Opérations - {produit}\s*\n([\s\S]*?)(?=\n## |$)"
section_produit_match = re.search(section_produit_pattern, ihh_content, re.IGNORECASE)
if section_produit_match:
section_produit = section_produit_match.group(1).strip()
# Maintenant, extraire les sous-sections individuelles à partir de la section du produit
# 1. Extraction - incluant le titre de niveau 3
extraction_pattern = r"(### Indice de Herfindahl-Hirschmann - Extraction\s*\n[\s\S]*?)(?=### Indice de Herfindahl-Hirschmann - Réserves|$)"
extraction_match = re.search(extraction_pattern, section_produit, re.IGNORECASE)
if extraction_match:
extraction_ihh = extraction_match.group(1).strip()
md = re.sub(
r"<!---- AUTO-BEGIN:SECTION-IHH-EXTRACTION -->.*?<!---- AUTO-END:SECTION-IHH-EXTRACTION -->",
f"<!---- AUTO-BEGIN:SECTION-IHH-EXTRACTION -->\n{extraction_ihh}\n<!---- AUTO-END:SECTION-IHH-EXTRACTION -->",
md,
flags=re.DOTALL
)
# 2. Réserves - incluant le titre de niveau 3
reserves_pattern = r"(### Indice de Herfindahl-Hirschmann - Réserves\s*\n[\s\S]*?)(?=### Indice de Herfindahl-Hirschmann - Traitement|$)"
reserves_match = re.search(reserves_pattern, section_produit, re.IGNORECASE)
if reserves_match:
reserves_ihh = reserves_match.group(1).strip()
md = re.sub(
r"<!---- AUTO-BEGIN:SECTION-IHH-RESERVES -->.*?<!---- AUTO-END:SECTION-IHH-RESERVES -->",
f"<!---- AUTO-BEGIN:SECTION-IHH-RESERVES -->\n{reserves_ihh}\n<!---- AUTO-END:SECTION-IHH-RESERVES -->",
md,
flags=re.DOTALL
)
# 3. Traitement - incluant le titre de niveau 3
traitement_pattern = r"(### Indice de Herfindahl-Hirschmann - Traitement\s*\n[\s\S]*?)(?=$)"
traitement_match = re.search(traitement_pattern, section_produit, re.IGNORECASE)
if traitement_match:
traitement_ihh = traitement_match.group(1).strip()
md = re.sub(
r"<!---- AUTO-BEGIN:SECTION-IHH-TRAITEMENT -->.*?<!---- AUTO-END:SECTION-IHH-TRAITEMENT -->",
f"<!---- AUTO-BEGIN:SECTION-IHH-TRAITEMENT -->\n{traitement_ihh}\n<!---- AUTO-END:SECTION-IHH-TRAITEMENT -->",
md,
flags=re.DOTALL
)
# suppression pour le dernier minerai dans la fiche IHH
md = re.sub(r"# Tableaux de synthèse.*<!---- AUTO-END:SECTION-IHH-TRAITEMENT -->", "", md, flags=re.DOTALL)
except Exception as e:
st.error(f"Erreur lors de la génération des sections IHH: {e}")
# Nettoyer les doubles sauts de ligne
md = re.sub(r"\n{3,}", "\n\n", md)
# Ajouter les informations IVC
md = build_minerai_ivc_section(md)
# Ajouter les informations ICS
md = build_minerai_ics_section(md)
# Ajouter les informations ICS pour les composants liés au minerai
md = build_minerai_ics_composant_section(md)
return md