Ajout du batch de traitement de l'IA

This commit is contained in:
Fabrication du Numérique 2025-05-26 17:27:59 +02:00
parent 5839098db6
commit 981c473204
20 changed files with 2907 additions and 2878 deletions

42
app/ia_nalyse/README.md Normal file
View File

@ -0,0 +1,42 @@
# Module d'Analyse
Ce module permet d'analyser les relations entre les différentes parties de la chaîne de fabrication du numérique. Il offre des outils pour visualiser les flux et identifier les vulnérabilités potentielles dans la chaîne d'approvisionnement.
## Structure du module
Le module d'analyse comprend deux composants principaux :
- **interface.py** : Gère l'interface utilisateur pour paramétrer les analyses
- **sankey.py** : Génère les diagrammes de flux (Sankey) pour visualiser les relations entre les éléments
## Fonctionnalités
### Interface d'analyse
L'interface permet de :
- Sélectionner les niveaux de départ et d'arrivée pour l'analyse (produits, composants, minerais, opérations, etc.)
- Filtrer les données par minerais spécifiques
- Effectuer une sélection fine des nœuds de départ et d'arrivée
- Appliquer des filtres pour identifier les vulnérabilités :
- Filtres ICS (criticité pour un composant)
- Filtres IVC (criticité par rapport à la concurrence sectorielle)
- Filtres IHH (concentration géographique ou industrielle)
- Filtres ISG (instabilité des pays)
- Choisir la logique de filtrage (OU, ET)
### Visualisation Sankey
Le module génère des diagrammes Sankey qui :
- Affichent les flux entre les différents éléments de la chaîne
- Mettent en évidence les relations de dépendance
- Permettent d'identifier visuellement les goulots d'étranglement potentiels
- Sont interactifs et permettent d'explorer la chaîne de valeur
## Utilisation
1. Sélectionnez un niveau de départ (ex : Produit final, Composant)
2. Choisissez un niveau d'arrivée (ex : Pays géographique, Acteur d'opération)
3. Si nécessaire, filtrez par minerais spécifiques
4. Affinez votre sélection avec des nœuds de départ et d'arrivée spécifiques
5. Appliquez les filtres de vulnérabilité souhaités
6. Lancez l'analyse pour générer le diagramme Sankey
Le diagramme résultant permet d'identifier visuellement les relations et points de vulnérabilité dans la chaîne d'approvisionnement du numérique.

View File

@ -0,0 +1,2 @@
# __init__.py app/fiches
from .interface import interface_ia_nalyse

191
app/ia_nalyse/interface.py Normal file
View File

