Code/batch_ia/utils/sections.py
2025-05-28 14:36:30 +02:00

767 lines
38 KiB
Python

import os
import re
from utils.config import (
CORPUS_DIR,
TEMPLATE_PATH,
determine_threshold_color
)
from utils.files import (
find_prefixed_directory,
find_corpus_file,
write_report,
read_corpus_file
)
from utils.sections_utils import (
trouver_dossier_composant,
extraire_sections_par_mot_cle
)
def generate_introduction_section(data):
"""
Génère la section d'introduction du rapport.
"""
products = [p["label"] for p in data["products"].values()]
components = [c["label"] for c in data["components"].values()]
minerals = [m["label"] for m in data["minerals"].values()]
template = []
template.append("## Introduction\n")
template.append("Ce rapport analyse les vulnérabilités de la chaîne de fabrication du numérique pour :\n")
template.append("* les produits finaux : " + ", ".join(products))
template.append("* les composants : " + ", ".join(components))
template.append("* les minerais : " + ", ".join(minerals) + "\n")
return "\n".join(template)
def generate_methodology_section():
"""
Génère la section méthodologie du rapport.
"""
template = []
template.append("## Méthodologie d'analyse des risques\n")
template.append("### Indices et seuils\n")
template.append("La méthode d'évaluation intègre 4 indices et leurs combinaisons pour identifier les chemins critiques.\n")
# IHH
template.append("#### IHH (Herfindahl-Hirschmann) : concentration géographiques ou industrielle d'une opération\n")
# Essayer d'abord avec le chemin exact
ihh_context_file = "Criticités/Fiche technique IHH/00-contexte-et-objectif.md"
if os.path.exists(os.path.join(CORPUS_DIR, ihh_context_file)):
template.append(read_corpus_file(ihh_context_file, remove_first_title=True))
else:
# Fallback à la recherche par motif
ihh_context_file = find_corpus_file("contexte-et-objectif", "Criticités/Fiche technique IHH")
if ihh_context_file:
template.append(read_corpus_file(ihh_context_file, remove_first_title=True))
# Essayer d'abord avec le chemin exact
ihh_calc_file = "Criticités/Fiche technique IHH/01-mode-de-calcul/_intro.md"
if os.path.exists(os.path.join(CORPUS_DIR, ihh_calc_file)):
template.append(read_corpus_file(ihh_calc_file, remove_first_title=True))
else:
# Fallback à la recherche par motif
ihh_calc_file = find_corpus_file("mode-de-calcul/_intro", "Criticités/Fiche technique IHH")
if ihh_calc_file:
template.append(read_corpus_file(ihh_calc_file, remove_first_title=True))
template.append(" * Seuils : <15 = Vert (Faible), 15-25 = Orange (Modérée), >25 = Rouge (Élevée)\n")
# ISG
template.append("#### ISG (Stabilité Géopolitique) : stabilité des pays\n")
# Essayer d'abord avec le chemin exact
isg_context_file = "Criticités/Fiche technique ISG/00-contexte-et-objectif.md"
if os.path.exists(os.path.join(CORPUS_DIR, isg_context_file)):
template.append(read_corpus_file(isg_context_file, remove_first_title=True))
else:
# Fallback à la recherche par motif
isg_context_file = find_corpus_file("contexte-et-objectif", "Criticités/Fiche technique ISG")
if isg_context_file:
template.append(read_corpus_file(isg_context_file, remove_first_title=True))
# Essayer d'abord avec le chemin exact
isg_calc_file = "Criticités/Fiche technique ISG/01-mode-de-calcul/_intro.md"
if os.path.exists(os.path.join(CORPUS_DIR, isg_calc_file)):
template.append(read_corpus_file(isg_calc_file, remove_first_title=True))
else:
# Fallback à la recherche par motif
isg_calc_file = find_corpus_file("mode-de-calcul/_intro", "Criticités/Fiche technique ISG")
if isg_calc_file:
template.append(read_corpus_file(isg_calc_file, remove_first_title=True))
template.append(" * Seuils : <40 = Vert (Stable), 40-60 = Orange, >60 = Rouge (Instable)\n")
# ICS
template.append("#### ICS (Criticité de Substituabilité) : capacité à remplacer / substituer un élément\n")
# Essayer d'abord avec le chemin exact
ics_context_file = "Criticités/Fiche technique ICS/00-contexte-et-objectif.md"
if os.path.exists(os.path.join(CORPUS_DIR, ics_context_file)):
template.append(read_corpus_file(ics_context_file, remove_first_title=True))
else:
# Fallback à la recherche par motif
ics_context_file = find_corpus_file("contexte-et-objectif", "Criticités/Fiche technique ICS")
if ics_context_file:
template.append(read_corpus_file(ics_context_file, remove_first_title=True))
# Essayer d'abord avec le chemin exact
ics_calc_file = "Criticités/Fiche technique ICS/01-mode-de-calcul/_intro.md"
if os.path.exists(os.path.join(CORPUS_DIR, ics_calc_file)):
template.append(read_corpus_file(ics_calc_file, remove_first_title=True))
else:
# Fallback à la recherche par motif
ics_calc_file = find_corpus_file("mode-de-calcul/_intro", "Criticités/Fiche technique ICS")
if ics_calc_file:
template.append(read_corpus_file(ics_calc_file, remove_first_title=True))
template.append(" * Seuils : <0.3 = Vert (Facile), 0.3-0.6 = Orange (Moyenne), >0.6 = Rouge (Difficile)\n")
# IVC
template.append("#### IVC (Vulnérabilité de Concurrence) : pression concurrentielle avec d'autres secteurs\n")
# Essayer d'abord avec le chemin exact
ivc_context_file = "Criticités/Fiche technique IVC/00-contexte-et-objectif.md"
if os.path.exists(os.path.join(CORPUS_DIR, ivc_context_file)):
template.append(read_corpus_file(ivc_context_file, remove_first_title=True))
else:
# Fallback à la recherche par motif
ivc_context_file = find_corpus_file("contexte-et-objectif", "Criticités/Fiche technique IVC")
if ivc_context_file:
template.append(read_corpus_file(ivc_context_file, remove_first_title=True))
# Essayer d'abord avec le chemin exact
ivc_calc_file = "Criticités/Fiche technique IVC/01-mode-de-calcul/_intro.md"
if os.path.exists(os.path.join(CORPUS_DIR, ivc_calc_file)):
template.append(read_corpus_file(ivc_calc_file, remove_first_title=True))
else:
# Fallback à la recherche par motif
ivc_calc_file = find_corpus_file("mode-de-calcul/_intro", "Criticités/Fiche technique IVC")
if ivc_calc_file:
template.append(read_corpus_file(ivc_calc_file, remove_first_title=True))
template.append(" * Seuils : <5 = Vert (Faible), 5-15 = Orange (Modérée), >15 = Rouge (Forte)\n")
# Combinaison des indices
template.append("### Combinaison des indices\n")
# IHH et ISG
template.append("**IHH et ISG**\n")
template.append("Ces deux indices s'appliquent à toutes les opérations et se combinent dans l'évaluation du risque (niveau d'impact et probabilité de survenance) :\n")
template.append("* l'IHH donne le niveau d'impact => une forte concentration implique un fort impact si le risque est avéré")
template.append("* l'ISG donne la probabilité de survenance => plus les pays sont instables (et donc plus l'ISG est élevé) et plus la survenance du risque est élevée\n")
template.append("Pour évaluer le risque pour une opération, les ISG des pays sont pondérés par les parts de marché respectives pour donner un ISG combiné dont le calcul est :")
template.append("ISG_combiné = (Somme des ISG des pays multipliée par leur part de marché) / Sommes de leur part de marché\n")
template.append("On établit alors une matrice (Vert = 1, Orange = 2, Rouge = 3) et en faisant le produit des poids de l'ISG combiné et de l'IHH\n")
template.append("| ISG combiné / IHH | Vert | Orange | Rouge |")
template.append("| :-- | :-- | :-- | :-- |")
template.append("| Vert | 1 | 2 | 3 |")
template.append("| Orange | 2 | 4 | 6 |")
template.append("| Rouge | 3 | 6 | 9 |\n")
template.append("Les vulnérabilités se classent en trois niveaux pour chaque opération :\n")
template.append("* Vulnérabilité combinée élevée à critique : poids 6 et 9")
template.append("* Vulnérabilité combinée moyenne : poids 3 et 4")
template.append("* Vulnérabilité combinée faible : poids 1 et 2\n")
# ICS et IVC
template.append("**ICS et IVC**\n")
template.append("Ces deux indices se combinent dans l'évaluation du risque pour un minerai :\n")
template.append("* l'ICS donne le niveau d'impact => une faible substituabilité (et donc un ICS élevé) implique un fort impact si le risque est avéré ; l'ICS est associé à la relation entre un composant et un minerai")
template.append("* l'IVC donne la probabilité de l'impact => une forte concurrence intersectorielle (IVC élevé) implique une plus forte probabilité de survenance\n")
template.append("Par simplification, on intègre un ICS moyen d'un minerai comme étant la moyenne des ICS pour chacun des composants dans lesquels il intervient.\n")
template.append("On établit alors une matrice (Vert = 1, Orange = 2, Rouge = 3) et en faisant le produit des poids de l'ICS moyen et de l'IVC.\n")
template.append("| ICS_moyen / IVC | Vert | Orange | Rouge |")
template.append("| :-- | :-- | :-- | :-- |")
template.append("| Vert | 1 | 2 | 3 |")
template.append("| Orange | 2 | 4 | 6 |")
template.append("| Rouge | 3 | 6 | 9 |\n")
template.append("Les vulnérabilités se classent en trois niveaux pour chaque minerai :\n")
template.append("* Vulnérabilité combinée élevée à critique : poids 6 et 9")
template.append("* Vulnérabilité combinée moyenne : poids 3 et 4")
template.append("* Vulnérabilité combinée faible : poids 1 et 2\n")
return "\n".join(template)
def generate_operations_section(data, results, config):
"""
Génère la section détaillant les opérations (assemblage, fabrication, extraction, traitement).
"""
# # print("DEBUG: Génération de la section des opérations")
# # print(f"DEBUG: Nombre de produits: {len(data['products'])}")
# # print(f"DEBUG: Nombre de composants: {len(data['components'])}")
# # print(f"DEBUG: Nombre d'opérations: {len(data['operations'])}")
template = []
template.append("## Détails des opérations\n")
# 1. Traiter les produits finaux (assemblage)
for product_id, product in data["products"].items():
# # print(f"DEBUG: Produit {product_id} ({product['label']}), assembly = {product['assembly']}")
if product["assembly"]:
template.append(f"### {product['label']} et Assemblage\n")
# Récupérer la présentation synthétique
# product_slug = product['label'].lower().replace(' ', '-')
sous_repertoire = f"{product['label']}"
if product["level"] == 0:
type = "Assemblage"
else:
type = "Connexe"
sous_repertoire = trouver_dossier_composant(sous_repertoire, type, "Fiche assemblage ")
product_slug = sous_repertoire.split(' ', 2)[2]
presentation_file = find_corpus_file("présentation-synthétique", f"{type}/Fiche assemblage {product_slug}")
if presentation_file:
template.append(read_corpus_file(presentation_file, remove_first_title=True))
template.append("")
# Récupérer les principaux assembleurs
assembleurs_file = find_corpus_file("principaux-assembleurs", f"{type}/Fiche assemblage {product_slug}")
if assembleurs_file:
template.append(read_corpus_file(assembleurs_file, shift_titles=2))
template.append("")
# ISG des pays impliqués
assembly_id = product["assembly"]
operation = data["operations"][assembly_id]
template.append("##### ISG des pays impliqués\n")
template.append("| Pays | Part de marché | ISG | Criticité |")
template.append("| :-- | :-- | :-- | :-- |")
isg_weighted_sum = 0
total_share = 0
for country_id, share in operation["countries"].items():
country = data["countries"][country_id]
geo_country = country.get("geo_country")
if geo_country and geo_country in data["geo_countries"]:
isg_value = data["geo_countries"][geo_country]["isg"]
color, suffix = determine_threshold_color(isg_value, "ISG", config.get('thresholds'))
template.append(f"| {country['label']} | {share}% | {isg_value} | {color} ({suffix}) |")
isg_weighted_sum += isg_value * share
total_share += share
# Calculer ISG combiné
if total_share > 0:
isg_combined = isg_weighted_sum / total_share
color, suffix = determine_threshold_color(isg_combined, "ISG", config.get('thresholds'))
template.append(f"\n**ISG combiné: {isg_combined:.0f} - {color} ({suffix})**")
# IHH
ihh_file = find_corpus_file("matrice-des-risques-liés-à-l-assemblage/indice-de-herfindahl-hirschmann", f"{type}/Fiche assemblage {product_slug}")
if ihh_file:
template.append(read_corpus_file(ihh_file, shift_titles=1))
template.append("\n")
# Vulnérabilité combinée
if assembly_id in results["ihh_isg_combined"]:
combined = results["ihh_isg_combined"][assembly_id]
template.append("#### Vulnérabilité combinée IHH-ISG\n")
template.append(f"* IHH: {combined['ihh_value']} - {combined['ihh_color']} ({combined['ihh_suffix']})")
template.append(f"* ISG combiné: {combined['isg_combined']:.0f} - {combined['isg_color']} ({combined['isg_suffix']})")
template.append(f"* Poids combiné: {combined['combined_weight']}")
template.append(f"* Niveau de vulnérabilité: **{combined['vulnerability']}**\n")
# 2. Traiter les composants (fabrication)
for component_id, component in data["components"].items():
# # print(f"DEBUG: Composant {component_id} ({component['label']}), manufacturing = {component['manufacturing']}")
if component["manufacturing"]:
template.append(f"### {component['label']} et Fabrication\n")
# Récupérer la présentation synthétique
# component_slug = component['label'].lower().replace(' ', '-')
sous_repertoire = f"{component['label']}"
sous_repertoire = trouver_dossier_composant(sous_repertoire, "Fabrication", "Fiche fabrication ")
component_slug = sous_repertoire.split(' ', 2)[2]
presentation_file = find_corpus_file("présentation-synthétique", f"Fabrication/Fiche fabrication {component_slug}")
if presentation_file:
template.append(read_corpus_file(presentation_file, remove_first_title=True))
template.append("\n")
# Récupérer les principaux fabricants
fabricants_file = find_corpus_file("principaux-fabricants", f"Fabrication/Fiche fabrication {component_slug}")
if fabricants_file:
template.append(read_corpus_file(fabricants_file, shift_titles=2))
template.append("\n")
# ISG des pays impliqués
manufacturing_id = component["manufacturing"]
operation = data["operations"][manufacturing_id]
template.append("#### ISG des pays impliqués\n")
template.append("| Pays | Part de marché | ISG | Criticité |")
template.append("| :-- | :-- | :-- | :-- |")
isg_weighted_sum = 0
total_share = 0
for country_id, share in operation["countries"].items():
country = data["countries"][country_id]
geo_country = country.get("geo_country")
if geo_country and geo_country in data["geo_countries"]:
isg_value = data["geo_countries"][geo_country]["isg"]
color, suffix = determine_threshold_color(isg_value, "ISG", config.get('thresholds'))
template.append(f"| {country['label']} | {share}% | {isg_value} | {color} ({suffix}) |")
isg_weighted_sum += isg_value * share
total_share += share
# Calculer ISG combiné
if total_share > 0:
isg_combined = isg_weighted_sum / total_share
color, suffix = determine_threshold_color(isg_combined, "ISG", config.get('thresholds'))
template.append(f"\n**ISG combiné: {isg_combined:.0f} - {color} ({suffix})**\n\n")
# IHH
ihh_file = find_corpus_file("matrice-des-risques-liés-à-la-fabrication/indice-de-herfindahl-hirschmann", f"Fabrication/Fiche fabrication {component_slug}")
if ihh_file:
template.append(read_corpus_file(ihh_file, shift_titles=1))
template.append("\n")
# Vulnérabilité combinée
if manufacturing_id in results["ihh_isg_combined"]:
combined = results["ihh_isg_combined"][manufacturing_id]
template.append("#### Vulnérabilité combinée IHH-ISG\n")
template.append(f"* IHH: {combined['ihh_value']} - {combined['ihh_color']} ({combined['ihh_suffix']})")
template.append(f"* ISG combiné: {combined['isg_combined']:.0f} - {combined['isg_color']} ({combined['isg_suffix']})")
template.append(f"* Poids combiné: {combined['combined_weight']}")
template.append(f"* Niveau de vulnérabilité: **{combined['vulnerability']}**\n")
# 3. Traiter les minerais (détaillés dans une section séparée)
result = "\n".join(template)
# # print(f"DEBUG: Fin de génération de la section des opérations. Taille: {len(result)} caractères")
if len(result) <= 30: # Juste le titre de section
# # print("DEBUG: ALERTE - La section des opérations est vide ou presque vide!")
# Ajout d'une section de débogage dans le rapport
template.append("### DÉBOGAGE - Opérations manquantes\n")
template.append("Aucune opération d'assemblage ou de fabrication n'a été trouvée dans les données.\n")
template.append("Informations disponibles:\n")
template.append(f"* Nombre de produits: {len(data['products'])}\n")
template.append(f"* Nombre de composants: {len(data['components'])}\n")
template.append(f"* Nombre d'opérations: {len(data['operations'])}\n")
template.append("\nDétail des produits et de leurs opérations d'assemblage:\n")
for pid, p in data["products"].items():
template.append(f"* {p['label']}: {'Assemblage: ' + str(p['assembly']) if p['assembly'] else 'Pas d\'assemblage'}\n")
template.append("\nDétail des composants et de leurs opérations de fabrication:\n")
for cid, c in data["components"].items():
template.append(f"* {c['label']}: {'Fabrication: ' + str(c['manufacturing']) if c['manufacturing'] else 'Pas de fabrication'}\n")
result = "\n".join(template)
return result
def generate_minerals_section(data, results, config):
"""
Génère la section détaillant les minerais et leurs opérations d'extraction et traitement.
"""
template = []
template.append("## Détails des minerais\n")
for mineral_id, mineral in data["minerals"].items():
mineral_slug = mineral['label'].lower().replace(' ', '-')
fiche_dir = f"{CORPUS_DIR}/Minerai/Fiche minerai {mineral_slug}"
if not os.path.exists(fiche_dir):
continue
template.append(f"---\n\n### {mineral['label']}\n")
# Récupérer la présentation synthétique
presentation_file = find_corpus_file("présentation-synthétique", f"Minerai/Fiche minerai {mineral_slug}")
if presentation_file:
template.append(read_corpus_file(presentation_file, remove_first_title=True))
template.append("\n")
# ICS
template.append("#### ICS\n")
ics_intro_file = find_corpus_file("risque-de-substituabilité/_intro", f"Minerai/Fiche minerai {mineral_slug}")
if ics_intro_file:
template.append(read_corpus_file(ics_intro_file, remove_first_title=True))
template.append("\n")
# Calcul de l'ICS moyen
ics_values = list(mineral["ics_values"].values())
if ics_values:
ics_average = sum(ics_values) / len(ics_values)
color, suffix = determine_threshold_color(ics_average, "ICS", config.get('thresholds'))
template.append("##### Valeurs d'ICS par composant\n")
template.append("| Composant | ICS | Criticité |")
template.append("| :-- | :-- | :-- |")
for comp_id, ics_value in mineral["ics_values"].items():
comp_name = data["components"][comp_id]["label"]
comp_color, comp_suffix = determine_threshold_color(ics_value, "ICS", config.get('thresholds'))
template.append(f"| {comp_name} | {ics_value:.2f} | {comp_color} ({comp_suffix}) |")
template.append(f"\n**ICS moyen : {ics_average:.2f} - {color} ({suffix})**\n")
# IVC
template.append("#### IVC\n\n")
# Valeur IVC
ivc_value = mineral.get("ivc", 0)
color, suffix = determine_threshold_color(ivc_value, "IVC", config.get('thresholds'))
template.append(f"**IVC: {ivc_value} - {color} ({suffix})**\n")
# Récupérer toutes les sections de vulnérabilité de concurrence
ivc_sections = []
ivc_dir = find_prefixed_directory("vulnérabilité-de-concurrence", f"Minerai/Fiche minerai {mineral_slug}")
corpus_path = os.path.join(CORPUS_DIR, ivc_dir) if os.path.exists(os.path.join(CORPUS_DIR, ivc_dir)) else None
if corpus_path:
for file in sorted(os.listdir(corpus_path)):
if file.endswith('.md') and "_intro.md" not in file and "sources" not in file:
ivc_sections.append(os.path.join(ivc_dir, file))
# Inclure chaque section IVC
for section_file in ivc_sections:
content = read_corpus_file(section_file, remove_first_title=False)
# Nettoyer les balises des fichiers IVC
content = re.sub(r'```.*?```', '', content, flags=re.DOTALL)
# Mettre le titre en italique s'il commence par un # (format Markdown pour titre)
if content and '\n' in content:
first_line, rest = content.split('\n', 1)
if first_line.strip().startswith('#'):
# Extraire le texte du titre sans les # et les espaces
title_text = first_line.strip().lstrip('#').strip()
content = f"\n*{title_text}*\n{rest.strip()}"
# Ne pas ajouter de contenu vide
if content.strip():
template.append(content.strip())
# ICS et IVC combinés
if mineral_id in results["ics_ivc_combined"]:
combined = results["ics_ivc_combined"][mineral_id]
template.append("\n#### Vulnérabilité combinée ICS-IVC\n")
template.append(f"* ICS moyen: {combined['ics_average']:.2f} - {combined['ics_color']} ({combined['ics_suffix']})")
template.append(f"* IVC: {combined['ivc_value']} - {combined['ivc_color']} ({combined['ivc_suffix']})")
template.append(f"* Poids combiné: {combined['combined_weight']}")
template.append(f"* Niveau de vulnérabilité: **{combined['vulnerability']}**\n")
# Extraction
if mineral["extraction"]:
template.append("#### Extraction\n")
# Récupérer les principaux producteurs
producers_file = find_corpus_file("principaux-producteurs-extraction", f"Minerai/Fiche minerai {mineral_slug}")
if producers_file:
template.append(read_corpus_file(producers_file, remove_first_title=True))
template.append("\n")
# ISG des pays impliqués
extraction_id = mineral["extraction"]
operation = data["operations"][extraction_id]
template.append("##### ISG des pays impliqués\n")
template.append("| Pays | Part de marché | ISG | Criticité |")
template.append("| :-- | :-- | :-- | :-- |")
isg_weighted_sum = 0
total_share = 0
for country_id, share in operation["countries"].items():
country = data["countries"][country_id]
geo_country = country.get("geo_country")
if geo_country and geo_country in data["geo_countries"]:
isg_value = data["geo_countries"][geo_country]["isg"]
color, suffix = determine_threshold_color(isg_value, "ISG", config.get('thresholds'))
template.append(f"| {country['label']} | {share}% | {isg_value} | {color} ({suffix}) |")
isg_weighted_sum += isg_value * share
total_share += share
# Calculer ISG combiné
if total_share > 0:
isg_combined = isg_weighted_sum / total_share
color, suffix = determine_threshold_color(isg_combined, "ISG", config.get('thresholds'))
template.append(f"\n**ISG combiné: {isg_combined:.0f} - {color} ({suffix})**\n")
# IHH extraction
ihh_file = find_corpus_file("matrice-des-risques/indice-de-herfindahl-hirschmann-extraction", f"Minerai/Fiche minerai {mineral_slug}")
if ihh_file:
template.append(read_corpus_file(ihh_file, shift_titles=1))
template.append("\n")
# Vulnérabilité combinée
if extraction_id in results["ihh_isg_combined"]:
combined = results["ihh_isg_combined"][extraction_id]
template.append("##### Vulnérabilité combinée IHH-ISG pour l'extraction\n")
template.append(f"* IHH: {combined['ihh_value']} - {combined['ihh_color']} ({combined['ihh_suffix']})")
template.append(f"* ISG combiné: {combined['isg_combined']:.0f} - {combined['isg_color']} ({combined['isg_suffix']})")
template.append(f"* Poids combiné: {combined['combined_weight']}")
template.append(f"* Niveau de vulnérabilité: **{combined['vulnerability']}**\n")
# Traitement
if mineral["treatment"]:
template.append("#### Traitement\n")
# Récupérer les principaux producteurs
producers_file = find_corpus_file("principaux-producteurs-traitement", f"Minerai/Fiche minerai {mineral_slug}")
if producers_file:
template.append(read_corpus_file(producers_file, remove_first_title=True))
template.append("\n")
# ISG des pays impliqués
treatment_id = mineral["treatment"]
operation = data["operations"][treatment_id]
template.append("##### ISG des pays impliqués\n")
template.append("| Pays | Part de marché | ISG | Criticité |")
template.append("| :-- | :-- | :-- | :-- |")
isg_weighted_sum = 0
total_share = 0
for country_id, share in operation["countries"].items():
country = data["countries"][country_id]
geo_country = country.get("geo_country")
if geo_country and geo_country in data["geo_countries"]:
isg_value = data["geo_countries"][geo_country]["isg"]
color, suffix = determine_threshold_color(isg_value, "ISG", config.get('thresholds'))
template.append(f"| {country['label']} | {share}% | {isg_value} | {color} ({suffix}) |")
isg_weighted_sum += isg_value * share
total_share += share
# Calculer ISG combiné
if total_share > 0:
isg_combined = isg_weighted_sum / total_share
color, suffix = determine_threshold_color(isg_combined, "ISG", config.get('thresholds'))
template.append(f"\n**ISG combiné: {isg_combined:.0f} - {color} ({suffix})**\n")
# IHH traitement
ihh_file = find_corpus_file("matrice-des-risques/indice-de-herfindahl-hirschmann-traitement", f"Minerai/Fiche minerai {mineral_slug}")
if ihh_file:
template.append(read_corpus_file(ihh_file, shift_titles=1))
template.append("\n")
# Vulnérabilité combinée
if treatment_id in results["ihh_isg_combined"]:
combined = results["ihh_isg_combined"][treatment_id]
template.append("##### Vulnérabilité combinée IHH-ISG pour le traitement\n")
template.append(f"* IHH: {combined['ihh_value']} - {combined['ihh_color']} ({combined['ihh_suffix']})")
template.append(f"* ISG combiné: {combined['isg_combined']:.0f} - {combined['isg_color']} ({combined['isg_suffix']})")
template.append(f"* Poids combiné: {combined['combined_weight']}")
template.append(f"* Niveau de vulnérabilité: **{combined['vulnerability']}**\n")
return "\n".join(template)
def generate_critical_paths_section(data, results):
"""
Génère la section des chemins critiques.
"""
template = []
template.append("## Chemins critiques\n")
# Récupérer les chaînes par niveau de risque
critical_chains = []
major_chains = []
medium_chains = []
for chain in results["chains"]:
if chain["risk_level"] == "critique":
critical_chains.append(chain)
elif chain["risk_level"] == "majeur":
major_chains.append(chain)
elif chain["risk_level"] == "moyen":
medium_chains.append(chain)
# 1. Chaînes critiques
template.append("### Chaînes avec risque critique\n")
template.append("*Ces chaînes comprennent au moins une vulnérabilité combinée élevée à critique*\n")
if critical_chains:
for chain in critical_chains:
product_name = data["products"][chain["product"]]["label"]
component_name = data["components"][chain["component"]]["label"]
mineral_name = data["minerals"][chain["mineral"]]["label"]
template.append(f"#### {product_name}{component_name}{mineral_name}\n")
# Vulnérabilités
template.append("**Vulnérabilités identifiées:**\n")
for vuln in chain["vulnerabilities"]:
vuln_type = vuln["type"].capitalize()
vuln_level = vuln["vulnerability"]
if vuln_type == "Minerai":
mineral_id = vuln["mineral_id"]
template.append(f"* {vuln_type} ({mineral_name}): {vuln_level}")
if mineral_id in results["ics_ivc_combined"]:
combined = results["ics_ivc_combined"][mineral_id]
template.append(f" * ICS moyen: {combined['ics_average']:.2f} - {combined['ics_color']}")
template.append(f" * IVC: {combined['ivc_value']} - {combined['ivc_color']}")
else:
op_id = vuln["operation_id"]
op_label = data["operations"][op_id]["label"]
template.append(f"* {vuln_type} ({op_label}): {vuln_level}")
if op_id in results["ihh_isg_combined"]:
combined = results["ihh_isg_combined"][op_id]
template.append(f" * IHH: {combined['ihh_value']} - {combined['ihh_color']}")
template.append(f" * ISG combiné: {combined['isg_combined']:.0f} - {combined['isg_color']}")
template.append("\n")
else:
template.append("Aucune chaîne à risque critique identifiée.\n")
# 2. Chaînes majeures
template.append("### Chaînes avec risque majeur\n")
template.append("*Ces chaînes comprennent au moins trois vulnérabilités combinées moyennes*\n")
if major_chains:
for chain in major_chains:
product_name = data["products"][chain["product"]]["label"]
component_name = data["components"][chain["component"]]["label"]
mineral_name = data["minerals"][chain["mineral"]]["label"]
template.append(f"#### {product_name}{component_name}{mineral_name}\n")
# Vulnérabilités
template.append("**Vulnérabilités identifiées:**\n")
for vuln in chain["vulnerabilities"]:
vuln_type = vuln["type"].capitalize()
vuln_level = vuln["vulnerability"]
if vuln_type == "Minerai":
mineral_id = vuln["mineral_id"]
template.append(f"* {vuln_type} ({mineral_name}): {vuln_level}\n")
if mineral_id in results["ics_ivc_combined"]:
combined = results["ics_ivc_combined"][mineral_id]
template.append(f" * ICS moyen: {combined['ics_average']:.2f} - {combined['ics_color']}\n")
template.append(f" * IVC: {combined['ivc_value']} - {combined['ivc_color']}\n")
else:
op_id = vuln["operation_id"]
op_label = data["operations"][op_id]["label"]
template.append(f"* {vuln_type} ({op_label}): {vuln_level}\n")
if op_id in results["ihh_isg_combined"]:
combined = results["ihh_isg_combined"][op_id]
template.append(f" * IHH: {combined['ihh_value']} - {combined['ihh_color']}\n")
template.append(f" * ISG combiné: {combined['isg_combined']:.0f} - {combined['isg_color']}\n")
template.append("\n")
else:
template.append("Aucune chaîne à risque majeur identifiée.\n")
# 3. Chaînes moyennes
template.append("### Chaînes avec risque moyen\n")
template.append("*Ces chaînes comprennent au moins une vulnérabilité combinée moyenne*\n")
if medium_chains:
for chain in medium_chains:
product_name = data["products"][chain["product"]]["label"]
component_name = data["components"][chain["component"]]["label"]
mineral_name = data["minerals"][chain["mineral"]]["label"]
template.append(f"#### {product_name}{component_name}{mineral_name}\n")
# Vulnérabilités
template.append("**Vulnérabilités identifiées:**\n")
for vuln in chain["vulnerabilities"]:
vuln_type = vuln["type"].capitalize()
vuln_level = vuln["vulnerability"]
if vuln_type == "Minerai":
mineral_id = vuln["mineral_id"]
template.append(f"* {vuln_type} ({mineral_name}): {vuln_level}\n")
if mineral_id in results["ics_ivc_combined"]:
combined = results["ics_ivc_combined"][mineral_id]
template.append(f" * ICS moyen: {combined['ics_average']:.2f} - {combined['ics_color']}\n")
template.append(f" * IVC: {combined['ivc_value']} - {combined['ivc_color']}\n")
else:
op_id = vuln["operation_id"]
op_label = data["operations"][op_id]["label"]
template.append(f"* {vuln_type} ({op_label}): {vuln_level}\n")
if op_id in results["ihh_isg_combined"]:
combined = results["ihh_isg_combined"][op_id]
template.append(f" * IHH: {combined['ihh_value']} - {combined['ihh_color']}\n")
template.append(f" * ISG combiné: {combined['isg_combined']:.0f} - {combined['isg_color']}\n")
template.append("\n")
else:
template.append("Aucune chaîne à risque moyen identifiée.\n")
return "\n".join(template)
def generate_report(data, results, config):
"""
Génère le rapport complet structuré selon les spécifications.
"""
# Titre principal
report_titre = ["# Évaluation des vulnérabilités critiques\n"]
# Section d'introduction
report_introduction = generate_introduction_section(data)
# report.append(generate_introduction_section(data))
# Section méthodologie
report_methodologie = generate_methodology_section()
# report.append(generate_methodology_section())
# Section détails des opérations
report_operations = generate_operations_section(data, results, config)
# report.append(generate_operations_section(data, results, config))
# Section détails des minerais
report_minerals = generate_minerals_section(data, results, config)
# report.append(generate_minerals_section(data, results, config))
# Section chemins critiques
report_critical_paths = generate_critical_paths_section(data, results)
suffixe = " - chemins critiques"
fichier = TEMPLATE_PATH.name.replace(".md", f"{suffixe}.md")
fichier_path = TEMPLATE_PATH.parent / fichier
# Élever les titres Markdown dans report_critical_paths
report_critical_paths = re.sub(r'^(#{2,})', lambda m: '#' * (len(m.group(1)) - 1), report_critical_paths, flags=re.MULTILINE)
write_report(report_critical_paths, fichier_path)
# Récupérer les sections critiques décomposées par mot-clé
chemins_critiques_sections = extraire_sections_par_mot_cle(fichier_path)
file_names = []
# Pour chaque mot-clé, écrire un fichier individuel
for mot_cle, contenu in chemins_critiques_sections.items():
print(mot_cle)
suffixe = f" - chemins critiques {mot_cle}"
fichier_personnalise = TEMPLATE_PATH.with_name(
TEMPLATE_PATH.name.replace(".md", f"{suffixe}.md")
)
# Ajouter du texte au début du contenu
introduction = f"# Détail des chemins critiques pour : {mot_cle}\n\n"
contenu = introduction + contenu
write_report(contenu, fichier_personnalise)
file_names.append(fichier_personnalise)
# report.append(generate_critical_paths_section(data, results))
# Ordre de composition final
report = (
report_titre +
[report_introduction] +
[report_critical_paths] +
[report_operations] +
[report_minerals] +
[report_methodologie]
)
return "\n".join(report), file_names