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