Ajout de la persistance
This commit is contained in:
parent
4d511cbe23
commit
8efc016014
1
.gitignore
vendored
1
.gitignore
vendored
@ -16,6 +16,7 @@ prompt.md
|
||||
*.tmp
|
||||
*.old
|
||||
tempo/
|
||||
tmp/
|
||||
|
||||
# Ignorer config locale
|
||||
.ropeproject/
|
||||
|
||||
@ -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("---")
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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)
|
||||
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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:
|
||||
|
||||
@ -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("---")
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
881
app/plan_d_action/utils/jobs/b2b0b4f8.md
Normal file
881
app/plan_d_action/utils/jobs/b2b0b4f8.md
Normal 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)
|
||||
|
||||
L’IHH 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
|
||||
|
||||
L’IHH 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 d’acteurs 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)
|
||||
|
||||
L’IHH 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
|
||||
|
||||
L’IHH 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 d’acteurs 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)
|
||||
|
||||
L’IHH 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
|
||||
|
||||
L’IHH 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 d’acteurs 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)
|
||||
|
||||
L’IHH 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
|
||||
|
||||
L’IHH 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 d’acteurs 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)
|
||||
|
||||
L’IHH 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
|
||||
|
||||
L’IHH 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 d’acteurs** (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)
|
||||
|
||||
L’IHH 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
|
||||
|
||||
L’IHH 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 d’acteurs 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)
|
||||
|
||||
L’IHH 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
|
||||
|
||||
L’IHH 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 d’acteurs** (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)
|
||||
|
||||
L’IHH 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
|
||||
|
||||
L’IHH 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 d’acteurs 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 d’implé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 n’est 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. C’est ce phénomène que l’IVC cherche à évaluer.L’Indice 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 d’un manque d’alternatives, mais parce que **d’autres secteurs captent l’essentiel 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
|
||||
1688
app/plan_d_action/utils/jobs/c1e8155c.md
Normal file
1688
app/plan_d_action/utils/jobs/c1e8155c.md
Normal file
File diff suppressed because it is too large
Load Diff
9028
app/plan_d_action/utils/jobs/ea9bcf50.md
Normal file
9028
app/plan_d_action/utils/jobs/ea9bcf50.md
Normal file
File diff suppressed because it is too large
Load Diff
@ -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()
|
||||
|
||||
|
||||
@ -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()
|
||||
|
||||
#
|
||||
|
||||
@ -12,4 +12,4 @@ RestartSec=5
|
||||
Environment=PYTHONUNBUFFERED=1
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.targe
|
||||
WantedBy=multi-user.target
|
||||
|
||||
17
fabnum.py
17
fabnum.py
@ -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
171
utils/persistance.py
Normal 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
|
||||
Loading…
x
Reference in New Issue
Block a user