822 lines
38 KiB
Python
822 lines
38 KiB
Python
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("**<u>Panneau de sélection</u>**", 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("**<u>Synthèse des criticités</u>**", 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)
|