@ -0,0 +1,191 @@
import streamlit as st
import networkx as nx
from utils.translations import _
from utils.widgets import html_expander
from utils.graph_utils import (
extraire_chemins_depuis,
extraire_chemins_vers
)
from batch_ia.batch_utils import soumettre_batch, statut_utilisateur, nettoyage_post_telechargement
niveau_labels = {
0: "Produit final",
1: "Composant",
2: "Minerai",
10: "Opération",
11: "Pays d'opération",
12: "Acteur d'opération",
99: "Pays géographique"
}
inverse_niveau_labels = {v: k for k, v in niveau_labels.items()}
def preparer_graphe(G):
"""Nettoie et prépare le graphe pour l'analyse."""
niveaux_temp = {
node: int(str(attrs.get("niveau")).strip('"'))
for node, attrs in G.nodes(data=True)
if attrs.get("niveau") and str(attrs.get("niveau")).strip('"').isdigit()
}
G.remove_nodes_from([n for n in G.nodes() if n not in niveaux_temp])
G.remove_nodes_from(
[n for n in G.nodes() if niveaux_temp.get(n) == 10 and 'Reserves' in n])
return G, niveaux_temp
def selectionner_minerais(G):
"""Interface pour sélectionner les minerais si nécessaire."""
minerais_selection = None
st.markdown(f"## {str(_('pages.ia_nalyse.select_minerals'))}")
# Tous les nœuds de niveau 2 (minerai)
minerais_nodes = sorted([
n for n, d in G.nodes(data=True)
if d.get("niveau") and int(str(d.get("niveau")).strip('"')) == 2
])
minerais_selection = st.multiselect(
str(_("pages.ia_nalyse.filter_by_minerals")),
minerais_nodes,
key="analyse_minerais"
)
return minerais_selection
def selectionner_noeuds(G, niveaux_temp, niveau_depart):
"""Interface pour sélectionner les nœuds spécifiques de départ et d'arrivée."""
st.markdown("---")
st.markdown(f"## {str(_('pages.ia_nalyse.fine_selection'))}")
depart_nodes = [n for n in G.nodes() if niveaux_temp.get(n) == niveau_depart]
noeuds_arrivee = [n for n in G.nodes() if niveaux_temp.get(n) == 99]
noeuds_depart = st.multiselect(str(_("pages.ia_nalyse.filter_start_nodes")),
sorted(depart_nodes),
key="analyse_noeuds_depart")
noeuds_depart = noeuds_depart if noeuds_depart else None
return noeuds_depart, noeuds_arrivee
def extraire_niveaux(G):
"""Extrait les niveaux des nœuds du graphe"""
niveaux = {}
for node, attrs in G.nodes(data=True):
niveau_str = attrs.get("niveau")
if niveau_str:
niveaux[node] = int(str(niveau_str).strip('"'))
return niveaux
def extraire_chemins_selon_criteres(G, niveaux, niveau_depart, noeuds_depart, noeuds_arrivee, minerais):
"""Extrait les chemins selon les critères spécifiés"""
chemins = []
if noeuds_depart and noeuds_arrivee:
for nd in noeuds_depart:
for na in noeuds_arrivee:
tous_chemins = extraire_chemins_depuis(G, nd)
chemins.extend([chemin for chemin in tous_chemins if na in chemin])
elif noeuds_depart:
for nd in noeuds_depart:
chemins.extend(extraire_chemins_depuis(G, nd))
elif noeuds_arrivee:
for na in noeuds_arrivee:
chemins.extend(extraire_chemins_vers(G, na, niveau_depart))
else:
sources_depart = [n for n in G.nodes() if niveaux.get(n) == niveau_depart]
for nd in sources_depart:
chemins.extend(extraire_chemins_depuis(G, nd))
if minerais:
chemins = [chemin for chemin in chemins if any(n in minerais for n in chemin)]
return chemins
def exporter_graphe_filtre(G, liens_chemins):
"""Gère l'export du graphe filtré au format DOT"""
if not st.session_state.get("logged_in", False) or not liens_chemins:
return
G_export = nx.DiGraph()
for u, v in liens_chemins:
G_export.add_node(u, **G.nodes[u])
G_export.add_node(v, **G.nodes[v])
data = G.get_edge_data(u, v)
if isinstance(data, dict) and all(isinstance(k, int) for k in data):
G_export.add_edge(u, v, **data[0])
elif isinstance(data, dict):
G_export.add_edge(u, v, **data)
else:
G_export.add_edge(u, v)
return(G_export)
def extraire_liens_filtres(chemins, niveaux, niveau_depart, niveau_arrivee, niveaux_speciaux):
"""Extrait les liens des chemins en respectant les niveaux"""
liens = set()
for chemin in chemins:
for i in range(len(chemin) - 1):
u, v = chemin[i], chemin[i + 1]
niveau_u = niveaux.get(u, 999)
niveau_v = niveaux.get(v, 999)
if (
(niveau_depart <= niveau_u <= niveau_arrivee or niveau_u in niveaux_speciaux)
and (niveau_depart <= niveau_v <= niveau_arrivee or niveau_v in niveaux_speciaux)
):
liens.add((u, v))
return liens
def interface_ia_nalyse(G_temp):
st.markdown(f"# {str(_('pages.ia_nalyse.title'))}")
html_expander(f"{str(_('pages.ia_nalyse.help'))}", content="\n".join(_("pages.ia_nalyse.help_content")), open_by_default=False, details_class="details_introduction")
st.markdown("---")
resultat = statut_utilisateur(st.session_state.username)
st.info(resultat["message"])
if resultat["statut"] is None:
# Préparation du graphe
G_temp, niveaux_temp = preparer_graphe(G_temp)
# Sélection des niveaux
niveau_depart = 0
niveau_arrivee = 99
# Sélection fine des noeuds
noeuds_depart, noeuds_arrivee = selectionner_noeuds(G_temp, niveaux_temp, niveau_depart)
# Sélection des minerais si nécessaire
minerais = selectionner_minerais(G_temp)
# Étape 1 : Extraction des niveaux des nœuds
niveaux = extraire_niveaux(G_temp)
# Étape 2 : Extraction des chemins selon les critères
chemins = extraire_chemins_selon_criteres(G_temp, niveaux, niveau_depart, noeuds_depart, noeuds_arrivee, minerais)
niveaux_speciaux = [1000, 1001, 1002, 1010, 1011, 1012]
# Extraction des liens sans filtrage
liens_chemins = extraire_liens_filtres(chemins, niveaux, niveau_depart, niveau_arrivee, niveaux_speciaux)
if liens_chemins:
G_final = exporter_graphe_filtre(G_temp, liens_chemins)
if st.button(str(_("pages.ia_nalyse.submit_request"))):
soumettre_batch(st.session_state.username, G_final)
st.rerun()
else:
st.info(str(_("pages.ia_nalyse.empty_graph")))
elif resultat["statut"] == "terminé" and resultat["telechargement"]:
st.download_button(str(_("buttons.download")), resultat["telechargement"], file_name="analyse.zip")
if st.button(str(_("pages.ia_nalyse.confirm_download"))):
nettoyage_post_telechargement(st.session_state.username)
st.success("Résultat supprimé. Vous pouvez relancer une nouvelle analyse.")
if st.button(str(_("buttons.refresh"))):
st.rerun()
else:
if st.button(str(_("buttons.refresh"))):
st.rerun()

View File

@ -43,6 +43,7 @@
"instructions": "Instructions",
"personnalisation": "Customization",
"analyse": "Analysis",
"ia_nalyse": "AI'nalysis",
"visualisations": "Visualizations",
"fiches": "Cards"
},
@ -128,6 +129,24 @@
"relation": "Relation"
}
},
"ia_nalyse": {
"title": "Graph Analysis by AI",
"help": "How to use this tab?",
"help_content": [
"The graph covers all levels, from end products to geographic countries.\n",
"1. You can select minerals through which the paths go.",
"2. You can choose end products to perform an analysis tailored to your context.\n",
"The analysis is carried out using a private AI on a minimalist server. The result is therefore not immediate (approximately 30 minutes) and you will be notified of the progress."
],
"select_minerals": "Select one or more minerals",
"filter_by_minerals": "Filter by minerals (optional, but highly recommended)",
"fine_selection": "End product selection",
"filter_start_nodes": "Filter by start nodes (optional, but recommended)",
"run_analysis": "Run analysis",
"confirm_download": "Confirm download",
"submit_request": "Submit your request",
"empty_graph": "The graph is empty. Please make another selection."
},
"visualisations": {
"title": "Visualizations",
"help": "How to use this tab?",
@ -269,6 +288,7 @@
"export": "Export",
"import": "Import",
"restore": "Restore",
"refresh": "Refresh",
"browse_files": "Browse files"
},
"ui": {
@ -276,5 +296,13 @@
"drag_drop_here": "Drag and drop file here",
"size_limit": "100 KB limit per file • JSON"
}
},
"batch": {
"in_queue": "In queue",
"in_progress": "Analysis in progress",
"failure": "Error",
"unknwon_error": "unknown error",
"no_task": "No task wainting or in progress",
"complete": "Analysis complete. Download the result in zip format, which contains the detailed report and analysis."
}
}

