769 lines
38 KiB
Python
769 lines
38 KiB
Python
import os
|
|
import re
|
|
|
|
from .config import (
|
|
CORPUS_DIR,
|
|
TEMPLATE_PATH,
|
|
determine_threshold_color
|
|
)
|
|
|
|
from .files import (
|
|
find_prefixed_directory,
|
|
find_corpus_file,
|
|
write_report,
|
|
read_corpus_file
|
|
)
|
|
|
|
from .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 concerné\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\n{rest.strip()}\n"
|
|
|
|
template.append(content)
|
|
|
|
# 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
|