Code/app/analyse/interface.py
2025-06-05 09:28:49 +02:00

242 lines
8.8 KiB
Python

from typing import List, Tuple, Dict, Optional
import networkx as nx
import streamlit as st
from utils.translations import _
from utils.widgets import html_expander
from .sankey import afficher_sankey
niveau_labels = {
0: "Produit final",
1: "Composant",
2: "Minerai",
10: "Opération",
11: "Pays d'opération",
12: "Acteur d'opération",
99: "Pays géographique"
}
inverse_niveau_labels = {v: k for k, v in niveau_labels.items()}
def preparer_graphe(
G: nx.DiGraph,
) -> Tuple[nx.DiGraph, Dict[str, int]]:
"""
Nettoie et prépare le graphe pour l'analyse.
Args:
G (nx.DiGraph): Le graphe NetworkX contenant les données des produits.
Returns:
Tuple[nx.DiGraph, Dict[str, int]]: Un tuple contenant :
- Le graphe NetworkX proprement configuré
- Un dictionnaire des niveaux associés aux nœuds
"""
niveaux_temp = {
node: int(str(attrs.get("niveau")).strip('"'))
for node, attrs in G.nodes(data=True)
if attrs.get("niveau") and str(attrs.get("niveau")).strip('"').isdigit()
}
G.remove_nodes_from([n for n in G.nodes() if n not in niveaux_temp])
G.remove_nodes_from(
[n for n in G.nodes() if niveaux_temp.get(n) == 10 and 'Reserves' in n])
return G, niveaux_temp
def selectionner_niveaux(
) -> Tuple[int|None, int|None]:
"""
Interface pour sélectionner les niveaux de départ et d'arrivée.
Returns:
Tuple[int, int]: Un tuple contenant deux nombres si des nœuds ont été sélectionnés,
- None sinon
"""
st.markdown(f"## {str(_('pages.analyse.selection_nodes'))}")
valeur_defaut = str(_("pages.analyse.select_level"))
niveau_choix = [valeur_defaut] + list(niveau_labels.values())
niveau_depart = st.selectbox(str(_("pages.analyse.start_level")), niveau_choix, key="analyse_niveau_depart")
if niveau_depart == valeur_defaut:
return None, None
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
analyse_niveau_arrivee = st.selectbox(str(_("pages.analyse.end_level")), niveaux_arrivee_choix, key="analyse_niveau_arrivee")
if analyse_niveau_arrivee == valeur_defaut:
return niveau_depart_int, None
niveau_arrivee_int = inverse_niveau_labels[analyse_niveau_arrivee]
return niveau_depart_int, niveau_arrivee_int
def selectionner_minerais(
G: nx.DiGraph,
niveau_depart: int,
niveau_arrivee: int
) -> Optional[List[str]]:
"""
Interface pour sélectionner les minerais si nécessaire.
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é.
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
])
minerais_selection = st.multiselect(
str(_("pages.analyse.filter_by_minerals")),
minerais_nodes,
key="analyse_minerais"
)
return minerais_selection
def selectionner_noeuds(
G: nx.DiGraph,
niveaux_temp: Dict[str, int],
niveau_depart: int,
niveau_arrivee: int
) -> Tuple[List[str]|None, List[str]|None]:
"""
Interface pour sélectionner les nœuds spécifiques de départ et d'arrivée.
Args:
G (nx.DiGraph): Le graphe NetworkX contenant les données des produits.
niveaux_temp (Dict[str, int]): Dictionnaire contenant les niveaux associés aux nœuds.
niveau_depart (int): Le niveau de départ sélectionné.
niveau_arrivee (int): Le niveau d'arrivée sélectionné.
Returns:
Optional[Tuple[List[str], List[str]]]: Un tuple contenant les listes des nœuds
- None sinon
"""
st.markdown("---")
st.markdown(f"## {str(_('pages.analyse.fine_selection'))}")
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")
noeuds_depart = noeuds_depart if noeuds_depart else None
noeuds_arrivee = noeuds_arrivee if noeuds_arrivee else None
return noeuds_depart, noeuds_arrivee
def configurer_filtres_vulnerabilite(
) -> Tuple[bool, bool, bool, str, bool, str]:
"""
Interface pour configurer les filtres de vulnérabilité.
Returns:
Tuple[bool, bool, bool, str, bool, str]: Un tuple contenant :
- La possibilité de filtrer les ICS
- La possibilité de filtrer les IV C
- La possibilité de filtrer les IH H
- Le type d'application pour les IH H (pays ou acteur)
- La possibilité de filtrer les IS G
- La logique de filtrage (ou ou and)
"""
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")
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")
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")
return filtrer_ics, filtrer_ivc, filtrer_ihh, ihh_type, filtrer_isg, logique_filtrage
def interface_analyse(
G_temp: nx.DiGraph,
) -> None:
"""
Interface utilisateur pour l'analyse des graphes.
Args:
G_temp (nx.DiGraph): Le graphe NetworkX à analyser.
"""
st.markdown(f"# {str(_('pages.analyse.title'))}")
html_expander(f"{str(_('pages.analyse.help'))}", content="\n".join(_("pages.analyse.help_content")), open_by_default=False, details_class="details_introduction")
st.markdown("---")
try:
# Préparation du graphe
G_temp, niveaux_temp = preparer_graphe(G_temp)
# Sélection des niveaux
niveau_depart, niveau_arrivee = selectionner_niveaux()
if niveau_depart is None or niveau_arrivee is None:
return
# Sélection des minerais si nécessaire
minerais_selection = selectionner_minerais(G_temp, niveau_depart, niveau_arrivee)
# Sélection fine des noeuds
noeuds_depart, noeuds_arrivee = selectionner_noeuds(G_temp, niveaux_temp, niveau_depart, niveau_arrivee)
# Configuration des filtres de vulnérabilité
filtrer_ics, filtrer_ivc, filtrer_ihh, ihh_type, filtrer_isg, logique_filtrage = configurer_filtres_vulnerabilite()
# Lancement de l'analyse
st.markdown("---")
if st.button(str(_("pages.analyse.run_analysis")), type="primary", key="analyse_lancer", icon=":material/graph_4:"):
afficher_sankey(
G_temp,
niveau_depart=niveau_depart,
niveau_arrivee=niveau_arrivee,
noeuds_depart=noeuds_depart,
noeuds_arrivee=noeuds_arrivee,
minerais=minerais_selection,
filtrer_ics=filtrer_ics,
filtrer_ivc=filtrer_ivc,
filtrer_ihh=filtrer_ihh,
ihh_type=ihh_type,
filtrer_isg=filtrer_isg,
logique_filtrage=logique_filtrage
)
except Exception as e:
st.error(f"{str(_('errors.graph_preview_error'))} {e}")