Ajout de la persistance

This commit is contained in:
Stéphan Peccini 2025-06-11 14:57:53 +02:00
parent 4d511cbe23
commit 8efc016014
23 changed files with 12152 additions and 142 deletions

1
.gitignore vendored
View File

@ -16,6 +16,7 @@ prompt.md
*.tmp
*.old
tempo/
tmp/
# Ignorer config locale
.ropeproject/

View File

@ -3,6 +3,7 @@ import networkx as nx
import streamlit as st
from utils.translations import _
from utils.widgets import html_expander
from utils.persistance import maj_champ_statut, get_champ_statut, supprime_champ_statut
from .sankey import afficher_sankey
@ -43,7 +44,6 @@ def preparer_graphe(
[n for n in G.nodes() if niveaux_temp.get(n) == 10 and 'Reserves' in n])
return G, niveaux_temp
def selectionner_niveaux(
) -> Tuple[int|None, int|None]:
"""
@ -56,57 +56,67 @@ def selectionner_niveaux(
st.markdown(f"## {str(_('pages.analyse.selection_nodes'))}")
valeur_defaut = str(_("pages.analyse.select_level"))
niveau_choix = [valeur_defaut] + list(niveau_labels.values())
default_index = next((i for i, opt in enumerate(niveau_choix) if get_champ_statut("pages.analyse.select_level.niveau_depart") in opt), 0)
niveau_depart = st.selectbox(str(_("pages.analyse.start_level")), niveau_choix, key="analyse_niveau_depart")
niveau_depart = st.selectbox(str(_("pages.analyse.start_level")), niveau_choix, index=default_index, key="analyse_niveau_depart")
if niveau_depart == valeur_defaut:
return None, None
else:
maj_champ_statut("pages.analyse.select_level.niveau_depart", niveau_depart)
niveau_depart_int = inverse_niveau_labels[niveau_depart]
niveaux_arrivee_possibles = [v for k, v in niveau_labels.items() if k > niveau_depart_int]
niveaux_arrivee_choix = [valeur_defaut] + niveaux_arrivee_possibles
niveau_choix = [valeur_defaut] + niveaux_arrivee_possibles
default_index = next((i for i, opt in enumerate(niveau_choix) if get_champ_statut("pages.analyse.select_level.niveau_arrivee") in opt), 0)
analyse_niveau_arrivee = st.selectbox(str(_("pages.analyse.end_level")), niveaux_arrivee_choix, key="analyse_niveau_arrivee")
if analyse_niveau_arrivee == valeur_defaut:
niveau_arrivee = st.selectbox(str(_("pages.analyse.end_level")), niveau_choix, index=default_index, key="analyse_niveau_arrivee")
if niveau_arrivee == valeur_defaut:
return niveau_depart_int, None
else:
maj_champ_statut("pages.analyse.select_level.niveau_arrivee", niveau_arrivee)
niveau_arrivee_int = inverse_niveau_labels[analyse_niveau_arrivee]
niveau_arrivee_int = inverse_niveau_labels[niveau_arrivee]
return niveau_depart_int, niveau_arrivee_int
def selectionner_minerais(G: nx.DiGraph, niveau_depart: int, niveau_arrivee: int) -> Optional[List[str]]:
if not (niveau_depart < 2 < niveau_arrivee):
return None
def selectionner_minerais(
G: nx.DiGraph,
niveau_depart: int,
niveau_arrivee: int
) -> Optional[List[str]]:
"""
Interface pour sélectionner les minerais si nécessaire.
st.markdown(f"### {str(_('pages.analyse.select_minerals'))}")
Args:
G (nx.DiGraph): Le graphe NetworkX contenant les données des produits.
niveau_depart (int): Le niveau de départ sélectionné.
niveau_arrivee (int): Le niveau d'arrivée sélectionné.
minerais_nodes = sorted([
n for n, d in G.nodes(data=True)
if d.get("niveau") and int(str(d.get("niveau")).strip('\"')) == 2
])
Returns:
Optional[List[str]]: La liste des minerais si une sélection a été effectuée,
- None sinon
"""
minerais_selection = None
if niveau_depart < 2 < niveau_arrivee:
st.markdown(f"### {str(_('pages.analyse.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
])
# Initialiser depuis champ_statut si besoin
if "analyse_minerais" not in st.session_state:
anciens = []
i = 0
while True:
m = get_champ_statut(f"pages.analyse.filter_by_minerals.{i}")
if not m:
break
anciens.append(m)
i += 1
st.session_state["analyse_minerais"] = anciens
minerais_selection = st.multiselect(
str(_("pages.analyse.filter_by_minerals")),
minerais_nodes,
key="analyse_minerais"
)
# Widget multiselect sans default, seulement key
st.multiselect(
str(_("pages.analyse.filter_by_minerals")),
minerais_nodes,
key="analyse_minerais"
)
return minerais_selection
selection = st.session_state["analyse_minerais"]
# Toujours purger, puis recréer si nécessaire
supprime_champ_statut("pages.analyse.filter_by_minerals")
if selection:
for i, m in enumerate(selection):
maj_champ_statut(f"pages.analyse.filter_by_minerals.{i}", m)
return selection if selection else None
def selectionner_noeuds(
G: nx.DiGraph,
@ -133,21 +143,62 @@ def selectionner_noeuds(
depart_nodes = [n for n in G.nodes() if niveaux_temp.get(n) == niveau_depart]
arrivee_nodes = [n for n in G.nodes() if niveaux_temp.get(n) == niveau_arrivee]
noeuds_depart = st.multiselect(str(_("pages.analyse.filter_start_nodes")),
sorted(depart_nodes),
key="analyse_noeuds_depart")
noeuds_arrivee = st.multiselect(str(_("pages.analyse.filter_end_nodes")),
sorted(arrivee_nodes),
key="analyse_noeuds_arrivee")
# DEPARTS -------------------------------------
if "analyse_noeuds_depart" not in st.session_state:
anciens_departs = []
i = 0
while True:
val = get_champ_statut(f"pages.analyse.filter_start_nodes.{i}")
if not val:
break
anciens_departs.append(val)
i += 1
st.session_state["analyse_noeuds_depart"] = anciens_departs
noeuds_depart = noeuds_depart if noeuds_depart else None
noeuds_arrivee = noeuds_arrivee if noeuds_arrivee else None
st.multiselect(
str(_("pages.analyse.filter_start_nodes")),
sorted(depart_nodes),
key="analyse_noeuds_depart"
)
return noeuds_depart, noeuds_arrivee
departs_selection = st.session_state["analyse_noeuds_depart"]
supprime_champ_statut("pages.analyse.filter_start_nodes")
if departs_selection:
for i, val in enumerate(departs_selection):
maj_champ_statut(f"pages.analyse.filter_start_nodes.{i}", val)
def configurer_filtres_vulnerabilite(
) -> Tuple[bool, bool, bool, str, bool, str]:
# ARRIVEES -------------------------------------
if "analyse_noeuds_arrivee" not in st.session_state:
anciens_arrivees = []
i = 0
while True:
val = get_champ_statut(f"pages.analyse.filter_end_nodes.{i}")
if not val:
break
anciens_arrivees.append(val)
i += 1
st.session_state["analyse_noeuds_arrivee"] = anciens_arrivees
st.multiselect(
str(_("pages.analyse.filter_end_nodes")),
sorted(arrivee_nodes),
key="analyse_noeuds_arrivee"
)
arrivees_selection = st.session_state["analyse_noeuds_arrivee"]
supprime_champ_statut("pages.analyse.filter_end_nodes")
if arrivees_selection:
for i, val in enumerate(arrivees_selection):
maj_champ_statut(f"pages.analyse.filter_end_nodes.{i}", val)
departs_selection = departs_selection if departs_selection else None
arrivees_selection = arrivees_selection if arrivees_selection else None
return departs_selection, arrivees_selection
def configurer_filtres_vulnerabilite() -> Tuple[bool, bool, bool, str, bool, str]:
"""
Interface pour configurer les filtres de vulnérabilité.
@ -163,30 +214,63 @@ def configurer_filtres_vulnerabilite(
st.markdown("---")
st.markdown(f"## {str(_('pages.analyse.vulnerability_filters'))}")
filtrer_ics = st.checkbox(str(_("pages.analyse.filter_ics")),
key="analyse_filtrer_ics")
filtrer_ivc = st.checkbox(str(_("pages.analyse.filter_ivc")),
key="analyse_filtrer_ivc")
filtrer_ihh = st.checkbox(str(_("pages.analyse.filter_ihh")),
key="analyse_filtrer_ihh")
def init_checkbox(key, champ):
if key not in st.session_state:
val = get_champ_statut(champ)
st.session_state[key] = val.lower() == "true"
def init_radio(key, champ, options, default):
if key not in st.session_state:
val = get_champ_statut(champ)
st.session_state[key] = val if val in options else default
# Initialiser les valeurs si F5
init_checkbox("analyse_filtrer_ics", "pages.analyse.filter_ics")
init_checkbox("analyse_filtrer_ivc", "pages.analyse.filter_ivc")
init_checkbox("analyse_filtrer_ihh", "pages.analyse.filter_ihh")
init_checkbox("analyse_filtrer_isg", "pages.analyse.filter_isg")
init_radio("analyse_ihh_type", "pages.analyse.apply_ihh_filter", ["Pays", "Acteurs"], "Pays")
init_radio("analyse_logique_filtrage", "pages.analyse.filter_logic", ["ou", "et"], "ou")
filtrer_ics = st.checkbox(str(_("pages.analyse.filter_ics")), key="analyse_filtrer_ics")
filtrer_ivc = st.checkbox(str(_("pages.analyse.filter_ivc")), key="analyse_filtrer_ivc")
filtrer_ihh = st.checkbox(str(_("pages.analyse.filter_ihh")), key="analyse_filtrer_ihh")
ihh_type = "Pays"
if filtrer_ihh:
ihh_type = st.radio(str(_("pages.analyse.apply_ihh_filter")),
[str(_("pages.analyse.countries")), str(_("pages.analyse.actors"))],
horizontal=True,
key="analyse_ihh_type")
ihh_type = st.radio(
str(_("pages.analyse.apply_ihh_filter")),
[str(_("pages.analyse.countries")), str(_("pages.analyse.actors"))],
horizontal=True,
key="analyse_ihh_type"
)
filtrer_isg = st.checkbox(str(_("pages.analyse.filter_isg")),
key="analyse_filtrer_isg")
logique_filtrage = st.radio(str(_("pages.analyse.filter_logic")),
[str(_("pages.analyse.or")), str(_("pages.analyse.and"))],
horizontal=True,
key="analyse_logique_filtrage")
filtrer_isg = st.checkbox(str(_("pages.analyse.filter_isg")), key="analyse_filtrer_isg")
logique_options = ["ou", "et"]
logique_labels = {
"ou": str(_("pages.analyse.or")),
"et": str(_("pages.analyse.and"))
}
logique_filtrage = st.radio(
str(_("pages.analyse.filter_logic")),
options=logique_options,
format_func=lambda x: logique_labels.get(x, x),
horizontal=True,
key="analyse_logique_filtrage"
)
# Sauvegarde de l'état
maj_champ_statut("pages.analyse.filter_ics", str(filtrer_ics))
maj_champ_statut("pages.analyse.filter_ivc", str(filtrer_ivc))
maj_champ_statut("pages.analyse.filter_ihh", str(filtrer_ihh))
maj_champ_statut("pages.analyse.apply_ihh_filter", ihh_type)
maj_champ_statut("pages.analyse.filter_isg", str(filtrer_isg))
maj_champ_statut("pages.analyse.filter_logic", logique_filtrage)
return filtrer_ics, filtrer_ivc, filtrer_ihh, ihh_type, filtrer_isg, logique_filtrage
def interface_analyse(
G_temp: nx.DiGraph,
) -> None:
@ -196,7 +280,9 @@ def interface_analyse(
Args:
G_temp (nx.DiGraph): Le graphe NetworkX à analyser.
"""
st.markdown(f"# {str(_('pages.analyse.title'))}")
titre = f"# {str(_('pages.analyse.title'))}"
maj_champ_statut("pages.analyse.title", titre)
st.markdown(titre)
html_expander(f"{str(_('pages.analyse.help'))}", content="\n".join(_("pages.analyse.help_content")), open_by_default=False, details_class="details_introduction")
st.markdown("---")

View File

@ -526,7 +526,8 @@ def exporter_graphe_filtre(
Returns:
None
"""
if not st.session_state.get("logged_in", False) or not liens_chemins:
from utils.persistance import get_champ_statut
if get_champ_statut("login") == "" or not liens_chemins:
return
G_export = nx.DiGraph()

View File

@ -94,7 +94,8 @@ def interface_fiches() -> None:
with open(html_path, "r", encoding="utf-8") as f:
st.markdown(f.read(), unsafe_allow_html=True)
if st.session_state.get("logged_in", False):
from utils.persistance import get_champ_statut
if not get_champ_statut("login") == "":
pdf_name = nom_fiche + ".pdf"
pdf_path = os.path.join("static", "Fiches", dossier_choisi, pdf_name)

View File

@ -65,6 +65,21 @@ def _synth(df: pd.DataFrame) -> str:
return "\n".join(lignes)
def build_dynamic_sections(md_raw: str) -> str:
"""
Procédure pour construire et remplacer les sections dynamiques dans les fiches d'analyse produit (ICS).
Cette fonction permet de :
1. Extraire les données structurées en YAML des blocs du markdown.
2. Générer un tableau pivotant les données sur la criticité et faisabilité technique.
3. Produire une synthèse finale avec l'analyse critique par composant.
Args:
md (str): Contenu brut du fichier Markdown contenant les structures YAML à analyser.
Returns:
str: Le markdown enrichi des tableaux de donnée analysés, ou le contenu original inchangé si aucun bloc structuré n'est trouvé.
"""
md_raw = _normalize_unicode(md_raw)
df = _pairs_dataframe(md_raw)
if df.empty:

View File

@ -138,6 +138,20 @@ def _synth_ihh(operations: list[dict]) -> str:
return "\n".join([t for t in tableaux if t])
def build_ihh_sections(md: str) -> str:
"""
Fonction principale pour générer les sections dynamiques dans le markdown, spécifiquement dédiée à l'analyse des indices IHH.
La fonction gère les différents types de données présents dans les fiches, notamment :
- Les opérations d'extraction et de traitement du minerai
- L'assemblage des produits finaux
- La fabrication des composants intermédiaires
Args:
md (str): Contenu brut du fichier Markdown contenant les structures YAML à analyser.
Returns:
str: Le markdown enrichi avec les tableaux de donnée analysés, ou le contenu original inchangé si aucun bloc structuré n'est trouvé.
"""
segments = []
operations = []
intro = None

View File

@ -25,6 +25,20 @@ def _synth_isg(md: str) -> str:
return "\n".join(lignes)
def build_isg_sections(md: str) -> str:
"""
Fonction principale pour générer les sections dynamiques dans le markdown, spécifiquement dédiée à l'analyse des indices ISG.
La fonction gère :
- La structure YAML front-matter pour vérifier si c'est bien un tableau ISG
- L'extraction et tri du pays selon la valeur ISG
- Le formatage des données de WGI, FSI, NDGain et ISG dans le tableau final
Args:
md (str): Contenu brut du fichier Markdown contenant les structures YAML à analyser.
Returns:
str: Le markdown enrichi avec le tableau de donnée analysé pour l'indice ISG, ou le contenu original inchangé si aucun bloc structuré n'est trouvé.
"""
front_match = re.match(r"(?s)^---\n(.*?)\n---\n", md)
if front_match:
front_matter = yaml.safe_load(front_match.group(1))

View File

@ -31,7 +31,20 @@ def _ivc_segments(md: str):
yield None, md[pos:] # reste éventuel
def build_ivc_sections(md: str) -> str:
"""Remplace les blocs YAML minerai + segment avec rendu Jinja2, conserve l'intro."""
"""
Fonction principale pour générer les sections dynamiques dans le markdown, spécifiquement dédiée à l'analyse des Indices de Vulnérabilité Complète (IVC).
La fonction gère :
- L'extraction et tri des données IVC pour chaque minerai
- Le formatage des données d'IVC, Vulnérabilité, etc. dans le tableau final
- La génération du tableau synthétique pour l'analyse globale
Args:
md (str): Contenu brut du fichier Markdown contenant les structures YAML à analyser.
Returns:
str: Le markdown enrichi avec le tableau de donnée analysé pour l'indice IVC, ou le contenu original inchangé si aucun bloc structuré n'est trouvé.
"""
segments = []
minerais = [] # Pour collecter les données de chaque minerai
intro = None

View File

@ -458,7 +458,6 @@ def build_minerai_ics_composant_section(md: str) -> str:
return md
def build_minerai_sections(md: str) -> str:
"""Traite les fiches de minerai et génère les tableaux des producteurs."""
# Extraire le type de fiche depuis l'en-tête YAML

View File

@ -6,7 +6,7 @@ PASTILLE_ICONS = {
"rouge": "🔴"
}
def pastille(indice: str, valeur: str, seuils: dict) -> str:
def pastille(indice: str, valeur: str) -> str:
"""Renvoie l'icône Unicode correspondante à la pastille en fonction de sa valeur et des seuils.
La pastille prend une couleur (vert, orange ou rouge) selon la valeur
@ -20,9 +20,6 @@ def pastille(indice: str, valeur: str, seuils: dict) -> str:
valeur (Any): Valeur numérique à comparer aux seuils.
Généralement une float, mais peut être convertie automatiquement
en nombre si possible.
seuils (dict, optional): Dictionnaire des seuils pour chaque indicateur.
Si None, les valeurs par défaut sont utilisées selon l'état de session
Stocké dans Streamlit.
Returns:
str: Une icône Unicode correspondante à la pastille. Si aucune icône n'est définie,
@ -30,8 +27,8 @@ def pastille(indice: str, valeur: str, seuils: dict) -> str:
"""
try:
import streamlit as st
seuils = seuils or st.session_state.get("seuils", {})
if indice not in seuils:
seuils = st.session_state.get("seuils", {})
if seuils and indice not in seuils:
return ""
seuil = seuils[indice]

View File

@ -191,7 +191,8 @@ def exporter_graphe_filtre(
nx.DiGraph: le graphe exporté
- Sinon aucun résultat (None)
"""
if not st.session_state.get("logged_in", False) or not liens_chemins:
from utils.persistance import get_champ_statut
if get_champ_statut("login") == "" or not liens_chemins:
return
G_export = nx.DiGraph()
@ -257,7 +258,8 @@ def interface_ia_nalyse(
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)
from utils.persistance import get_champ_statut
resultat = statut_utilisateur(get_champ_statut("login"))
if resultat:
st.info(resultat["message"])
@ -288,7 +290,7 @@ def interface_ia_nalyse(
if liens_chemins:
G_final = exporter_graphe_filtre(G_temp, liens_chemins)
if st.button(str(_("pages.ia_nalyse.submit_request")), icon=":material/send:"):
soumettre_batch(st.session_state.username, G_final)
soumettre_batch(get_champ_statut("login"), G_final)
st.rerun()
else:
st.info(str(_("pages.ia_nalyse.empty_graph")))
@ -297,7 +299,7 @@ def interface_ia_nalyse(
if not st.session_state.get("telechargement_confirme"):
st.download_button(str(_("buttons.download")), resultat["telechargement"], file_name="analyse.zip", icon=":material/download:")
if st.button(str(_("pages.ia_nalyse.confirm_download")), icon=":material/task_alt:"):
nettoyage_post_telechargement(st.session_state.username)
nettoyage_post_telechargement(get_champ_statut("login"))
st.session_state["telechargement_confirme"] = True
st.rerun()
else:

View File

@ -1,6 +1,7 @@
# interface.py app/personnalisation
import streamlit as st
from utils.persistance import maj_champ_statut
from utils.translations import _
from utils.widgets import html_expander
@ -9,7 +10,9 @@ from app.personnalisation.utils import modifier_produit
from app.personnalisation.utils import importer_exporter_graph
def interface_personnalisation(G):
st.markdown(f"# {str(_('pages.personnalisation.title'))}")
titre = f"# {str(_('pages.personnalisation.title'))}"
maj_champ_statut("pages.personnalisation.title", titre)
st.markdown(titre)
html_expander(f"{str(_('pages.personnalisation.help'))}", content="\n".join(_("pages.personnalisation.help_content")), open_by_default=False, details_class="details_introduction")
st.markdown("---")

View File

@ -1,6 +1,7 @@
import streamlit as st
import networkx as nx
from utils.translations import _
from utils.persistance import maj_champ_statut, get_champ_statut
def ajouter_produit(G: nx.DiGraph) -> nx.DiGraph:
"""
@ -17,6 +18,30 @@ def ajouter_produit(G: nx.DiGraph) -> nx.DiGraph:
au graphe de référence.
"""
st.markdown(f"## {str(_('pages.personnalisation.add_new_product'))}")
if "pages.personnalisation.create_product.fait" not in st.session_state and get_champ_statut("pages.personnalisation.create_product.fait") == "oui":
index = 0
while True:
new_prod = get_champ_statut(f"pages.personnalisation.create_product.{index}.nom")
if new_prod == "":
break # Fin de la liste
G.add_node(new_prod, niveau="0", personnalisation="oui", label=new_prod)
sel_new_op = get_champ_statut(f"pages.personnalisation.create_product.{index}.edge")
if sel_new_op:
G.add_edge(new_prod, sel_new_op)
i = 0
while True:
comp = get_champ_statut(f"pages.personnalisation.create_product.{index}.composants.{i}")
if comp == "":
break
G.add_edge(new_prod, comp)
i += 1
index += 1
new_prod = st.text_input(str(_("pages.personnalisation.new_product_name")), key="new_prod")
if new_prod:
ops_dispo = sorted([
@ -25,13 +50,27 @@ def ajouter_produit(G: nx.DiGraph) -> nx.DiGraph:
and any(G.has_edge(p, n) and G.nodes[p].get("niveau") == "0" for p in G.predecessors(n))
])
sel_new_op = st.selectbox(str(_("pages.personnalisation.assembly_operation")), [str(_("pages.personnalisation.none"))] + ops_dispo, index=0)
niveau1 = sorted([n for n, d in G.nodes(data=True) if d.get("niveau") == "1"])
sel_comps = st.multiselect(str(_("pages.personnalisation.components_to_link")), options=niveau1)
if st.button(str(_("pages.personnalisation.create_product"))):
G.add_node(new_prod, niveau="0", personnalisation="oui", label=new_prod)
maj_champ_statut("pages.personnalisation.create_product.fait", "oui")
st.session_state["pages.personnalisation.create_product.fait"] = "oui"
i = 0
while get_champ_statut(f"pages.personnalisation.create_product.{i}.nom") != "":
i += 1
maj_champ_statut(f"pages.personnalisation.create_product.{i}.nom", new_prod)
if sel_new_op != str(_("pages.personnalisation.none")):
G.add_edge(new_prod, sel_new_op)
for comp in sel_comps:
maj_champ_statut(f"pages.personnalisation.create_product.{i}.edge", sel_new_op)
for j, comp in enumerate(sel_comps):
G.add_edge(new_prod, comp)
maj_champ_statut(f"pages.personnalisation.create_product.{i}.composants.{j}", comp)
st.success(f"{new_prod} {str(_('pages.personnalisation.added'))}")
return G

View File

@ -2,6 +2,7 @@ from typing import List
import streamlit as st
from utils.translations import _
import networkx as nx
from utils.persistance import supprime_champ_statut, get_champ_statut, maj_champ_statut
def get_produits_personnalises(
G: nx.DiGraph
@ -117,7 +118,7 @@ def mettre_a_jour_operations(
selon la sélection effectuée par l'utilisateur.
"""
none_option = str(_("pages.personnalisation.none", "-- Aucune --"))
none_option = str(_("pages.personnalisation.none"))
for op in curr_ops:
if sel_op == none_option or op != sel_op:
G.remove_edge(prod, op)
@ -172,16 +173,22 @@ def modifier_produit(
# Sélection du produit à modifier
produits0 = get_produits_personnalises(G)
sel_display = st.multiselect(str(_("pages.personnalisation.products_to_modify")), options=produits0)
sel_display = st.selectbox(label=str(_("pages.personnalisation.products_to_modify")), options=produits0)
if not sel_display:
return G
# Obtention du produit sélectionné
prod = sel_display[0]
prod = sel_display
index = 0
while get_champ_statut(f"pages.personnalisation.create_product.{index}.nom") != sel_display:
index += 1
# Suppression du produit si demandé
if st.button(f"{str(_('pages.personnalisation.delete'))} {prod}"):
supprime_champ_statut(f"pages.personnalisation.create_product.{index}")
if index == 0:
supprime_champ_statut("pages.personnalisation.create_product")
return supprimer_produit(G, prod)
# Gestion des opérations d'assemblage
@ -198,7 +205,13 @@ def modifier_produit(
# Mise à jour des liens si demandé
if st.button(f"{str(_('pages.personnalisation.update'))} {prod}"):
G = mettre_a_jour_operations(G, prod, curr_ops, sel_op)
if sel_op:
maj_champ_statut(f"pages.personnalisation.create_product.{index}.edge", sel_op)
G = mettre_a_jour_composants(G, prod, linked, nouveaux)
if nouveaux:
supprime_champ_statut(f"pages.personnalisation.create_product.{index}.composants")
for j, comp in enumerate(nouveaux):
maj_champ_statut(f"pages.personnalisation.create_product.{index}.composants.{j}", comp)
st.success(f"{prod} {str(_('pages.personnalisation.updated'))}")
return G

View File

@ -2,17 +2,18 @@ from typing import Any, Dict, Tuple, List, Union, Optional
import streamlit as st
import networkx as nx
from utils.translations import _
from utils.persistance import maj_champ_statut, get_champ_statut, supprime_champ_statut
from utils.graph_utils import (
extraire_chemins_depuis,
extraire_chemins_vers
)
def selectionner_minerais(G: nx.DiGraph, noeuds_depart: List[Any]) -> List[Union[str, int]]:
def selectionner_minerais(G: nx.Graph, noeuds_depart: List[Any]) -> List[Union[str, int]]:
"""Interface pour sélectionner les minerais si nécessaire.
Args:
G (nx.DiGraph): Le graphe des relations entre les nœuds.
G (nx.Graph): Le graphe des relations entre les nœuds.
noeuds_depart (list): Les nœuds de départ qui doivent être considérés.
Returns:
@ -33,23 +34,44 @@ def selectionner_minerais(G: nx.DiGraph, noeuds_depart: List[Any]) -> List[Union
if G.nodes[n].get("niveau") and int(str(G.nodes[n].get("niveau")).strip('"')) == 2
])
minerais_selection = st.multiselect(
# Initialiser depuis champ_statut si besoin
if "analyse_minerais" not in st.session_state:
anciens = []
i = 0
while True:
val = get_champ_statut(f"pages.plan_d_action.filter_by_minerals.{i}")
if not val:
break
anciens.append(val)
i += 1
st.session_state["analyse_minerais"] = anciens
# Afficher le multiselect
st.multiselect(
str(_("pages.plan_d_action.filter_by_minerals")),
minerais_nodes,
key="analyse_minerais"
)
minerais_selection = st.session_state["analyse_minerais"]
# Toujours purger, puis réécrire si nécessaire
supprime_champ_statut("pages.plan_d_action.filter_by_minerals")
if minerais_selection:
for i, val in enumerate(minerais_selection):
maj_champ_statut(f"pages.plan_d_action.filter_by_minerals.{i}", val)
return minerais_selection
def selectionner_noeuds(
G: nx.DiGraph,
G: nx.Graph,
niveaux_temp: Dict[Union[str, int], int],
niveau_depart: int
) -> Tuple[Optional[List[Union[str, int]]], List[Union[str, int]]]:
"""Interface pour sélectionner les nœuds spécifiques de départ et d'arrivée.
Args:
G (nx.DiGraph): Le graphe des relations entre les nœuds.
G (nx.Graph): Le graphe des relations entre les nœuds.
niveaux_temp (dict): Dictionnaire contenant les niveaux des nœuds.
niveau_depart (int): Niveau à partir duquel commencer la sélection.
@ -62,16 +84,36 @@ def selectionner_noeuds(
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_action.filter_start_nodes")),
sorted(depart_nodes),
key="analyse_noeuds_depart")
if "analyse_noeuds_depart" not in st.session_state:
anciens = []
i = 0
while True:
val = get_champ_statut(f"pages.plan_d_action.filter_start_nodes.{i}")
if not val:
break
anciens.append(val)
i += 1
st.session_state["analyse_noeuds_depart"] = anciens
st.multiselect(
str(_("pages.plan_d_action.filter_start_nodes")),
sorted(depart_nodes),
key="analyse_noeuds_depart"
)
noeuds_depart = st.session_state["analyse_noeuds_depart"]
supprime_champ_statut("pages.plan_d_action.filter_start_nodes")
if noeuds_depart:
for i, val in enumerate(noeuds_depart):
maj_champ_statut(f"pages.plan_d_action.filter_start_nodes.{i}", val)
noeuds_depart = noeuds_depart if noeuds_depart else None
return noeuds_depart, noeuds_arrivee
def extraire_chemins_selon_criteres(
G: nx.DiGraph,
G: nx.Graph,
niveaux: Dict[str | int, int],
niveau_depart: int,
noeuds_depart: Optional[List[Union[str, int]]],
@ -81,7 +123,7 @@ def extraire_chemins_selon_criteres(
"""Extrait les chemins selon les critères spécifiés.
Args:
G (nx.DiGraph): Le graphe des relations entre les nœuds.
G (nx.Graph): Le graphe des relations entre les nœuds.
niveaux (dict): Dictionnaire contenant les niveaux des nœuds.
niveau_depart (int): Niveau à partir duquel commencer la sélection.
noeuds_depart (list, optional): Les nœuds de départ qui doivent être considérés.

View File

@ -0,0 +1,881 @@
# Évaluation des vulnérabilités critiques
## Introduction
Ce rapport analyse les vulnérabilités de la chaîne de fabrication du numérique pour :
* les produits finaux : Smartphone
* les composants : Écran LCD/TFT, Écran Mini et Micro LED, Audio
* les minerais : Dysprosium, Terbium
# Chemins critiques
## Chaînes avec risque critique
*Ces chaînes comprennent au moins une vulnérabilité combinée élevée à critique*
### Smartphone → Écran LCD/TFT → Terbium
**Vulnérabilités identifiées:**
* Assemblage (Assemblage): :red-badge[ÉLEVÉE à CRITIQUE]
* IHH: 45 - :red-badge[Rouge]
* ISG combiné: 53 - :orange-badge[Orange]
* Fabrication (Fabrication): :red-badge[ÉLEVÉE à CRITIQUE]
* IHH: 28 - :red-badge[Rouge]
* ISG combiné: 42 - :orange-badge[Orange]
* Minerai (Terbium): :red-badge[ÉLEVÉE à CRITIQUE]
* ICS moyen: 0.81 - :red-badge[Rouge]
* IVC: 28 - :orange-badge[Orange]
* Extraction (Extraction): :red-badge[ÉLEVÉE à CRITIQUE]
* IHH: 53 - :red-badge[Rouge]
* ISG combiné: 53 - :orange-badge[Orange]
* Traitement (Traitement): :red-badge[ÉLEVÉE à CRITIQUE]
* IHH: 62 - :red-badge[Rouge]
* ISG combiné: 52 - :orange-badge[Orange]
### Smartphone → Écran Mini et Micro LED → Terbium
**Vulnérabilités identifiées:**
* Assemblage (Assemblage): :red-badge[ÉLEVÉE à CRITIQUE]
* IHH: 45 - :red-badge[Rouge]
* ISG combiné: 53 - :orange-badge[Orange]
* Fabrication (Fabrication): :green-badge[FAIBLE]
* IHH: 21 - :orange-badge[Orange]
* ISG combiné: 37 - :green-badge[Vert]
* Minerai (Terbium): :red-badge[ÉLEVÉE à CRITIQUE]
* ICS moyen: 0.81 - :red-badge[Rouge]
* IVC: 28 - :orange-badge[Orange]
* Extraction (Extraction): :red-badge[ÉLEVÉE à CRITIQUE]
* IHH: 53 - :red-badge[Rouge]
* ISG combiné: 53 - :orange-badge[Orange]
* Traitement (Traitement): :red-badge[ÉLEVÉE à CRITIQUE]
* IHH: 62 - :red-badge[Rouge]
* ISG combiné: 52 - :orange-badge[Orange]
### Smartphone → Audio → Dysprosium
**Vulnérabilités identifiées:**
* Assemblage (Assemblage): :red-badge[ÉLEVÉE à CRITIQUE]
* IHH: 45 - :red-badge[Rouge]
* ISG combiné: 53 - :orange-badge[Orange]
* Fabrication (Fabrication): :red-badge[ÉLEVÉE à CRITIQUE]
* IHH: 36 - :red-badge[Rouge]
* ISG combiné: 45 - :orange-badge[Orange]
* Minerai (Dysprosium): :red-badge[ÉLEVÉE à CRITIQUE]
* ICS moyen: 0.70 - :red-badge[Rouge]
* IVC: 36 - :orange-badge[Orange]
* Extraction (Extraction): :red-badge[ÉLEVÉE à CRITIQUE]
* IHH: 96 - :red-badge[Rouge]
* ISG combiné: 53 - :orange-badge[Orange]
* Traitement (Traitement): :red-badge[ÉLEVÉE à CRITIQUE]
* IHH: 38 - :red-badge[Rouge]
* ISG combiné: 51 - :orange-badge[Orange]
## Chaînes avec risque majeur
*Ces chaînes comprennent au moins trois vulnérabilités combinées moyennes*
Aucune chaîne à risque majeur identifiée.
## Chaînes avec risque moyen
*Ces chaînes comprennent au moins une vulnérabilité combinée moyenne*
Aucune chaîne à risque moyen identifiée.
## Détails des opérations
### Smartphone et Assemblage
Le smartphone constitue l'un des périphériques numériques les plus répandus au monde, avec plus de 6 milliards d'utilisateurs. Son assemblage représente l'étape finale d'une chaîne de valeur mondiale complexe impliquant des dizaines de fournisseurs et sous-traitants répartis sur plusieurs continents. Cet assemblage se déroule principalement dans des usines spécialisées en Asie, où les différents composants (écran, processeur, batterie, caméras, etc.) sont intégrés pour former un appareil fonctionnel. Le processus d'assemblage comprend le montage de la carte mère, l'intégration des modules de connectivité, la fixation de l'écran et des caméras, l'installation de la batterie et la fermeture du boîtier. Chaque unité passe ensuite par des tests rigoureux de qualité et de fonctionnalité avant d'être emballée et expédiée vers les marchés mondiaux. La tendance actuelle montre une diversification géographique progressive des sites d'assemblage, avec un déplacement partiel de la concentration historique en Chine vers d'autres pays comme l'Inde, le Vietnam et le Brésil.
#### Principaux assembleurs
| **Pays d'implantation** | **Entreprise** | **Pays d'origine** | **Part de marché** |
| :-- | :-- | :-- | :-- |
| Chine | Foxconn | Taïwan | 40 % |
| Chine | Pegatron | Taïwan | 15 % |
| Chine | Wistron | Taïwan | 10 % |
| **Chine** | **Total** | **Chine** | **65 %** |
| Vietnam | Samsung Electronics Vietnam | Corée du Sud | 9 % |
| Vietnam | LG Electronics Vietnam | Corée du Sud | 3 % |
| **Vietnam** | **Total** | **Vietnam** | **12 %** |
| Inde | Foxconn India | Taïwan | 6 % |
| Inde | Samsung Electronics India | Corée du Sud | 5 % |
| **Inde** | **Total** | **Inde** | **11 %** |
| Brésil | Foxconn Brasil | Taïwan | 4 % |
| Brésil | Samsung Electronics Brasil | Corée du Sud | 2 % |
| **Brésil** | **Total** | **Brésil** | **6 %** |
| Corée du Sud | Samsung Electronics | Corée du Sud | 4 % |
| **Corée du Sud** | **Total** | **Corée du Sud** | **4 %** |
_Note: Les capacités indiquées représentent la capacité d'assemblage annuelle en 2024-2025. L'assemblage final est fortement concentré en Asie, avec une diversification progressive vers d'autres régions pour des raisons géopolitiques et économiques._
##### ISG des pays impliqués
| Pays | Part de marché | ISG | Criticité |
| :-- | :-- | :-- | :-- |
| Corée du Sud | 4.0% | 33 | :green-badge[Vert] (Stable) |
| Brésil | 6.0% | 55 | :orange-badge[Orange] (Intermédiaire) |
| Chine | 65.0% | 54 | :orange-badge[Orange] (Intermédiaire) |
| Inde | 11.0% | 60 | :orange-badge[Orange] (Intermédiaire) |
| Vietnam | 12.0% | 48 | :orange-badge[Orange] (Intermédiaire) |
**ISG combiné: 53 - :orange-badge[Orange] (Intermédiaire)**
#### Indice de Herfindahl-Hirschmann
| **IHH** | **Faible** | **Modéré** | **Élevé** |
| :-- | :-- | :-- | :-- |
| **Acteurs** | | **21** | |
| **Pays** | | | **45** |
##### IHH par entreprise (acteurs)
LIHH pour les assembleurs est de **21**, ce qui indique une **concentration modérée**. **Foxconn (40 %)**, **Pegatron (15 %)**, **Wistron (10 %)** regroupent une part importante du marché. Cette structure permet une **résilience relative**, mais dépend encore de quelques grands groupes.
##### IHH par pays
LIHH par pays atteint **45**, révélant une **concentration géographique élevée**. La répartition est dominée par **Chine (65 %)**, **Vietnam (12 %)**, **Inde (11 %)**, représentant ensemble plus de 88 % des capacités. Cette configuration expose la chaîne à des **risques géopolitiques ou logistiques localisés**.
##### En résumé
- Le secteur présente une **structure dacteurs moyennement concentrée** (IHH 21)
- La **concentration géographique est élevée** (IHH 45)
#### Vulnérabilité combinée IHH-ISG
* IHH: 45 - :red-badge[Rouge] (Élevée)
* ISG combiné: 53 - :orange-badge[Orange] (Intermédiaire)
* Poids combiné: 6
* Niveau de vulnérabilité: **:red-badge[ÉLEVÉE à CRITIQUE]**
### Écran LCD/TFT et Fabrication
Les écrans LCD (Liquid Crystal Display) représentent une technologie d'affichage mature et largement répandue, utilisant les propriétés optiques des cristaux liquides pour moduler la lumière. Le principe fondamental repose sur des cristaux liquides emprisonnés entre deux plaques de verre polarisées, qui changent d'orientation sous l'effet d'un champ électrique, permettant ou bloquant ainsi le passage de la lumière émise par un rétroéclairage. La variante TFT (Thin-Film Transistor) ajoute une matrice active de transistors contrôlant individuellement chaque pixel, améliorant significativement la qualité d'image et le temps de réponse. Les écrans LCD modernes intègrent diverses technologies d'amélioration comme l'IPS (In-Plane Switching) pour des angles de vision plus larges, le VA (Vertical Alignment) pour un meilleur contraste, ou le local dimming pour des noirs plus profonds. Bien que concurrencés par les technologies OLED et Mini-LED sur le segment premium, les écrans LCD continuent de dominer le marché global grâce à leur excellent rapport coût/performance, leur longévité et leur adaptabilité à diverses tailles, des smartphones aux grands téléviseurs en passant par les moniteurs professionnels.
#### Principaux fabricants
| **Pays d'implantation** | **Entreprise** | **Pays d'origine** | **Part de marché** |
| :-- | :-- | :-- | :-- |
| Chine | BOE Technology | Chine | 25 % |
| Chine | CSOT (TCL) | Chine | 13 % |
| Chine | CEC-Panda | Chine | 5 % |
| **Chine** | **Total** | **Chine** | **43 %** |
| Corée du Sud | LG Display | Corée du Sud | 13 % |
| Corée du Sud | Samsung Display | Corée du Sud | 12 % |
| **Corée du Sud** | **Total** | **Corée du Sud** | **25 %** |
| Taïwan | AU Optronics | Taïwan | 9 % |
| Taïwan | Innolux | Taïwan | 8 % |
| **Taïwan** | **Total** | **Taïwan** | **17 %** |
| Japon | Sharp/Foxconn | Japon | 4 % |
| Japon | Japan Display Inc. | Japon | 3 % |
| **Japon** | **Total** | **Japon** | **7 %** |
| Mexique | LG Display Mexico | Corée du Sud | 3 % |
| **Mexique** | **Total** | **Mexique** | **3 %** |
| Pologne | LG Display Poland | Corée du Sud | 2 % |
| **Pologne** | **Total** | **Pologne** | **2 %** |
| Vietnam | Samsung Display Vietnam | Corée du Sud | 2 % |
| **Vietnam** | **Total** | **Vietnam** | **2 %** |
**Unités** : million d'unité/an
**Total** : 707
_Note: Les capacités indiquées représentent la production annuelle estimée en 2024-2025. Le marché LCD connaît une concentration accrue suite à la concurrence des technologies OLED et Mini-LED sur les segments premium._
##### ISG des pays impliqués
| Pays | Part de marché | ISG | Criticité |
| :-- | :-- | :-- | :-- |
| Pologne | 2.0% | 37 | :green-badge[Vert] (Stable) |
| Japon | 7.0% | 29 | :green-badge[Vert] (Stable) |
| Taiwan | 17.0% | 30 | :green-badge[Vert] (Stable) |
| Chine | 43.0% | 54 | :orange-badge[Orange] (Intermédiaire) |
| Corée du Sud | 25.0% | 33 | :green-badge[Vert] (Stable) |
| Vietnam | 2.0% | 48 | :orange-badge[Orange] (Intermédiaire) |
| Mexique | 3.0% | 57 | :orange-badge[Orange] (Intermédiaire) |
**ISG combiné: 42 - :orange-badge[Orange] (Intermédiaire)**
#### Indice de Herfindahl-Hirschmann
| **IHH** | **Faible** | **Modéré** | **Élevé** |
| :-- | :-- | :-- | :-- |
| **Acteurs** | **13** | | |
| **Pays** | | | **28** |
##### IHH par entreprise (acteurs)
LIHH pour les assembleurs est de **13**, ce qui indique une **concentration faible**. Bien que **BOE Technology (25 %)**, **CSOT (TCL) (13 %)**, **LG Display (13 %)** regroupent plus de 51 % du marché, plusieurs autres groupes viennent équilibrer le secteur. Cette structure permet une **certaine résilience industrielle**, avec plusieurs options en cas de tension sur un acteur majeur.
##### IHH par pays
LIHH par pays atteint **28**, révélant une **concentration géographique élevée**. La répartition est dominée par **Chine (43 %)**, **Corée du Sud (25 %)**, **Taïwan (17 %)**, représentant ensemble plus de 85 % des capacités. Cette configuration expose la chaîne à des **risques géopolitiques ou logistiques localisés**.
##### En résumé
- Le secteur présente une **structure dacteurs plutôt diversifiée** (IHH 13)
- La **concentration géographique est élevée** (IHH 28)
#### Vulnérabilité combinée IHH-ISG
* IHH: 28 - :red-badge[Rouge] (Élevée)
* ISG combiné: 42 - :orange-badge[Orange] (Intermédiaire)
* Poids combiné: 6
* Niveau de vulnérabilité: **:red-badge[ÉLEVÉE à CRITIQUE]**
### Écran Mini et Micro LED et Fabrication
Les écrans Mini LED représentent une évolution majeure des technologies d'affichage, positionnée entre les écrans LCD traditionnels et les OLED. Cette technologie conserve la structure de base d'un écran LCD (couche de cristaux liquides entre deux substrats) mais révolutionne le système de rétroéclairage en remplaçant les quelques dizaines de LED conventionnelles par des milliers de mini-LED, chacune mesurant généralement entre 50 et 200 microns. Cette multiplication des sources lumineuses permet d'obtenir un contrôle beaucoup plus précis de l'éclairage par zones (local dimming), améliorant significativement le contraste, la luminosité maximale et la précision des noirs. Les écrans Mini LED atteignent ainsi des performances visuelles proches des OLED tout en conservant les avantages des LCD: durée de vie prolongée, absence de risque de marquage et luminosité supérieure. Cette technologie est principalement employée dans les téléviseurs premium, les moniteurs professionnels, les ordinateurs portables haut de gamme et certains appareils mobiles. Le marché mondial des écrans Mini LED connaît une croissance rapide, estimée à plus de 35% annuellement, atteignant environ 6 milliards de dollars en 2024.
#### Principaux fabricants
| **Pays d'implantation** | **Entreprise** | **Pays d'origine** | **Part de marché** |
| :-- | :-- | :-- | :-- |
| Taïwan | Epistar | Taïwan | 12 % |
| Taïwan | AU Optronics | Taïwan | 10 % |
| Taïwan | Innolux | Taïwan | 8 % |
| **Taïwan** | **Total** | **Taïwan** | **30 %** |
| Corée du Sud | Samsung Display | Corée du Sud | 14 % |
| Corée du Sud | LG Display | Corée du Sud | 11 % |
| **Corée du Sud** | **Total** | **Corée du Sud** | **25 %** |
| Chine | BOE Technology | Chine | 11 % |
| Chine | CSOT (TCL) | Chine | 9 % |
| **Chine** | **Total** | **Chine** | **20 %** |
| Japon | Sony | Japon | 5 % |
| Japon | Sharp/Foxconn | Japon | 4 % |
| **Japon** | **Total** | **Japon** | **9 %** |
| États-Unis | Apple (design) | États-Unis | 4 % |
| États-Unis | Lumileds | États-Unis | 3 % |
| **États-Unis** | **Total** | **États-Unis** | **7 %** |
| Allemagne | OSRAM Opto | Allemagne | 4 % |
| **Allemagne** | **Total** | **Allemagne** | **4 %** |
| Vietnam | Samsung Vietnam | Corée du Sud | 3 % |
| **Vietnam** | **Total** | **Vietnam** | **3 %** |
**Unités** : million d'unité/an
**Total** : 81
_Note: Les capacités de production concernent spécifiquement les écrans Mini LED (pas LCD standard) et sont estimées pour 2024-2025. Le marché est en évolution rapide avec une augmentation notable des capacités prévue dans les prochaines années._
##### ISG des pays impliqués
| Pays | Part de marché | ISG | Criticité |
| :-- | :-- | :-- | :-- |
| Allemagne | 4.0% | 30 | :green-badge[Vert] (Stable) |
| Corée du Sud | 25.0% | 33 | :green-badge[Vert] (Stable) |
| Japon | 9.0% | 29 | :green-badge[Vert] (Stable) |
| Vietnam | 3.0% | 48 | :orange-badge[Orange] (Intermédiaire) |
| États-Unis | 7.0% | 42 | :orange-badge[Orange] (Intermédiaire) |
| Chine | 20.0% | 54 | :orange-badge[Orange] (Intermédiaire) |
| Taiwan | 30.0% | 30 | :green-badge[Vert] (Stable) |
**ISG combiné: 37 - :green-badge[Vert] (Stable)**
#### Indice de Herfindahl-Hirschmann
| **IHH** | **Faible** | **Modéré** | **Élevé** |
| :-- | :-- | :-- | :-- |
| **Acteurs** | **9** | | |
| **Pays** | | **21** | |
##### IHH par entreprise (acteurs)
LIHH pour les assembleurs est de **9**, ce qui indique une **concentration faible**. Bien que **Samsung Display (14 %)**, **Epistar (12 %)**, **LG Display (11 %)** regroupent plus de 37 % du marché, plusieurs autres groupes viennent équilibrer le secteur. Cette structure permet une **certaine résilience industrielle**, avec plusieurs options en cas de tension sur un acteur majeur.
##### IHH par pays
LIHH par pays atteint **21**, révélant une **concentration géographique modérée**. La répartition est dominée par **Taïwan (30 %)**, **Corée du Sud (25 %)**, **Chine (20 %)**, représentant ensemble plus de 75 % des capacités.
##### En résumé
- Le secteur présente une **structure dacteurs plutôt diversifiée** (IHH 9)
- La **concentration géographique est modérée** (IHH 21)
#### Vulnérabilité combinée IHH-ISG
* IHH: 21 - :orange-badge[Orange] (Modérée)
* ISG combiné: 37 - :green-badge[Vert] (Stable)
* Poids combiné: 2
* Niveau de vulnérabilité: **:green-badge[FAIBLE]**
### Audio et Fabrication
Les composants audio constituent un élément essentiel des appareils électroniques modernes, assurant la capture, le traitement et la reproduction du son. Cette catégorie englobe principalement les haut-parleurs, écouteurs, microphones, amplificateurs, codecs et circuits de traitement audio spécialisés. Les haut-parleurs et écouteurs, qui forment la majorité du marché, reposent sur des aimants permanents en terres rares pour générer le champ magnétique nécessaire à la conversion des signaux électriques en ondes sonores. Les microphones, quant à eux, fonctionnent selon divers principes (électrostatique, électrodynamique, piézoélectrique). L'industrie des composants audio représente un marché mondial de plus de 20 milliards de dollars, largement dominé par des acteurs asiatiques, avec une forte concentration en Chine. La fabrication à grande échelle nécessite des processus de précision pour les membranes et bobines acoustiques, ainsi qu'un approvisionnement fiable en terres rares pour les aimants hautes performances. La demande croissante de miniaturisation, de qualité sonore supérieure et d'efficacité énergétique oriente l'évolution des technologies et des procédés de fabrication.
#### Principaux fabricants
| **Pays d'implantation** | **Entreprise** | **Pays d'origine** | **Part de marché** |
| :-- | :-- | :-- | :-- |
| Chine | Goertek | Chine | 24 % |
| Chine | AAC Technologies | Chine | 18 % |
| Chine | Bose Manufacturing | États-Unis | 9 % |
| Chine | Knowles Electronics | États-Unis | 7 % |
| **Chine** | **Total** | **Chine** | **56 %** |
| Taïwan | Foxconn | Taïwan | 6 % |
| Taïwan | MediaTek | Taïwan | 5 % |
| Taïwan | Realtek | Taïwan | 4 % |
| **Taïwan** | **Total** | **Taïwan** | **15 %** |
| Corée du Sud | Samsung Electro-Mechanics | Corée du Sud | 7 % |
| Corée du Sud | LG Innotek | Corée du Sud | 3 % |
| **Corée du Sud** | **Total** | **Corée du Sud** | **10 %** |
| Japon | Sony | Japon | 4 % |
| Japon | Panasonic | Japon | 3 % |
| Japon | Alps Alpine | Japon | 1 % |
| **Japon** | **Total** | **Japon** | **8 %** |
| États-Unis | Cirrus Logic | États-Unis | 2 % |
| États-Unis | Texas Instruments | États-Unis | 2 % |
| **États-Unis** | **Total** | **États-Unis** | **4 %** |
| Allemagne | Infineon | Allemagne | 2 % |
| Allemagne | Beyer Dynamic | Allemagne | 1 % |
| **Allemagne** | **Total** | **Allemagne** | **3 %** |
**Unités** : million d'unité/an
**Total** : 3540
_Note: Les capacités indiquées représentent la production annuelle en 2024-2025. La concentration en Chine s'explique par l'intégration verticale avec les chaînes d'assemblage d'appareils électroniques._
##### ISG des pays impliqués
| Pays | Part de marché | ISG | Criticité |
| :-- | :-- | :-- | :-- |
| Taiwan | 15.0% | 30 | :green-badge[Vert] (Stable) |
| Corée du Sud | 10.0% | 33 | :green-badge[Vert] (Stable) |
| Allemagne | 3.0% | 30 | :green-badge[Vert] (Stable) |
| Chine | 56.0% | 54 | :orange-badge[Orange] (Intermédiaire) |
| États-Unis | 4.0% | 42 | :orange-badge[Orange] (Intermédiaire) |
| Japon | 8.0% | 29 | :green-badge[Vert] (Stable) |
**ISG combiné: 45 - :orange-badge[Orange] (Intermédiaire)**
#### Indice de Herfindahl-Hirschmann
| **IHH** | **Faible** | **Modéré** | **Élevé** |
| :-- | :-- | :-- | :-- |
| **Acteurs** | 12 | | |
| **Pays** | | | 36 |
##### IHH par entreprise (acteurs)
LIHH pour les assembleurs est de **12**, ce qui indique une **concentration faible**. Bien que **Goertek (24 %)**, **AAC Technologies (18 %)**, **Bose Manufacturing (9 %)** regroupent plus de 51 % du marché, plusieurs autres groupes viennent équilibrer le secteur. Cette structure permet une **certaine résilience industrielle**, avec plusieurs options en cas de tension sur un acteur majeur.
##### IHH par pays
LIHH par pays atteint **36**, révélant une **concentration géographique élevée**. La répartition est dominée par **Chine (56 %)**, **Taïwan (15 %)**, **Corée du Sud (10 %)**, représentant ensemble plus de 81 % des capacités. Cette configuration expose la chaîne à des **risques géopolitiques ou logistiques localisés**.
##### En résumé
- Le secteur présente une **structure dacteurs plutôt diversifiée** (IHH 12)
- La **concentration géographique est élevée** (IHH 36)
#### Vulnérabilité combinée IHH-ISG
* IHH: 36 - :red-badge[Rouge] (Élevée)
* ISG combiné: 45 - :orange-badge[Orange] (Intermédiaire)
* Poids combiné: 6
* Niveau de vulnérabilité: **:red-badge[ÉLEVÉE à CRITIQUE]**
## Détails des minerais
---
### Dysprosium
Le dysprosium est un métal lanthanide rare de couleur blanc-argenté, découvert en 1886 par le Français Lecoq de Boisbaudran. Ce métal au nom issu du grec "dysprositos" (difficile à obtenir) se caractérise par sa malléabilité, sa ductilité, sa forte réactivité à l'air et à l'eau, et ses remarquables propriétés magnétiques. Classé parmi les terres rares lourdes, il est faiblement concentré dans la croûte terrestre (environ 0,3 ppm). Le dysprosium se distingue par sa capacité à renforcer considérablement la résistance à la démagnétisation à haute température des aimants permanents, propriété qui représente 98% de ses applications et en fait un élément stratégique pour les technologies de pointe. Sa production mondiale est largement dominée par la Chine, qui maintient une situation de quasi-monopole sur l'ensemble de la chaîne de valeur, depuis l'extraction jusqu'à la transformation en produits finis.
#### ICS
| Composant | ICS | Faisabilité technique | Délai d'implémentation | Impact économique |
| :-- | :--: | :--: | :--: | :--: |
| Audio | 0.70 | 0.70 | 0.70 | 0.70 |
##### Valeurs d'ICS par composant concerné
| Composant | ICS | Criticité |
| :-- | :-- | :-- |
| Audio | 0.70 | :red-badge[Rouge] (Difficile) |
**ICS moyen : 0.70 - :red-badge[Rouge] (Difficile)**
#### IVC
**IVC: 36 - :orange-badge[Orange] (Modérée)**
*Usage numérique*
Aimants pour capteurs, disques durs, nanoparticules pour électronique, cibles de pulvérisation
*Secteurs concurrents*
Moteurs EV, éoliennes, alliages industriels, technologies de défense
*Remarques*
Très forte dépendance au numérique embarqué. Usage final numérique plus faible mais stratégique.
*Répartition des usages*
* Numérique final : 20%
* Numérique embarqué : 30%
* Autres secteurs : 50%
*Tendance*
* Demande : +8%
* Production : +3%
* Ratio capacité/demande : 0.95
*Concurrence & tension*
* Ratio concurrence : 4.0
* Tension marché : 5.0
*Réserves*
* Niveau : Très limité
* Pondération : 1.8
#### Vulnérabilité combinée ICS-IVC
* ICS moyen: 0.70 - :red-badge[Rouge] (Difficile)
* IVC: 36 - :orange-badge[Orange] (Modérée)
* Poids combiné: 6
* Niveau de vulnérabilité: **:red-badge[ÉLEVÉE à CRITIQUE]**
#### Extraction
| **Pays d'implantation** | **Entreprise** | **Pays d'origine** | **Part de marché** |
| :-- | :-- | :-- | :-- |
| Chine | China Northern Rare Earth Group | Chine | 75 % |
| **Chine** | **Total** | **Chine** | **98 %** |
| Australie | Northern Minerals | Australie | 2 % |
| **Australie** | **Total** | **Australie** | **2 %** |
| États-Unis | MP Materials | États-Unis | 0 % |
| **États-Unis** | **Total** | **États-Unis** | **0 %** |
Unités : tonnes/an
Total : 1835
##### ISG des pays impliqués
| Pays | Part de marché | ISG | Criticité |
| :-- | :-- | :-- | :-- |
| Australie | 2.0% | 26 | :green-badge[Vert] (Stable) |
| Chine | 98.0% | 54 | :orange-badge[Orange] (Intermédiaire) |
| EtatsUnis | 0.0% | 42 | :orange-badge[Orange] (Intermédiaire) |
**ISG combiné: 53 - :orange-badge[Orange] (Intermédiaire)**
#### Indice de Herfindahl-Hirschmann - Extraction
| **IHH** | **Faible** | **Modéré** | **Élevé** |
| :-- | :-- | :-- | :-- |
| **Acteurs** | | | **56** |
| **Pays** | | | **96** |
##### IHH par entreprise (acteurs)
LIHH pour les assembleurs est de **56**, signalant une **concentration élevée**. Le marché est largement dominé par **China Northern Rare Earth Group (75 %)**, **Northern Minerals (2 %)**, **MP Materials (0 %)**, ce qui pourrait poser des **risques industriels en cas de défaillance**.
##### IHH par pays
LIHH par pays atteint **96**, révélant une **concentration géographique élevée**. La répartition est dominée par **Chine (98 %)**, **Australie (2 %)**, **États-Unis (0 %)**, représentant ensemble plus de 100 % des capacités. Cette configuration expose la chaîne à des **risques géopolitiques ou logistiques localisés**.
##### En résumé
- Le secteur présente une **concentration forte en nombre dacteurs** (IHH 56)
- La **concentration géographique est élevée** (IHH 96)
##### Vulnérabilité combinée IHH-ISG pour l'extraction
* IHH: 96 - :red-badge[Rouge] (Élevée)
* ISG combiné: 53 - :orange-badge[Orange] (Intermédiaire)
* Poids combiné: 6
* Niveau de vulnérabilité: **:red-badge[ÉLEVÉE à CRITIQUE]**
#### Traitement
| **Pays d'implantation** | **Entreprise** | **Pays d'origine** | **Origines du minerai** | **Part de marché** |
| :-- | :-- | :-- | :-- | :-- |
| Chine | China Minmetals | Chine | Chine (95%) | 39 % |
| Chine | Northern Rare Earth | Chine | Chine (100%) | 20 % |
| **Chine** | **Total** | **Chine** | **-** | **59 %** |
| Malaisie | Lynas Advanced Materials | Australie | Australie (100%) | 13 % |
| **Malaisie** | **Total** | **Malaisie** | **-** | **13 %** |
| États-Unis | MP Materials | États-Unis | États-Unis (100%) | 10 % |
| **États-Unis** | **Total** | **États-Unis** | **-** | **10 %** |
| Estonie | NPM Silmet | Canada | Australie (100%) | 6 % |
| **Estonie** | **Total** | **Estonie** | **-** | **6 %** |
| Inde | Indian Rare Earths | Inde | Chine (100%) | 5 % |
| **Inde** | **Total** | **Inde** | **-** | **5 %** |
| Russie | Solikamsk Magnesium | Russie | États-Unis (100%) | 4 % |
| **Russie** | **Total** | **Russie** | **-** | **4 %** |
Unités : tonnes/an
Total : 900
_Note: La production de dysprosium est étroitement liée à celle des autres terres rares, créant une interdépendance complexe entre l'offre et la demande des différents éléments._
##### ISG des pays impliqués
| Pays | Part de marché | ISG | Criticité |
| :-- | :-- | :-- | :-- |
| Russie | 4.0% | 65 | :orange-badge[Orange] (Intermédiaire) |
| Inde | 5.0% | 60 | :orange-badge[Orange] (Intermédiaire) |
| Chine | 59.0% | 54 | :orange-badge[Orange] (Intermédiaire) |
| EtatsUnis | 10.0% | 42 | :orange-badge[Orange] (Intermédiaire) |
| Malaisie | 13.0% | 44 | :orange-badge[Orange] (Intermédiaire) |
| Estonie | 6.0% | 33 | :green-badge[Vert] (Stable) |
**ISG combiné: 51 - :orange-badge[Orange] (Intermédiaire)**
#### Indice de Herfindahl-Hirschmann - Traitement
| **IHH** | **Faible** | **Modéré** | **Élevé** |
| :-- | :-- | :-- | :-- |
| **Acteurs** | | **23** | |
| **Pays** | | | **38** |
##### IHH par entreprise (acteurs)
LIHH pour les assembleurs est de **23**, ce qui indique une **concentration modérée**. **China Minmetals (39 %)**, **Northern Rare Earth (20 %)**, **Lynas Advanced Materials (13 %)** regroupent une part importante du marché. Cette structure permet une **résilience relative**, mais dépend encore de quelques grands groupes.
##### IHH par pays
LIHH par pays atteint **38**, révélant une **concentration géographique élevée**. La répartition est dominée par **Chine (59 %)**, **Malaisie (13 %)**, **États-Unis (10 %)**, représentant ensemble plus de 82 % des capacités. Cette configuration expose la chaîne à des **risques géopolitiques ou logistiques localisés**.
##### En résumé
- Le secteur présente une **structure dacteurs moyennement concentrée** (IHH 23)
- La **concentration géographique est élevée** (IHH 38)
##### Vulnérabilité combinée IHH-ISG pour le traitement
* IHH: 38 - :red-badge[Rouge] (Élevée)
* ISG combiné: 51 - :orange-badge[Orange] (Intermédiaire)
* Poids combiné: 6
* Niveau de vulnérabilité: **:red-badge[ÉLEVÉE à CRITIQUE]**
---
### Terbium
Le terbium est un élément métallique du groupe des lanthanides, découvert en 1843 par Carl Gustaf Mosander lors de l'analyse de la "terre d'yttria". Ce métal de couleur gris-argenté, malléable et ductile se caractérise par sa relative stabilité à l'air, ses propriétés magnétiques remarquables et sa capacité à émettre une luminescence verte lorsqu'il est excité. Le terbium se trouve principalement dans des minéraux comme la bastnäsite, la monazite et la xénotime, toujours en association avec d'autres terres rares et en concentrations extrêmement faibles. Son extraction et sa séparation impliquent des procédés hydrometallurgiques complexes, notamment l'extraction par solvant et l'échange d'ions, rendus particulièrement difficiles par sa similitude chimique avec les autres lanthanides. La chaîne d'approvisionnement mondiale est fortement concentrée en Chine, qui contrôle environ 85% de la production. Malgré sa rareté (production mondiale d'environ 450 tonnes par an), le terbium est essentiel pour plusieurs applications de haute technologie, notamment les phosphores verts pour écrans, les disques magnéto-optiques, les aimants permanents Terfenol-D et les dispositifs sonar.
#### ICS
| Composant | ICS | Faisabilité technique | Délai d'implémentation | Impact économique |
| :-- | :--: | :--: | :--: | :--: |
| EcranLCD | 0.81 | 0.90 | 0.80 | 0.70 |
| EcranMiniLED | 0.81 | 0.90 | 0.80 | 0.70 |
##### Valeurs d'ICS par composant concerné
| Composant | ICS | Criticité |
| :-- | :-- | :-- |
| Écran LCD/TFT | 0.81 | :red-badge[Rouge] (Difficile) |
| Écran Mini et Micro LED | 0.81 | :red-badge[Rouge] (Difficile) |
**ICS moyen : 0.81 - :red-badge[Rouge] (Difficile)**
#### IVC
**IVC: 28 - :orange-badge[Orange] (Modérée)**
*Usage numérique*
Phosphores pour écrans, LED, disques durs, aimants pour moteurs, capteurs de position.
*Secteurs concurrents*
Éclairage, transition énergétique (VE, éoliennes), optique
*Remarques*
Ressource rare, cruciale pour les écrans et les aimants à haut rendement. Fortes tensions avec les industries de la transition énergétique.
*Répartition des usages*
* Numérique final : 45%
* Numérique embarqué : 30%
* Autres secteurs : 25%
*Tendance*
* Demande : +50%
* Production : +35%
* Ratio capacité/demande : 0.9
*Concurrence & tension*
* Ratio concurrence : 1.22
* Tension marché : 15.0
*Réserves*
* Niveau : Limité
* Pondération : 1.5
#### Vulnérabilité combinée ICS-IVC
* ICS moyen: 0.81 - :red-badge[Rouge] (Difficile)
* IVC: 28 - :orange-badge[Orange] (Modérée)
* Poids combiné: 6
* Niveau de vulnérabilité: **:red-badge[ÉLEVÉE à CRITIQUE]**
#### Extraction
| **Pays d'implantation** | **Entreprise** | **Pays d'origine** | **Part de marché** |
| :-- | :-- | :-- | :-- |
| Chine | China Northern Rare Earth Group | Chine | 71 % |
| **Chine** | **Total** | **Chine** | **71 %** |
| États-Unis | MP Materials | États-Unis | 14 % |
| **États-Unis** | **Total** | **États-Unis** | **14 %** |
| Myanmar | Diverses entreprises locales | Myanmar | 7 % |
| **Myanmar** | **Total** | **Myanmar** | **7 %** |
| Australie | Lynas Rare Earths | Australie | 4 % |
| **Australie** | **Total** | **Australie** | **4 %** |
| Brésil | CBMM | Brésil | 3 % |
| **Brésil** | **Total** | **Brésil** | **3 %** |
**Unités** : t/an
**Total** : 700
##### ISG des pays impliqués
| Pays | Part de marché | ISG | Criticité |
| :-- | :-- | :-- | :-- |
| EtatsUnis | 14.0% | 42 | :orange-badge[Orange] (Intermédiaire) |
| Chine | 71.0% | 54 | :orange-badge[Orange] (Intermédiaire) |
| Australie | 4.0% | 26 | :green-badge[Vert] (Stable) |
| Myanmar | 7.0% | 84 | :red-badge[Rouge] (Instable) |
| Bresil | 3.0% | 55 | :orange-badge[Orange] (Intermédiaire) |
**ISG combiné: 53 - :orange-badge[Orange] (Intermédiaire)**
#### Indice de Herfindahl-Hirschmann - Extraction
| **IHH** | **Faible** | **Modéré** | **Élevé** |
| :-- | :-- | :-- | :-- |
| **Acteurs** | | | **53** |
| **Pays** | | | **53** |
##### IHH par entreprise (acteurs)
LIHH pour les assembleurs est de **53**, signalant une **concentration élevée**. Le marché est largement dominé par **China Northern Rare Earth Group (71 %)**, **MP Materials (14 %)**, **Diverses entreprises locales (7 %)**, ce qui pourrait poser des **risques industriels en cas de défaillance**.
##### IHH par pays
LIHH par pays atteint **53**, révélant une **concentration géographique élevée**. La répartition est dominée par **Chine (71 %)**, **États-Unis (14 %)**, **Myanmar (7 %)**, représentant ensemble plus de 92 % des capacités. Cette configuration expose la chaîne à des **risques géopolitiques ou logistiques localisés**.
##### En résumé
- Le secteur présente une **concentration forte en nombre dacteurs** (IHH 53)
- La **concentration géographique est élevée** (IHH 53)
##### Vulnérabilité combinée IHH-ISG pour l'extraction
* IHH: 53 - :red-badge[Rouge] (Élevée)
* ISG combiné: 53 - :orange-badge[Orange] (Intermédiaire)
* Poids combiné: 6
* Niveau de vulnérabilité: **:red-badge[ÉLEVÉE à CRITIQUE]**
#### Traitement
| **Pays d'implantation** | **Entreprise** | **Pays d'origine** | **Origines du minerai** | **Part de marché** |
| :-- | :-- | :-- | :-- | :-- |
| Chine | China Minmetals | Chine | | 33 % |
| Chine | China Northern Rare Earth | Chine | | 27 % |
| Chine | Xiamen Tungsten | Chine | | 18 % |
| **Chine** | **Total** | **Chine** | **-** | **78 %** |
| Malaisie | Lynas Advanced Materials | Australie | | 7 % |
| **Malaisie** | **Total** | **Malaisie** | **-** | **7 %** |
| Estonie | NPM Silmet | Canada | | 4 % |
| **Estonie** | **Total** | **Estonie** | **-** | **4 %** |
| États-Unis | MP Materials | États-Unis | | 3 % |
| **États-Unis** | **Total** | **États-Unis** | **-** | **3 %** |
| Inde | Indian Rare Earths | Inde | | 3 % |
| **Inde** | **Total** | **Inde** | **-** | **3 %** |
| Vietnam | Vietnam Rare Earth | Vietnam | | 2 % |
| **Vietnam** | **Total** | **Vietnam** | **-** | **2 %** |
**Unités** : t/an
**Total** : 450
_Note: Les capacités indiquées représentent la production d'oxyde de terbium (Tb₄O₇). La Chine domine largement le marché mondial avec environ 78% de la capacité de traitement, notamment grâce à ses gisements d'argiles ioniques du sud du pays, particulièrement riches en terres rares lourdes._
##### ISG des pays impliqués
| Pays | Part de marché | ISG | Criticité |
| :-- | :-- | :-- | :-- |
| EtatsUnis | 3.0% | 42 | :orange-badge[Orange] (Intermédiaire) |
| Vietnam | 2.0% | 48 | :orange-badge[Orange] (Intermédiaire) |
| Chine | 78.0% | 54 | :orange-badge[Orange] (Intermédiaire) |
| Inde | 3.0% | 60 | :orange-badge[Orange] (Intermédiaire) |
| Estonie | 4.0% | 33 | :green-badge[Vert] (Stable) |
| Malaisie | 7.0% | 44 | :orange-badge[Orange] (Intermédiaire) |
**ISG combiné: 52 - :orange-badge[Orange] (Intermédiaire)**
#### Indice de Herfindahl-Hirschmann - Traitement
| **IHH** | **Faible** | **Modéré** | **Élevé** |
| :-- | :-- | :-- | :-- |
| **Acteurs** | | **22** | |
| **Pays** | | | **62** |
##### IHH par entreprise (acteurs)
LIHH pour les assembleurs est de **22**, ce qui indique une **concentration modérée**. **China Minmetals (33 %)**, **China Northern Rare Earth (27 %)**, **Xiamen Tungsten (18 %)** regroupent une part importante du marché. Cette structure permet une **résilience relative**, mais dépend encore de quelques grands groupes.
##### IHH par pays
LIHH par pays atteint **62**, révélant une **concentration géographique élevée**. La répartition est dominée par **Chine (78 %)**, **Malaisie (7 %)**, **Estonie (4 %)**, représentant ensemble plus de 89 % des capacités. Cette configuration expose la chaîne à des **risques géopolitiques ou logistiques localisés**.
##### En résumé
- Le secteur présente une **structure dacteurs moyennement concentrée** (IHH 22)
- La **concentration géographique est élevée** (IHH 62)
##### Vulnérabilité combinée IHH-ISG pour le traitement
* IHH: 62 - :red-badge[Rouge] (Élevée)
* ISG combiné: 52 - :orange-badge[Orange] (Intermédiaire)
* Poids combiné: 6
* Niveau de vulnérabilité: **:red-badge[ÉLEVÉE à CRITIQUE]**
## 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
Cet indice consiste à sommer les parts de marché au carré pour évaluer la concentration d'une industrie ou d'une zone géographique.
Plus l'IHH est faible (typiquement en dessous de 15), plus le secteur considéré est diversifié en acteurs ou en pays producteurs ; plus il est élevé (au-delà de 25), plus on se rapproche d'une situation de quasi-monopole ou de forte dépendance, qu'elle soit géopolitique ou industrielle ; un IHH de 100 indique un monopole.
$$
\text{IHH} = \frac{\sum_{i=1}^{N} s_i^2}{100}
$$
où :
- $N$ : Nombre total d'acteurs ou d'entités considérées.
- $s_{i}$ : Part relative de l'acteur ${i}$, exprimée en proportion mondiale (entre 1 et 100).
* Seuils : <15 = :green-badge[Vert] (Faible), 15-25 = :orange-badge[Orange] (Modérée), >25 = :red-badge[Rouge] (Élevée)
#### ISG (Stabilité Géopolitique) : stabilité des pays
Cet indicateur synthétise trois bases reconnues :
* WGI (World Bank, “Political Stability & Absence of Violence”),
* FSI (Fragile States Index)
* et ND-GAIN (Climat & capacité d'adaptation)
afin de mesurer, pour chaque pays, la probabilité qu'un choc politique, social ou climatique perturbe la chaîne d'approvisionnement.
Plus l'indice est faible et plus la pays est stable.
$$
\text{ISG}=%
\operatorname{arrondi}\!\left(
100 \times \Bigl(
50\% \times \frac{2.5-\mathrm{WGI}}{5}
+ 30\% \times \frac{\mathrm{FSI}}{120}
+ 20\% \times \frac{100-\mathrm{NDGAIN}}{100}
\Bigr)
\right)
$$
* Seuils : <40 = :green-badge[Vert] (Stable), 40-60 = :orange-badge[Orange], >60 = :red-badge[Rouge] (Instable)
#### ICS (Criticité de Substituabilité) : capacité à remplacer / substituer un élément
La criticité a pour objectif de pondérer une part de marché qui se trouve sur un chemin critique de la chaine d'approvisionnement.
À titre d'exemple, un minerai utilisé dans le numérique et dont le traitement est réalisé à 95% par un acteur, mais dont la criticité est faible aura un moindre impact qu'un autre minerai à 80% de part de marché pour un acteur, mais avec une forte criticité.
Sur la base des 3 axes suivants, le calcul de l'ICS se fait sous la forme suivante :
$$
\text{ICS}=40\% \times F_{tech} + 30\% \times D_{impl} + 30\% \times I_{eco}
$$
(où $F_{tech}$ = faisabilité technique, $D_{impl}$ = délai dimplémentation, $I_{eco}$ = impact économique).
* Seuils : <0.3 = :green-badge[Vert] (Facile), 0.3-0.6 = :orange-badge[Orange] (Moyenne), >0.6 = :red-badge[Rouge] (Difficile)
#### IVC (Vulnérabilité de Concurrence) : pression concurrentielle avec d'autres secteurs
Dans un contexte de transition numérique et énergétique simultanée, de nombreuses ressources minérales sont soumises à **une demande croissante et multisectorielle**. Le numérique nest plus le seul acteur consommateur : les véhicules électriques, les technologies vertes, la défense ou le médical mobilisent les mêmes minerais.
Cette situation crée un **effet déviction potentiel**, où les ressources disponibles pourraient être absorbées par des usages concurrents. Cest ce phénomène que lIVC cherche à évaluer.LIndice de Vulnérabilité Concurrentielle (IVC) est un indicateur composite qui estime le risque que le secteur numérique **ne puisse plus accéder à une ressource**, non pas à cause de la concentration ou dun manque dalternatives, mais parce que **dautres secteurs captent lessentiel de la ressource**.
Il permet de quantifier la pression que les usages **non numériques** font peser sur les **usages numériques** pour une ressource donnée.
Sur la base des 3 axes suivants, le calcul de l'IVC se fait sous la forme suivante :
$$
\text{IVC} = \left( \frac{C_c}{C_n} \right) \times \left( \frac{P_c}{P_n} \right) \times T_m \times W_r
$$
où :
$C_{c}$ est *Croissance des concurrents*,
$C_{n}$ est *Croissance numérique*,
$P_{c}$ est *Part des concurrents*,
$P_{n}$ est *Part numérique*,
$T_{m}$ est *Tension du marché*,
$W_{r}$ est *Pondération des réserves*.
* Seuils : <5 = :green-badge[Vert] (Faible), 5-15 = :orange-badge[Orange] (Modérée), >15 = :red-badge[Rouge] (Forte)
### 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 (:green-badge[Vert] = 1, :orange-badge[Orange] = 2, :red-badge[Rouge] = 3) et en faisant le produit des poids de l'ISG combiné et de l'IHH
| ISG combiné / IHH | :green-badge[Vert] | :orange-badge[Orange] | :red-badge[Rouge] |
| :-- | :-- | :-- | :-- |
| :green-badge[Vert] | 1 | 2 | 3 |
| :orange-badge[Orange] | 2 | 4 | 6 |
| :red-badge[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 (:green-badge[Vert] = 1, :orange-badge[Orange] = 2, :red-badge[Rouge] = 3) et en faisant le produit des poids de l'ICS moyen et de l'IVC.
| ICS_moyen / IVC | :green-badge[Vert] | :orange-badge[Orange] | :red-badge[Rouge] |
| :-- | :-- | :-- | :-- |
| :green-badge[Vert] | 1 | 2 | 3 |
| :orange-badge[Orange] | 2 | 4 | 6 |
| :red-badge[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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -3,6 +3,7 @@ import requests
import logging
import os
from utils.translations import _
from utils.persistance import get_champ_statut, maj_champ_statut
def initialiser_logger():
LOG_FILE_PATH = "/var/log/fabnum-auth.log"
@ -19,7 +20,8 @@ def initialiser_logger():
return logger
def connexion():
if "logged_in" not in st.session_state or not st.session_state.logged_in:
login = get_champ_statut("login")
if login == "":
auth_title = str(_("auth.title"))
st.html(f"""
<section role="region" aria-label="region-authentification">
@ -33,12 +35,7 @@ def connexion():
logger = initialiser_logger()
if "logged_in" not in st.session_state:
st.session_state.logged_in = False
st.session_state.username = ""
st.session_state.token = ""
if not st.session_state.logged_in:
if get_champ_statut("login") == "":
with st.form("auth_form"):
# Ajout d'un champ identifiant fictif pour activer l'autocomplétion navigateur
# et permettre de stocker le token comme un mot de passe par le navigateur
@ -71,10 +68,8 @@ def connexion():
check_url = f"{GITEA_URL}/teams/{team_id}/members/{username}"
check_response = requests.get(check_url, headers=headers, timeout=5)
if check_response.status_code == 200:
st.session_state.logged_in = True
st.session_state.username = username
st.session_state.token = token
erreur = False
maj_champ_statut("login", username)
logger.info(f"Connexion réussie pour {username} depuis IP {ip}")
st.rerun()
@ -92,7 +87,8 @@ def connexion():
def bouton_deconnexion():
if st.session_state.get("logged_in", False):
login = get_champ_statut("login")
if not login == "":
auth_title = str(_("auth.title"))
st.html(f"""
<section role="region" aria-label="region-authentification">
@ -100,11 +96,9 @@ def bouton_deconnexion():
<p id="Authentification" class="decorative-heading">{auth_title}</p>
""")
st.sidebar.markdown(f"{str(_('auth.logged_as'))} `{st.session_state.username}`")
st.sidebar.markdown(f"{str(_('auth.logged_as'))} `{login}`")
if st.sidebar.button(str(_("auth.logout")), icon=":material/logout:"):
st.session_state.logged_in = False
st.session_state.username = ""
st.session_state.token = ""
maj_champ_statut("login", "")
st.success(str(_("auth.success")))
st.rerun()

View File

@ -2,7 +2,7 @@ import streamlit as st
from components.connexion import connexion, bouton_deconnexion
import streamlit.components.v1 as components
from utils.translations import _
from utils.persistance import get_champ_statut, maj_champ_statut
def afficher_menu():
with st.sidebar:
@ -13,22 +13,24 @@ def afficher_menu():
# Définir la variable instructions_text une seule fois en haut de la fonction
instructions_text = str(_("navigation.instructions"))
if "onglet" not in st.session_state:
st.session_state.onglet = instructions_text
navigation_onglet = get_champ_statut("navigation_onglet")
if navigation_onglet == "":
navigation_onglet = instructions_text
maj_champ_statut("navigation_onglet", navigation_onglet)
onglet_choisi = None
onglets = [
str(_("navigation.instructions")),
str(_("navigation.personnalisation")),
str(_("navigation.analyse")),
*([str(_("navigation.ia_nalyse"))] if st.session_state.get("logged_in", False) else []),
*([str(_("navigation.ia_nalyse"))] if not get_champ_statut("login") == "" else []),
*([str(_("navigation.plan_d_action"))]),
str(_("navigation.visualisations")),
str(_("navigation.fiches"))
]
for nom in onglets:
if st.session_state.onglet == nom:
if navigation_onglet == nom:
st.markdown(f'<div class="bouton-fictif">{nom}</div>', unsafe_allow_html=True)
else:
if st.button(str(nom)):
@ -45,9 +47,12 @@ def afficher_menu():
# Pour éviter de perdre les informations dans les formulaires,
# le changement de thème n'est proposé que si l'utilisateur est sur l'onglet "Instructions"
#
if st.session_state.onglet == instructions_text:
if "theme_mode" not in st.session_state:
st.session_state.theme_mode = str(_("sidebar.theme_light"))
#
theme_mode = get_champ_statut("theme_mode")
if navigation_onglet == instructions_text:
if theme_mode == "":
theme_mode = str(_("sidebar.theme_light"))
maj_champ_statut("theme_mode", theme_mode)
theme_title = str(_("sidebar.theme"))
st.markdown(f"""
@ -63,7 +68,7 @@ def afficher_menu():
theme = st.radio(
str(_("sidebar.theme")),
theme_options,
index=theme_options.index(st.session_state.theme_mode),
index=theme_options.index(theme_mode),
horizontal=True,
label_visibility="hidden"
)
@ -87,18 +92,18 @@ def afficher_menu():
</div>
</nav>""", unsafe_allow_html=True)
theme = st.session_state.theme_mode
theme = theme_mode
connexion()
if st.session_state.get("logged_in", False):
if not get_champ_statut("login") == "":
bouton_deconnexion()
# === RERUN SI BESOIN ===
if (onglet_choisi and onglet_choisi != st.session_state.onglet) or (theme != st.session_state.theme_mode):
if (onglet_choisi and onglet_choisi != navigation_onglet) or (theme != theme_mode):
if onglet_choisi: # Ne met à jour que si on a cliqué
st.session_state.onglet = onglet_choisi
st.session_state.theme_mode = theme
maj_champ_statut("navigation_onglet", onglet_choisi)
maj_champ_statut("theme_mode", theme)
st.rerun()
#

View File

@ -12,4 +12,4 @@ RestartSec=5
Environment=PYTHONUNBUFFERED=1
[Install]
WantedBy=multi-user.targe
WantedBy=multi-user.target

View File

@ -174,12 +174,15 @@ ia_nalyse_tab = _("navigation.ia_nalyse")
plan_d_action_tab = _("navigation.plan_d_action")
visualisations_tab = _("navigation.visualisations")
if st.session_state.onglet == instructions_tab:
from utils.persistance import get_champ_statut
navigation_onglet = get_champ_statut("navigation_onglet")
if navigation_onglet == instructions_tab:
markdown_content = charger_instructions_depuis_gitea(INSTRUCTIONS)
if markdown_content:
afficher_instructions_avec_expanders(markdown_content)
elif st.session_state.onglet == fiches_tab:
elif navigation_onglet == fiches_tab:
interface_fiches()
else:
@ -187,24 +190,24 @@ else:
# Le graphe n'est pas nécessaire pour Instructions ou Fiches
dot_file_path = charger_graphe()
if dot_file_path and st.session_state.onglet == analyse_tab:
if dot_file_path and navigation_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:
elif dot_file_path and navigation_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_action_tab:
elif dot_file_path and navigation_onglet == plan_d_action_tab:
G_temp = st.session_state["G_temp"]
interface_plan_d_action(G_temp)
elif dot_file_path and st.session_state.onglet == visualisations_tab:
elif dot_file_path and navigation_onglet == visualisations_tab:
G_temp = st.session_state["G_temp"]
G_temp_ivc = st.session_state["G_temp_ivc"]
interface_visualisations(G_temp, G_temp_ivc)
elif dot_file_path and st.session_state.onglet == personnalisation_tab:
elif dot_file_path and navigation_onglet == personnalisation_tab:
G_temp = st.session_state["G_temp"]
G_temp = interface_personnalisation(G_temp)

171
utils/persistance.py Normal file
View File

@ -0,0 +1,171 @@
import streamlit as st
import json
import os
from datetime import date
from utils.translations import _
from dotenv import load_dotenv
from pathlib import Path
load_dotenv(".env")
def initialise():
SAVE_SESSIONS_PATH.mkdir(parents=True, exist_ok=True)
def get_session_id():
if "session_id" not in st.session_state:
session_id = st.context.headers.get("x-session-id", "anonymous")
st.session_state["session_id"] = session_id
else:
session_id = st.session_state["session_id"]
return session_id
SAVE_STATUT = os.getenv("SAVE_STATUT", "statut_general.json")
session_id = get_session_id()
SAVE_SESSIONS_PATH = Path(f"tmp/sessions/{session_id}")
SAVE_STATUT_PATH = SAVE_SESSIONS_PATH / SAVE_STATUT
initialise()
def _maj_champ(fichier, cle: str, contenu: str = "") -> bool:
def serialize(obj):
return obj.isoformat() if isinstance(obj, date) else obj
def inserer_cle_json(structure: dict, cle: str, valeur: any) -> dict:
"""
Insère une clé de type 'a.b.c' dans un dictionnaire JSON imbriqué.
Args:
structure: Dictionnaire racine à mettre à jour
cle: Chaîne de clé séparée par des points
valeur: Valeur à insérer
Returns:
Le dictionnaire mis à jour
"""
parties = cle.split(".")
d = structure
for p in parties[:-1]:
d = d.setdefault(p, {})
d[parties[-1]] = valeur
return structure
if fichier.exists():
try:
with open(fichier, "r", encoding="utf-8") as f:
sauvegarde = json.load(f)
sauvegarde = inserer_cle_json(sauvegarde, cle, serialize(contenu))
except Exception as e:
st.error(_("persistance.errors.read_file").format(function="_maj_champ", file=fichier, error=e))
return False
else:
sauvegarde = {}
sauvegarde = inserer_cle_json(sauvegarde, cle, serialize(contenu))
try:
with open(fichier, "w", encoding="utf-8") as f:
json.dump(sauvegarde, f, ensure_ascii=False, indent=2)
return True
except Exception as e:
st.error(_("persistance.errors.write_file").format(function="_maj_champ", file=fichier, error=e))
return False
def _get_champ(fichier, cle: str) -> str:
def extraire_valeur_par_cle(structure: dict, cle: str):
"""
Extrait une valeur depuis un dictionnaire imbriqué avec une clé au format 'a.b.c'.
Args:
structure: Dictionnaire d'origine
cle: Chaîne représentant la clé imbriquée
Returns:
La valeur trouvée, ou `par_defaut` si elle est introuvable
"""
parties = cle.split(".")
d = structure
for p in parties:
if isinstance(d, dict) and p in d:
d = d[p]
else:
return ""
return d
import json
def charger_json_sain(fichier: str) -> dict:
with open(fichier, "r", encoding="utf-8") as f:
contenu = json.load(f)
if isinstance(contenu, str):
try:
contenu = json.loads(contenu) # On essaie de parser une 2e fois si nécessaire
except json.JSONDecodeError:
raise ValueError("Le fichier contient une chaîne JSON invalide.")
if not isinstance(contenu, dict):
raise ValueError("Le contenu JSON n'est pas un objet/dictionnaire valide.")
return contenu
if fichier.exists():
try:
sauvegarde = charger_json_sain(fichier)
return extraire_valeur_par_cle(sauvegarde, cle)
except Exception as e:
st.error(_("persistance.errors.read_file").format(function="_get_champ", file=fichier, error=e))
return ""
def _supprime_champ(fichier: Path, cle: str) -> bool:
def supprimer_cle_profonde(d: dict, chemin: str, separateur="."):
cles = chemin.split(separateur)
sous_dict = d
for cle in cles[:-1]:
sous_dict = sous_dict.get(cle, {})
if not isinstance(sous_dict, dict):
return False # Le chemin est invalide
return sous_dict.pop(cles[-1], None) is not None
if fichier.exists():
try:
with open(fichier, "r", encoding="utf-8") as f:
sauvegarde = json.load(f)
except Exception as e:
st.error(_("persistance.errors.read_file").format(function="_maj_champ", file=fichier, error=e))
return False
supprimer_cle_profonde(sauvegarde, cle)
try:
with open(fichier, "w", encoding="utf-8") as f:
json.dump(sauvegarde, f, indent=4)
except Exception as e:
st.error(_("persistance.errors.write_file").format(function="_supprime_champ", file=fichier, error=e))
return False
return True
def maj_champ_statut(cle: str, contenu: str = "") -> bool:
return _maj_champ(SAVE_STATUT_PATH, cle, contenu)
def get_champ_statut(cle: str) -> str:
return _get_champ(SAVE_STATUT_PATH, cle)
def supprime_champ_statut(cle: str) -> None:
_supprime_champ(SAVE_STATUT_PATH, cle)
def get_full_structure() -> dict|None:
fichier = SAVE_STATUT_PATH
if fichier.exists():
try:
with open(fichier, "r", encoding="utf-8") as f:
sauvegarde = json.load(f)
return sauvegarde
except Exception as e:
st.error(_("persistance.errors.read_file").format(function="_maj_champ", file=fichier, error=e))
return None
else:
return None