View File

@ -43,6 +43,7 @@
"instructions": "Instructions",
"personnalisation": "Personnalisation",
"analyse": "Analyse",
"ia_nalyse": "IA'nalyse",
"visualisations": "Visualisations",
"fiches": "Fiches"
},
@ -128,6 +129,24 @@
"relation": "Relation"
}
},
"ia_nalyse": {
"title": "Analyse du graphe par IA",
"help": "Comment utiliser cet onglet ?",
"help_content": [
"Le graphe intègre l'ensemble des niveaux, des produits finaux aux pays géographiques.\n",
"1. Vous pouvez sélectionner des minerais par lesquels passent les chemins.",
"2. Vous pouvez choisir des produits finaux pour faire une analyse adaptée à votre contexte.\n",
"L'analyse se réalise à l'aide d'une IA privée, sur un serveur minimaliste. Le résultat n'est donc pas immédiat (ordre de grandeur : 30 minutes) et vous serez informé de l'avancement."
],
"select_minerals": "Sélectionner un ou plusieurs minerais",
"filter_by_minerals": "Filtrer par minerais (optionnel, mais recommandé)",
"fine_selection": "Sélection des produits finaux",
"filter_start_nodes": "Filtrer par noeuds de départ (optionnel, mais recommandé)",
"run_analysis": "Lancer l'analyse",
"confirm_download": "Confirmer le téléchargement",
"submit_request": "Soumettre votre demande",
"empty_graph": "Le graphe est vide. Veuillez faire une autre sélection."
},
"visualisations": {
"title": "Visualisations",
"help": "Comment utiliser cet onglet ?",
@ -269,6 +288,7 @@
"export": "Exporter",
"import": "Importer",
"restore": "Restaurer",
"refresh": "Rafraîchir",
"browse_files": "Parcourir les fichiers"
},
"ui": {
@ -276,5 +296,13 @@
"drag_drop_here": "Glissez-déposez votre fichier ici",
"size_limit": "Limite 100 Ko par fichier • JSON"
}
},
"batch": {
"in_queue": "En attente",
"in_progress": "Analyse en cours",
"failure": "Échec",
"unknwon_error": "erreur inconnue",
"no_task": "Aucune tâche en attente ou en cours",
"complete": "Analyse terminée. Télécharger le résultat au format zip, qui contient le rapport détaillé et l'analyse."
}
}

0
batch_ia/__init__.py Normal file
View File

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,31 @@
[Unit]
Description=Service batch IA pour utilisateur fabnum
After=network.target
[Service]
Type=simple
User=fabnum
WorkingDirectory=/home/fabnum/fabnum-dev/batch_ia
Environment=PYTHONPATH=/home/fabnum/fabnum-dev
ExecStart=/home/fabnum/fabnum-dev/venv/bin/python /home/fabnum/fabnum-dev/batch_ia/batch_runner.py
Restart=always
Nice=10
CPUSchedulingPolicy=batch
# Limites de ressources
CPUQuota=87.5% # ~14 cores sur 16
MemoryMax=12G # RAM maximale autorisée
TasksMax=1 # maximum 1 subprocess/thread simultané
# Sécurité renforcée
ProtectSystem=full
ReadWritePaths=/home/fabnum/fabnum-dev/batch_ia
# Journal propre
StandardOutput=journal
StandardError=journal
[Install]
WantedBy=multi-user.target
# semanage fcontext -a -t svirt_sandbox_file_t "/home/fabnum/fabnum-dev/batch_ia(/.*)?"

View File

@ -1,7 +0,0 @@
[Unit]
Description=Batch IA Processing Service
[Service]
ExecStart=/usr/bin/systemd-run --scope --property=CPUQuota=87.5% --property=MemoryMax=12G /usr/bin/python3 /chemin/complet/vers/batch_ia/batch_runner.py
WorkingDirectory=/chemin/complet/vers/batch_ia
Restart=always

View File

@ -1,6 +1,7 @@
import time
import subprocess
from batch_utils import *
from batch_utils import charger_status, sauvegarder_status, JOBS_DIR
while True:
status = charger_status()
@ -13,7 +14,7 @@ while True:
if jobs:
login, _ = jobs[0]
dot_file = JOBS_DIR / f"{login}.dot"
result_file = JOBS_DIR / f"{login}.result.txt"
result_file = JOBS_DIR / f"{login}.zip"
status[login]["status"] = "en cours"
sauvegarder_status(status)
@ -27,4 +28,4 @@ while True:
sauvegarder_status(status)
time.sleep(10)
time.sleep(60)

View File

