diff --git a/IA/analyze_graph.py b/IA/analyze_graph.py new file mode 100644 index 0000000..108142c --- /dev/null +++ b/IA/analyze_graph.py @@ -0,0 +1,209 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Script d'analyse de la structure du graphe DOT pour comprendre +comment intégrer l'ISG dans le générateur de template. +""" + +import os +from pathlib import Path +from networkx.drawing.nx_agraph import read_dot + +# Chemins +BASE_DIR = Path(__file__).resolve().parent +GRAPH_PATH = BASE_DIR / "graphe.dot" + +def analyze_graph_structure(dot_path): + """Analyse la structure du graphe et affiche ses caractéristiques.""" + print(f"Analyse du fichier: {dot_path}") + + # Lire le graphe + G = read_dot(dot_path) + + # Informations de base + print(f"Nombre total de nœuds: {len(G.nodes())}") + print(f"Nombre total d'arêtes: {len(G.edges())}") + + # Analyse des attributs des nœuds + node_attrs = {} + for node, attrs in G.nodes(data=True): + for key in attrs: + if key not in node_attrs: + node_attrs[key] = set() + node_attrs[key].add(attrs[key]) + + print("\nAttributs des nœuds:") + for attr, values in node_attrs.items(): + print(f"- {attr}: {len(values)} valeurs différentes") + if len(values) < 20: # Afficher seulement si le nombre de valeurs est raisonnable + print(f" Valeurs: {', '.join(sorted(values))}") + + # Analyse des niveaux (si l'attribut existe) + if 'level' in node_attrs: + print("\nAnalyse par niveau:") + levels = {} + for node, attrs in G.nodes(data=True): + if 'level' in attrs: + level = attrs['level'] + if level not in levels: + levels[level] = [] + levels[level].append(node) + + for level, nodes in sorted(levels.items()): + print(f"- Niveau {level}: {len(nodes)} nœuds") + # Afficher quelques exemples + if len(nodes) < 5: + print(f" Exemples: {', '.join(nodes)}") + else: + print(f" Exemples: {', '.join(nodes[:3])}... (et {len(nodes)-3} autres)") + + # Analyse des attributs ISG + print("\nRecherche des attributs ISG:") + isg_nodes = [] + for node, attrs in G.nodes(data=True): + if 'isg' in attrs: + isg_nodes.append((node, attrs['isg'])) + + if isg_nodes: + print(f"- {len(isg_nodes)} nœuds avec attribut ISG") + print(" Exemples:") + for node, isg in isg_nodes[:5]: + print(f" - {node}: ISG = {isg}") + else: + print("- Aucun nœud avec attribut ISG trouvé") + + # Analyse des connexions pour les nœuds critiques (IHH) + print("\nAnalyse des nœuds avec IHH:") + ihh_nodes = [] + for node, attrs in G.nodes(data=True): + if 'ihh_pays' in attrs or 'ihh_acteurs' in attrs: + ihh_value_pays = attrs.get('ihh_pays', 'N/A') + ihh_value_acteurs = attrs.get('ihh_acteurs', 'N/A') + ihh_nodes.append((node, ihh_value_pays, ihh_value_acteurs)) + + if ihh_nodes: + print(f"- {len(ihh_nodes)} nœuds avec attributs IHH") + print(" Exemples:") + for node, ihh_pays, ihh_acteurs in ihh_nodes[:5]: + print(f" - {node}: IHH pays = {ihh_pays}, IHH acteurs = {ihh_acteurs}") + + # Analyser les connexions de ce nœud + print(f" Connexions sortantes:") + out_edges = list(G.out_edges(node)) + if out_edges: + for i, (_, target) in enumerate(out_edges[:3]): + print(f" - Vers {target}") + if len(out_edges) > 3: + print(f" - ... et {len(out_edges)-3} autres") + else: + print(" - Aucune connexion sortante") + + print(f" Connexions entrantes:") + in_edges = list(G.in_edges(node)) + if in_edges: + for i, (source, _) in enumerate(in_edges[:3]): + print(f" - Depuis {source}") + if len(in_edges) > 3: + print(f" - ... et {len(in_edges)-3} autres") + else: + print(" - Aucune connexion entrante") + else: + print("- Aucun nœud avec attributs IHH trouvé") + + # Vérifier si un nœud a un attribut de niveau 99 (ISG supposé) + print("\nRecherche des nœuds de niveau 99 (ISG):") + level_99_nodes = [] + for node, attrs in G.nodes(data=True): + if attrs.get('level') == '99': + level_99_nodes.append(node) + + if level_99_nodes: + print(f"- {len(level_99_nodes)} nœuds de niveau 99") + print(" Exemples:") + for node in level_99_nodes[:5]: + print(f" - {node}") + # Analyser les connexions de ce nœud + print(f" Connexions entrantes:") + in_edges = list(G.in_edges(node)) + if in_edges: + for i, (source, _) in enumerate(in_edges[:3]): + print(f" - Depuis {source}") + if len(in_edges) > 3: + print(f" - ... et {len(in_edges)-3} autres") + else: + print(" - Aucune connexion entrante") + else: + print("- Aucun nœud de niveau 99 trouvé") + +def check_isg_paths(dot_path): + """Vérifie les chemins entre les nœuds critiques (IHH) et les nœuds ISG.""" + print("\nAnalyse des chemins entre nœuds IHH et nœuds ISG:") + + # Lire le graphe + G = read_dot(dot_path) + + # Identifier les nœuds avec IHH + ihh_nodes = [] + for node, attrs in G.nodes(data=True): + 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) + if ihh_pays > 25 or ihh_acteurs > 25: # Seuil critique + ihh_nodes.append(node) + except (ValueError, TypeError): + pass + + if not ihh_nodes: + print("- Aucun nœud IHH critique trouvé") + return + + print(f"- {len(ihh_nodes)} nœuds IHH critiques identifiés") + + # Pour chaque nœud IHH critique, chercher des chemins vers des nœuds ISG + for node in ihh_nodes[:5]: # Limiter à 5 exemples + print(f"\n Analyse des chemins pour {node}:") + + # Analyser les voisins directs + successors = list(G.successors(node)) + print(f" - {len(successors)} successeurs directs") + + if successors: + for succ in successors[:3]: + print(f" - Vers {succ}") + + # Vérifier les attributs de ce successeur + succ_attrs = G.nodes[succ] + print(f" Attributs: {', '.join(f'{k}={v}' for k, v in succ_attrs.items() if k in ['level', 'isg'])}") + + # Chercher les successeurs de niveau 2 + succ2 = list(G.successors(succ)) + print(f" {len(succ2)} successeurs de niveau 2") + + if succ2: + for s2 in succ2[:2]: + print(f" - Vers {s2}") + s2_attrs = G.nodes[s2] + print(f" Attributs: {', '.join(f'{k}={v}' for k, v in s2_attrs.items() if k in ['level', 'isg'])}") + + # Chercher encore plus loin si nécessaire + succ3 = list(G.successors(s2)) + print(f" {len(succ3)} successeurs de niveau 3") + + if succ3: + for s3 in succ3[:2]: + print(f" - Vers {s3}") + s3_attrs = G.nodes[s3] + print(f" Attributs: {', '.join(f'{k}={v}' for k, v in s3_attrs.items() if k in ['level', 'isg'])}") + +def main(): + """Fonction principale.""" + print("=== Analyse de la structure du graphe ===") + analyze_graph_structure(GRAPH_PATH) + + print("\n=== Analyse des chemins entre nœuds critiques et ISG ===") + check_isg_paths(GRAPH_PATH) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/IA/check_paths.py b/IA/check_paths.py new file mode 100644 index 0000000..a0a14c9 --- /dev/null +++ b/IA/check_paths.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import os +import re +import sys +from collections import defaultdict + +def extract_paths(file_path): + """Extrait tous les chemins du fichier rapport_template.md""" + paths = [] + try: + with open(file_path, 'r', encoding='utf-8') as f: + for line in f: + # Extraire les lignes qui commencent par "Corpus/" + if line.strip().startswith("Corpus/"): + paths.append(line.strip()) + except Exception as e: + print(f"Erreur lors de la lecture du fichier {file_path}: {e}") + sys.exit(1) + + return paths + +def check_paths(paths, base_dir): + """Vérifie si les chemins existent dans le système de fichiers""" + results = { + "existing": [], + "missing": [], + "problematic": [] # Chemins qui pourraient nécessiter des corrections + } + + for path in paths: + # Vérifier si le chemin est absolu ou relatif + abs_path = os.path.join(base_dir, path) + + if os.path.exists(abs_path): + results["existing"].append(path) + else: + # Essayer de détecter des problèmes potentiels + problem_detected = False + + # Vérifier les chemins avec "Fiche minerai" ou "Fiche fabrication" + if "Fiche minerai" in path or "Fiche fabrication" in path: + # Problème courant: mauvaise casse ou absence du mot "minerai" + path_lower = path.lower() + if "minerai" not in path_lower and "/minerai/" in path_lower: + corrected_path = path.replace("/Fiche ", "/Fiche minerai ") + if os.path.exists(os.path.join(base_dir, corrected_path)): + results["problematic"].append((path, corrected_path, "Mot 'minerai' manquant")) + problem_detected = True + + # Vérifier les chemins SSD + if "SSD25" in path: + corrected_path = path.replace("SSD25", "SSD 2.5") + if os.path.exists(os.path.join(base_dir, corrected_path)): + results["problematic"].append((path, corrected_path, "Format 'SSD25' au lieu de 'SSD 2.5'")) + problem_detected = True + + # Si aucun problème spécifique n'a été détecté, marquer comme manquant + if not problem_detected: + results["missing"].append(path) + + return results + +def find_similar_paths(missing_path, base_dir): + """Essaie de trouver des chemins similaires pour aider à diagnostiquer le problème""" + missing_parts = missing_path.split('/') + similar_paths = [] + + # Rechercher dans les sous-répertoires correspondants + search_dir = os.path.join(base_dir, *missing_parts[:-1]) + if os.path.exists(search_dir): + for file in os.listdir(search_dir): + if file.endswith('.md'): + similar_path = os.path.join(search_dir, file).replace(base_dir + '/', '') + similar_paths.append(similar_path) + + # Si aucun chemin similaire n'est trouvé, remonter d'un niveau + if not similar_paths and len(missing_parts) > 2: + parent_dir = os.path.join(base_dir, *missing_parts[:-2]) + if os.path.exists(parent_dir): + for dir_name in os.listdir(parent_dir): + if dir_name.lower() in missing_parts[-2].lower(): + dir_path = os.path.join(parent_dir, dir_name) + if os.path.isdir(dir_path): + for file in os.listdir(dir_path): + if file.endswith('.md'): + similar_path = os.path.join(dir_path, file).replace(base_dir + '/', '') + similar_paths.append(similar_path) + + return similar_paths + +def main(): + # Vérifier que nous sommes dans le bon répertoire + script_dir = os.path.dirname(os.path.abspath(__file__)) + base_dir = script_dir + + # Chemin vers le rapport_template.md + template_path = os.path.join(base_dir, "Corpus", "rapport_template.md") + + if not os.path.exists(template_path): + print(f"Erreur: Le fichier {template_path} n'existe pas.") + sys.exit(1) + + print("=== Vérification des chemins dans rapport_template.md ===") + + # Extraire les chemins + paths = extract_paths(template_path) + print(f"Nombre total de chemins trouvés: {len(paths)}") + + # Vérifier les chemins + results = check_paths(paths, base_dir) + + # Afficher les résultats + print("\n=== Résultats ===") + print(f"Chemins existants: {len(results['existing'])}") + print(f"Chemins manquants: {len(results['missing'])}") + print(f"Chemins problématiques: {len(results['problematic'])}") + + # Afficher les chemins manquants + if results["missing"]: + print("\n=== Chemins manquants ===") + for path in results["missing"]: + print(f"- {path}") + similar = find_similar_paths(path, base_dir) + if similar: + print(" Chemins similaires trouvés:") + for sim_path in similar[:3]: # Limiter à 3 suggestions + print(f" * {sim_path}") + + # Afficher les chemins problématiques avec suggestions + if results["problematic"]: + print("\n=== Chemins problématiques ===") + for orig, corrected, reason in results["problematic"]: + print(f"- {orig}") + print(f" Suggestion: {corrected}") + print(f" Raison: {reason}") + + # Résumé + if not results["missing"] and not results["problematic"]: + print("\nTous les chemins dans le rapport sont valides !") + else: + print("\nDes chemins problématiques ont été détectés. Veuillez corriger les erreurs.") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/IA/generate_template.py b/IA/generate_template.py new file mode 100644 index 0000000..d1696b5 --- /dev/null +++ b/IA/generate_template.py @@ -0,0 +1,1267 @@ +#!/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() diff --git a/IA/replace_paths.py b/IA/replace_paths.py new file mode 100644 index 0000000..cc3bfb2 --- /dev/null +++ b/IA/replace_paths.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +Script pour remplacer les références de chemins dans le rapport par le contenu des fichiers. +Ajuste automatiquement les niveaux de titres pour maintenir la hiérarchie. +""" + +import os +import re +from pathlib import Path + +# Chemins de base +BASE_DIR = Path(__file__).resolve().parent +BASE_DIR = BASE_DIR / ".." +CORPUS_DIR = BASE_DIR / "Corpus" +INPUT_PATH = CORPUS_DIR / "rapport_template.md" +OUTPUT_PATH = CORPUS_DIR / "rapport_final.md" + +def determine_heading_level(line): + """Détermine le niveau de titre d'une ligne.""" + match = re.match(r'^(#+)\s+', line) + if match: + return len(match.group(1)) + return 0 + +def determine_parent_level(lines, current_index): + """Détermine le niveau de titre parent pour une ligne donnée.""" + # Remonter dans les lignes précédentes pour trouver le titre parent + for i in range(current_index - 1, -1, -1): + level = determine_heading_level(lines[i]) + if level > 0: + return level + return 0 + +def adjust_heading_levels(content, parent_level, is_intro_file=False, is_ivc_section=False): + """Ajuste les niveaux de titres dans le contenu pour s'adapter à la hiérarchie.""" + lines = content.split('\n') + + # Si le contenu est vide, retourner une chaîne vide + if not lines: + return "" + + # Déterminer le niveau minimum de titre dans le contenu original + min_level = 10 + for line in lines: + level = determine_heading_level(line) + if level > 0 and level < min_level: + min_level = level + + # Si aucun titre trouvé, simplement supprimer la première ligne si nécessaire + if min_level == 10: + if not is_intro_file and not is_ivc_section and lines: + return '\n'.join(lines[1:]) + return content + + # Traitement spécial pour les fichiers IVC et intro + if is_ivc_section or is_intro_file: + adjusted_lines = [] + # Pour les fichiers IVC ou intro, on garde toutes les lignes mais on ajuste les niveaux des titres + for line in lines: + level = determine_heading_level(line) + if level > 0: + # Nouveau niveau = niveau parent + 1 + (niveau actuel - min_level) + new_level = parent_level + 1 + (level - min_level) + # S'assurer que le niveau ne dépasse pas 6 (limite en markdown) + new_level = min(new_level, 6) + line = re.sub(r'^#+\s+', '#' * new_level + ' ', line) + adjusted_lines.append(line) + else: + # Pour les fichiers standards, on supprime la première ligne + lines = lines[1:] + adjusted_lines = [] + # Ajuster les niveaux de titres pour les lignes restantes + for line in lines: + level = determine_heading_level(line) + if level > 0: + # Nouveau niveau = niveau parent + 1 + (niveau actuel - min_level) + new_level = parent_level + 1 + (level - min_level) + # S'assurer que le niveau ne dépasse pas 6 (limite en markdown) + new_level = min(new_level, 6) + line = re.sub(r'^#+\s+', '#' * new_level + ' ', line) + adjusted_lines.append(line) + + return '\n'.join(adjusted_lines) + +def process_report(): + """Traite le rapport pour remplacer les chemins par le contenu.""" + if not os.path.exists(INPUT_PATH): + print(f"Fichier d'entrée introuvable: {INPUT_PATH}") + return + + # Lire le rapport + with open(INPUT_PATH, 'r', encoding='utf-8') as f: + lines = f.readlines() + + output_lines = [] + i = 0 + while i < len(lines): + line = lines[i].strip() + + # Vérifier si la ligne est un chemin + if line.startswith('Corpus/'): + path = line + full_path = BASE_DIR / path + + # Déterminer le niveau de titre parent + parent_level = determine_parent_level(lines, i) + + try: + # Lire le contenu du fichier + if os.path.exists(full_path): + # Vérifier si c'est un fichier _intro.md + is_intro_file = os.path.basename(full_path) == "_intro.md" + + # Vérifier si c'est une section d'Indice de Vulnérabilité de Concurrence + is_ivc_section = "Vulnérabilité de Concurrence" in line or "/ivc-" in path.lower() or "/fiche technique ivc/" in path.lower() + + with open(full_path, 'r', encoding='utf-8') as f: + content = f.read() + + # Ajuster les niveaux de titres + adjusted_content = adjust_heading_levels(content, parent_level, is_intro_file, is_ivc_section) + + # Ajouter le contenu ajusté + output_lines.append(f"") + output_lines.append(adjusted_content) + output_lines.append(f"") + else: + output_lines.append(f"") + output_lines.append(line) + except Exception as e: + output_lines.append(f"") + output_lines.append(line) + else: + # Conserver la ligne telle quelle + output_lines.append(line) + + i += 1 + + # Écrire le rapport final + with open(OUTPUT_PATH, 'w', encoding='utf-8') as f: + f.write('\n'.join(output_lines)) + + print(f"Rapport final généré: {OUTPUT_PATH}") + +if __name__ == "__main__": + process_report() diff --git a/appel_IA.py b/appel_IA.py new file mode 100644 index 0000000..d098149 --- /dev/null +++ b/appel_IA.py @@ -0,0 +1,79 @@ +import requests + +MODEL = "llama3-8b-fast:latest" +OLLAMA_URL = "http://localhost:11434/api/generate" +TEMP = 0.1 + +with open("Corpus/rapport_final.md", "r", encoding="utf-8") as f: + contenu = f.read() + +prompt = f""" +Tu es un assistant stratégique expert chargé d’analyser les vulnérabilités systémiques dans des chaînes de valeur numériques. Tu t'exprimes en français. + +Tu travailles uniquement à partir du fichier Markdown `rapport_final.md`. Ce fichier est complet : n’ajoute aucune connaissance externe. + +== Objectif == +Produire un rapport stratégique complet destiné à un COMEX ou à une direction des risques industrielles. Ce rapport doit permettre d’identifier les vulnérabilités critiques qui menacent la résilience des produits numériques. + +== Structure attendue == +Le rapport que tu dois produire contient 4 sections : +1. Synthèse de l’analyse (style narratif pour décideurs, suivie d’un encadré synthétique) +2. Analyse détaillée (explication structurée par niveau de vulnérabilité, encadré de données) +3. Points de vigilance (indicateurs clés à surveiller, horizon temporel) +4. Conclusion (scénario d’impact en cas de choc, encadré des conséquences sur les produits numériques) + +== Données à analyser == +Les données se trouvent uniquement dans les sections : +- `## Éléments factuels` : données brutes à exploiter +- `## Annexes` : fiches techniques détaillées pour comprendre les indices et les valeurs des éléments factuels + +== Indices à utiliser == + +• **IHH (Herfindahl-Hirschmann)** : mesure la concentration géographique ou industrielle. + - Interprétation : >25 = concentration élevée (rouge), 15–25 = modérée (orange), <15 = faible (vert) + +• **ICS (Indice de Criticité de Substituabilité)** : évalue la difficulté à substituer un matériau. + - Calculé à partir de la faisabilité technique, des délais d’implémentation et du coût économique. + - Interprétation : >0.6 = critique, 0.3–0.6 = modéré, <0.3 = faible + +• **ISG (Indice de Stabilité Géopolitique)** : reflète la vulnérabilité politique, sociale ou climatique d’un pays producteur. + - Interprétation : >70 = instabilité forte, 40–70 = instabilité modérée, <40 = stable + +• **IVC (Indice de Vulnérabilité Concurrentielle)** : mesure la pression d’autres secteurs sur l’accès aux ressources du numérique. + - Interprétation : >15 = forte, 5–15 = modérée, <5 = faible + +== Logique d’analyse attendue == + +1. **Croise les indices** pour identifier les vulnérabilités critiques (ex. IHH élevé + ISG élevé + ICS élevé = vulnérabilité systémique) +2. **Hiérarchise clairement** : vulnérabilité critique, élevée, modérée +3. **Distingue les horizons temporels** (court terme = <2 ans, moyen terme = 2–5 ans, long terme >5 ans) +4. **Détaille les effets en cascade** sur les produits numériques (ex : minerai → composant → infrastructure) +5. **Évite toute recommandation industrielle ou politique** + +== Exemples d’encadrés synthétiques à inclure == +POINTS CLÉS - [NOM DU MINÉRAI] : +• Concentration critique : IHH 89 (Chine 94%) +• Substituabilité : ICS 0.64 (difficile), délai 2–8 ans +• Instabilité géopolitique : ISG 54 (modérée) +• Vulnérabilité concurrentielle : IVC 1 (faible) +• Horizon : court à moyen terme +• Impact : semi-conducteurs, détecteurs IR, fibres optiques + + +== Important == +Tu dois raisonner comme un analyste stratégique, pas comme un chatbot. +Tu rédiges un rapport professionnel prêt à être diffusé à la direction générale. + +== Contenu à analyser == + +{contenu} +""" + +response = requests.post(OLLAMA_URL, json={ + "model": MODEL, + "prompt": prompt, + "stream": False, + "temperature": TEMP +}) + +print(response.json()["response"])