import streamlit as st import yaml import re import matplotlib.pyplot as plt PRECONISATIONS = { 'Facile': [ "Constituer des stocks stratégiques.", "Surveiller activement les signaux géopolitiques.", "Renforcer la surveillance des régions critiques." ], 'Modérée': [ "Diversifier progressivement les fournisseurs.", "Favoriser la modularité des produits.", "Augmenter progressivement les taux de recyclage." ], 'Difficile': [ "Investir fortement en R&D pour la substitution.", "Développer des technologies alternatives robustes.", "Établir des partenariats stratégiques locaux solides." ], 'Extraction': { 'Facile': [ "Constituer des stocks « in-country » (site minier / port) pour 30 jours.", "Activer un moniteur de prix spot quotidien.", "Lancer une veille ESG locale (manifestations, météo extrême)." ], 'Modérée': [ "Négocier des contrats « take-or-pay » avec au moins 2 exploitants distincts.", "Mettre en place un audit semestriel des pratiques de sécurité/logistique des mines.", "Financer en co-investissement un entrepôt portuaire multi-produits." ], 'Difficile': [ "Participer au capital d’un producteur émergent hors zone de concentration.", "Obtenir des droits d’« off-take » de 5 ans sur 20 % de la production d’une mine alternative.", "Soutenir (CAPEX) l’ouverture d’une nouvelle voie ferroviaire ou portuaire sécurisée." ] }, 'Traitement': { 'Facile': [ "Sécuriser un stock tampon sur site (90 jours).", "Faire certifier la traçabilité chimique du concentré." ], 'Modérée': [ "Valider un second affineur dans une région politiquement stable.", "Imposer des clauses « force-majeure » limitant l’arrêt total à 48 h.", "Explorer les possibilités de recyclage et d'économie circulaire" ], 'Difficile': [ "Co-développer un site de raffinage dans une zone « friend-shore ».", "Financer un procédé de purification à rendement plus élevé (réduit la dépendance au minerai primaire).", "Constituer des réserves stratégiques pour les périodes de tension" ] }, 'Fabrication': { 'Facile': [ "Mettre un seuil minimal de sécurité (45 jours) sur le composant critique en usine SMT.", "Suivre hebdomadairement la capacité libre des fondeurs/EMS.", "Maintenir une veille technologique sur les évolutions du marché" ], 'Modérée': [ "Dual-sourcer le composant critique intégrant un minerai critique (au moins 30 % chez un second fondeur).", "Déployer le « design-for-substitution » : même PCB compatible avec le composant concerné.", "Optimiser les processus d'approvisionnement existants" ], 'Difficile': [ "Lancer un programme R&D de substitution ou d'alternative budgeté sur 3 ans.", "Contractualiser un accord exclusif avec un fondeur hors zone rouge pour 25 % des volumes." ] }, 'Assemblage': { 'Facile': [ "Allonger la rotation des stocks de produits finis (en aval) pour amortir un retard de 2 semaines.", "Mettre en place un plan de re-déploiement du personnel sur d’autres lignes en cas de rupture composant." ], 'Modérée': [ "Avoir un site d’assemblage secondaire (low-volume) dans une région verte, testé tous les 6 mois.", "Segmenter les nomenclatures : version « premium » avec composant haut de gamme, version « fallback » avec composant moins critique." ], 'Difficile': [ "Investir dans une plateforme d’assemblage flexible (robots modulaires) capable de basculer vers un composant de substitution en < 72 h.", "Signer un accord gouvernemental pour un soutien logistique prioritaire (corridor aérien dédié) en cas de crise géopolitique.", "Mettre en place des contrats à long terme avec des clauses de garantie d'approvisionnement" ] } } INDICATEURS = { 'Facile': [ "Suivi régulier de la stabilité géopolitique (ISG).", "Durée réelle d'utilisation du matériel.", "Niveau des stocks stratégiques disponibles." ], 'Modérée': [ "Taux de diversification des fournisseurs par région.", "Évolution trimestrielle de la concurrence intersectorielle (IVC).", "Taux annuel de recyclage des composants critiques." ], 'Difficile': [ "Budget annuel investi dans la recherche technologique.", "Nombre de brevets déposés pour des substituts.", "Progrès réel en matière de substitution technologique (ICS)." ], 'Extraction': { 'Facile': [ "Jours de stock portuaire (objectif ≥ 30).", "Indice ISG moyen pondéré des pays extracteurs (alerte ≥ 60).", "Volatilité hebdo du prix spot (écart-type %)." ], 'Modérée': [ "Part du 2ᵉ fournisseur dans le volume total (objectif ≥ 20 %).", "Délai moyen d’obtention des permis d’export." ], 'Difficile': [ "Capacité annuelle d’une mine alternative financée (% du besoin interne).", "Progrès physique de l’infrastructure logistique (Km de voie, % achevé)." ] }, 'Traitement': { 'Facile': [ "Couverture stock tampon (jours).", "Certificats de traçabilité obtenus (% lots)." ], 'Modérée': [ "Nombre d’affineurs validés (objectif ≥ 2).", "Taux de rendement global du procédé (%)." ], 'Difficile': [ "Part de production refinée hors zone rouge (%).", "Capex cumulé investi dans de nouveaux procédés (M€)." ] }, 'Fabrication': { 'Facile': [ "Stock de composants critiques (jours).", "Capacité libre des EMS (%) rapportée chaque vendredi." ], 'Modérée': [ "Part du second fondeur dans la production du composant audio (%).", "Nombre de PCB « design-for-substitution » validés." ], 'Difficile': [ "Dépenses R&D substituts (€) vs budget.", "TRI attendu sur les investisseurs fondeurs alternatifs." ] }, 'Assemblage': { 'Facile': [ "Jours de produits finis en entrepôt.", "Temps de retouche ligne en cas de rupture (heures)." ], 'Modérée': [ "Volume annuel produit sur le site de secours (%).", "Temps de requalification d’une ligne vers la version « fallback »." ], 'Difficile': [ "Taux d’automatisation reconfigurable (% machines modulaires).", "Nb d’heures du corridor aérien prioritaire utilisé vs capacité." ] } } # ------------------------- PARSEUR ------------------------- def parse_chains_md(filepath: str) -> tuple[dict, dict, dict, list, dict, dict]: re_start_section = re.compile(r"^##\s*Chaînes\s+avec\s+risque\s+critique", re.IGNORECASE) re_other_h2 = re.compile(r"^##\s+(?!(Chaînes\s+avec\s+risque\s+critique))") re_chain_heading = re.compile(r"^###\s*(.+)\s*→\s*(.+)\s*→\s*(.+)$") re_phase = re.compile(r"^\*\s*(Assemblage|Fabrication|Minerai|Extraction|Traitement)", re.IGNORECASE) re_IHH = re.compile(r"IHH\s*[:]\s*([0-9]+(?:\.[0-9]+)?)") re_ISG = re.compile(r"ISG\s*combiné\s*[:]\s*([0-9]+(?:\.[0-9]+)?)|ISG\s*[:]\s*([0-9]+(?:\.[0-9]+)?)", re.IGNORECASE) re_ICS = re.compile(r"ICS\s*moyen\s*[:]\s*([0-9]+(?:\.[0-9]+)?)", re.IGNORECASE) re_IVC = re.compile(r"IVC\s*[:]\s*([0-9]+(?:\.[0-9]+)?)", re.IGNORECASE) produits, composants, mineraux, chains = {}, {}, {}, [] descriptions = {} details_sections = {} current_chain = None current_phase = None current_section = None in_section = False with open(filepath, encoding="utf-8") as f: for raw_line in f: line = raw_line.strip() if not in_section: if re_start_section.match(line): in_section = True continue if re_other_h2.match(line): break m_chain = re_chain_heading.match(line) if m_chain: prod, comp, miner = map(str.strip, m_chain.groups()) produits.setdefault(prod, {"IHH_Assemblage": None, "ISG_Assemblage": None}) composants.setdefault(comp, {"IHH_Fabrication": None, "ISG_Fabrication": None}) mineraux.setdefault(miner, { "ICS": None, "IVC": None, "IHH_Extraction": None, "ISG_Extraction": None, "IHH_Traitement": None, "ISG_Traitement": None }) chains.append({"produit": prod, "composant": comp, "minerai": miner}) current_chain = {"prod": prod, "comp": comp, "miner": miner} current_phase = None current_section = f"{prod} → {comp} → {miner}" descriptions[current_section] = "" continue if current_chain is None: continue m_phase = re_phase.match(line) if m_phase: current_phase = m_phase.group(1).capitalize() continue if current_phase: p = current_chain if current_phase == "Assemblage": if (m := re_IHH.search(line)): produits[p["prod"]]["IHH_Assemblage"] = float(m.group(1)) continue if (m := re_ISG.search(line)): raw = m.group(1) or m.group(2) produits[p["prod"]]["ISG_Assemblage"] = float(raw) continue if current_phase == "Fabrication": if (m := re_IHH.search(line)): composants[p["comp"]]["IHH_Fabrication"] = float(m.group(1)) continue if (m := re_ISG.search(line)): raw = m.group(1) or m.group(2) composants[p["comp"]]["ISG_Fabrication"] = float(raw) continue if current_phase == "Minerai": if (m := re_ICS.search(line)): mineraux[p["miner"]]["ICS"] = float(m.group(1)) continue if (m := re_IVC.search(line)): mineraux[p["miner"]]["IVC"] = float(m.group(1)) continue if current_phase == "Extraction": if (m := re_IHH.search(line)): mineraux[p["miner"]]["IHH_Extraction"] = float(m.group(1)) continue if (m := re_ISG.search(line)): raw = m.group(1) or m.group(2) mineraux[p["miner"]]["ISG_Extraction"] = float(raw) continue if current_phase == "Traitement": if (m := re_IHH.search(line)): mineraux[p["miner"]]["IHH_Traitement"] = float(m.group(1)) continue if (m := re_ISG.search(line)): raw = m.group(1) or m.group(2) mineraux[p["miner"]]["ISG_Traitement"] = float(raw) continue else: if current_section: descriptions[current_section] += raw_line # Parse detailed sections from the complete file with open(filepath, encoding="utf-8") as f: content = f.read() # Extract sections using regex patterns lines = content.split('\n') # Find section boundaries operations_start = None minerais_start = None for i, line in enumerate(lines): if line.strip() == "## Détails des opérations": operations_start = i elif line.strip() == "## Détails des minerais": minerais_start = i if operations_start is not None: # Parse operations section (assemblage and fabrication) operations_end = minerais_start if minerais_start else len(lines) operations_lines = lines[operations_start:operations_end] current_section_name = None current_content = [] for line in operations_lines: if line.startswith("### ") and " et " in line: # Save previous section if current_section_name and current_content: details_sections[current_section_name] = '\n'.join(current_content) # Start new section section_title = line.replace("### ", "").strip() if " et Assemblage" in section_title: product_name = section_title.replace(" et Assemblage", "").strip() current_section_name = f"{product_name}_assemblage" elif " et Fabrication" in section_title: component_name = section_title.replace(" et Fabrication", "").strip() current_section_name = f"{component_name}_fabrication" current_content = [] elif current_section_name: current_content.append(line) # Save last section if current_section_name and current_content: details_sections[current_section_name] = '\n'.join(current_content) if minerais_start is not None: # Parse minerais section minerais_lines = lines[minerais_start:] current_minerai = None current_section_type = "general" current_content = [] for line in minerais_lines: if line.startswith("### ") and "→" not in line and " et " not in line: # Save previous section if current_minerai and current_content: details_sections[f"{current_minerai}_{current_section_type}"] = '\n'.join(current_content) # Start new minerai current_minerai = line.replace("### ", "").strip() current_section_type = "general" current_content = [] elif line.startswith("#### Extraction"): # Save previous section if current_minerai and current_content: details_sections[f"{current_minerai}_{current_section_type}"] = '\n'.join(current_content) current_section_type = "extraction" current_content = [] elif line.startswith("#### Traitement"): # Save previous section if current_minerai and current_content: details_sections[f"{current_minerai}_{current_section_type}"] = '\n'.join(current_content) current_section_type = "traitement" current_content = [] elif line.startswith("## ") and current_minerai: # End of minerais section if current_content: details_sections[f"{current_minerai}_{current_section_type}"] = '\n'.join(current_content) break elif current_minerai: current_content.append(line) # Save last section if current_minerai and current_content: details_sections[f"{current_minerai}_{current_section_type}"] = '\n'.join(current_content) return produits, composants, mineraux, chains, descriptions, details_sections # ---------------------- AFFICHAGE ---------------------- def afficher_bloc_ihh_isg(titre, ihh, isg, details_content=""): st.markdown(f"### {titre}") if not details_content: st.markdown("Données non disponibles") return lines = details_content.split('\n') # 1. Afficher vulnérabilité combinée en premier if "#### Vulnérabilité combinée IHH-ISG" in details_content: conteneur, = st.columns([1], gap="small", border=True) with conteneur: st.markdown("#### Vulnérabilité combinée IHH-ISG") afficher_section_texte(lines, "#### Vulnérabilité combinée IHH-ISG", "###") # 2. Afficher ISG des pays impliqués if "##### ISG des pays impliqués" in details_content: print(details_content) st.markdown("#### ISG des pays impliqués") afficher_section_avec_tableau(lines, "##### ISG des pays impliqués") # Afficher le résumé ISG combiné for line in lines: if "**ISG combiné:" in line: st.markdown(line) break # 3. Afficher la section IHH complète if "#### Indice de Herfindahl-Hirschmann" in details_content: st.markdown("#### Indice de Herfindahl-Hirschmann") # Tableau de résumé IHH afficher_section_avec_tableau(lines, "#### Indice de Herfindahl-Hirschmann") # IHH par entreprise if "##### IHH par entreprise (acteurs)" in details_content: st.markdown("##### IHH par entreprise (acteurs)") afficher_section_texte(lines, "##### IHH par entreprise (acteurs)", "##### IHH par pays") # IHH par pays if "##### IHH par pays" in details_content: st.markdown("##### IHH par pays") afficher_section_texte(lines, "##### IHH par pays", "##### En résumé") # En résumé if "##### En résumé" in details_content: st.markdown("##### En résumé") afficher_section_texte(lines, "##### En résumé", "####") def afficher_section_avec_tableau(lines, section_start, section_end=None): """Affiche une section contenant un tableau""" in_section = False table_lines = [] for line in lines: if section_start in line: in_section = True continue elif in_section and section_end and section_end in line: break elif in_section and line.startswith('#') and section_start not in line: break elif in_section: if line.strip().startswith('|'): table_lines.append(line) elif table_lines and not line.strip().startswith('|'): # Fin du tableau break if table_lines: st.markdown('\n'.join(table_lines)) def afficher_section_texte(lines, section_start, section_end_marker=None): """Affiche le texte d'une section sans les tableaux""" in_section = False for line in lines: if section_start in line: in_section = True continue elif in_section and section_end_marker and line.startswith(section_end_marker): break elif in_section and line.startswith('#') and section_start not in line: break elif in_section and line.strip() and not line.strip().startswith('|'): st.markdown(line) def afficher_description(titre, description): st.markdown(f"## {titre}") conteneur, = st.columns([1], gap="small", border=True) with conteneur: if description: lines = description.split('\n') description_lines = [] # Extraire le premier paragraphe descriptif for line in lines: line = line.strip() if not line: if description_lines: # Si on a déjà du contenu, une ligne vide termine le paragraphe break continue # Arrêter aux titres de sections ou tableaux if (line.startswith('####') or line.startswith('|') or line.startswith('**Unité')): break description_lines.append(line) if description_lines: # Rejoindre les lignes en un seul paragraphe full_description = ' '.join(description_lines) st.markdown(full_description) else: st.markdown("Description non disponible") else: st.markdown("Description non disponible") def afficher_caracteristiques_minerai(minerai, mineraux_data, details_content=""): st.markdown("### Caractéristiques générales") if not details_content: st.markdown("Données non disponibles") return lines = details_content.split('\n') # 3. Afficher la vulnérabilité combinée ICS-IVC en dernier if "#### Vulnérabilité combinée ICS-IVC" in details_content: conteneur, = st.columns([1], gap="small", border=True) with conteneur: st.markdown("#### Vulnérabilité combinée ICS-IVC") afficher_section_texte(lines, "#### Vulnérabilité combinée ICS-IVC", "####") # 1. Afficher la section ICS complète if "#### ICS" in details_content: st.markdown("#### ICS") # Afficher le premier tableau ICS (avec toutes les colonnes) afficher_section_avec_tableau(lines, "#### ICS", "##### Valeurs d'ICS par composant") # Afficher la sous-section "Valeurs d'ICS par composant" if "##### Valeurs d'ICS par composant" in details_content: st.markdown("##### Valeurs d'ICS par composant") afficher_section_avec_tableau(lines, "##### Valeurs d'ICS par composant", "**ICS moyen") # Afficher le résumé ICS moyen for line in lines: if "**ICS moyen" in line: st.markdown(line) break # 2. Afficher la section IVC complète if "#### IVC" in details_content: st.markdown("#### IVC") # Afficher la valeur IVC principale for line in lines: if "**IVC:" in line: st.markdown(line) break # Afficher tous les détails de la section IVC afficher_section_texte(lines, "#### IVC", "#### Vulnérabilité combinée ICS-IVC") def get_seuil(seuils_dict, key): try: if key in seuils_dict: data = seuils_dict[key] for niveau in ["rouge", "orange", "vert"]: if niveau in data: seuil = data[niveau] if "min" in seuil and seuil["min"] is not None: return seuil["min"] if "max" in seuil and seuil["max"] is not None: return seuil["max"] except: pass return None def set_vulnerability(v1, v2, t1, t2, seuils): v1_poids = 1 v1_couleur = "Vert" if v1 > seuils[t1]["rouge"]["min"]: v1_poids = 3 v1_couleur = "Rouge" elif v1 > seuils[t1]["vert"]["max"]: v1_poids = 2 v1_couleur = "Orange" v2_poids = 1 v2_couleur = "Vert" if v2 > seuils[t2]["rouge"]["min"]: v2_poids = 3 v2_couleur = "Rouge" elif v2 > seuils[t2]["vert"]["max"]: v2_poids = 2 v2_couleur = "Orange" poids = v1_poids * v2_poids couleur = "Rouge" if poids <= 2: couleur = "Vert" elif poids <= 4: couleur = "Orange" return poids, couleur, v1_couleur, v2_couleur def colorer_couleurs(la_couleur): t = la_couleur.lower() if t == "rouge" or t == "difficile": return f":red-badge[{la_couleur}]" if t == "orange" or t == "modérée": return f":orange-badge[{la_couleur}]" if t == "vert" or t == "facile": return f":green-badge[{la_couleur}]" return la_couleur def initialiser_interface(filepath: str, config_path: str = "assets/config.yaml"): produits, composants, mineraux, chains, descriptions, details_sections = parse_chains_md(filepath) if not chains: st.warning("Aucune chaîne critique trouvée dans le fichier.") return seuils = {} try: with open(config_path, "r", encoding="utf-8") as f: config = yaml.safe_load(f) seuils = config.get("seuils", seuils) except FileNotFoundError: st.warning(f"Fichier de configuration {config_path} non trouvé.") col_left, col_right = st.columns([1, 1], gap="small", border=True) with col_left: st.markdown("**Panneau de sélection**", unsafe_allow_html=True) produits_disponibles = sorted({c["produit"] for c in chains}) sel_prod = st.selectbox("Produit", produits_disponibles) composants_dispo = sorted({c["composant"] for c in chains if c["produit"] == sel_prod}) sel_comp = st.selectbox("Composant", composants_dispo) mineraux_dispo = sorted({c["minerai"] for c in chains if c["produit"] == sel_prod and c["composant"] == sel_comp}) sel_miner = st.selectbox("Minerai", mineraux_dispo) with col_right: st.markdown("**Synthèse des criticités**", unsafe_allow_html=True) poids_A, couleur_A, couleur_A_ihh, couleur_A_isg = set_vulnerability(produits[sel_prod]["IHH_Assemblage"], produits[sel_prod]["ISG_Assemblage"], "IHH", "ISG", seuils) poids_F, couleur_F, couleur_F_ihh, couleur_F_isg = set_vulnerability(composants[sel_comp]["IHH_Fabrication"], composants[sel_comp]["ISG_Fabrication"], "IHH", "ISG", seuils) poids_T, couleur_T, couleur_T_ihh, couleur_T_isg = set_vulnerability(mineraux[sel_miner]["IHH_Traitement"], mineraux[sel_miner]["ISG_Traitement"], "IHH", "ISG", seuils) poids_E, couleur_E, couleur_E_ihh, couleur_E_isg = set_vulnerability(mineraux[sel_miner]["IHH_Extraction"], mineraux[sel_miner]["ISG_Extraction"], "IHH", "ISG", seuils) poids_M, couleur_M, couleur_M_ics, couleur_M_ivc = set_vulnerability(mineraux[sel_miner]["ICS"], mineraux[sel_miner]["IVC"], "ICS", "IVC", seuils) st.markdown(f"* **{sel_prod} - Assemblage** : {colorer_couleurs(couleur_A)} ({poids_A})") st.markdown(f"* **{sel_comp} - Fabrication** : {colorer_couleurs(couleur_F)} ({poids_F})") st.markdown(f"* **{sel_miner} - Traitement** : {colorer_couleurs(couleur_T)} ({poids_T})") st.markdown(f"* **{sel_miner} - Extraction** : {colorer_couleurs(couleur_E)} ({poids_E})") st.markdown(f"* **{sel_miner} - Minerai** : {colorer_couleurs(couleur_M)} ({poids_M})") poids_operation = { 'Extraction': 1, 'Traitement': 1.5, 'Assemblage': 1.5, 'Fabrication': 2, 'Substitution': 2 } poids_total = (\ poids_A * poids_operation["Assemblage"] + \ poids_F * poids_operation["Fabrication"] + \ poids_T * poids_operation["Traitement"] + \ poids_E * poids_operation["Extraction"] + \ poids_M * poids_operation["Substitution"] \ ) / sum(poids_operation.values()) if poids_total < 3: criticite_chaine = "Modérée" niveau_criticite = {"Facile"} elif poids_total < 6: criticite_chaine = "Élevée" niveau_criticite = {"Facile", "Modérée"} else: criticite_chaine = "Critique" niveau_criticite = {"Facile", "Modérée", "Difficile"} st.error(f"**Criticité globale : {criticite_chaine} ({poids_total})**") with st.expander("Vue d’ensemble des criticités", expanded=True): st.markdown("## Vue d’ensemble des criticités", unsafe_allow_html=True) col_left, col_right = st.columns([1, 1], gap="small", border=True) with col_left: fig1, ax1 = plt.subplots(figsize=(1, 1)) ax1.scatter([produits[sel_prod]["ISG_Assemblage"]], [produits[sel_prod]["IHH_Assemblage"]], label="Assemblage".ljust(20), s=5) ax1.scatter([composants[sel_comp]["ISG_Fabrication"]], [composants[sel_comp]["IHH_Fabrication"]], label="Fabrication".ljust(20), s=5) ax1.scatter([mineraux[sel_miner]["ISG_Extraction"]], [mineraux[sel_miner]["IHH_Extraction"]], label="Extraction".ljust(20), s=5) ax1.scatter([mineraux[sel_miner]["ISG_Traitement"]], [mineraux[sel_miner]["IHH_Traitement"]], label="Traitement".ljust(20), s=5) # Seuils ISG (vertical) ax1.axvline(seuils["ISG"]["vert"]["max"], linestyle='--', color='green', alpha=0.7, linewidth=0.5) # Seuil vert-orange ax1.axvline(seuils["ISG"]["rouge"]["min"], linestyle='--', color='red', alpha=0.7, linewidth=0.5) # Seuil orange-rouge # Seuils IHH (horizontal) ax1.axhline(seuils["IHH"]["vert"]["max"], linestyle='--', color='green', alpha=0.7, linewidth=0.5) # Seuil vert-orange ax1.axhline(seuils["IHH"]["rouge"]["min"], linestyle='--', color='red', alpha=0.7, linewidth=0.5) # Seuil orange-rouge ax1.set_xlim(0, 100) ax1.set_ylim(0, 100) ax1.set_xlabel("ISG", fontsize=4) ax1.set_ylabel("IHH", fontsize=4) ax1.tick_params(axis='both', which='major', labelsize=4) ax1.legend(bbox_to_anchor=(1.05, 1), loc='upper left', fontsize=4) plt.tight_layout() st.pyplot(fig1) with col_right: fig2, ax2 = plt.subplots(figsize=(1, 1)) ax2.scatter([mineraux[sel_miner]["IVC"]], [mineraux[sel_miner]["ICS"]], color='green', s=5, label=sel_miner.ljust(20)) # Seuils IVC (vertical) ax2.axvline(seuils["IVC"]["vert"]["max"], linestyle='--', color='green', alpha=0.7, linewidth=0.5) # Seuil vert-orange ax2.axvline(seuils["IVC"]["rouge"]["min"], linestyle='--', color='red', alpha=0.7, linewidth=0.5) # Seuil orange-rouge # Seuils ICS (horizontal) ax2.axhline(seuils["ICS"]["vert"]["max"], linestyle='--', color='green', alpha=0.7, linewidth=0.5) # Seuil vert-orange ax2.axhline(seuils["ICS"]["rouge"]["min"], linestyle='--', color='red', alpha=0.7, linewidth=0.5) # Seuil orange-rouge ax2.set_xlim(0, max(100, mineraux[sel_miner]["IVC"])) ax2.set_ylim(0, 1) ax2.set_xlabel("IVC", fontsize=4) ax2.set_ylabel("ICS", fontsize=4) ax2.tick_params(axis='both', which='major', labelsize=4) ax2.legend(bbox_to_anchor=(1.05, 1), loc='upper left', fontsize=4) plt.tight_layout() st.pyplot(fig2) st.markdown(f""" Les lignes pointillées en {colorer_couleurs("vert")} ou {colorer_couleurs("rouge")} représentent les seuils des indices concernés.\n Les indices ISG (stabilité géopolitique) et IVC (concurrence intersectorielle) influent sur la probabilité de survenance d'un risque.\n Les indices IHH (concentration géographique) et ICS (capacité de substitution) influent sur le niveau d'impact d'un risque.\n Une opération se trouvant au-dessus des deux seuils a donc une forte probabilité d'être impactée avec un niveau élevé sur l'incapacité à continuer la production. """) with st.expander("Explications et détails", expanded = True): from collections import Counter couleurs = [couleur_A, couleur_F, couleur_T, couleur_E, couleur_M] compte = Counter(couleurs) nb_rouge = compte["Rouge"] nb_orange = compte["Orange"] nb_vert = compte["Vert"] st.markdown(f""" Pour cette chaîne :blue-background[**{sel_prod} <-> {sel_comp} <-> {sel_miner}**], avec {nb_rouge} criticité(s) de niveau {colorer_couleurs("Rouge")}, {nb_orange} {colorer_couleurs("Orange")} et {nb_vert} {colorer_couleurs("Vert")}, les indices individuels par opération sont : * **{sel_prod} - Assemblage** : {colorer_couleurs(couleur_A)} ({poids_A}) * IHH = {produits[sel_prod]["IHH_Assemblage"]} ({colorer_couleurs(couleur_A_ihh)}) <-> ISG = {produits[sel_prod]["ISG_Assemblage"]} ({colorer_couleurs(couleur_A_isg)}) * pondération de l'Assemblage dans le calcul de la criticité globale : 1,5 * se référer à **{sel_prod} et Assemblage** plus bas pour le détail complet * **{sel_comp} - Fabrication** : {colorer_couleurs(couleur_F)} ({poids_F}) * IHH = {composants[sel_comp]["IHH_Fabrication"]} ({colorer_couleurs(couleur_F_ihh)}) <-> ISG = {composants[sel_comp]["ISG_Fabrication"]} ({colorer_couleurs(couleur_F_isg)}) * pondération de la Fabrication dans le calcul de la criticité globale : 2 * se référer à **{sel_comp} et Fabrication** plus bas pour le détail complet * **{sel_miner} - Traitement** : {colorer_couleurs(couleur_A)} ({poids_A}) * IHH = {mineraux[sel_miner]["IHH_Traitement"]} ({colorer_couleurs(couleur_T_ihh)}) <-> ISG = {mineraux[sel_miner]["ISG_Traitement"]} ({colorer_couleurs(couleur_T_isg)}) * pondération du Traitement dans le calcul de la criticité globale : 1,5 * se référer à **{sel_miner} — Vue globale** plus bas pour le détail complet de l'ensemble du minerai * **{sel_miner} - Extraction** : {colorer_couleurs(couleur_E)} ({poids_E}) * IHH = {mineraux[sel_miner]["IHH_Extraction"]} ({colorer_couleurs(couleur_E_ihh)}) <-> ISG = {mineraux[sel_miner]["ISG_Extraction"]} ({colorer_couleurs(couleur_E_isg)}) * pondération de l'Extraction dans le calcul de la criticité globale : 1 * **{sel_miner} - Minerai** : {colorer_couleurs(couleur_M)} ({poids_M}) * ICS = {mineraux[sel_miner]["ICS"]} ({colorer_couleurs(couleur_M_ics)}) <-> IVC = {mineraux[sel_miner]["IVC"]} ({colorer_couleurs(couleur_M_ivc)}) * pondération de la Substitution dans le calcul de la criticité globale : 2 """) st.markdown("## Préconisations et indicateurs") with st.expander("Préconisations et indicateurs génériques"): col_left, col_right = st.columns([1, 1], gap="small", border=True) with col_left: st.markdown("### Préconisations :\n\n") st.markdown("Mise en œuvre : \n") for niveau, contenu in PRECONISATIONS.items(): if niveau in niveau_criticite: contenu_md = f"* {colorer_couleurs(niveau)}\n" for p in PRECONISATIONS[niveau]: contenu_md += f" - {p}\n" st.markdown(contenu_md) with col_right: st.markdown("### Indicateurs :\n\n") st.markdown("Mise en œuvre : \n") for niveau, contenu in INDICATEURS.items(): if niveau in niveau_criticite: contenu_md = f"* {colorer_couleurs(niveau)}\n" for p in INDICATEURS[niveau]: contenu_md += f" - {p}\n" st.markdown(contenu_md) def affectation_poids(poids_operation): if poids_operation < 3: niveau_criticite = {"Facile"} elif poids_operation < 6: niveau_criticite = {"Facile", "Modérée"} else: niveau_criticite = {"Facile", "Modérée", "Difficile"} return niveau_criticite niveau_criticite_operation = {} niveau_criticite_operation["Assemblage"] = affectation_poids(poids_A) niveau_criticite_operation["Fabrication"] = affectation_poids(poids_F) niveau_criticite_operation["Traitement"] = affectation_poids(poids_T) niveau_criticite_operation["Extraction"] = affectation_poids(poids_E) for operation in ["Assemblage", "Fabrication", "Traitement", "Extraction"]: if operation == "Assemblage": item = sel_prod elif operation == "Fabrication": item = sel_comp else: item = sel_miner with st.expander(f"Préconisations et indicateurs spécifiques - {operation}"): st.markdown(f"### {operation} -> :blue-background[{item}]") col_left, col_right = st.columns([1, 1], gap="small", border=True) with col_left: st.markdown("#### Préconisations :\n\n") st.markdown("Mise en œuvre : \n") for niveau, contenu in PRECONISATIONS[operation].items(): if niveau in niveau_criticite_operation[operation]: contenu_md = f"* {colorer_couleurs(niveau)}\n" for p in PRECONISATIONS[operation][niveau]: contenu_md += f" - {p}\n" st.markdown(contenu_md) with col_right: st.markdown("#### Indicateurs :\n\n") st.markdown("Mise en œuvre : \n") for niveau, contenu in INDICATEURS[operation].items(): if niveau in niveau_criticite_operation[operation]: contenu_md = f"* {colorer_couleurs(niveau)}\n" for p in INDICATEURS[operation][niveau]: contenu_md += f" - {p}\n" st.markdown(contenu_md) st.markdown("## Détails des opérations") with st.expander(f"{sel_prod} et Assemblage"): assemblage_details = details_sections.get(f"{sel_prod}_assemblage", "") afficher_description(f"{sel_prod} et Assemblage", assemblage_details) afficher_bloc_ihh_isg("Assemblage", produits[sel_prod]["IHH_Assemblage"], produits[sel_prod]["ISG_Assemblage"], assemblage_details) with st.expander(f"{sel_comp} et Fabrication"): fabrication_details = details_sections.get(f"{sel_comp}_fabrication", "") afficher_description(f"{sel_comp} et Fabrication", fabrication_details) afficher_bloc_ihh_isg("Fabrication", composants[sel_comp]["IHH_Fabrication"], composants[sel_comp]["ISG_Fabrication"], fabrication_details) with st.expander(f"{sel_miner} — Vue globale"): minerai_general = details_sections.get(f"{sel_miner}_general", "") afficher_description(f"{sel_miner} — Vue globale", minerai_general) extraction_details = details_sections.get(f"{sel_miner}_extraction", "") afficher_bloc_ihh_isg("Extraction", mineraux[sel_miner]["IHH_Extraction"], mineraux[sel_miner]["ISG_Extraction"], extraction_details) traitement_details = details_sections.get(f"{sel_miner}_traitement", "").removesuffix("\n---\n") afficher_bloc_ihh_isg("Traitement", mineraux[sel_miner]["IHH_Traitement"], mineraux[sel_miner]["ISG_Traitement"], traitement_details) afficher_caracteristiques_minerai(sel_miner, mineraux[sel_miner], minerai_general)