@ -2,10 +2,14 @@ import json
import time
from pathlib import Path
from networkx.drawing.nx_agraph import write_dot
import streamlit as st
from utils.translations import _
BATCH_DIR = Path(__file__).resolve().parent
JOBS_DIR = BATCH_DIR / "jobs"
STATUS_FILE = BATCH_DIR / "status.json"
ANALYSE = " - analyse.md"
RAPPORT = " - rapport.md"
def charger_status():
if STATUS_FILE.exists():
@ -20,27 +24,27 @@ def statut_utilisateur(login):
entry = status.get(login)
if not entry:
return {"statut": None, "position": None, "telechargement": None,
"message": "Aucune tâche en cours."}
"message": f"{str(_('batch.no_task'))}."}
if entry["status"] == "en attente":
return {"statut": "en attente", "position": entry.get("position"),
"telechargement": None,
"message": f"En attente (position {entry.get('position', '?')})."}
"message": f"{str(_('batch.in_queue'))} (position {entry.get('position', '?')})."}
if entry["status"] == "en cours":
return {"statut": "en cours", "position": 0,
"telechargement": None, "message": "Analyse en cours."}
"telechargement": None, "message": f"{str(_('batch.in_progress'))}."}
if entry["status"] == "terminé":
result_file = JOBS_DIR / f"{login}.result.txt"
result_file = JOBS_DIR / f"{login}.zip"
if result_file.exists():
return {"statut": "terminé", "position": None,
"telechargement": result_file.read_text(),
"message": "Analyse terminée. Télécharger le résultat."}
"telechargement": result_file.read_bytes(),
"message": f"{str(_('batch.complete'))}."}
if entry["status"] == "échoué":
return {"statut": "échoué", "position": None, "telechargement": None,
"message": f"Échec : {entry.get('error', 'erreur inconnue')}"}
"message": f"{str(_('batch.failure'))} : {entry.get('error', {str(_('batch.unknown_error'))})}"}
def soumettre_batch(login, G):
if statut_utilisateur(login)["statut"]:
@ -52,7 +56,7 @@ def soumettre_batch(login, G):
def nettoyage_post_telechargement(login):
(JOBS_DIR / f"{login}.dot").unlink(missing_ok=True)
(JOBS_DIR / f"{login}.result.txt").unlink(missing_ok=True)
(JOBS_DIR / f"{login}.zip").unlink(missing_ok=True)
status = charger_status()
status.pop(login, None)
sauvegarder_status(status)

100
batch_ia/nettoyer_pgpt.py Normal file
View File

@ -0,0 +1,100 @@
#!/usr/bin/env python3
"""
Script de nettoyage pour PrivateGPT
Ce script permet de lister et supprimer les documents ingérés dans PrivateGPT.
Options:
- Lister tous les documents
- Supprimer des documents par préfixe (ex: "temp_section_")
- Supprimer des documents par motif
- Supprimer tous les documents
"""
import json
import re
import requests
import time
from typing import List, Dict, Any, Optional
# Configuration de l'API PrivateGPT
PGPT_URL = "http://127.0.0.1:8001"
API_URL = f"{PGPT_URL}/v1"
def list_documents() -> List[Dict[str, Any]]:
"""Liste tous les documents ingérés et renvoie la liste des métadonnées"""
try:
# Récupérer la liste des documents
response = requests.get(f"{API_URL}/ingest/list")
response.raise_for_status()
data = response.json()
# Format de réponse OpenAI
if "data" in data:
documents = data.get("data", [])
# Format alternatif
else:
documents = data.get("documents", [])
# Construire une liste normalisée des documents
normalized_docs = []
for doc in documents:
doc_id = doc.get("doc_id") or doc.get("id")
metadata = doc.get("doc_metadata", {})
filename = metadata.get("file_name") or metadata.get("filename", "Inconnu")
normalized_docs.append({
"id": doc_id,
"filename": filename,
"metadata": metadata
})
return normalized_docs
except Exception as e:
print(f"❌ Erreur lors de la récupération des documents: {e}")
return []
def delete_document(doc_id: str) -> bool:
"""Supprime un document par son ID"""
try:
response = requests.delete(f"{API_URL}/ingest/{doc_id}")
if response.status_code == 200:
return True
else:
print(f"⚠️ Échec de la suppression de l'ID {doc_id}: Code {response.status_code}")
return False
except Exception as e:
print(f"❌ Erreur lors de la suppression de l'ID {doc_id}: {e}")
return False
def delete_documents_by_criteria(pattern) -> int:
"""
Supprime des documents selon différents critères
Retourne le nombre de documents supprimés
"""
documents = list_documents()
if not documents or not pattern:
return 0
# Comptage des suppressions réussies
success_count = 0
# Filtrer les documents à supprimer
docs_to_delete = []
try:
regex = re.compile(pattern)
docs_to_delete = [doc for doc in documents if regex.search(doc["filename"])]
except re.error as e:
return 0
# Supprimer les documents
for doc in docs_to_delete:
delete_document(doc["id"])
# Petite pause pour éviter de surcharger l'API
time.sleep(0.1)
return success_count

1
batch_ia/status.json Normal file
View File

@ -0,0 +1 @@
{}

View File

@ -21,6 +21,7 @@ def afficher_menu():
str(_("navigation.instructions")),
str(_("navigation.personnalisation")),
str(_("navigation.analyse")),
*([str(_("navigation.ia_nalyse"))] if st.session_state.get("logged_in", False) else []),
str(_("navigation.visualisations")),
str(_("navigation.fiches"))
]

