242 lines
9.2 KiB
Python
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
|