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".*?", f"\n{tableau_final}\n", 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".*?", f"\n{tableau_final}\n", 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".*?", f"\n{tableau_final}\n", 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("produit") # 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".*?", f"\n{ivc_content_formatted}\n", 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("produit") # 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".*?", f"\n{minerai_content}\n", 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("produit") # 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 = rf"## ([^>]+) -> {produit} - .*?\n([\s\S]*?)(?=\n## |$)" 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".*?", f"\n{combined_content}\n", 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("produit") # 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".*?", f"\n{extraction_ihh}\n", 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".*?", f"\n{reserves_ihh}\n", 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".*?", f"\n{traitement_ihh}\n", 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