Code/utils/graph_utils.py
Fabrication du Numérique 967ca4bcf2 Trop de modifications
2025-05-08 00:26:02 +02:00

242 lines
9.2 KiB
Python

import networkx as nx
import pandas as pd
import logging
import streamlit as st
import json
def extraire_chemins_depuis(G, source):
chemins = []
stack = [(source, [source])]
while stack:
(node, path) = stack.pop()
voisins = list(G.successors(node))
if not voisins:
chemins.append(path)
else:
for voisin in voisins:
if voisin not in path:
stack.append((voisin, path + [voisin]))
return chemins
def extraire_chemins_vers(G, target, niveau_demande):
chemins = []
reverse_G = G.reverse()
niveaux = nx.get_node_attributes(G, "niveau")
stack = [(target, [target])]
while stack:
(node, path) = stack.pop()
voisins = list(reverse_G.successors(node))
if not voisins:
chemin_inverse = list(reversed(path))
contient_niveau = any(
int(niveaux.get(n, -1)) == niveau_demande for n in chemin_inverse
)
if contient_niveau:
chemins.append(chemin_inverse)
else:
for voisin in voisins:
if voisin not in path:
stack.append((voisin, path + [voisin]))
return chemins
def recuperer_donnees(graph, noeuds):
donnees = []
criticite = {}
for noeud in noeuds:
try:
operation, minerai = noeud.split('_', 1)
except ValueError:
logging.warning(f"Nom de nœud inattendu : {noeud}")
continue
if operation == "Traitement":
try:
fabrications = list(graph.predecessors(minerai))
valeurs = [
int(float(graph.get_edge_data(f, minerai)[0].get('criticite', 0)) * 100)
for f in fabrications
if graph.get_edge_data(f, minerai)
]
if valeurs:
criticite[minerai] = round(sum(valeurs) / len(valeurs))
except Exception as e:
logging.warning(f"Erreur criticité pour {noeud} : {e}")
criticite[minerai] = 50
for noeud in noeuds:
try:
operation, minerai = noeud.split('_', 1)
ihh_pays = int(graph.nodes[noeud].get('ihh_pays', 0))
ihh_acteurs = int(graph.nodes[noeud].get('ihh_acteurs', 0))
criticite_val = criticite.get(minerai, 50)
criticite_cat = 1 if criticite_val <= 33 else (2 if criticite_val <= 66 else 3)
donnees.append({
'categorie': operation,
'nom': minerai,
'ihh_pays': ihh_pays,
'ihh_acteurs': ihh_acteurs,
'criticite_minerai': criticite_val,
'criticite_cat': criticite_cat
})
except Exception as e:
logging.error(f"Erreur sur le nœud {noeud} : {e}", exc_info=True)
return pd.DataFrame(donnees)
def recuperer_donnees_2(graph, noeuds_2):
donnees = []
for minerai in noeuds_2:
try:
missing = []
if not graph.has_node(minerai):
missing.append(minerai)
if not graph.has_node(f"Extraction_{minerai}"):
missing.append(f"Extraction_{minerai}")
if not graph.has_node(f"Reserves_{minerai}"):
missing.append(f"Reserves_{minerai}")
if missing:
print(f"⚠️ Nœuds manquants pour {minerai} : {', '.join(missing)} — Ignoré.")
continue
ivc = int(graph.nodes[minerai].get('ivc', 0))
ihh_extraction_pays = int(graph.nodes[f"Extraction_{minerai}"].get('ihh_pays', 0))
ihh_reserves_pays = int(graph.nodes[f"Reserves_{minerai}"].get('ihh_pays', 0))
donnees.append({
'nom': minerai,
'ivc': ivc,
'ihh_extraction': ihh_extraction_pays,
'ihh_reserves': ihh_reserves_pays
})
except Exception as e:
print(f"Erreur avec le nœud {minerai} : {e}")
return donnees
def lancer_personnalisation(G):
st.markdown("""
# Personnalisation des produits finaux
Dans cette section, vous pouvez ajouter des produits finaux qui ne sont pas présents dans la liste,
par exemple des produits que vous concevez vous-même.
Vous pouvez aussi enregistrer ou recharger vos modifications.
---
""")
st.markdown("## Ajouter un nouveau produit final")
new_prod = st.text_input("Nom du nouveau produit (unique)", key="new_prod")
if new_prod:
ops_dispo = sorted([
n for n, d in G.nodes(data=True)
if d.get("niveau") == "10"
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("Opération d'assemblage (optionnelle)", ["-- Aucune --"] + ops_dispo, index=0)
niveau1 = sorted([n for n, d in G.nodes(data=True) if d.get("niveau") == "1"])
sel_comps = st.multiselect("Composants à lier", options=niveau1)
if st.button("Créer le produit"):
G.add_node(new_prod, niveau="0", personnalisation="oui", label=new_prod)
if sel_new_op != "-- Aucune --":
G.add_edge(new_prod, sel_new_op)
for comp in sel_comps:
G.add_edge(new_prod, comp)
st.success(f"{new_prod} ajouté.")
st.markdown("## Modifier un produit final ajouté")
produits0 = sorted([n for n, d in G.nodes(data=True) if d.get("niveau") == "0" and d.get("personnalisation") == "oui"])
sel_display = st.multiselect("Produits à modifier", options=produits0)
if sel_display:
prod = sel_display[0]
if st.button(f"Supprimer {prod}"):
G.remove_node(prod)
st.success(f"{prod} supprimé.")
st.session_state.pop("prod_sel", None)
return G
ops_dispo = sorted([
n for n, d in G.nodes(data=True)
if d.get("niveau") == "10"
and any(G.has_edge(p, n) and G.nodes[p].get("niveau") == "0" for p in G.predecessors(n))
])
curr_ops = [succ for succ in G.successors(prod) if G.nodes[succ].get("niveau") == "10"]
default_idx = ops_dispo.index(curr_ops[0]) + 1 if curr_ops and curr_ops[0] in ops_dispo else 0
sel_op = st.selectbox("Opération d'assemblage liée", ["-- Aucune --"] + ops_dispo, index=default_idx)
niveau1 = sorted([n for n, d in G.nodes(data=True) if d.get("niveau") == "1"])
linked = [succ for succ in G.successors(prod) if G.nodes[succ].get("niveau") == "1"]
nouveaux = st.multiselect(f"Composants liés à {prod}", options=niveau1, default=linked)
if st.button(f"Mettre à jour {prod}"):
for op in curr_ops:
if sel_op == "-- Aucune --" or op != sel_op:
G.remove_edge(prod, op)
if sel_op != "-- Aucune --" and (not curr_ops or sel_op not in curr_ops):
G.add_edge(prod, sel_op)
for comp in set(linked) - set(nouveaux):
G.remove_edge(prod, comp)
for comp in set(nouveaux) - set(linked):
G.add_edge(prod, comp)
st.success(f"{prod} mis à jour.")
st.markdown("## Sauvegarder ou restaurer la configuration")
if st.button("Exporter configuration"):
nodes = [n for n, d in G.nodes(data=True) if d.get("personnalisation") == "oui"]
edges = [(u, v) for u, v in G.edges() if u in nodes]
conf = {"nodes": nodes, "edges": edges}
json_str = json.dumps(conf, ensure_ascii=False)
st.download_button(
label="Télécharger (JSON)",
data=json_str,
file_name="config_personnalisation.json",
mime="application/json"
)
uploaded = st.file_uploader("Importer une configuration JSON (max 100 Ko)", type=["json"])
if uploaded:
if uploaded.size > 100 * 1024:
st.error("Fichier trop volumineux (max 100 Ko).")
else:
try:
conf = json.loads(uploaded.read().decode("utf-8"))
all_nodes = conf.get("nodes", [])
all_edges = conf.get("edges", [])
if not all_nodes:
st.warning("Aucun produit trouvé dans le fichier.")
else:
st.markdown("### Sélection des produits à restaurer")
sel_nodes = st.multiselect(
"Produits à restaurer",
options=all_nodes,
default=all_nodes,
key="restaurer_selection"
)
if st.button("Restaurer les éléments sélectionnés", type="primary"):
for node in sel_nodes:
if not G.has_node(node):
G.add_node(node, niveau="0", personnalisation="oui", label=node)
for u, v in all_edges:
if u in sel_nodes and v in sel_nodes + list(G.nodes()) and not G.has_edge(u, v):
G.add_edge(u, v)
st.success("Configuration partielle restaurée avec succès.")
except Exception as e:
st.error(f"Erreur d'import : {e}")
return G