#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Script de génération de template de rapport IHH/ISG. Ce script lit un fichier DOT, identifie les éléments Orange ou Rouge, et génère un template avec les chemins d'accès aux sections à récupérer. """ import os import re import glob import yaml import argparse import unicodedata import networkx as nx from pathlib import Path from networkx.drawing.nx_agraph import read_dot # Chemins de base BASE_DIR = Path(__file__).resolve().parent BASE_DIR = BASE_DIR / ".." CORPUS_DIR = BASE_DIR / "Corpus" GRAPH_PATH = BASE_DIR / "graphe.dot" CONFIG_PATH = BASE_DIR / "assets/config.yaml" OUTPUT_PATH = CORPUS_DIR / "rapport_template.md" # Chemins pour les indices et documents de référence # Chemins utilisés pour les références vers les fiches techniques IHH_ROOT_PATH = 'Criticités/Fiche technique IHH' ISG_ROOT_PATH = 'Criticités/Fiche technique ISG' IVC_ROOT_PATH = 'Criticités/Fiche technique IVC' ICS_ROOT_PATH = 'Criticités/Fiche technique ICS' # Utiliser simplement le chemin relatif pour accéder aux fichiers CRIT_PATH = 'Criticités' def load_config(): """Charge les seuils depuis le fichier de configuration""" try: with open(CONFIG_PATH, 'r') as file: config = yaml.safe_load(file) return config.get('seuils', {}) except Exception as e: print(f"Erreur lors du chargement de la configuration: {e}") # Valeurs par défaut si le fichier est inaccessible return { 'ISG': { 'vert': {'max': 40}, 'orange': {'min': 40, 'max': 70}, 'rouge': {'min': 70} }, 'IVC': { 'vert': {'max': 5}, 'orange': {'min': 5, 'max': 15}, 'rouge': {'min': 15} }, 'IHH': { 'vert': {'max': 15}, 'orange': {'min': 15, 'max': 25}, 'rouge': {'min': 25} }, 'ICS': { 'vert': {'max': 0.30}, 'orange': {'min': 0.30, 'max': 0.60}, 'rouge': {'min': 0.60} } } def determine_threshold_color(value, index_type): """Détermine la couleur du seuil en fonction de la valeur et du type d'indice""" seuils = load_config().get(index_type, {}) if not seuils: return None # Vérifier d'abord le seuil rouge (priorité la plus haute) if 'rouge' in seuils and 'min' in seuils['rouge'] and value >= seuils['rouge']['min']: return 'rouge' # Ensuite le seuil orange if 'orange' in seuils and 'min' in seuils['orange'] and 'max' in seuils['orange']: if value >= seuils['orange']['min'] and value < seuils['orange']['max']: return 'orange' # Par défaut, c'est vert return 'vert' # Définition des types de ressources et leurs opérations associées RESOURCE_TYPES = { "produit": {"folder": "Assemblage", "operation": "assemblage"}, "composant": {"folder": "Fabrication", "operation": "fabrication"}, "minerai": {"folder": "Minerai", "operations": ["extraction", "traitement"]} } def parse_graph(dot_path, filter_level='orange'): """Analyse le fichier DOT et extrait les éléments avec criticité Orange ou Rouge. Args: dot_path: Chemin vers le fichier graphe.dot filter_level: Niveau de seuil à utiliser ('orange' ou 'rouge') """ G = read_dot(dot_path) elements = [] # Charger les seuils depuis la configuration seuils = load_config() # Définir les seuils pour IHH, ISG, et IVC (Orange et Rouge) ihh_seuil_orange = seuils.get('IHH', {}).get('orange', {}).get('min', 15) ihh_seuil_rouge = seuils.get('IHH', {}).get('rouge', {}).get('min', 25) isg_seuil_orange = seuils.get('ISG', {}).get('orange', {}).get('min', 40) isg_seuil_rouge = seuils.get('ISG', {}).get('rouge', {}).get('min', 70) ivc_seuil_orange = seuils.get('IVC', {}).get('orange', {}).get('min', 5) ivc_seuil_rouge = seuils.get('IVC', {}).get('rouge', {}).get('min', 15) # Sélectionner les seuils en fonction du niveau de filtrage if filter_level == 'rouge': ihh_seuil = ihh_seuil_rouge isg_seuil = isg_seuil_rouge ivc_seuil = ivc_seuil_rouge else: # 'orange' par défaut ihh_seuil = ihh_seuil_orange isg_seuil = isg_seuil_orange ivc_seuil = ivc_seuil_orange # Traiter les nœuds for node, attrs in G.nodes(data=True): if '_' not in node: continue op, res = node.split('_', 1) # Récupérer les valeurs IHH si disponibles ihh_pays = attrs.get('ihh_pays', 0) ihh_acteurs = attrs.get('ihh_acteurs', 0) try: ihh_pays = float(ihh_pays) ihh_acteurs = float(ihh_acteurs) except (ValueError, TypeError): ihh_pays = 0 ihh_acteurs = 0 # Récupérer la valeur ISG si disponible isg = attrs.get('isg', 0) try: isg = float(isg) except (ValueError, TypeError): isg = 0 # Récupérer la valeur IVC si disponible ivc = attrs.get('ivc', 0) try: ivc = float(ivc) except (ValueError, TypeError): ivc = 0 # Déterminer si l'élément est critique selon le seuil configuré ihh_critique = ihh_pays > ihh_seuil or ihh_acteurs > ihh_seuil isg_critique = isg > isg_seuil ivc_critique = ivc > ivc_seuil if ihh_critique or isg_critique or ivc_critique: elements.append({ 'operation': op, 'resource': res, 'ihh_pays': ihh_pays, 'ihh_acteurs': ihh_acteurs, 'isg': isg, 'ivc': ivc, 'ihh_critique': ihh_critique, 'isg_critique': isg_critique, 'ivc_critique': ivc_critique, 'node_id': node }) # Traiter les arêtes pour détecter les ICS critiques for u, v, attrs in G.edges(data=True): if 'ics' in attrs: ics_value = attrs.get('ics', 0) try: ics_value = float(ics_value) except (ValueError, TypeError): ics_value = 0 ics_seuil_orange = seuils.get('ICS', {}).get('orange', {}).get('min', 0.30) ics_seuil_rouge = seuils.get('ICS', {}).get('rouge', {}).get('min', 0.60) # Sélectionner le seuil ICS en fonction du niveau de filtrage ics_seuil = ics_seuil_rouge if filter_level == 'rouge' else ics_seuil_orange ics_critique = ics_value >= ics_seuil if ics_critique: # Ajouter l'ICS critique au nœud de destination (minerai) # Trouver d'abord si le nœud de destination existe déjà dans elements dest_node = v dest_element = next((e for e in elements if e['node_id'] == dest_node), None) if dest_element: # Mettre à jour l'élément existant dest_element['ics_critique'] = True dest_element['ics_value'] = ics_value else: # Si le nœud destination n'est pas déjà dans elements (pas critique pour d'autres raisons) # Créer une entrée s'il s'agit d'un format operation_resource if '_' in dest_node: op, res = dest_node.split('_', 1) elements.append({ 'operation': op, 'resource': res, 'node_id': dest_node, 'ics_critique': True, 'ics_value': ics_value }) return elements def analyze_geopolitical_stability(dot_path, filter_levels=None): """Analyse la stabilité géopolitique à partir du graphe. Args: dot_path: Chemin vers le fichier graphe.dot filter_levels: Liste de niveaux de criticité à considérer ('orange', 'rouge') Si None, prend les deux niveaux par défaut """ G = read_dot(dot_path) # Si aucun niveau n'est spécifié, on considère orange et rouge if filter_levels is None: filter_levels = ['orange', 'rouge'] elif isinstance(filter_levels, str): filter_levels = [filter_levels] print(f"Analyse ISG du fichier {dot_path} avec filtres {', '.join(filter_levels)}") # Charger les seuils depuis la configuration seuils = load_config().get('ISG', {}) # Déterminer le seuil en fonction des niveaux demandés isg_threshold = None if 'orange' in filter_levels and 'rouge' not in filter_levels: isg_threshold = seuils.get('orange', {}).get('min', 40) print(f"Niveau orange: ISG >= {isg_threshold}") elif 'rouge' in filter_levels and 'orange' not in filter_levels: isg_threshold = seuils.get('rouge', {}).get('min', 70) print(f"Niveau rouge: ISG >= {isg_threshold}") else: # Les deux niveaux ou cas par défaut isg_threshold = seuils.get('orange', {}).get('min', 40) print(f"Niveaux orange et rouge: ISG >= {isg_threshold}") print(f"Seuil ISG critique: {isg_threshold}") results = {} # Trouver d'abord tous les nœuds géographiques avec ISG geo_isg = {} for node, attrs in G.nodes(data=True): if 'isg' in attrs: pays_name = node.split('_')[0] if '_' in node else node isg_value = float(attrs['isg']) geo_isg[pays_name] = isg_value print(f"Nœud géographique avec ISG trouvé: {node}, ISG: {isg_value}") # Pour chaque nœud d'opération operations_count = 0 for node, attrs in G.nodes(data=True): if '_' in node and ('ihh_pays' in attrs or 'ihh_acteurs' in attrs): operations_count += 1 parts = node.split('_') op, res = parts[0], '_'.join(parts[1:]) if len(parts) > 1 else parts[0] operation_key = f"{op}_{res}" print(f"Analyse de l'opération: {operation_key}") results[operation_key] = { 'pays_critiques': [], 'acteurs_critiques': [] } # Chercher les pays liés directement à cette opération for pays_node in G.successors(node): # Vérifier que c'est un nœud de pays valide (contient "_" et son premier segment est dans geo_isg) # et n'est pas un nœud géographique pays_prefix = pays_node.split('_')[0] if '_' in pays_node else pays_node if '_' in pays_node and 'geographique' not in pays_node and pays_prefix in geo_isg: pays_name = pays_prefix # Extraire le pourcentage de l'attribut label de l'arête edge_label = G.edges[node, pays_node].get('label', '0%') pays_part = int(edge_label.strip('%')) if edge_label.strip('%').isdigit() else 0 # Chercher le nœud géographique correspondant geo_node = f"{pays_name}_geographique" isg_value = geo_isg.get(pays_name, 0) print(f" - Pays trouvé: {pays_name}, ISG: {isg_value}, Part: {pays_part}%, seuil: {isg_threshold}") if isg_value >= isg_threshold: results[operation_key]['pays_critiques'].append({ 'nom': pays_name, 'isg': isg_value, 'part': pays_part }) print(f" => Pays critique ajouté: {pays_name}, ISG: {isg_value}") # Chercher les acteurs liés à ce pays for acteur_node in G.successors(pays_node): # Vérifier que c'est bien un nœud d'acteur (format: NomActeur_Pays_Operation) if '_' in acteur_node: acteur_parts = acteur_node.split("_") if len(acteur_parts) >= 3 and acteur_parts[1] == pays_name: acteur_name = acteur_parts[0] # Ignorer si l'acteur a le même nom que le pays if acteur_name.lower() != pays_name.lower(): edge_label = G.edges[pays_node, acteur_node].get('label', '0%') acteur_part = int(edge_label.strip('%')) if edge_label.strip('%').isdigit() else 0 # Utiliser l'ISG du pays pour l'acteur if isg_value >= isg_threshold: results[operation_key]['acteurs_critiques'].append({ 'nom': acteur_name, 'pays': pays_name, 'isg': isg_value, 'part': acteur_part }) print(f" => Acteur critique ajouté: {acteur_name}, ISG: {isg_value}") # Résumé de l'analyse total_critical_countries = sum(len(data['pays_critiques']) for data in results.values()) total_critical_actors = sum(len(data['acteurs_critiques']) for data in results.values()) print(f"Analyse ISG terminée: {operations_count} opérations analysées") print(f"Résultats: {total_critical_countries} pays critiques, {total_critical_actors} acteurs critiques") return results # Function removed as no longer needed with the new approach def find_real_path(base_dir, resource_type, resource_name, file_pattern, G=None): """Recherche le chemin réel d'un fichier/dossier.""" # Obtenir le label du graphe si disponible resource_label = resource_name if G: for node, attrs in G.nodes(data=True): if node == resource_name and 'label' in attrs: resource_label = attrs['label'] break resource_name_lower = resource_name.lower() resource_label_lower = resource_label.lower() # Créer des variantes du label pour les cas spéciaux comme "SSD 2.5 pouces" -> "SSD 2.5" label_variants = [resource_label, resource_label_lower] # Ajouter une variante sans "pouces" ou autre suffixe if " " in resource_label: # Pour "SSD 2.5 pouces", ajouter "SSD 2.5" # Pour "Carte mère", ajouter "Carte" parts = resource_label.split(" ") if len(parts) >= 2: short_label = " ".join(parts[:2]) # Prendre les deux premiers mots label_variants.append(short_label) label_variants.append(short_label.lower()) # Version avec première lettre majuscule capitalized_label = resource_label_lower.title() label_variants.append(capitalized_label) short_capitalized = short_label.title() label_variants.append(short_capitalized) # Cas spécial pour les principaux assembleurs/fabricants is_principaux_pattern = "principaux" in file_pattern.lower() if is_principaux_pattern: # Rechercher les fichiers des principaux assembleurs/fabricants if resource_type == "produit": patterns = [] for variant in label_variants: patterns.extend([ f"Assemblage/Fiche assemblage {variant}/??-principaux-assembleurs.md", f"Assemblage/Fiche*{variant}*/??-principaux-assembleurs.md", f"**/*{variant}*/*principaux*assembleurs*.md" ]) # Ajouter les patterns avec le nom du nœud aussi patterns.extend([ f"Assemblage/Fiche assemblage {resource_name}/??-principaux-assembleurs.md", f"Assemblage/Fiche assemblage {resource_name_lower}/??-principaux-assembleurs.md", f"Assemblage/Fiche*{resource_name}*/??-principaux-assembleurs.md", f"Assemblage/Fiche*{resource_name_lower}*/??-principaux-assembleurs.md", f"**/*{resource_name}*/*principaux*assembleurs*.md", f"**/*{resource_name_lower}*/*principaux*assembleurs*.md" ]) elif resource_type == "composant": patterns = [] for variant in label_variants: patterns.extend([ f"Fabrication/Fiche fabrication {variant}/??-principaux-fabricants.md", f"Fabrication/Fiche*{variant}*/??-principaux-fabricants.md", f"**/*{variant}*/*principaux*fabricants*.md" ]) # Ajouter les patterns avec le nom du nœud aussi patterns.extend([ f"Fabrication/Fiche fabrication {resource_name}/??-principaux-fabricants.md", f"Fabrication/Fiche fabrication {resource_name_lower}/??-principaux-fabricants.md", f"Fabrication/Fiche*{resource_name}*/??-principaux-fabricants.md", f"Fabrication/Fiche*{resource_name_lower}*/??-principaux-fabricants.md", f"**/*{resource_name}*/*principaux*fabricants*.md", f"**/*{resource_name_lower}*/*principaux*fabricants*.md" ]) elif resource_type == "minerai": operation = file_pattern.split("-")[-1] if "-" in file_pattern else "" patterns = [] for variant in label_variants: # S'assurer qu'il n'y a pas de tiret à la fin du nom du minerai clean_variant = variant.rstrip(" -") patterns.extend([ f"Minerai/Fiche minerai {clean_variant}/??-principaux-producteurs-{operation}.md", f"Minerai/Fiche {clean_variant}/??-principaux-producteurs-{operation}.md", f"Minerai/Fiche*minerai*{clean_variant}*/??-principaux-producteurs*.md", f"Minerai/Fiche*{clean_variant}*/??-principaux-producteurs*.md", f"**/*minerai*{clean_variant}*/*principaux*producteurs*.md", f"**/*{clean_variant}*/*principaux*producteurs*.md" ]) # Ajouter les patterns avec le nom du nœud aussi clean_resource = resource_name.rstrip(" -") clean_resource_lower = resource_name_lower.rstrip(" -") patterns.extend([ f"Minerai/Fiche minerai {clean_resource}/??-principaux-producteurs-{operation}.md", f"Minerai/Fiche {clean_resource}/??-principaux-producteurs-{operation}.md", f"Minerai/Fiche minerai {clean_resource_lower}/??-principaux-producteurs-{operation}.md", f"Minerai/Fiche {clean_resource_lower}/??-principaux-producteurs-{operation}.md", f"Minerai/Fiche*minerai*{clean_resource}*/??-principaux-producteurs*.md", f"Minerai/Fiche*{clean_resource}*/??-principaux-producteurs*.md", f"Minerai/Fiche*minerai*{clean_resource_lower}*/??-principaux-producteurs*.md", f"Minerai/Fiche*{clean_resource_lower}*/??-principaux-producteurs*.md", f"**/*minerai*{clean_resource}*/*principaux*producteurs*.md", f"**/*{clean_resource}*/*principaux*producteurs*.md", f"**/*minerai*{clean_resource_lower}*/*principaux*producteurs*.md", f"**/*{clean_resource_lower}*/*principaux*producteurs*.md" ]) else: # Pour les IHH, privilégier la recherche dans le répertoire centralisé if resource_type == "produit": patterns = [] for variant in label_variants: patterns.extend([ f"{IHH_ROOT_PATH}/*-assemblage-{variant}/*-indice-de-herfindahl-hirschmann.md", f"{IHH_ROOT_PATH}/*-{variant}/*-indice-de-herfindahl-hirschmann.md" ]) # Ajouter les patterns avec le nom du nœud aussi patterns.extend([ f"{IHH_ROOT_PATH}/*-assemblage-{resource_name}/*-indice-de-herfindahl-hirschmann.md", f"{IHH_ROOT_PATH}/*-assemblage-{resource_name_lower}/*-indice-de-herfindahl-hirschmann.md", f"{IHH_ROOT_PATH}/*-{resource_name}/*-indice-de-herfindahl-hirschmann.md", f"{IHH_ROOT_PATH}/*-{resource_name_lower}/*-indice-de-herfindahl-hirschmann.md" ]) elif resource_type == "composant": patterns = [] for variant in label_variants: patterns.extend([ f"{IHH_ROOT_PATH}/*-fabrication-{variant}/*-indice-de-herfindahl-hirschmann.md", f"{IHH_ROOT_PATH}/*-{variant}/*-indice-de-herfindahl-hirschmann.md" ]) # Ajouter les patterns avec le nom du nœud aussi patterns.extend([ f"{IHH_ROOT_PATH}/*-fabrication-{resource_name}/*-indice-de-herfindahl-hirschmann.md", f"{IHH_ROOT_PATH}/*-fabrication-{resource_name_lower}/*-indice-de-herfindahl-hirschmann.md", f"{IHH_ROOT_PATH}/*-{resource_name}/*-indice-de-herfindahl-hirschmann.md", f"{IHH_ROOT_PATH}/*-{resource_name_lower}/*-indice-de-herfindahl-hirschmann.md" ]) elif resource_type == "minerai": operation = file_pattern.split("-")[-1] if "-" in file_pattern else "" if operation: patterns = [] for variant in label_variants: patterns.extend([ # Nouvelle structure f"{IHH_ROOT_PATH}/*-opérations-{variant}/*-indice-de-herfindahl-hirschmann-{operation}.md", # Ancienne structure f"{IHH_ROOT_PATH}/*-{operation}-{variant}/*-indice-de-herfindahl-hirschmann.md", f"{IHH_ROOT_PATH}/*-{variant}*/*-{operation}*.md" ]) # Ajouter les patterns avec le nom du nœud aussi patterns.extend([ # Nouvelle structure f"{IHH_ROOT_PATH}/*-opérations-{resource_name}/*-indice-de-herfindahl-hirschmann-{operation}.md", f"{IHH_ROOT_PATH}/*-opérations-{resource_name_lower}/*-indice-de-herfindahl-hirschmann-{operation}.md", # Ancienne structure f"{IHH_ROOT_PATH}/*-{operation}-{resource_name}/*-indice-de-herfindahl-hirschmann.md", f"{IHH_ROOT_PATH}/*-{operation}-{resource_name_lower}/*-indice-de-herfindahl-hirschmann.md", f"{IHH_ROOT_PATH}/*-{resource_name}*/*-{operation}*.md", f"{IHH_ROOT_PATH}/*-{resource_name_lower}*/*-{operation}*.md" ]) else: patterns = [] for variant in label_variants: patterns.extend([ f"{IHH_ROOT_PATH}/*-opérations-{variant}/*.md", f"{IHH_ROOT_PATH}/*-{variant}*/*.md" ]) # Ajouter les patterns avec le nom du nœud aussi patterns.extend([ f"{IHH_ROOT_PATH}/*-opérations-{resource_name}/*.md", f"{IHH_ROOT_PATH}/*-opérations-{resource_name_lower}/*.md", f"{IHH_ROOT_PATH}/*-{resource_name}*/*.md", f"{IHH_ROOT_PATH}/*-{resource_name_lower}*/*.md" ]) # Chercher dans les patterns for pattern in patterns: matches = glob.glob(os.path.join(base_dir, pattern), recursive=True) if matches: return os.path.relpath(matches[0], base_dir) # Si aucun chemin réel n'est trouvé, construire un chemin générique dans la structure appropriée # Utiliser le label pour le chemin par défaut, et choisir la meilleure variante best_label = resource_label # Si le label contient "pouces" ou d'autres termes descriptifs longs, utiliser une version plus courte if " " in resource_label and len(resource_label.split(" ")) > 2: parts = resource_label.split(" ") best_label = " ".join(parts[:2]) # Prendre les deux premiers mots if resource_type == "produit": if is_principaux_pattern: return f"Assemblage/Fiche assemblage {best_label}/02-principaux-assembleurs.md" else: # Numéros arbitraires mais dans le format correct return f"{IHH_ROOT_PATH}/10-assemblage-{best_label}/00-indice-de-herfindahl-hirschmann.md" elif resource_type == "composant": if is_principaux_pattern: return f"Fabrication/Fiche fabrication {best_label}/02-principaux-fabricants.md" else: # Numéros arbitraires mais dans le format correct return f"{IHH_ROOT_PATH}/20-fabrication-{best_label}/00-indice-de-herfindahl-hirschmann.md" elif resource_type == "minerai": operation = file_pattern.split("-")[-1] if "-" in file_pattern else "" if is_principaux_pattern: # Choisir le numéro du préfixe en fonction de l'opération prefix_num = "03" if operation == "extraction" else "05" if operation == "traitement" else "02" # S'assurer que le nom du minerai est correctement formaté (sans tiret à la fin et en minuscules) clean_label = best_label.rstrip(" -").lower() return f"Minerai/Fiche minerai {clean_label}/{prefix_num}-principaux-producteurs-{operation}.md" else: # Numéros arbitraires mais dans le format correct return f"{IHH_ROOT_PATH}/30-{operation}-{best_label}/00-indice-de-herfindahl-hirschmann.md" return "" def categorize_elements(elements): """Catégorise les éléments en produits finaux, composants et minerais.""" produits = [] composants = [] minerais = [] for element in elements: resource = element['resource'].lower() if resource in ['serveur', 'smartphone', 'ordinateur', 'véhicule électrique']: produits.append(element) elif resource in ['boitier', 'cartemere', 'ssd25', 'ssdm2', 'batterie', 'puce', 'cpu', 'mémoire', 'écran', 'disquedur', 'connecteurs', 'connectivite']: composants.append(element) else: minerais.append(element) # On regroupe d'abord les minerais par ressource minerais_par_ressource = {} for m in minerais: resource = m['resource'] if resource not in minerais_par_ressource: minerais_par_ressource[resource] = [] minerais_par_ressource[resource].append(m) # Reconstruire la liste des minerais, groupés par ressource et triés alphabétiquement minerais = [] for resource in sorted(minerais_par_ressource.keys(), key=str.lower): minerais.extend(minerais_par_ressource[resource]) # Tri alphabétique des produits et composants produits.sort(key=lambda x: x['resource'].lower()) composants.sort(key=lambda x: x['resource'].lower()) return produits, composants, minerais def generate_template(elements, isg_data=None, G=None): """Génère le template de rapport avec les chemins d'accès aux sections.""" template = [] # Corps principal template.append("# Évaluation des vulnérabilités critiques") template.append("") template.append("## Introduction") template.append("Ce rapport vise à évaluer les vulnérabilités systémiques associées à la chaîne de valeur représentée dans le graphe fourni.") template.append("") # Éléments factuels template.append("## Éléments factuels") template.append("Cette section présente les données clés collectées automatiquement à partir du graphe et du corpus documentaire.") template.append("") # Catégoriser les éléments produits, composants, minerais = categorize_elements(elements) # Obtenir les labels des éléments à partir du graphe resource_labels = {} if G: for node, attrs in G.nodes(data=True): if 'label' in attrs: resource_labels[node] = attrs['label'] # Obtenir les labels des éléments à partir du graphe resource_labels = {} if G: for node, attrs in G.nodes(data=True): if 'label' in attrs: resource_labels[node] = attrs['label'] # Traiter les produits finaux for produit in produits: resource = produit['resource'] # Utiliser le label du graphe si disponible display_name = resource_labels.get(resource, resource) template.append(f"### {display_name}") template.append("") if produit['ihh_critique']: template.append(f"#### Assemblage {display_name}") template.append("") # Déterminer la couleur du seuil IHH ihh_pays_color = determine_threshold_color(produit['ihh_pays'], 'IHH') ihh_acteurs_color = determine_threshold_color(produit['ihh_acteurs'], 'IHH') ihh_pays_color_text = f" - seuil {ihh_pays_color.capitalize()}" if ihh_pays_color else "" ihh_acteurs_color_text = f" - seuil {ihh_acteurs_color.capitalize()}" if ihh_acteurs_color else "" template.append("##### Concentration") template.append(f"- IHH pays : {produit['ihh_pays']}{ihh_pays_color_text}") template.append(f"- IHH acteurs : {produit['ihh_acteurs']}{ihh_acteurs_color_text}") template.append("- Principaux pays producteurs : ...") template.append("- Principaux acteurs : ...") template.append("") # Vérifier si nous avons des données ISG pour cette opération if isg_data: operation_key = f"{produit['operation']}_{produit['resource']}" print(f"Vérification ISG pour produit: {operation_key}") if operation_key in isg_data: print(f" - Trouvé dans isg_data: pays={len(isg_data[operation_key]['pays_critiques'])}, acteurs={len(isg_data[operation_key]['acteurs_critiques'])}") if operation_key in isg_data and (isg_data[operation_key]['pays_critiques'] or isg_data[operation_key]['acteurs_critiques']): template.append("#### Stabilité Géopolitique") print(f" => Section ISG ajoutée pour {operation_key}") if isg_data[operation_key]['pays_critiques']: template.append("**Pays à risque critique** :") for pays in isg_data[operation_key]['pays_critiques']: isg_color = determine_threshold_color(pays['isg'], 'ISG') isg_color_text = f" - seuil {isg_color.capitalize()}" if isg_color else "" template.append(f"- {pays['nom']} (ISG: {pays['isg']}{isg_color_text}) - Production de {pays['part']}% des ressources") template.append("") if isg_data[operation_key]['acteurs_critiques']: template.append("**Acteurs à risque critique** :") for acteur in isg_data[operation_key]['acteurs_critiques']: isg_color = determine_threshold_color(acteur['isg'], 'ISG') isg_color_text = f" - seuil {isg_color.capitalize()}" if isg_color else "" template.append(f"- {acteur['nom']} ({acteur['pays']}, ISG: {acteur['isg']}{isg_color_text}) - Contrôle de {acteur['part']}% du marché") template.append("") elif produit['isg_critique']: template.append("#### Stabilité Géopolitique") template.append("- Analyse des risques géopolitiques non disponible") template.append("") # Traiter les composants for composant in composants: resource = composant['resource'] # Utiliser le label du graphe si disponible display_name = resource_labels.get(resource, resource) template.append(f"### {display_name}") template.append("") if composant['ihh_critique']: template.append(f"#### Fabrication {display_name}") template.append("") # Déterminer la couleur du seuil IHH ihh_pays_color = determine_threshold_color(composant['ihh_pays'], 'IHH') ihh_acteurs_color = determine_threshold_color(composant['ihh_acteurs'], 'IHH') ihh_pays_color_text = f" - seuil {ihh_pays_color.capitalize()}" if ihh_pays_color else "" ihh_acteurs_color_text = f" - seuil {ihh_acteurs_color.capitalize()}" if ihh_acteurs_color else "" template.append("##### Concentration") template.append(f"- IHH pays : {composant['ihh_pays']}{ihh_pays_color_text}") template.append(f"- IHH acteurs : {composant['ihh_acteurs']}{ihh_acteurs_color_text}") template.append("- Principaux pays producteurs : ...") template.append("- Principaux fabricants : ...") template.append("") # Vérifier si nous avons des données ISG pour cette opération if isg_data: operation_key = f"{composant['operation']}_{composant['resource']}" print(f"Vérification ISG pour composant: {operation_key}") if operation_key in isg_data: print(f" - Trouvé dans isg_data: pays={len(isg_data[operation_key]['pays_critiques'])}, acteurs={len(isg_data[operation_key]['acteurs_critiques'])}") if operation_key in isg_data and (isg_data[operation_key]['pays_critiques'] or isg_data[operation_key]['acteurs_critiques']): template.append("#### Stabilité Géopolitique") print(f" => Section ISG ajoutée pour {operation_key}") if isg_data[operation_key]['pays_critiques']: template.append("**Pays à risque critique** :") for pays in isg_data[operation_key]['pays_critiques']: isg_color = determine_threshold_color(pays['isg'], 'ISG') isg_color_text = f" - seuil {isg_color.capitalize()}" if isg_color else "" template.append(f"- {pays['nom']} (ISG: {pays['isg']}{isg_color_text}) - Production de {pays['part']}% des ressources") template.append("") if isg_data[operation_key]['acteurs_critiques']: template.append("**Acteurs à risque critique** :") for acteur in isg_data[operation_key]['acteurs_critiques']: isg_color = determine_threshold_color(acteur['isg'], 'ISG') isg_color_text = f" - seuil {isg_color.capitalize()}" if isg_color else "" template.append(f"- {acteur['nom']} ({acteur['pays']}, ISG: {acteur['isg']}{isg_color_text}) - Contrôle de {acteur['part']}% du marché") template.append("") elif composant['isg_critique']: template.append("#### Stabilité Géopolitique") template.append("- Analyse des risques géopolitiques non disponible") template.append("") # Regrouper les minerais par nom de ressource pour le tri alphabétique minerais_par_ressource = {} for minerai in minerais: resource = minerai['resource'] if resource not in minerais_par_ressource: minerais_par_ressource[resource] = [] minerais_par_ressource[resource].append(minerai) # Traiter les ressources dans l'ordre alphabétique for resource in sorted(minerais_par_ressource.keys(), key=str.lower): # Utiliser le label du graphe si disponible display_name = resource_labels.get(resource, resource) template.append(f"### {display_name}") template.append("") # Pour chaque ressource, traiter ses opérations minerais_resource = minerais_par_ressource[resource] # Identifier les types d'opérations disponibles pour cette ressource operations_disponibles = {'Extraction': None, 'Traitement': None} for m in minerais_resource: if m['operation'] in operations_disponibles: operations_disponibles[m['operation']] = m # Traiter les opérations dans un ordre spécifique (Extraction puis Traitement) operations_ordre = ['Extraction', 'Traitement'] for operation in operations_ordre: m = operations_disponibles.get(operation) if m and m['ihh_critique']: # Utiliser le label du graphe si disponible display_name = resource_labels.get(resource, resource) template.append(f"#### {operation} {display_name}") template.append("") # Déterminer la couleur du seuil IHH ihh_pays_color = determine_threshold_color(m['ihh_pays'], 'IHH') ihh_acteurs_color = determine_threshold_color(m['ihh_acteurs'], 'IHH') ihh_pays_color_text = f" - seuil {ihh_pays_color.capitalize()}" if ihh_pays_color else "" ihh_acteurs_color_text = f" - seuil {ihh_acteurs_color.capitalize()}" if ihh_acteurs_color else "" template.append("##### Concentration") template.append(f"- IHH pays : {m['ihh_pays']}{ihh_pays_color_text}") template.append(f"- IHH acteurs : {m['ihh_acteurs']}{ihh_acteurs_color_text}") template.append("- Principaux pays producteurs : ...") template.append("- Principaux acteurs : ...") template.append("") # Vérifier si nous avons des données ISG pour cette opération if isg_data: operation_key = f"{m['operation']}_{resource}" print(f"Vérification ISG pour minerai: {operation_key}") if operation_key in isg_data: print(f" - Trouvé dans isg_data: pays={len(isg_data[operation_key]['pays_critiques'])}, acteurs={len(isg_data[operation_key]['acteurs_critiques'])}") if operation_key in isg_data and (isg_data[operation_key]['pays_critiques'] or isg_data[operation_key]['acteurs_critiques']): template.append("#### Stabilité Géopolitique") print(f" => Section ISG ajoutée pour {operation_key}") if isg_data[operation_key]['pays_critiques']: template.append("**Pays à risque critique** :") for pays in isg_data[operation_key]['pays_critiques']: isg_color = determine_threshold_color(pays['isg'], 'ISG') isg_color_text = f" - seuil {isg_color.capitalize()}" if isg_color else "" template.append(f"- {pays['nom']} (ISG: {pays['isg']}{isg_color_text}) - Production de {pays['part']}% des ressources") template.append("") if isg_data[operation_key]['acteurs_critiques']: template.append("**Acteurs à risque critique** :") for acteur in isg_data[operation_key]['acteurs_critiques']: isg_color = determine_threshold_color(acteur['isg'], 'ISG') isg_color_text = f" - seuil {isg_color.capitalize()}" if isg_color else "" template.append(f"- {acteur['nom']} ({acteur['pays']}, ISG: {acteur['isg']}{isg_color_text}) - Contrôle de {acteur['part']}% du marché") template.append("") elif m['isg_critique']: template.append("#### Stabilité Géopolitique") template.append("- Analyse des risques géopolitiques non disponible") template.append("") if minerai['ihh_critique']: # Déterminer la couleur du seuil IHH ihh_color = determine_threshold_color(minerai['ihh_pays'], 'IHH') ihh_color_text = f" - seuil {ihh_color.capitalize()}" if ihh_color else "" template.append("#### Substituabilité") # Ajouter la section ICS pour les minerais ics_path = find_ics_path(resource) if ics_path: template.append(f"Corpus/{ics_path}") else: template.append("[résumé]") template.append("") template.append("#### Concurrence") # Rechercher l'IVC pour ce minerai ivc_info = find_ivc_path(resource) if ivc_info: template.append(f"IVC: {ivc_info['ivc_value']} - seuil {ivc_info['threshold'].capitalize()}") template.append("") # Ajouter le lien vers les secteurs concurrents secteurs_path = f"{ivc_info['path']}/01-secteurs-concurrents.md" if os.path.exists(os.path.join(CORPUS_DIR, secteurs_path)): template.append(f"Corpus/{secteurs_path}") else: template.append("[Information sur les secteurs concurrents non disponible]") else: template.append("[résumé]") template.append("") # Annexes template.append("## Annexes") template.append("Contenus documentaires complets :") template.append("") # Indices documentaires try: has_ihh = any(element['ihh_critique'] for element in elements) except: has_ihh = False try: has_isg = any(element['isg_critique'] for element in elements) except: has_isg = False try: has_ivc = any(element['ivc_critique'] for element in elements) except: has_ivc = False try: has_ics = any(element['ics_critique'] for element in elements) except: has_ics = False if has_ihh: template.append("### Indice Herfindahl-Hirschmann (IHH)") template.append("") template.append("#### Contexte et objectif") template.append(f"Corpus/{IHH_ROOT_PATH}/00-contexte-et-objectif.md") template.append("") template.append("#### Mode de calcul") template.append(f"Corpus/{IHH_ROOT_PATH}/01-mode-de-calcul/_intro.md") template.append("") template.append("#### Seuils") template.append(f"Corpus/{IHH_ROOT_PATH}/01-mode-de-calcul/00-seuils-d-interprétation.md") template.append("") if has_isg: template.append("### Indice de Stabilité Géopolitique (ISG)") template.append("") template.append("#### Contexte et objectif") template.append(f"Corpus/{ISG_ROOT_PATH}/00-contexte-et-objectif.md") template.append("") template.append("#### Mode de calcul") template.append(f"Corpus/{ISG_ROOT_PATH}/01-mode-de-calcul/_intro.md") template.append("") template.append("#### Seuils") template.append(f"Corpus/{ISG_ROOT_PATH}/01-mode-de-calcul/04-seuils-d-interprétation.md") template.append("") if has_ivc: template.append("### Indice de Vulnérabilité Concurrentielle (IVC)") template.append("") template.append("#### Contexte et objectif") template.append(f"Corpus/{IVC_ROOT_PATH}/00-contexte-et-objectif.md") template.append("") template.append("#### Mode de calcul") template.append(f"Corpus/{IVC_ROOT_PATH}/01-mode-de-calcul/_intro.md") template.append("") template.append("#### Paramètres") template.append(f"Corpus/{IVC_ROOT_PATH}/01-mode-de-calcul/00-paramètres.md") template.append("") template.append("#### Seuils") template.append(f"Corpus/{IVC_ROOT_PATH}/01-mode-de-calcul/01-seuils-d-interprétation.md") template.append("") if has_ics: template.append("### Indice de Criticité de Substituabilité (ICS)") template.append("") template.append("#### Contexte et objectif") template.append(f"Corpus/{ICS_ROOT_PATH}/00-contexte-et-objectif.md") template.append("") template.append("#### Mode de calcul") template.append(f"Corpus/{ICS_ROOT_PATH}/01-mode-de-calcul/_intro.md") template.append("") template.append("#### Paramètres") template.append(f"Corpus/{ICS_ROOT_PATH}/01-mode-de-calcul/00-paramètres.md") template.append("") template.append("#### Seuils") template.append(f"Corpus/{ICS_ROOT_PATH}/01-mode-de-calcul/01-seuils-d-interprétation.md") template.append("") # Détails des éléments critiques for produit in produits: resource = produit['resource'] # Utiliser le label du graphe si disponible display_name = resource_labels.get(resource, resource) template.append(f"### {display_name}") template.append("") if produit['ihh_critique']: template.append("#### Indice de Herfindahl-Hirschmann") ihh_path = find_real_path(CORPUS_DIR, "produit", resource, "indice-de-herfindahl-hirschmann", G) template.append(f"Corpus/{ihh_path}") template.append("") template.append("#### Principaux assembleurs") assembleurs_path = find_real_path(CORPUS_DIR, "produit", resource, "principaux-assembleurs", G) template.append(f"Corpus/{assembleurs_path}") template.append("") for composant in composants: resource = composant['resource'] # Utiliser le label du graphe si disponible display_name = resource_labels.get(resource, resource) template.append(f"### {display_name}") template.append("") if composant['ihh_critique']: template.append("#### Indice de Herfindahl-Hirschmann") ihh_path = find_real_path(CORPUS_DIR, "composant", resource, "indice-de-herfindahl-hirschmann", G) template.append(f"Corpus/{ihh_path}") template.append("") template.append("#### Principaux fabricants") fabricants_path = find_real_path(CORPUS_DIR, "composant", resource, "principaux-fabricants", G) template.append(f"Corpus/{fabricants_path}") template.append("") # Détails des minerais - on traite Extraction puis Traitement # Créer un dictionnaire pour regrouper les entrées par ressource minerais_par_ressource = {} for minerai in minerais: resource = minerai['resource'] if resource not in minerais_par_ressource: minerais_par_ressource[resource] = [] minerais_par_ressource[resource].append(minerai) # Parcourir les ressources dans l'ordre alphabétique for resource in sorted(minerais_par_ressource.keys(), key=str.lower): # Utiliser le label du graphe si disponible display_name = resource_labels.get(resource, resource) template.append(f"### {display_name}") template.append("") # Identifier les types d'opérations disponibles pour cette ressource operations_disponibles = {'Extraction': None, 'Traitement': None} for m in minerais_par_ressource[resource]: if m['operation'] in operations_disponibles: operations_disponibles[m['operation']] = m # Traiter les opérations dans un ordre spécifique (Extraction puis Traitement) operations_ordre = ['Extraction', 'Traitement'] for operation in operations_ordre: m = operations_disponibles.get(operation) if m and m['ihh_critique']: # Utiliser le label du graphe si disponible display_name = resource_labels.get(resource, resource) template.append(f"#### Indice de Herfindahl-Hirschmann - {operation}") ihh_path = find_real_path(CORPUS_DIR, "minerai", resource, f"indice-de-herfindahl-{operation.lower()}", G) if ihh_path: template.append(f"Corpus/{ihh_path}") else: # Chercher d'abord avec la nouvelle structure new_pattern = f"{IHH_ROOT_PATH}/*-opérations-{resource.lower()}/*-indice-de-herfindahl-hirschmann-{operation.lower()}.md" matches = glob.glob(os.path.join(CORPUS_DIR, new_pattern)) if matches: template.append(f"Corpus/{os.path.relpath(matches[0], CORPUS_DIR)}") else: # Utiliser l'ancienne structure template.append(f"Corpus/{IHH_ROOT_PATH}/30-{operation.lower()}-{resource}/00-indice-de-herfindahl-hirschmann.md") template.append("") template.append(f"##### Principaux producteurs - {operation}") producteurs_path = find_real_path(CORPUS_DIR, "minerai", resource, f"principaux-producteurs-{operation.lower()}", G) if producteurs_path: template.append(f"Corpus/{producteurs_path}") else: template.append(f"Corpus/Minerai/Fiche {resource}/02-principaux-producteurs-{operation.lower()}.md") template.append("") # Ajouter la section IVC pour les minerais ivc_info = find_ivc_path(resource) if ivc_info: template.append("#### Indice de Vulnérabilité de Concurrence (IVC)") # Ajouter les liens vers les différents fichiers de la fiche IVC ivc_sections = [ "03-répartition-des-usages.md", "04-tendance.md", "05-procédés-alternatifs.md", "06-réserves.md" ] for section in ivc_sections: section_path = f"{ivc_info['path']}/{section}" if os.path.exists(os.path.join(CORPUS_DIR, section_path)): template.append(f"Corpus/{section_path}") template.append("") # Ajouter la section ICS pour les minerais if G: ics_components = find_ics_component_paths(resource, G) if ics_components: template.append("#### Indice de Criticité de Substituabilité (ICS)") template.append("") for component_name, info in ics_components.items(): template.append(f"##### {info['label']}") template.append("") for file_path in info['files']: template.append(f"Corpus/{file_path}") template.append("") return "\n".join(template) def find_ivc_path(resource): """Trouve le chemin de la fiche IVC pour un minerai donné. Args: resource: Le nom du minerai (par exemple 'Cuivre', 'Antimoine') Returns: Un dictionnaire contenant les informations IVC ou None si non trouvé """ # Normaliser le nom de la ressource en minuscules resource_lower = resource.lower() # Chercher dans le répertoire Criticités avec caractères accentués ivc_base_dir = "Criticités" ivc_dir = CORPUS_DIR / ivc_base_dir / "Fiche technique IVC" if not os.path.exists(ivc_dir): print(f"Répertoire IVC introuvable: {ivc_dir}") return None # Utiliser glob pattern pour chercher les dossiers IVC contenant le nom du minerai # Format attendu dans les dossiers: "XX-minerai-ivc-VALEUR-vulnérabilité-SEUIL" search_pattern = os.path.join(ivc_dir, f"*{resource_lower}*ivc*") matching_dirs = [] # Vérifier d'abord avec le nom exact du minerai for item_path in glob.glob(search_pattern, recursive=False): if os.path.isdir(item_path): item = os.path.basename(item_path) matching_dirs.append((item, item_path)) # Si aucune correspondance exacte, essayer avec une recherche plus générale if not matching_dirs: for item_path in glob.glob(os.path.join(ivc_dir, "*ivc*"), recursive=False): if os.path.isdir(item_path): item = os.path.basename(item_path) if resource_lower in item.lower(): matching_dirs.append((item, item_path)) # Traiter les correspondances trouvées for item, item_path in matching_dirs: try: # Format attendu: "XX-minerai-ivc-VALEUR-vulnérabilité-SEUIL" parts = item.split('-') ivc_index = parts.index('ivc') ivc_value = int(parts[ivc_index + 1]) # Déterminer le seuil threshold = determine_threshold_color(ivc_value, 'IVC') relative_path = os.path.join(ivc_base_dir, "Fiche technique IVC", item) return { 'path': relative_path, 'ivc_value': ivc_value, 'threshold': threshold } except (ValueError, IndexError) as e: print(f"Erreur lors de l'analyse du chemin IVC {item}: {e}") continue print(f"Aucune fiche IVC trouvée pour {resource}") return None def write_template(template, output_path): """Écrit le template dans un fichier.""" with open(output_path, 'w', encoding='utf-8') as f: f.write(template) print(f"✅ Template généré : {output_path}") def find_ics_path(resource): """Trouve le chemin de la fiche ICS pour un minerai donné.""" resource_lower = resource.lower() # Utiliser le chemin relatif pour éviter les problèmes d'encodage ics_dir = os.path.join(CORPUS_DIR, CRIT_PATH, "Fiche technique ICS") if not os.path.exists(ics_dir): print(f"Répertoire ICS introuvable: {ics_dir}") return None # Rechercher un fichier qui contient le nom du minerai for item in os.listdir(ics_dir): item_path = os.path.join(ics_dir, item) # Recherche à la fois par nom exact et par inclusion dans le nom du fichier if os.path.isfile(item_path) and (resource_lower in item.lower() or item.lower().endswith(f"-{resource_lower}.md")): return os.path.join(ICS_ROOT_PATH, item) print(f"Aucune fiche ICS trouvée pour {resource}") return None def find_ics_component_paths(resource, G): """Trouve les sous-répertoires ICS pour les relations entre composants et un minerai.""" resource_lower = resource.lower() ics_dir = os.path.join(CORPUS_DIR, CRIT_PATH, "Fiche technique ICS") component_info = {} if not os.path.exists(ics_dir): print(f"Répertoire ICS introuvable: {ics_dir}") return component_info # Rechercher des répertoires correspondant au format attendu for item in os.listdir(ics_dir): item_path = os.path.join(ics_dir, item) if os.path.isdir(item_path): # Format attendu: XX-composant-minerai-coefficient-Y-YY parts = item.lower().split('-') if len(parts) >= 4 and resource_lower in parts: # Trouver le composant en cherchant dans les parties avant 'coefficient' component_idx = -1 for i, part in enumerate(parts): if part == "coefficient": component_idx = i - 2 # Le composant est 2 segments avant "coefficient" break if component_idx >= 0 and component_idx < len(parts): component_name = parts[component_idx] # Rechercher le label du composant dans le graphe et vérifier qu'il existe label, exists_in_graph = get_component_label(G, component_name) # Ne continuer que si le composant existe dans le graphe if exists_in_graph: # Préparer la liste des fichiers files = [] # D'abord vérifier si _intro.md existe et l'ajouter en premier intro_file = os.path.join(item_path, "_intro.md") if os.path.exists(intro_file): files.append(os.path.join(ICS_ROOT_PATH, item, "_intro.md")) # Ensuite ajouter les fichiers importants dans l'ordre important_files = ['faisabilité-technique', 'délai-d-implémentation', 'impact-coût'] for prefix in important_files: for file in sorted(os.listdir(item_path)): if file.endswith('.md') and prefix in file.lower(): files.append(os.path.join(ICS_ROOT_PATH, item, file)) # Ne pas ajouter les fichiers de sources ou autres non nécessaires excluded_files = ["sources.md", "03-sources.md"] for file in sorted(os.listdir(item_path)): file_path = os.path.join(ICS_ROOT_PATH, item, file) if (file.endswith('.md') and file_path not in files and not any(excluded in file.lower() for excluded in excluded_files) and file != "_intro.md"): # Ne pas ajouter _intro.md à nouveau files.append(file_path) component_info[component_name] = { 'label': label, 'files': files } return component_info def get_component_label(G, component_name): """Récupère le label d'un composant à partir du graphe. Retourne: Un tuple (label, exists_in_graph) où: - label est le nom formaté du composant - exists_in_graph est un booléen indiquant si le composant existe dans le graphe """ # D'abord, essayer de trouver une correspondance exacte for node, attrs in G.nodes(data=True): if node.lower() == component_name.lower(): if 'label' in attrs: return attrs['label'], True else: return component_name.capitalize(), True # Si pas de correspondance exacte, chercher un nœud qui contient le nom du composant for node, attrs in G.nodes(data=True): if component_name.lower() in node.lower(): if 'label' in attrs: return attrs['label'], True else: return component_name.capitalize(), True # Si toujours pas trouvé, le composant n'existe pas dans le graphe return component_name.capitalize(), False def main(): """Fonction principale.""" # Analyser les arguments de la ligne de commande parser = argparse.ArgumentParser(description='Génère un rapport de vulnérabilités') parser.add_argument('--filter', choices=['all', 'orange', 'rouge'], default='all', help='Niveau de criticité pour l\'ISG: "all" (orange+rouge), "orange" ou "rouge"') args = parser.parse_args() # Déterminer les niveaux de filtrage filter_levels = None if args.filter == 'orange': filter_levels = ['orange'] elif args.filter == 'rouge': filter_levels = ['rouge'] else: # 'all' filter_levels = ['orange', 'rouge'] print("=== Début de la génération du template ===") elements = parse_graph(GRAPH_PATH, filter_level=args.filter) print(f"Éléments extraits du graphe: {len(elements)}") # Analyser la stabilité géopolitique print("\n=== Analyse de la stabilité géopolitique ===") isg_data = analyze_geopolitical_stability(GRAPH_PATH, filter_levels=filter_levels) # Vérifier les données ISG print("\n=== Résumé des données ISG ===") found_isg_data = False for op_key, data in isg_data.items(): if data['pays_critiques'] or data['acteurs_critiques']: found_isg_data = True print(f"Opération {op_key}:") if data['pays_critiques']: print(f" - {len(data['pays_critiques'])} pays critiques") for pays in data['pays_critiques']: print(f" * {pays['nom']} (ISG: {pays['isg']}, part: {pays['part']}%)") if data['acteurs_critiques']: print(f" - {len(data['acteurs_critiques'])} acteurs critiques") for acteur in data['acteurs_critiques']: print(f" * {acteur['nom']} ({acteur['pays']}, ISG: {acteur['isg']}, part: {acteur['part']}%)") if not found_isg_data: print("Aucune donnée ISG critique trouvée.") # Charger le graphe pour obtenir les labels des composants G = read_dot(GRAPH_PATH) # Générer le template avec les données ISG et le graphe print("\n=== Génération du template ===") template = generate_template(elements, isg_data, G) write_template(template, OUTPUT_PATH) print("=== Fin de la génération du template ===") if __name__ == "__main__": main()