Nouvelle analyse avec plan d'actions et correction de schema.txt pour le
traitement du béryllium.
This commit is contained in:
parent
8a601aa24a
commit
d47e8608cc
2
app/plan_d_actions/__init__.py
Normal file
2
app/plan_d_actions/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
# __init__.py – app/fiches
|
||||
from .interface import interface_plan_d_actions
|
||||
237
app/plan_d_actions/interface.py
Normal file
237
app/plan_d_actions/interface.py
Normal file
@ -0,0 +1,237 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Script pour générer un rapport factorisé des vulnérabilités critiques
|
||||
suivant la structure définie dans Remarques.md.
|
||||
"""
|
||||
|
||||
import streamlit as st
|
||||
import networkx as nx
|
||||
import uuid
|
||||
from utils.translations import _
|
||||
from utils.widgets import html_expander
|
||||
from networkx.drawing.nx_agraph import write_dot
|
||||
|
||||
from batch_ia import (
|
||||
load_config,
|
||||
write_report,
|
||||
parse_graphs,
|
||||
extract_data_from_graph,
|
||||
calculate_vulnerabilities,
|
||||
generate_report,
|
||||
)
|
||||
|
||||
from .plan_d_actions import initialiser_interface
|
||||
|
||||
from utils.graph_utils import (
|
||||
extraire_chemins_depuis,
|
||||
extraire_chemins_vers
|
||||
)
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
# Répertoire courant du script
|
||||
CURRENT_DIR = Path(__file__).resolve().parent
|
||||
|
||||
# Répertoire "jobs" dans app/plan_d_actions
|
||||
JOBS = CURRENT_DIR / "jobs"
|
||||
JOBS.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
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, noeuds_depart):
|
||||
"""Interface pour sélectionner les minerais si nécessaire."""
|
||||
minerais_selection = None
|
||||
|
||||
st.markdown(f"## {str(_('pages.plan_d_actions.select_minerals'))}")
|
||||
|
||||
# Étape 1 : récupérer tous les nœuds descendants depuis les produits finaux
|
||||
descendants = set()
|
||||
for start in noeuds_depart:
|
||||
descendants.update(nx.descendants(G, start)) # tous les successeurs (récursifs)
|
||||
|
||||
# Étape 2 : ne garder que les nœuds de niveau 2 parmi les descendants
|
||||
minerais_nodes = sorted([
|
||||
n for n in descendants
|
||||
if G.nodes[n].get("niveau") and int(str(G.nodes[n].get("niveau")).strip('"')) == 2
|
||||
])
|
||||
|
||||
minerais_selection = st.multiselect(
|
||||
str(_("pages.plan_d_actions.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.plan_d_actions.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.plan_d_actions.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"""
|
||||
|
||||
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_plan_d_actions(G_temp):
|
||||
st.markdown(f"# {str(_('pages.plan_d_actions.title'))}")
|
||||
|
||||
if "plan_d_actions" not in st.session_state:
|
||||
st.session_state["plan_d_actions"] = 0
|
||||
if "g_md_done" not in st.session_state:
|
||||
st.session_state["g_md_done"] = False
|
||||
if "uuid" not in st.session_state:
|
||||
st.session_state["uuid"] = str(uuid.uuid4())[:8]
|
||||
st.session_state["G_dot"] = f"{JOBS}/{st.session_state["uuid"]}.dot"
|
||||
st.session_state["G_md"] = f"{JOBS}/{st.session_state["uuid"]}.md"
|
||||
|
||||
if st.session_state["plan_d_actions"] == 0:
|
||||
html_expander(f"{str(_('pages.plan_d_actions.help'))}", content="\n".join(_("pages.plan_d_actions.help_content")), open_by_default=False, details_class="details_introduction")
|
||||
# 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
|
||||
if noeuds_depart:
|
||||
minerais = selectionner_minerais(G_temp, noeuds_depart)
|
||||
# É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)
|
||||
else:
|
||||
liens_chemins = None
|
||||
|
||||
if liens_chemins:
|
||||
G_final = exporter_graphe_filtre(G_temp, liens_chemins)
|
||||
st.session_state["G_final"] = G_final
|
||||
# formulaire ou sélection
|
||||
if st.button(str(_("pages.plan_d_actions.submit_request"))):
|
||||
# On déclenche la suite — mais on NE traite rien maintenant
|
||||
st.session_state["plan_d_actions"] = 1
|
||||
st.rerun() # force la réexécution immédiatement avec état mis à jour
|
||||
|
||||
|
||||
elif st.session_state["plan_d_actions"] == 1:
|
||||
# Traitement lourd une seule fois
|
||||
if not st.session_state["g_md_done"]:
|
||||
write_dot(st.session_state["G_final"], st.session_state["G_dot"])
|
||||
config = load_config()
|
||||
graph, ref_graph = parse_graphs(st.session_state["G_dot"])
|
||||
data = extract_data_from_graph(graph, ref_graph)
|
||||
results = calculate_vulnerabilities(data, config)
|
||||
report, file_names = generate_report(data, results, config)
|
||||
write_report(report, st.session_state["G_md"])
|
||||
st.session_state["g_md_done"] = True # pour ne pas re-traiter à chaque affichage
|
||||
|
||||
# Affichage de l’interface Streamlit
|
||||
initialiser_interface(st.session_state["G_md"])
|
||||
if (st.button("Réinitialiser")):
|
||||
st.session_state["plan_d_actions"] = 0
|
||||
st.session_state["g_md_done"] = False
|
||||
for f in JOBS.glob(f"*{st.session_state["uuid"]}*"):
|
||||
if f.is_file():
|
||||
f.unlink()
|
||||
st.rerun()
|
||||
10349
app/plan_d_actions/jobs/68ed674a.md
Normal file
10349
app/plan_d_actions/jobs/68ed674a.md
Normal file
File diff suppressed because it is too large
Load Diff
10405
app/plan_d_actions/jobs/d8eaac37.md
Normal file
10405
app/plan_d_actions/jobs/d8eaac37.md
Normal file
File diff suppressed because it is too large
Load Diff
788
app/plan_d_actions/plan_d_actions.py
Normal file
788
app/plan_d_actions/plan_d_actions.py
Normal file
@ -0,0 +1,788 @@
|
||||
import streamlit as st
|
||||
import yaml
|
||||
import re
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
PRECONISATIONS = {
|
||||
'Facile': [
|
||||
"Constituer des stocks stratégiques.",
|
||||
"Surveiller activement les signaux géopolitiques.",
|
||||
"Renforcer la surveillance des régions critiques."
|
||||
],
|
||||
'Modérée': [
|
||||
"Diversifier progressivement les fournisseurs.",
|
||||
"Favoriser la modularité des produits.",
|
||||
"Augmenter progressivement les taux de recyclage."
|
||||
],
|
||||
'Difficile': [
|
||||
"Investir fortement en R&D pour la substitution.",
|
||||
"Développer des technologies alternatives robustes.",
|
||||
"Établir des partenariats stratégiques locaux solides."
|
||||
],
|
||||
'Extraction': {
|
||||
'Facile': [
|
||||
"Constituer des stocks « in-country » (site minier / port) pour 30 jours.",
|
||||
"Activer un moniteur de prix spot quotidien.",
|
||||
"Lancer une veille ESG locale (manifestations, météo extrême)."
|
||||
],
|
||||
'Modérée': [
|
||||
"Négocier des contrats « take-or-pay » avec au moins 2 exploitants distincts.",
|
||||
"Mettre en place un audit semestriel des pratiques de sécurité/logistique des mines.",
|
||||
"Financer en co-investissement un entrepôt portuaire multi-produits."
|
||||
],
|
||||
'Difficile': [
|
||||
"Participer au capital d’un producteur émergent hors zone de concentration.",
|
||||
"Obtenir des droits d’« off-take » de 5 ans sur 20 % de la production d’une mine alternative.",
|
||||
"Soutenir (CAPEX) l’ouverture d’une nouvelle voie ferroviaire ou portuaire sécurisée."
|
||||
]
|
||||
},
|
||||
'Traitement': {
|
||||
'Facile': [
|
||||
"Sécuriser un stock tampon sur site (90 jours).",
|
||||
"Faire certifier la traçabilité chimique du concentré."
|
||||
],
|
||||
'Modérée': [
|
||||
"Valider un second affineur dans une région politiquement stable.",
|
||||
"Imposer des clauses « force-majeure » limitant l’arrêt total à 48 h."
|
||||
],
|
||||
'Difficile': [
|
||||
"Co-développer un site de raffinage dans une zone « friend-shore ».",
|
||||
"Financer un procédé de purification à rendement plus élevé (réduit la dépendance au minerai primaire)."
|
||||
]
|
||||
},
|
||||
'Fabrication': {
|
||||
'Facile': [
|
||||
"Mettre un seuil minimal de sécurité (45 jours) sur le composant critique en usine SMT.",
|
||||
"Suivre hebdomadairement la capacité libre des fondeurs/EMS."
|
||||
],
|
||||
'Modérée': [
|
||||
"Dual-sourcer le composant critique intégrant un minerai critique (au moins 30 % chez un second fondeur).",
|
||||
"Déployer le « design-for-substitution » : même PCB compatible avec le composant concerné."
|
||||
],
|
||||
'Difficile': [
|
||||
"Lancer un programme R&D de substitution ou d'alternative budgeté sur 3 ans.",
|
||||
"Contractualiser un accord exclusif avec un fondeur hors zone rouge pour 25 % des volumes."
|
||||
]
|
||||
},
|
||||
'Assemblage': {
|
||||
'Facile': [
|
||||
"Allonger la rotation des stocks de produits finis (en aval) pour amortir un retard de 2 semaines.",
|
||||
"Mettre en place un plan de re-déploiement du personnel sur d’autres lignes en cas de rupture composant."
|
||||
],
|
||||
'Modérée': [
|
||||
"Avoir un site d’assemblage secondaire (low-volume) dans une région verte, testé tous les 6 mois.",
|
||||
"Segmenter les nomenclatures : version « premium » avec composant haut de gamme, version « fallback » avec composant moins critique."
|
||||
],
|
||||
'Difficile': [
|
||||
"Investir dans une plateforme d’assemblage flexible (robots modulaires) capable de basculer vers un composant de substitution en < 72 h.",
|
||||
"Signer un accord gouvernemental pour un soutien logistique prioritaire (corridor aérien dédié) en cas de crise géopolitique."
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
INDICATEURS = {
|
||||
'Facile': [
|
||||
"Suivi régulier de la stabilité géopolitique (ISG).",
|
||||
"Durée réelle d'utilisation du matériel.",
|
||||
"Niveau des stocks stratégiques disponibles."
|
||||
],
|
||||
'Modérée': [
|
||||
"Taux de diversification des fournisseurs par région.",
|
||||
"Évolution trimestrielle de la concurrence intersectorielle (IVC).",
|
||||
"Taux annuel de recyclage des composants critiques."
|
||||
],
|
||||
'Difficile': [
|
||||
"Budget annuel investi dans la recherche technologique.",
|
||||
"Nombre de brevets déposés pour des substituts.",
|
||||
"Progrès réel en matière de substitution technologique (ICS)."
|
||||
],
|
||||
'Extraction': {
|
||||
'Facile': [
|
||||
"Jours de stock portuaire (objectif ≥ 30).",
|
||||
"Indice ISG moyen pondéré des pays extracteurs (alerte ≥ 60).",
|
||||
"Volatilité hebdo du prix spot (écart-type %)."
|
||||
],
|
||||
'Modérée': [
|
||||
"Part du 2ᵉ fournisseur dans le volume total (objectif ≥ 20 %).",
|
||||
"Délai moyen d’obtention des permis d’export."
|
||||
],
|
||||
'Difficile': [
|
||||
"Capacité annuelle d’une mine alternative financée (% du besoin interne).",
|
||||
"Progrès physique de l’infrastructure logistique (Km de voie, % achevé)."
|
||||
]
|
||||
},
|
||||
'Traitement': {
|
||||
'Facile': [
|
||||
"Couverture stock tampon (jours).",
|
||||
"Certificats de traçabilité obtenus (% lots)."
|
||||
],
|
||||
'Modérée': [
|
||||
"Nombre d’affineurs validés (objectif ≥ 2).",
|
||||
"Taux de rendement global du procédé (%)."
|
||||
],
|
||||
'Difficile': [
|
||||
"Part de production refinée hors zone rouge (%).",
|
||||
"Capex cumulé investi dans de nouveaux procédés (M€)."
|
||||
]
|
||||
},
|
||||
'Fabrication': {
|
||||
'Facile': [
|
||||
"Stock de composants critiques (jours).",
|
||||
"Capacité libre des EMS (%) rapportée chaque vendredi."
|
||||
],
|
||||
'Modérée': [
|
||||
"Part du second fondeur dans la production du composant audio (%).",
|
||||
"Nombre de PCB « design-for-substitution » validés."
|
||||
],
|
||||
'Difficile': [
|
||||
"Dépenses R&D substituts (€) vs budget.",
|
||||
"TRI attendu sur les investisseurs fondeurs alternatifs."
|
||||
]
|
||||
},
|
||||
'Assemblage': {
|
||||
'Facile': [
|
||||
"Jours de produits finis en entrepôt.",
|
||||
"Temps de retouche ligne en cas de rupture (heures)."
|
||||
],
|
||||
'Modérée': [
|
||||
"Volume annuel produit sur le site de secours (%).",
|
||||
"Temps de requalification d’une ligne vers la version « fallback »."
|
||||
],
|
||||
'Difficile': [
|
||||
"Taux d’automatisation reconfigurable (% machines modulaires).",
|
||||
"Nb d’heures du corridor aérien prioritaire utilisé vs capacité."
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
# ------------------------- PARSEUR -------------------------
|
||||
def parse_chains_md(filepath: str) -> tuple[dict, dict, dict, list, dict, dict]:
|
||||
re_start_section = re.compile(r"^##\s*Chaînes\s+avec\s+risque\s+critique", re.IGNORECASE)
|
||||
re_other_h2 = re.compile(r"^##\s+(?!(Chaînes\s+avec\s+risque\s+critique))")
|
||||
re_chain_heading = re.compile(r"^###\s*(.+)\s*→\s*(.+)\s*→\s*(.+)$")
|
||||
re_phase = re.compile(r"^\*\s*(Assemblage|Fabrication|Minerai|Extraction|Traitement)", re.IGNORECASE)
|
||||
re_IHH = re.compile(r"IHH\s*[:]\s*([0-9]+(?:\.[0-9]+)?)")
|
||||
re_ISG = re.compile(r"ISG\s*combiné\s*[:]\s*([0-9]+(?:\.[0-9]+)?)|ISG\s*[:]\s*([0-9]+(?:\.[0-9]+)?)", re.IGNORECASE)
|
||||
re_ICS = re.compile(r"ICS\s*moyen\s*[:]\s*([0-9]+(?:\.[0-9]+)?)", re.IGNORECASE)
|
||||
re_IVC = re.compile(r"IVC\s*[:]\s*([0-9]+(?:\.[0-9]+)?)", re.IGNORECASE)
|
||||
|
||||
produits, composants, mineraux, chains = {}, {}, {}, []
|
||||
descriptions = {}
|
||||
details_sections = {}
|
||||
current_chain = None
|
||||
current_phase = None
|
||||
current_section = None
|
||||
in_section = False
|
||||
|
||||
with open(filepath, encoding="utf-8") as f:
|
||||
for raw_line in f:
|
||||
line = raw_line.strip()
|
||||
if not in_section:
|
||||
if re_start_section.match(line):
|
||||
in_section = True
|
||||
continue
|
||||
if re_other_h2.match(line):
|
||||
break
|
||||
m_chain = re_chain_heading.match(line)
|
||||
if m_chain:
|
||||
prod, comp, miner = map(str.strip, m_chain.groups())
|
||||
produits.setdefault(prod, {"IHH_Assemblage": None, "ISG_Assemblage": None})
|
||||
composants.setdefault(comp, {"IHH_Fabrication": None, "ISG_Fabrication": None})
|
||||
mineraux.setdefault(miner, {
|
||||
"ICS": None, "IVC": None,
|
||||
"IHH_Extraction": None, "ISG_Extraction": None,
|
||||
"IHH_Traitement": None, "ISG_Traitement": None
|
||||
})
|
||||
chains.append({"produit": prod, "composant": comp, "minerai": miner})
|
||||
current_chain = {"prod": prod, "comp": comp, "miner": miner}
|
||||
current_phase = None
|
||||
current_section = f"{prod} → {comp} → {miner}"
|
||||
descriptions[current_section] = ""
|
||||
continue
|
||||
if current_chain is None:
|
||||
continue
|
||||
m_phase = re_phase.match(line)
|
||||
if m_phase:
|
||||
current_phase = m_phase.group(1).capitalize()
|
||||
continue
|
||||
if current_phase:
|
||||
p = current_chain
|
||||
if current_phase == "Assemblage":
|
||||
if (m := re_IHH.search(line)):
|
||||
produits[p["prod"]]["IHH_Assemblage"] = float(m.group(1))
|
||||
continue
|
||||
if (m := re_ISG.search(line)):
|
||||
raw = m.group(1) or m.group(2)
|
||||
produits[p["prod"]]["ISG_Assemblage"] = float(raw)
|
||||
continue
|
||||
if current_phase == "Fabrication":
|
||||
if (m := re_IHH.search(line)):
|
||||
composants[p["comp"]]["IHH_Fabrication"] = float(m.group(1))
|
||||
continue
|
||||
if (m := re_ISG.search(line)):
|
||||
raw = m.group(1) or m.group(2)
|
||||
composants[p["comp"]]["ISG_Fabrication"] = float(raw)
|
||||
continue
|
||||
if current_phase == "Minerai":
|
||||
if (m := re_ICS.search(line)):
|
||||
mineraux[p["miner"]]["ICS"] = float(m.group(1))
|
||||
continue
|
||||
if (m := re_IVC.search(line)):
|
||||
mineraux[p["miner"]]["IVC"] = float(m.group(1))
|
||||
continue
|
||||
if current_phase == "Extraction":
|
||||
if (m := re_IHH.search(line)):
|
||||
mineraux[p["miner"]]["IHH_Extraction"] = float(m.group(1))
|
||||
continue
|
||||
if (m := re_ISG.search(line)):
|
||||
raw = m.group(1) or m.group(2)
|
||||
mineraux[p["miner"]]["ISG_Extraction"] = float(raw)
|
||||
continue
|
||||
if current_phase == "Traitement":
|
||||
if (m := re_IHH.search(line)):
|
||||
mineraux[p["miner"]]["IHH_Traitement"] = float(m.group(1))
|
||||
continue
|
||||
if (m := re_ISG.search(line)):
|
||||
raw = m.group(1) or m.group(2)
|
||||
mineraux[p["miner"]]["ISG_Traitement"] = float(raw)
|
||||
continue
|
||||
else:
|
||||
if current_section:
|
||||
descriptions[current_section] += raw_line
|
||||
|
||||
# Parse detailed sections from the complete file
|
||||
with open(filepath, encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
# Extract sections using regex patterns
|
||||
lines = content.split('\n')
|
||||
|
||||
# Find section boundaries
|
||||
operations_start = None
|
||||
minerais_start = None
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
if line.strip() == "## Détails des opérations":
|
||||
operations_start = i
|
||||
elif line.strip() == "## Détails des minerais":
|
||||
minerais_start = i
|
||||
|
||||
if operations_start is not None:
|
||||
# Parse operations section (assemblage and fabrication)
|
||||
operations_end = minerais_start if minerais_start else len(lines)
|
||||
operations_lines = lines[operations_start:operations_end]
|
||||
|
||||
current_section_name = None
|
||||
current_content = []
|
||||
|
||||
for line in operations_lines:
|
||||
if line.startswith("### ") and " et " in line:
|
||||
# Save previous section
|
||||
if current_section_name and current_content:
|
||||
details_sections[current_section_name] = '\n'.join(current_content)
|
||||
|
||||
# Start new section
|
||||
section_title = line.replace("### ", "").strip()
|
||||
if " et Assemblage" in section_title:
|
||||
product_name = section_title.replace(" et Assemblage", "").strip()
|
||||
current_section_name = f"{product_name}_assemblage"
|
||||
elif " et Fabrication" in section_title:
|
||||
component_name = section_title.replace(" et Fabrication", "").strip()
|
||||
current_section_name = f"{component_name}_fabrication"
|
||||
current_content = []
|
||||
elif current_section_name:
|
||||
current_content.append(line)
|
||||
|
||||
# Save last section
|
||||
if current_section_name and current_content:
|
||||
details_sections[current_section_name] = '\n'.join(current_content)
|
||||
|
||||
if minerais_start is not None:
|
||||
# Parse minerais section
|
||||
minerais_lines = lines[minerais_start:]
|
||||
|
||||
current_minerai = None
|
||||
current_section_type = "general"
|
||||
current_content = []
|
||||
|
||||
for line in minerais_lines:
|
||||
if line.startswith("### ") and "→" not in line and " et " not in line:
|
||||
# Save previous section
|
||||
if current_minerai and current_content:
|
||||
details_sections[f"{current_minerai}_{current_section_type}"] = '\n'.join(current_content)
|
||||
|
||||
# Start new minerai
|
||||
current_minerai = line.replace("### ", "").strip()
|
||||
current_section_type = "general"
|
||||
current_content = []
|
||||
|
||||
elif line.startswith("#### Extraction"):
|
||||
# Save previous section
|
||||
if current_minerai and current_content:
|
||||
details_sections[f"{current_minerai}_{current_section_type}"] = '\n'.join(current_content)
|
||||
|
||||
current_section_type = "extraction"
|
||||
current_content = []
|
||||
|
||||
elif line.startswith("#### Traitement"):
|
||||
# Save previous section
|
||||
if current_minerai and current_content:
|
||||
details_sections[f"{current_minerai}_{current_section_type}"] = '\n'.join(current_content)
|
||||
|
||||
current_section_type = "traitement"
|
||||
current_content = []
|
||||
|
||||
elif line.startswith("## ") and current_minerai:
|
||||
# End of minerais section
|
||||
if current_content:
|
||||
details_sections[f"{current_minerai}_{current_section_type}"] = '\n'.join(current_content)
|
||||
break
|
||||
|
||||
elif current_minerai:
|
||||
current_content.append(line)
|
||||
|
||||
# Save last section
|
||||
if current_minerai and current_content:
|
||||
details_sections[f"{current_minerai}_{current_section_type}"] = '\n'.join(current_content)
|
||||
|
||||
return produits, composants, mineraux, chains, descriptions, details_sections
|
||||
|
||||
# ---------------------- AFFICHAGE ----------------------
|
||||
def afficher_bloc_ihh_isg(titre, ihh, isg, details_content=""):
|
||||
st.markdown(f"### {titre}")
|
||||
|
||||
if not details_content:
|
||||
st.markdown("Données non disponibles")
|
||||
return
|
||||
|
||||
lines = details_content.split('\n')
|
||||
|
||||
# 1. Afficher vulnérabilité combinée en premier
|
||||
if "#### Vulnérabilité combinée IHH-ISG" in details_content:
|
||||
st.markdown("#### Vulnérabilité combinée IHH-ISG")
|
||||
afficher_section_texte(lines, "#### Vulnérabilité combinée IHH-ISG", "###")
|
||||
|
||||
# 2. Afficher ISG des pays impliqués
|
||||
if "##### ISG des pays impliqués" in details_content:
|
||||
print(details_content)
|
||||
st.markdown("#### ISG des pays impliqués")
|
||||
afficher_section_avec_tableau(lines, "##### ISG des pays impliqués")
|
||||
|
||||
# Afficher le résumé ISG combiné
|
||||
for line in lines:
|
||||
if "**ISG combiné:" in line:
|
||||
st.markdown(line)
|
||||
break
|
||||
|
||||
# 3. Afficher la section IHH complète
|
||||
if "#### Indice de Herfindahl-Hirschmann" in details_content:
|
||||
st.markdown("#### Indice de Herfindahl-Hirschmann")
|
||||
|
||||
# Tableau de résumé IHH
|
||||
afficher_section_avec_tableau(lines, "#### Indice de Herfindahl-Hirschmann")
|
||||
|
||||
# IHH par entreprise
|
||||
if "##### IHH par entreprise (acteurs)" in details_content:
|
||||
st.markdown("##### IHH par entreprise (acteurs)")
|
||||
afficher_section_texte(lines, "##### IHH par entreprise (acteurs)", "##### IHH par pays")
|
||||
|
||||
# IHH par pays
|
||||
if "##### IHH par pays" in details_content:
|
||||
st.markdown("##### IHH par pays")
|
||||
afficher_section_texte(lines, "##### IHH par pays", "##### En résumé")
|
||||
|
||||
# En résumé
|
||||
if "##### En résumé" in details_content:
|
||||
st.markdown("##### En résumé")
|
||||
afficher_section_texte(lines, "##### En résumé", "####")
|
||||
|
||||
def afficher_section_avec_tableau(lines, section_start, section_end=None):
|
||||
"""Affiche une section contenant un tableau"""
|
||||
in_section = False
|
||||
table_lines = []
|
||||
|
||||
for line in lines:
|
||||
if section_start in line:
|
||||
in_section = True
|
||||
continue
|
||||
elif in_section and section_end and section_end in line:
|
||||
break
|
||||
elif in_section and line.startswith('#') and section_start not in line:
|
||||
break
|
||||
elif in_section:
|
||||
if line.strip().startswith('|'):
|
||||
table_lines.append(line)
|
||||
elif table_lines and not line.strip().startswith('|'):
|
||||
# Fin du tableau
|
||||
break
|
||||
|
||||
if table_lines:
|
||||
st.markdown('\n'.join(table_lines))
|
||||
|
||||
def afficher_section_texte(lines, section_start, section_end_marker=None):
|
||||
"""Affiche le texte d'une section sans les tableaux"""
|
||||
in_section = False
|
||||
|
||||
for line in lines:
|
||||
if section_start in line:
|
||||
in_section = True
|
||||
continue
|
||||
elif in_section and section_end_marker and line.startswith(section_end_marker):
|
||||
break
|
||||
elif in_section and line.startswith('#') and section_start not in line:
|
||||
break
|
||||
elif in_section and line.strip() and not line.strip().startswith('|'):
|
||||
st.markdown(line)
|
||||
|
||||
def afficher_description(titre, description):
|
||||
st.markdown(f"## {titre}")
|
||||
if description:
|
||||
lines = description.split('\n')
|
||||
description_lines = []
|
||||
|
||||
# Extraire le premier paragraphe descriptif
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
if description_lines: # Si on a déjà du contenu, une ligne vide termine le paragraphe
|
||||
break
|
||||
continue
|
||||
# Arrêter aux titres de sections ou tableaux
|
||||
if (line.startswith('####') or
|
||||
line.startswith('|') or
|
||||
line.startswith('**Unité')):
|
||||
break
|
||||
description_lines.append(line)
|
||||
|
||||
if description_lines:
|
||||
# Rejoindre les lignes en un seul paragraphe
|
||||
full_description = ' '.join(description_lines)
|
||||
st.markdown(full_description)
|
||||
else:
|
||||
st.markdown("Description non disponible")
|
||||
else:
|
||||
st.markdown("Description non disponible")
|
||||
|
||||
def afficher_caracteristiques_minerai(minerai, mineraux_data, details_content=""):
|
||||
st.markdown("### Caractéristiques générales")
|
||||
|
||||
if not details_content:
|
||||
st.markdown("Données non disponibles")
|
||||
return
|
||||
|
||||
lines = details_content.split('\n')
|
||||
|
||||
# 3. Afficher la vulnérabilité combinée ICS-IVC en dernier
|
||||
if "#### Vulnérabilité combinée ICS-IVC" in details_content:
|
||||
st.markdown("#### Vulnérabilité combinée ICS-IVC")
|
||||
afficher_section_texte(lines, "#### Vulnérabilité combinée ICS-IVC", "####")
|
||||
|
||||
# 1. Afficher la section ICS complète
|
||||
if "#### ICS" in details_content:
|
||||
st.markdown("#### ICS")
|
||||
|
||||
# Afficher le premier tableau ICS (avec toutes les colonnes)
|
||||
afficher_section_avec_tableau(lines, "#### ICS", "##### Valeurs d'ICS par composant")
|
||||
|
||||
# Afficher la sous-section "Valeurs d'ICS par composant"
|
||||
if "##### Valeurs d'ICS par composant" in details_content:
|
||||
st.markdown("##### Valeurs d'ICS par composant")
|
||||
afficher_section_avec_tableau(lines, "##### Valeurs d'ICS par composant", "**ICS moyen")
|
||||
|
||||
# Afficher le résumé ICS moyen
|
||||
for line in lines:
|
||||
if "**ICS moyen" in line:
|
||||
st.markdown(line)
|
||||
break
|
||||
|
||||
# 2. Afficher la section IVC complète
|
||||
if "#### IVC" in details_content:
|
||||
st.markdown("#### IVC")
|
||||
|
||||
# Afficher la valeur IVC principale
|
||||
for line in lines:
|
||||
if "**IVC:" in line:
|
||||
st.markdown(line)
|
||||
break
|
||||
|
||||
# Afficher tous les détails de la section IVC
|
||||
afficher_section_texte(lines, "#### IVC", "#### Vulnérabilité combinée ICS-IVC")
|
||||
|
||||
def get_seuil(seuils_dict, key):
|
||||
try:
|
||||
if key in seuils_dict:
|
||||
data = seuils_dict[key]
|
||||
for niveau in ["rouge", "orange", "vert"]:
|
||||
if niveau in data:
|
||||
seuil = data[niveau]
|
||||
if "min" in seuil and seuil["min"] is not None:
|
||||
return seuil["min"]
|
||||
if "max" in seuil and seuil["max"] is not None:
|
||||
return seuil["max"]
|
||||
except:
|
||||
pass
|
||||
return None
|
||||
|
||||
def set_vulnerability(v1, v2, t1, t2, seuils):
|
||||
v1_poids = 1
|
||||
v1_couleur = "Vert"
|
||||
if v1 > seuils[t1]["rouge"]["min"]:
|
||||
v1_poids = 3
|
||||
v1_couleur = "Rouge"
|
||||
elif v1 > seuils[t1]["vert"]["max"]:
|
||||
v1_poids = 2
|
||||
v1_couleur = "Orange"
|
||||
|
||||
v2_poids = 1
|
||||
v2_couleur = "Vert"
|
||||
if v2 > seuils[t2]["rouge"]["min"]:
|
||||
v2_poids = 3
|
||||
v2_couleur = "Rouge"
|
||||
elif v2 > seuils[t2]["vert"]["max"]:
|
||||
v2_poids = 2
|
||||
v2_couleur = "Orange"
|
||||
|
||||
poids = v1_poids * v2_poids
|
||||
couleur = "Rouge"
|
||||
if poids <= 2:
|
||||
couleur = "Vert"
|
||||
elif poids <= 4:
|
||||
couleur = "Orange"
|
||||
|
||||
return poids, couleur, v1_couleur, v2_couleur
|
||||
|
||||
def initialiser_interface(filepath: str, config_path: str = "assets/config.yaml"):
|
||||
produits, composants, mineraux, chains, descriptions, details_sections = parse_chains_md(filepath)
|
||||
|
||||
if not chains:
|
||||
st.warning("Aucune chaîne critique trouvée dans le fichier.")
|
||||
return
|
||||
|
||||
seuils = {}
|
||||
|
||||
try:
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
config = yaml.safe_load(f)
|
||||
seuils = config.get("seuils", seuils)
|
||||
except FileNotFoundError:
|
||||
st.warning(f"Fichier de configuration {config_path} non trouvé.")
|
||||
|
||||
col_left, col_right = st.columns([1, 1], gap="small", border=True)
|
||||
with col_left:
|
||||
st.markdown("**<u>Panneau de sélection</u>**", unsafe_allow_html=True)
|
||||
produits_disponibles = sorted({c["produit"] for c in chains})
|
||||
sel_prod = st.selectbox("Produit", produits_disponibles)
|
||||
composants_dispo = sorted({c["composant"] for c in chains if c["produit"] == sel_prod})
|
||||
sel_comp = st.selectbox("Composant", composants_dispo)
|
||||
mineraux_dispo = sorted({c["minerai"] for c in chains if c["produit"] == sel_prod and c["composant"] == sel_comp})
|
||||
sel_miner = st.selectbox("Minerai", mineraux_dispo)
|
||||
with col_right:
|
||||
st.markdown("**<u>Synthèse des criticités</u>**", unsafe_allow_html=True)
|
||||
poids_A, couleur_A, couleur_A_ihh, couleur_A_isg = set_vulnerability(produits[sel_prod]["IHH_Assemblage"], produits[sel_prod]["ISG_Assemblage"], "IHH", "ISG", seuils)
|
||||
poids_F, couleur_F, couleur_F_ihh, couleur_F_isg = set_vulnerability(composants[sel_comp]["IHH_Fabrication"], composants[sel_comp]["ISG_Fabrication"], "IHH", "ISG", seuils)
|
||||
poids_T, couleur_T, couleur_T_ihh, couleur_T_isg = set_vulnerability(mineraux[sel_miner]["IHH_Traitement"], mineraux[sel_miner]["ISG_Traitement"], "IHH", "ISG", seuils)
|
||||
poids_E, couleur_E, couleur_E_ihh, couleur_E_isg = set_vulnerability(mineraux[sel_miner]["IHH_Extraction"], mineraux[sel_miner]["ISG_Extraction"], "IHH", "ISG", seuils)
|
||||
poids_M, couleur_M, couleur_M_ics, couleur_M_ivc = set_vulnerability(mineraux[sel_miner]["ICS"], mineraux[sel_miner]["IVC"], "ICS", "IVC", seuils)
|
||||
|
||||
st.markdown(f"* **{sel_prod} - Assemblage** : {couleur_A} ({poids_A})")
|
||||
st.markdown(f"* **{sel_comp} - Fabrication** : {couleur_F} ({poids_F})")
|
||||
st.markdown(f"* **{sel_miner} - Traitement** : {couleur_T} ({poids_T})")
|
||||
st.markdown(f"* **{sel_miner} - Extraction** : {couleur_E} ({poids_E})")
|
||||
st.markdown(f"* **{sel_miner} - Minerai** : {couleur_M} ({poids_M})")
|
||||
|
||||
poids_operation = {
|
||||
'Extraction': 1,
|
||||
'Traitement': 1.5,
|
||||
'Assemblage': 1.5,
|
||||
'Fabrication': 2,
|
||||
'Substitution': 2
|
||||
}
|
||||
|
||||
poids_total = (\
|
||||
poids_A * poids_operation["Assemblage"] + \
|
||||
poids_F * poids_operation["Fabrication"] + \
|
||||
poids_T * poids_operation["Traitement"] + \
|
||||
poids_E * poids_operation["Extraction"] + \
|
||||
poids_M * poids_operation["Substitution"] \
|
||||
) / sum(poids_operation.values())
|
||||
|
||||
if poids_total < 3:
|
||||
criticite_chaine = "Modérée"
|
||||
niveau_criticite = {"Facile"}
|
||||
elif poids_total < 6:
|
||||
criticite_chaine = "Élevée"
|
||||
niveau_criticite = {"Facile", "Modérée"}
|
||||
else:
|
||||
criticite_chaine = "Critique"
|
||||
niveau_criticite = {"Facile", "Modérée", "Difficile"}
|
||||
|
||||
st.info(f"**Criticité globale : {criticite_chaine} ({poids_total})**")
|
||||
|
||||
with st.expander("Vue d’ensemble des criticités", expanded=True):
|
||||
st.markdown("## Vue d’ensemble des criticités", unsafe_allow_html=True)
|
||||
|
||||
col_left, col_right = st.columns([1, 1], gap="small", border=True)
|
||||
|
||||
with col_left:
|
||||
fig1, ax1 = plt.subplots(figsize=(1, 1))
|
||||
ax1.scatter([produits[sel_prod]["ISG_Assemblage"]], [produits[sel_prod]["IHH_Assemblage"]], label="Assemblage".ljust(20), s=5)
|
||||
ax1.scatter([composants[sel_comp]["ISG_Fabrication"]], [composants[sel_comp]["IHH_Fabrication"]], label="Fabrication".ljust(20), s=5)
|
||||
ax1.scatter([mineraux[sel_miner]["ISG_Extraction"]], [mineraux[sel_miner]["IHH_Extraction"]], label="Extraction".ljust(20), s=5)
|
||||
ax1.scatter([mineraux[sel_miner]["ISG_Traitement"]], [mineraux[sel_miner]["IHH_Traitement"]], label="Traitement".ljust(20), s=5)
|
||||
|
||||
# Seuils ISG (vertical)
|
||||
ax1.axvline(seuils["ISG"]["vert"]["max"], linestyle='--', color='green', alpha=0.7, linewidth=0.5) # Seuil vert-orange
|
||||
ax1.axvline(seuils["ISG"]["rouge"]["min"], linestyle='--', color='red', alpha=0.7, linewidth=0.5) # Seuil orange-rouge
|
||||
|
||||
# Seuils IHH (horizontal)
|
||||
ax1.axhline(seuils["IHH"]["vert"]["max"], linestyle='--', color='green', alpha=0.7, linewidth=0.5) # Seuil vert-orange
|
||||
ax1.axhline(seuils["IHH"]["rouge"]["min"], linestyle='--', color='red', alpha=0.7, linewidth=0.5) # Seuil orange-rouge
|
||||
|
||||
ax1.set_xlim(0, 100)
|
||||
ax1.set_ylim(0, 100)
|
||||
ax1.set_xlabel("ISG", fontsize=4)
|
||||
ax1.set_ylabel("IHH", fontsize=4)
|
||||
ax1.tick_params(axis='both', which='major', labelsize=4)
|
||||
ax1.legend(bbox_to_anchor=(1.05, 1), loc='upper left', fontsize=4)
|
||||
plt.tight_layout()
|
||||
st.pyplot(fig1)
|
||||
|
||||
with col_right:
|
||||
fig2, ax2 = plt.subplots(figsize=(1, 1))
|
||||
ax2.scatter([mineraux[sel_miner]["IVC"]], [mineraux[sel_miner]["ICS"]], color='green', s=5, label=sel_miner.ljust(20))
|
||||
|
||||
# Seuils IVC (vertical)
|
||||
ax2.axvline(seuils["IVC"]["vert"]["max"], linestyle='--', color='green', alpha=0.7, linewidth=0.5) # Seuil vert-orange
|
||||
ax2.axvline(seuils["IVC"]["rouge"]["min"], linestyle='--', color='red', alpha=0.7, linewidth=0.5) # Seuil orange-rouge
|
||||
|
||||
# Seuils ICS (horizontal)
|
||||
ax2.axhline(seuils["ICS"]["vert"]["max"], linestyle='--', color='green', alpha=0.7, linewidth=0.5) # Seuil vert-orange
|
||||
ax2.axhline(seuils["ICS"]["rouge"]["min"], linestyle='--', color='red', alpha=0.7, linewidth=0.5) # Seuil orange-rouge
|
||||
|
||||
ax2.set_xlim(0, max(100, mineraux[sel_miner]["IVC"]))
|
||||
ax2.set_ylim(0, 1)
|
||||
ax2.set_xlabel("IVC", fontsize=4)
|
||||
ax2.set_ylabel("ICS", fontsize=4)
|
||||
ax2.tick_params(axis='both', which='major', labelsize=4)
|
||||
ax2.legend(bbox_to_anchor=(1.05, 1), loc='upper left', fontsize=4)
|
||||
plt.tight_layout()
|
||||
st.pyplot(fig2)
|
||||
|
||||
st.markdown("""
|
||||
Les lignes pointillées vertes et rouges représentent les seuils des indices concernés.\n
|
||||
Les indices ISG (stabilité géopolitique) et IVC (concurrence intersectorielle) influent sur la probabilité de survenance d'un risque.\n
|
||||
Les indices IHH (concentration géographique) et ICS (capacité de substitution) influent sur le niveau d'impact d'un risque.\n
|
||||
Une opération se trouvant au-dessus des deux seuils a donc une forte probabilité d'être impactée avec un fort impact sur la capacité à continuer la production.
|
||||
""")
|
||||
|
||||
with st.expander("Explications et détails", expanded = True):
|
||||
from collections import Counter
|
||||
couleurs = [couleur_A, couleur_F, couleur_T, couleur_E, couleur_M]
|
||||
compte = Counter(couleurs)
|
||||
nb_rouge = compte["Rouge"]
|
||||
nb_orange = compte["Orange"]
|
||||
|
||||
st.markdown(f"""
|
||||
Pour cette chaîne **{sel_prod} => {sel_comp} => {sel_miner}**, avec {nb_rouge} criticité(s) Rouge(s), {nb_orange} criticité(s) Orange(s), les indices individuels par opération sont :
|
||||
|
||||
* **{sel_prod} - Assemblage** : {couleur_A} ({poids_A})
|
||||
* IHH = {produits[sel_prod]["IHH_Assemblage"]} ({couleur_A_ihh}) — ISG = {produits[sel_prod]["ISG_Assemblage"]} ({couleur_A_isg})
|
||||
* pondération de l'Assemblage dans le calcul de la criticité globale : 1,5
|
||||
* se référer à **{sel_prod} et Assemblage** plus bas pour le détail complet
|
||||
* **{sel_comp} - Fabrication** : {couleur_F} ({poids_F})
|
||||
* IHH = {composants[sel_comp]["IHH_Fabrication"]} ({couleur_F_ihh}) — ISG = {composants[sel_comp]["ISG_Fabrication"]} ({couleur_F_isg})
|
||||
* pondération de la Fabrication dans le calcul de la criticité globale : 2
|
||||
* se référer à **{sel_comp} et Fabrication** plus bas pour le détail complet
|
||||
* **{sel_miner} - Traitement** : {couleur_A} ({poids_A})
|
||||
* IHH = {mineraux[sel_miner]["IHH_Traitement"]} ({couleur_T_ihh}) — ISG = {mineraux[sel_miner]["ISG_Traitement"]} ({couleur_T_isg})
|
||||
* pondération du Traitement dans le calcul de la criticité globale : 1,5
|
||||
* se référer à **{sel_miner} — Vue globale** plus bas pour le détail complet de l'ensemble du minerai
|
||||
* **{sel_miner} - Extraction** : {couleur_F} ({poids_F})
|
||||
* IHH = {mineraux[sel_miner]["IHH_Extraction"]} ({couleur_E_ihh}) — ISG = {mineraux[sel_miner]["ISG_Extraction"]} ({couleur_E_isg})
|
||||
* pondération de l'Extraction dans le calcul de la criticité globale : 1
|
||||
* **{sel_miner} - Minerai** : {couleur_F} ({poids_F})
|
||||
* ICS = {mineraux[sel_miner]["ICS"]} ({couleur_M_ics}) — IVC = {mineraux[sel_miner]["IVC"]} ({couleur_M_ivc})
|
||||
* pondération de la Substitution dans le calcul de la criticité globale : 2
|
||||
""")
|
||||
|
||||
with st.expander("Préconisations et indicateurs génériques"):
|
||||
col_left, col_right = st.columns([1, 1], gap="small", border=True)
|
||||
with col_left:
|
||||
st.markdown("### Préconisations :\n\n")
|
||||
st.markdown("Mise en œuvre : \n")
|
||||
for niveau, contenu in PRECONISATIONS.items():
|
||||
if niveau in niveau_criticite:
|
||||
contenu_md = f"* {niveau}\n"
|
||||
for p in PRECONISATIONS[niveau]:
|
||||
contenu_md += f" - {p}\n"
|
||||
st.markdown(contenu_md)
|
||||
with col_right:
|
||||
st.markdown("### Indicateurs :\n\n")
|
||||
st.markdown("Mise en œuvre : \n")
|
||||
for niveau, contenu in INDICATEURS.items():
|
||||
if niveau in niveau_criticite:
|
||||
contenu_md = f"* {niveau}\n"
|
||||
for p in INDICATEURS[niveau]:
|
||||
contenu_md += f" - {p}\n"
|
||||
st.markdown(contenu_md)
|
||||
|
||||
def affectation_poids(poids_operation):
|
||||
if poids_operation < 3:
|
||||
niveau_criticite = {"Facile"}
|
||||
elif poids_operation < 6:
|
||||
niveau_criticite = {"Facile", "Modérée"}
|
||||
else:
|
||||
niveau_criticite = {"Facile", "Modérée", "Difficile"}
|
||||
return niveau_criticite
|
||||
|
||||
niveau_criticite_operation = {}
|
||||
niveau_criticite_operation["Assemblage"] = affectation_poids(poids_A)
|
||||
niveau_criticite_operation["Fabrication"] = affectation_poids(poids_F)
|
||||
niveau_criticite_operation["Traitement"] = affectation_poids(poids_T)
|
||||
niveau_criticite_operation["Extraction"] = affectation_poids(poids_E)
|
||||
|
||||
for operation in ["Assemblage", "Fabrication", "Traitement", "Extraction"]:
|
||||
with st.expander(f"Préconisations et indicateurs spécifiques - {operation}"):
|
||||
st.markdown(f"### {operation}")
|
||||
col_left, col_right = st.columns([1, 1], gap="small", border=True)
|
||||
with col_left:
|
||||
st.markdown("#### Préconisations :\n\n")
|
||||
st.markdown("Mise en œuvre : \n")
|
||||
for niveau, contenu in PRECONISATIONS[operation].items():
|
||||
if niveau in niveau_criticite_operation[operation]:
|
||||
contenu_md = f"* {niveau}\n"
|
||||
for p in PRECONISATIONS[operation][niveau]:
|
||||
contenu_md += f" - {p}\n"
|
||||
st.markdown(contenu_md)
|
||||
with col_right:
|
||||
st.markdown("#### Indicateurs :\n\n")
|
||||
st.markdown("Mise en œuvre : \n")
|
||||
for niveau, contenu in INDICATEURS[operation].items():
|
||||
if niveau in niveau_criticite_operation[operation]:
|
||||
contenu_md = f"* {niveau}\n"
|
||||
for p in INDICATEURS[operation][niveau]:
|
||||
contenu_md += f" - {p}\n"
|
||||
st.markdown(contenu_md)
|
||||
|
||||
with st.expander(f"{sel_prod} et Assemblage"):
|
||||
assemblage_details = details_sections.get(f"{sel_prod}_assemblage", "")
|
||||
|
||||
afficher_description(f"{sel_prod} et Assemblage", assemblage_details)
|
||||
afficher_bloc_ihh_isg("Assemblage", produits[sel_prod]["IHH_Assemblage"], produits[sel_prod]["ISG_Assemblage"], assemblage_details)
|
||||
|
||||
with st.expander(f"{sel_comp} et Fabrication"):
|
||||
fabrication_details = details_sections.get(f"{sel_comp}_fabrication", "")
|
||||
afficher_description(f"{sel_comp} et Fabrication", fabrication_details)
|
||||
afficher_bloc_ihh_isg("Fabrication", composants[sel_comp]["IHH_Fabrication"], composants[sel_comp]["ISG_Fabrication"], fabrication_details)
|
||||
|
||||
with st.expander(f"{sel_miner} — Vue globale"):
|
||||
minerai_general = details_sections.get(f"{sel_miner}_general", "")
|
||||
afficher_description(f"{sel_miner} — Vue globale", minerai_general)
|
||||
|
||||
extraction_details = details_sections.get(f"{sel_miner}_extraction", "")
|
||||
afficher_bloc_ihh_isg("Extraction", mineraux[sel_miner]["IHH_Extraction"], mineraux[sel_miner]["ISG_Extraction"], extraction_details)
|
||||
|
||||
traitement_details = details_sections.get(f"{sel_miner}_traitement", "")
|
||||
afficher_bloc_ihh_isg("Traitement", mineraux[sel_miner]["IHH_Traitement"], mineraux[sel_miner]["ISG_Traitement"], traitement_details)
|
||||
|
||||
afficher_caracteristiques_minerai(sel_miner, mineraux[sel_miner], minerai_general)
|
||||
@ -44,6 +44,7 @@
|
||||
"personnalisation": "Customization",
|
||||
"analyse": "Analysis",
|
||||
"ia_nalyse": "AI'nalysis",
|
||||
"plan_d_actions": "Actions plan",
|
||||
"visualisations": "Visualizations",
|
||||
"fiches": "Cards"
|
||||
},
|
||||
@ -148,6 +149,26 @@
|
||||
"submit_request": "Submit your request",
|
||||
"empty_graph": "The graph is empty. Please make another selection."
|
||||
},
|
||||
"plan_d_actions": {
|
||||
"title": "Graph analysis for action",
|
||||
"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",
|
||||
"These two selections are optional, but strongly recommended for a more relevant analysis.",
|
||||
"The recommendations for actions and indicator monitoring are generic. They must therefore be adapted to the context.",
|
||||
"The proposed actions or indicators depend on the type of organization concerned and can be applied directly or required of digital providers."
|
||||
],
|
||||
"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?",
|
||||
|
||||
@ -44,6 +44,7 @@
|
||||
"personnalisation": "Personnalisation",
|
||||
"analyse": "Analyse",
|
||||
"ia_nalyse": "IA'nalyse",
|
||||
"plan_d_actions": "Plan d'actions",
|
||||
"visualisations": "Visualisations",
|
||||
"fiches": "Fiches"
|
||||
},
|
||||
@ -148,6 +149,26 @@
|
||||
"submit_request": "Soumettre votre demande",
|
||||
"empty_graph": "Le graphe est vide. Veuillez faire une autre sélection."
|
||||
},
|
||||
"plan_d_actions": {
|
||||
"title": "Analyse du graphe pour action",
|
||||
"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",
|
||||
" Ces deux sélections sont optionnelles, mais fortement recommandées pour avoir une meilleure pertinence de l'analyse.",
|
||||
"Les préconisations d'actions et de suivi d'indicateurs sont génériques. Elles doivent donc être adaptées au contexte.",
|
||||
"Les actions ou les indicateurs proposés dépendent du type d'organisation concernée et peuvent être appliquées directement ou exigées des fournisseurs de numérique."
|
||||
],
|
||||
"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 ?",
|
||||
|
||||
27
batch_ia/__init__.py
Normal file
27
batch_ia/__init__.py
Normal file
@ -0,0 +1,27 @@
|
||||
# batch_ia/__init__.py
|
||||
|
||||
# config.py
|
||||
from .utils.config import TEMPLATE_PATH, load_config
|
||||
|
||||
# files.py
|
||||
from .utils.files import write_report
|
||||
|
||||
# graphs.py
|
||||
from .utils.graphs import (
|
||||
parse_graphs,
|
||||
extract_data_from_graph,
|
||||
calculate_vulnerabilities
|
||||
)
|
||||
|
||||
# sections.py
|
||||
from .utils.sections import generate_report
|
||||
|
||||
__all__ = [
|
||||
"TEMPLATE_PATH",
|
||||
"load_config",
|
||||
"write_report",
|
||||
"parse_graphs",
|
||||
"extract_data_from_graph",
|
||||
"calculate_vulnerabilities",
|
||||
"generate_report",
|
||||
]
|
||||
@ -0,0 +1 @@
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import os
|
||||
import re
|
||||
|
||||
from utils.config import (
|
||||
from .config import (
|
||||
CORPUS_DIR
|
||||
)
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ import os
|
||||
import sys
|
||||
from networkx.drawing.nx_agraph import read_dot
|
||||
|
||||
from utils.config import (
|
||||
from .config import (
|
||||
REFERENCE_GRAPH_PATH,
|
||||
determine_threshold_color, get_weight_for_color
|
||||
)
|
||||
|
||||
@ -1,20 +1,20 @@
|
||||
import os
|
||||
import re
|
||||
|
||||
from utils.config import (
|
||||
from .config import (
|
||||
CORPUS_DIR,
|
||||
TEMPLATE_PATH,
|
||||
determine_threshold_color
|
||||
)
|
||||
|
||||
from utils.files import (
|
||||
from .files import (
|
||||
find_prefixed_directory,
|
||||
find_corpus_file,
|
||||
write_report,
|
||||
read_corpus_file
|
||||
)
|
||||
|
||||
from utils.sections_utils import (
|
||||
from .sections_utils import (
|
||||
trouver_dossier_composant,
|
||||
extraire_sections_par_mot_cle
|
||||
)
|
||||
@ -302,7 +302,7 @@ def generate_operations_section(data, results, config):
|
||||
manufacturing_id = component["manufacturing"]
|
||||
operation = data["operations"][manufacturing_id]
|
||||
|
||||
template.append("#### ISG des pays impliqués\n")
|
||||
template.append("##### ISG des pays impliqués\n")
|
||||
template.append("| Pays | Part de marché | ISG | Criticité |")
|
||||
template.append("| :-- | :-- | :-- | :-- |")
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ import re
|
||||
from pathlib import Path
|
||||
from collections import defaultdict
|
||||
|
||||
from utils.config import (
|
||||
from .config import (
|
||||
CORPUS_DIR
|
||||
)
|
||||
|
||||
|
||||
@ -22,6 +22,7 @@ def afficher_menu():
|
||||
str(_("navigation.personnalisation")),
|
||||
str(_("navigation.analyse")),
|
||||
*([str(_("navigation.ia_nalyse"))] if st.session_state.get("logged_in", False) else []),
|
||||
*([str(_("navigation.plan_d_actions"))]),
|
||||
str(_("navigation.visualisations")),
|
||||
str(_("navigation.fiches"))
|
||||
]
|
||||
|
||||
@ -79,6 +79,7 @@ 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
|
||||
from app.plan_d_actions import interface_plan_d_actions
|
||||
|
||||
# Initialisation des traductions (langue française par défaut)
|
||||
init_translations()
|
||||
@ -170,6 +171,7 @@ fiches_tab = _("navigation.fiches")
|
||||
personnalisation_tab = _("navigation.personnalisation")
|
||||
analyse_tab = _("navigation.analyse")
|
||||
ia_nalyse_tab = _("navigation.ia_nalyse")
|
||||
plan_d_actions_tab = _("navigation.plan_d_actions")
|
||||
visualisations_tab = _("navigation.visualisations")
|
||||
|
||||
if st.session_state.onglet == instructions_tab:
|
||||
@ -193,6 +195,10 @@ 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 == plan_d_actions_tab:
|
||||
G_temp = st.session_state["G_temp"]
|
||||
interface_plan_d_actions(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"]
|
||||
|
||||
61
schema.txt
61
schema.txt
@ -9648,7 +9648,7 @@ digraph Hierarchie_Composants_Electroniques_Simplifiee {
|
||||
// Relations sortantes
|
||||
Beryllium -> Reserves_Beryllium [];
|
||||
Beryllium -> Extraction_Beryllium [];
|
||||
|
||||
Beryllium -> Traitement_Beryllium [];
|
||||
|
||||
subgraph cluster_Reserves_Beryllium {
|
||||
label="Reserves_Beryllium";
|
||||
@ -9732,6 +9732,65 @@ digraph Hierarchie_Composants_Electroniques_Simplifiee {
|
||||
XinjiangNonferrous_Chine_Extraction_Beryllium -> Chine_geographique [color="darkgreen"];
|
||||
}
|
||||
}
|
||||
|
||||
subgraph cluster_Traitement_Beryllium {
|
||||
label="Traitement_Beryllium";
|
||||
fillcolor="#ffd699";
|
||||
style="filled";
|
||||
Traitement_Beryllium [fillcolor="#ffd699", ihh_acteurs="47", ihh_pays="47", label="Traitement", niveau="10"];
|
||||
|
||||
// Relations sortantes du nœud de niveau 10
|
||||
Traitement_Beryllium -> USA_Traitement_Beryllium [color="purple", fontcolor="purple", label="65%", poids="3"];
|
||||
Traitement_Beryllium -> Chine_Traitement_Beryllium [color="purple", fontcolor="purple", label="20%", poids="1"];
|
||||
Traitement_Beryllium -> Russie_Traitement_Beryllium [color="purple", fontcolor="purple", label="10%", poids="1"];
|
||||
|
||||
subgraph cluster_USA_Traitement_Beryllium {
|
||||
label="USA_Traitement_Beryllium";
|
||||
fillcolor="#e6f2ff";
|
||||
style="filled";
|
||||
USA_Traitement_Beryllium [fillcolor="#e6f2ff", label="États-Unis", niveau="11"];
|
||||
|
||||
// Relations sortantes du nœud de niveau 11
|
||||
USA_Traitement_Beryllium -> USA_geographique [color="darkgreen", poids="0", type_lien="10"];
|
||||
USA_Traitement_Beryllium -> Materion_EtatUnis_Traitement_Beryllium [color="purple", fontcolor="purple", label="65%", poids="3"];
|
||||
Materion_EtatUnis_Traitement_Beryllium [fillcolor="#d1e0ff", label="Materion Corporation", niveau="12"];
|
||||
|
||||
// Relations des nœuds destination
|
||||
Materion_EtatUnis_Traitement_Beryllium -> USA_geographique [color="darkgreen", poids="0", type_lien="10"];
|
||||
Materion_EtatUnis_Traitement_Beryllium -> EtatsUnis_Extraction_Beryllium [color="darkblue", fontcolor="darkblue", label="100%"];
|
||||
}
|
||||
|
||||
subgraph cluster_Chine_Traitement_Beryllium {
|
||||
label="Chine_Traitement_Beryllium";
|
||||
fillcolor="#e6f2ff";
|
||||
style="filled";
|
||||
Chine_Traitement_Beryllium [fillcolor="#e6f2ff", label="Chine", niveau="11"];
|
||||
|
||||
// Relations sortantes du nœud de niveau 11
|
||||
Chine_Traitement_Beryllium -> Chine_geographique [color="darkgreen", poids="0", type_lien="10"];
|
||||
Chine_Traitement_Beryllium -> CNMCNickel_Chine_Traitement_Beryllium [color="purple", fontcolor="purple", label="20%", poids="1"];
|
||||
CNMCNickel_Chine_Traitement_Beryllium [fillcolor="#d1e0ff", label="CNMC Nickel", niveau="12"];
|
||||
|
||||
// Relations des nœuds destination
|
||||
CNMCNickel_Chine_Traitement_Beryllium -> Chine_geographique [color="darkgreen", poids="0", type_lien="10"];
|
||||
CNMCNickel_Chine_Traitement_Beryllium -> Chine_Extraction_Beryllium [color="darkblue", fontcolor="darkblue", label="100%"];
|
||||
}
|
||||
|
||||
subgraph cluster_Russie_Traitement_Beryllium {
|
||||
label="Russie_Traitement_Beryllium";
|
||||
fillcolor="#e6f2ff";
|
||||
style="filled";
|
||||
Russie_Traitement_Beryllium [fillcolor="#e6f2ff", label="Russie", niveau="11"];
|
||||
|
||||
// Relations sortantes du nœud de niveau 11
|
||||
Russie_Traitement_Beryllium -> Russie_geographique [color="darkgreen", poids="0", type_lien="10"];
|
||||
Russie_Traitement_Beryllium -> Ulba_Russie_Traitement_Beryllium [color="purple", fontcolor="purple", label="10%", poids="1"];
|
||||
Ulba_Russie_Traitement_Beryllium [fillcolor="#d1e0ff", label="Ulba Metallurgical Plant", niveau="12"];
|
||||
|
||||
// Relations des nœuds destination
|
||||
Ulba_Russie_Traitement_Beryllium -> Russie_geographique [color="darkgreen", poids="0", type_lien="10"];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
subgraph cluster_Or {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user