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 slugify(text): return re.sub(r'\W+', '-', text.strip()).strip('-').lower() 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) mot_cle_slug = slugify(mot_cle) suffixe = f" - chemins critiques {mot_cle_slug}" 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