View File

@ -78,6 +78,7 @@ from app.fiches import interface_fiches
from app.visualisations import interface_visualisations
from app.personnalisation import interface_personnalisation
from app.analyse import interface_analyse
from app.ia_nalyse import interface_ia_nalyse
# Initialisation des traductions (langue française par défaut)
init_translations()
@ -168,6 +169,7 @@ instructions_tab = _("navigation.instructions")
fiches_tab = _("navigation.fiches")
personnalisation_tab = _("navigation.personnalisation")
analyse_tab = _("navigation.analyse")
ia_nalyse_tab = _("navigation.ia_nalyse")
visualisations_tab = _("navigation.visualisations")
if st.session_state.onglet == instructions_tab:
@ -187,6 +189,10 @@ if dot_file_path and st.session_state.onglet == analyse_tab:
G_temp = st.session_state["G_temp"]
interface_analyse(G_temp)
elif dot_file_path and st.session_state.onglet == ia_nalyse_tab:
G_temp = st.session_state["G_temp"]
interface_ia_nalyse(G_temp)
elif dot_file_path and st.session_state.onglet == visualisations_tab:
G_temp = st.session_state["G_temp"]
G_temp_ivc = st.session_state["G_temp_ivc"]

View File

@ -1,179 +0,0 @@
On ne peut pas proposer le rapport sous cette forme : plus de 6000 lignes pour une entrée simple.
Je propose plutôt de commencer par établir la liste de ce qui est couvert par le rapport : (enlever les """ qui sont là pour isoler ce que j'attends)
Dans tout ce qui suit, les fichiers à incorporer se présente sous la forme suivante :
* Corpus/Assemblage/Fiche assemblage serveur/04-matrice-des-risques-liés-à-l-assemblage/00-indice-de-herfindahl-hirschmann.md
Les préfixes numériques peuvent varier, mais pas les slugs. Il faut donc chercher le bon fichier dans l'arborescence qui contient les slugs indépendamment des préfixes.
"""
## Introduction
Ce rapport analyse les vulnérabilités de la chaîne de fabrication du numérique pour :
* les produits finaux : (lister les produits)
* les composants : (lister les composants)
* les minerais : (lister les minerais)
"""
Je verrai par la suite comment améliorer.
"""
## Méthodologie d'analyse des risques
### Indices et seuils
La méthode d'évaluation intègre 4 indices et leurs combinaisons pour identifier les chemins critiques.
#### IHH (Herfindahl-Hirschmann) : concentration géographiques ou industrielle d'une opération
(Récupérer => Corpus/Criticités/Fiche technique IHH/00-contexte-et-objectif.md et enlever la première ligne de titre)
(Récupérer => Corpus/Criticités/Fiche technique IHH/01-mode-de-calcul/_intro.md et enlever la première ligne de titre)
* Seuils : <15 = Vert (Faible), 15-25 = Orange (Modérée), >25 = Rouge (Élevée)
#### ISG (Stabilité Géopolitique) : stabilité des pays
(Récupérer => Corpus/Criticités/Fiche technique ISG/00-contexte-et-objectif.md et enlever la première ligne de titre)
(Récupérer => Corpus/Criticités/Fiche technique ISG/01-mode-de-calcul/_intro.md et enlever la première ligne de titre)
* Seuils : <40 = Vert (Stable), 40-60 = Orange, >60 = Rouge (Instable)
… on répète pour les autres
"""
"""
### Combinaison des indices
**IHH et ISG**
Ces deux indices s'appliquent à toutes les opérations et se combinent dans l'évaluation du risque (niveau d'impact et probabilité de survenance) :
* l'IHH donne le niveau d'impact => une forte concentration implique un fort impact si le risque est avéré
* l'ISG donne la probabilité de survenance => plus les pays sont instables (et donc plus l'ISG est élevé) et plus la survenance du risque est élevée
Pour évaluer le risque pour une opération, les ISG des pays sont pondérés par les parts de marché respectives pour donner un ISG combiné dont le calcul est :
ISG_combiné = (Somme des ISG des pays multipliée par leur part de marché) / Sommes de leur part de marché
On établit alors une matrice (Vert = 1, Orange = 2, Rouge = 3) et en faisant le produit des poids de l'ISG combiné et de l'IHH
| ISG combiné / IHH | Vert | Orange | Rouge |
| :-- | :-- | :-- | :-- |
| Vert | 1 | 2 | 3 |
| Orange | 2 | 4 | 6 |
| Rouge | 3 | 6 | 9 |
Les vulnérabilités se classent en trois niveaux pour chaque opération :
* Vulnérabilité combinée élevée à critique : poids 6 et 9
* Vulnérabilité combinée moyenne : poids 3 et 4
* Vulnérabilité combinée faible : poids 1 et 2
**ICS et IVC**
Ces deux indices se combinent dans l'évaluation du risque pour un minerai :
* l'ICS donne le niveau d'impact => une faible substituabilité (et donc un ICS élevé) implique un fort impact si le risque est avéré ; l'ICS est associé à la relation entre un composant et un minerai
* l'IVC donne la probabilité de l'impact => une forte concurrence intersectorielle (IVC élevé) implique une plus forte probabilité de survenance
Par simplification, on intègre un ICS moyen d'un minerai comme étant la moyenne des ICS pour chacun des composants dans lesquels il intervient.
On établit alors une matrice (Vert = 1, Orange = 2, Rouge = 3) et en faisant le produit des poids de l'ICS moyen et de l'IVC.
| ICS_moyen / IVC | Vert | Orange | Rouge |
| :-- | :-- | :-- | :-- |
| Vert | 1 | 2 | 3 |
| Orange | 2 | 4 | 6 |
| Rouge | 3 | 6 | 9 |
Les vulnérabilités se classent en trois niveaux pour chaque minerai :
* Vulnérabilité combinée élevée à critique : poids 6 et 9
* Vulnérabilité combinée moyenne : poids 3 et 4
* Vulnérabilité combinée faible : poids 1 et 2
"""
À partir de là on passe aux opérations ; on ne peut pas répéter les opérations du minerai Germanium sur tous les composants dans lesquels il intervient. On va factoriser. On présente donc chaque opération par rapport à l'item auquel elles sont associées.
"""
## Détails des opérations
"""
Assemblage et Fabrication se présente de la même manière. On remplace assemblage par fabrication, assembleurs par fabricants.
Prenons un exemple pour illustre : Serveur ; on ne fait ce qui suit que pour les produits finaux (N0) ou les composants (N1) qui sont présents dans le graphe DOT. Même si les opérations en sont pas présentes dans le graphe.
"""
### Serveur et Assemblage
(Récupérer Corpus/Assemblage/Fiche assemblage serveur/00-présentation-synthétique.md et enlever la première ligne de titre)
(Récupérer Corpus/Assemblage/Fiche assemblage serveur/02-principaux-assembleurs.md et décaler le titre initial de 2 niveaux)
(En fonction des pays, récupérer leur ISG, en faire un tableau et faire ensuite le calcul de l'ISG combiné en récupérant les parts de marché <== On va devoir regarder le point de récupération attentivement ==>)
(Récupérer Corpus/Assemblage/Fiche assemblage serveur/04-matrice-des-risques-liés-à-l-assemblage/00-indice-de-herfindahl-hirschmann.md et décaler tous les titres d'un niveau)
(En fonction de l'ISG combiné et de l'IHH, donner le niveau de Vulnérabilité et son poids)
"""
À refaire donc, pour tous les produits finaux et tous les composants présents dans le graphe DOT à analyser.
Pour l'ISG, on va utiliser le graphe initial, schema.txt, base de données de l'application. Il se trouve à la racine du projet.
Dans ce graphe DOT de référence, les pays (N99) sont référencés comme suit :
Luxembourg_geographique [fillcolor="#e6f2ff", label="Luxembourg", isg="24", niveau="99"];
On charge comme le graphe à analyser et on cherche le nœud de niveau=99 (et non niveau="99" du fichier) dont le label = le nom réel recherché.
On passe ensuite aux minerais. Ils ont deux opérations, Extraction et Traitement qui se gèrent de la même manière. Je prends l'exemple du cuivre.
"""
### Cuivre
(Récupérer Corpus/Minerai/Fiche minerai cuivre/00-présentation-synthétique.md et enlever la première ligne de titre)
**ICS**
(Récupérer Corpus/Minerai/Fiche minerai cuivre/11-risque-de-substituabilité/_intro.md et enlever la première ligne de titre)
(Récupérer dans le graphe à analyser toutes les arêtes où le Cuivre est le nœud target et faire la moyenne pour obtenir l'ICS moyen)
**IVC**
(Récupérer toutes les sections sous Corpus/Minerai/Fiche minerai cuivre/12-vulnérabilité-de-concurrence/ en mettant _intro.md en premier et les autres par ordre alphabérique (préfixes numériques) et remplacer la première ligne de titre de chaque section par le contenu du titre mis en italique)
**ICS et IVC combinés**
(En fonction de l'ICS moyen et de l'IVC, donner le niveau de Vulnérabilité et son poids)
#### Extraction
(Récupérer Corpus/Minerai/Fiche minerai cuivre/03-principaux-producteurs-extraction.md et enlever la première ligne de titre)
(En fonction des pays, récupérer leur ISG, en faire un tableau et faire ensuite le calcul de l'ISG combiné en récupérant les parts de marché)
(Récupérer Corpus/Minerai/Fiche minerai cuivre/10-matrices-des-risques/01-indice-de-herfindahl-hirschmann-extraction.md et décaler tous les titres d'un niveau)
(En fonction de l'ISG combiné et de l'IHH, donner le niveau de Vulnérabilité et son poids)
#### Traitement
(Idem à extraction en remplaçant extration par traitement)
"""
On en a fini avec les opérations.
"""
## Chemins critiques
(
Il faut maintenant faire une passe complète de tous les chemins entre produit final et minerai et les positionner dans :
* Chaîne avec risque critique, elle comprend :
* au moins une vulnérabilité combinée élevée à critique
* Chaîne avec risque majeur, elle comprend :
* au moins trois vulnérabilités combinée moyennes
* Chaîne avec risque moyen, elle comprend :
* au moins une vulnérabilité combinée moyenne
en donnant les éléments nécessaires pour identifier les Vulnérabilités combinées concernées
)
"""
On en a fini. L'IA par la suite va examiner cette section et expliquer pourquoi c'est critique. À voir si cette partie ne devrait pas être le prompt ou tout au moins une partie.

View File

@ -1,87 +0,0 @@
# Quels sont les indices et leurs seuils
* IHH (Herfindahl-Hirschmann) : concentration géographiques ou industrielle d'un opération
* Seuils : <15 = Vert (Faible), 15-25 = Orange (Modérée), >25 = Rouge (Élevée)
* ICS (Criticité de Substituabilité) : capacité à remplacer / substituer un élément dans le composant ou le procédé
* Seuils : <0.3 = Vert (Facile), 0.3-0.6 = Orange (Moyenne), >0.6 = Rouge (Difficile)
* ISG (Stabilité Géopolitique) : stabilité des pays
* Seuils : <40 = Vert (stable), 40-60 = Orange, >60 = Rouge (instable)
* IVC (Vulnérabilité de Concurrence) : pression concurrentielle avec d'autres secteurs que le numérique
* Seuils : <5 = Vert (Faible), 5-15 = Moyenne (Modérée), >15 = Rouge (Forte)
Les seuils permettent de définir une échelle facile à comprendre.
Il est important de comprendre que se trouver en bas ou en haut d'une plage (verte, orange ou rouge) n'a pas le même niveau de risque. un niveau ICS à 0.65 (capacité de substitution faible) est moindre qu'un niveau d'ICS à 1 (aucune capacité à remplacer), tout en restant élevé.
# Combinaison des risques
**IHH et ISG**
Ces deux indices se combinent dans l'évaluation du risque (niveau d'impact et probabilité de survenance) :
* l'IHH donne le niveau d'impact => une forte concentration implique un fort impact si le risque est avéré
* l'ISG donne la probabilité de survenance => plus les pays sont instables (et donc plus l'ISG est élevé) et plus la survenance du risque est élevée
Toutefois, l'ISG s'adresse à un pays et l'IHH à une opération (soit pour un pays, soit pour un acteur) ; il faut donc calculer l'ISG combiné des pays intervenant pour une opération. Voici le calcul que l'on va utiliser :
ISG_combiné = (Somme des ISG des pays multipliée par leur part de marché) / Sommes de leur part de marché
Il faut donc produire ici le tableau des producteurs/fabricants/assembleurs (selon l'opération concernée) avec leur part de marché respective. Dans un premier temps, on se contentera de faire l'analyse pour les pays seulement. L'IHH des acteurs sera uniquement mentionné à titre informatif avec un commentaire léger.
On établit alors une matrice en mettant des poids (Vert = 1, Orange = 2, Rouge = 3) et en faisant le produit des poids de l'ISG_combiné et de l'IHH
| ISG_combiné / IHH | Vert | Orange | Rouge |
| :-- | :-- | :-- | :-- |
| Vert | 1 | 2 | 3 |
| Orange | 2 | 4 | 6 |
| Rouge | 3 | 6 | 9 |
On peut alors dire que l'on classe en trois niveaux pour chaque opération :
* Vulnérabilité combinée élevée à critique : poids 6 et 9
* Vulnérabilité combinée moyenne : poids 3 et 4
* Vulnérabilité combinée faible : poids 1 et 2
**ICS et IVC**
Ces deux indices se combinent dans l'évaluation du risque :
* l'ICS donne le niveau d'impact => une faible substituabilité (et donc un ICS élevé) implique un fort impact si le risque est avéré ; l'ICS est associé à la relation entre un composant et un minerai
* l'IVC donne la probabilité de l'impact => une forte concurrence intersectorielle (IVC élevé) implique une plus forte probabilité de survenance
L'ICS et l'IVC sont au niveau des minerais.
Par simplification, on va faire un calcul d'un ICS_moyen d'un minerai comme étant la moyenne des ICS pour chacun des composants dans lesquels il intervient.
Il faut donc récupérer pour le minerai son tableau des ICS pour faire cette moyenne.
On établit alors une matrice en mettant des poids (Vert = 1, Orange = 2, Rouge = 3) et en faisant le produit des poids de l'ICS et de l'IVC.
| ICS_moyen / IVC | Vert | Orange | Rouge |
| :-- | :-- | :-- | :-- |
| Vert | 1 | 2 | 3 |
| Orange | 2 | 4 | 6 |
| Rouge | 3 | 6 | 9 |
On peut alors dire que l'on classe en trois niveaux pour chaaque minerai :
* Vulnérabilité combinée élevée à critique : poids 6 et 9
* Vulnérabilité combinée moyenne : poids 3 et 4
* Vulnérabilité combinée faible : poids 1 et 2
# Interdépendances
Il va falloir maintenant remonter toute la chaîne de fabrication pour faire la recherche des dépendances entre indices, indices combinés.
Un produit final est assemblé à partir de composants. Il y a donc une opération d'assemblage avec IHH et ISG_combiné
Un composant est fabriqué à partir de minerais. Il y a donc une opération de fabrication avec IHH et ISG_combiné.
Un minerai est extrait et traité. Il y a donc associé au minerai un ICS_moyen et un IVC, une opération d'extraction avec un IHH et un ISG_combiné et une opération de traitement avec un IHH et un ISG_combiné.
Il faut donc reprendre ici toutes les chaines du produit final au minerai en classant comme suit :
* Chaîne avec risque critique, elle comprend :
* au moins une vulnérabilité combinée élevée à critique
* Chaîne avec risque majeur, elle comprend :
* au moins trois vulnérabilités combinée moyennes
* Chaîne avec risque moyen, elle comprend :
* au moins une vulnérabilité combinée moyenne
# Compléments
Pour chacun des indices (avant de les combiner), il récupérer leur détail dans le corpus (ce qui est mis en annexe du rapport_final actuel). cela doit faire partie de l'analyse détaillée à fournir.

View File

@ -1,111 +0,0 @@
Questions pour clarifier la mise en œuvre
1. **Structure du graphe DOT**: Le graphe DOT contient-il déjà les relations hiérarchiques (produit → composant → minerai)? Comment sont-elles représentées?
La structure du graphe est un digraph, donc avec une organisation hiérarchique :
Dans la suite, quand j'écris N0, N1, … cela veut dire que le nœud portant l'item a un attribut niveau 0, 1, …
Exemple :
LynasAdvanced_Malaisie_Traitement_Erbium [fillcolor="#d1e0ff",
label="Lynas Advanced Materials",
niveau=12];
LynasAdvanced_Malaisie_Traitement_Erbium est un N12. Important avec ce nom : on sait tout de suite que l'acteur est LynasAdvanced dont le nom réel est porté par le label (Lynas Advanced Materials), qu'il opère en Malaisie, pour faire le Traitement de l'Erbium.
* un produit final (N0) est associé à :
* un ou plusieurs composants (N1)
* aucune ou une opération d'assemblage (N10)
* un composant (N1) est associé à :
* un ou plusieurs minerais (N2)
* aucune ou une opération de fabrication (N10)
* un minerai (N2) est associé à :
* une opération d'extraction (N10)
* une opération de traitement (N10)
Exemple pour produit final (avec une seule chaîne) :
MaterielIA [fillcolor="#a0d6ff",
label="Matériel dédié IA",
niveau=0];
MaterielIA -> CarteMere;
CarteMere [fillcolor="#b3ffe0",
label="Carte mère",
niveau=1];
CarteMere -> Germanium [cout=0.6,
delai=0.6,
ics=0.64,
technique=0.7];
Germanium [fillcolor="#ffd699",
ivc=1,
label="Germanium - Semi-conducteurs, détecteurs infrarouge, fibre optique",
niveau=2];
Germanium -> Traitement_Germanium;
Traitement_Germanium [fillcolor="#ffd699",
ihh_acteurs=19,
ihh_pays=31,
label=Traitement,
niveau=10];
Traitement_Germanium -> Chine_Traitement_Germanium [color=purple,
fontcolor=purple,
label="50%",
poids=2];
Chine_Traitement_Germanium [fillcolor="#e6f2ff",
label=Chine,
niveau=11];
Chine_Traitement_Germanium -> YunnanGermanium_Chine_Traitement_Germanium [color=purple,
fontcolor=purple,
label="30%",
poids=2];
Chine_Traitement_Germanium -> Chine_geographique [color=darkgreen,
fontcolor=darkgreen];
YunnanChihong_Chine_Traitement_Germanium [fillcolor="#d1e0ff",
label="Yunnan Chihong Zinc",
niveau=12];
YunnanChihong_Chine_Traitement_Germanium -> Chine_geographique [color=darkgreen,
fontcolor=darkgreen];
Chaque opération (N10) (assemblage, fabrication, traitement, extraction) se décompose comme suit :
* l'opération elle-même (N10)
* un ou plusieurs pays (N11) où l'opération est réalisée
* pour chaque pays (N11, il est associé à :
* un ou plusieurs acteurs (N12) opérant dans le pays
* un pays géographique (N99)
2. **Calcul d'ISG_combiné**: Les parts de marché par pays sont-elles disponibles dans le graphe ou doivent-elles être extraites d'ailleurs?
Oui, le parts de marché sont disponble dans le graphe.
Exemple pour les pays :
Traitement_Germanium -> Chine_Traitement_Germanium [color=purple,
fontcolor=purple,
label="50%",
poids=2];
Chine_Traitement_Germanium [fillcolor="#e6f2ff",
label=Chine,
niveau=11];
La part de marché de Chine_Traitement_Germanium (et donc Chine que l'on récupère dans le label de Chine_Traitement_Germanium) est de 50% à récupérer dans le label de l'arête Traitement_Germanium -> Chine_Traitement_Germanium
Pour un acteur, on la récupère dans l'arête entre le pays et l'acteur :
Chine_Traitement_Germanium -> YunnanGermanium_Chine_Traitement_Germanium [color=purple,
fontcolor=purple,
label="30%",
poids=2];
La part de marché de YunnanGermanium_Chine_Traitement_Germanium est de 30% (label de l'arête).
3. **Traitement des matrices**: Souhaitez-vous que les matrices de vulnérabilité combinée soient:
- Calculées dynamiquement dans le script
- Présentées sous forme de tableaux dans le rapport
- Ou les deux?
Les matrices sont à présenter dans le rapport et donc calculées dynamiquement. Toutes les informations nécessaire sont dans le graphe pour faire ces calculs.
4. **Niveau de profondeur**: Pour chaque chaîne identifiée, quel niveau de détail souhaitez-vous dans le rapport? Uniquement les vulnérabilités combinées ou aussi le détail des indices individuels?
Les deux sont à présenter. En premier une partie factuelle avec les éléments individuels et leur explication et ensuite la combinaison.
5. **Approche structurelle**: Préférez-vous une organisation du rapport:
- Par niveau de risque (critique → majeur → moyen)
- Par produit/matière (en incluant tous les niveaux de risque pour chaque chaîne)
- Une combinaison des deux?
Il faut se rappeler que ce rapport est le point d'entrée pour l'IA qui aura pour objectif de textualiser tout cela et de proposer des scénarios de veille et de prévention.
Dans ce rapport factuel, il faut qu'il soit efficace pour l'IA et il viendra se joindre, de mon point de vue au rapport d'analyse que l'IA génèrera.

File diff suppressed because it is too large Load Diff