Modifications mineures
This commit is contained in:
parent
91bb36fb8b
commit
120f5a7af8
3
.gitignore
vendored
3
.gitignore
vendored
@ -19,7 +19,8 @@ __pycache__/
|
|||||||
venv/
|
venv/
|
||||||
.venv/
|
.venv/
|
||||||
Local/
|
Local/
|
||||||
HTML
|
HTML/
|
||||||
|
static/
|
||||||
|
|
||||||
# Ignorer données Fiches (adapté à ton projet)
|
# Ignorer données Fiches (adapté à ton projet)
|
||||||
Instructions.md
|
Instructions.md
|
||||||
|
|||||||
@ -152,6 +152,7 @@ fabnum-dev/
|
|||||||
│ │ └── README.md # Documentation du module
|
│ │ └── README.md # Documentation du module
|
||||||
│ └── visualisations/ # Visualisations graphiques
|
│ └── visualisations/ # Visualisations graphiques
|
||||||
│ ├── interface.py # Interface des visualisations
|
│ ├── interface.py # Interface des visualisations
|
||||||
|
│ ├── graphes.py # Gestion des graphes à visualiser
|
||||||
│ └── README.md # Documentation du module
|
│ └── README.md # Documentation du module
|
||||||
├── components/ # Composants d'interface réutilisables
|
├── components/ # Composants d'interface réutilisables
|
||||||
│ ├── sidebar.py # Barre latérale de navigation
|
│ ├── sidebar.py # Barre latérale de navigation
|
||||||
|
|||||||
@ -37,7 +37,7 @@ def selectionner_niveaux():
|
|||||||
niveau_depart = st.selectbox("Niveau de départ", niveau_choix, key="analyse_niveau_depart")
|
niveau_depart = st.selectbox("Niveau de départ", niveau_choix, key="analyse_niveau_depart")
|
||||||
if niveau_depart == valeur_defaut:
|
if niveau_depart == valeur_defaut:
|
||||||
return None, None
|
return None, None
|
||||||
|
|
||||||
niveau_depart_int = inverse_niveau_labels[niveau_depart]
|
niveau_depart_int = inverse_niveau_labels[niveau_depart]
|
||||||
niveaux_arrivee_possibles = [v for k, v in niveau_labels.items() if k > niveau_depart_int]
|
niveaux_arrivee_possibles = [v for k, v in niveau_labels.items() if k > niveau_depart_int]
|
||||||
niveaux_arrivee_choix = [valeur_defaut] + niveaux_arrivee_possibles
|
niveaux_arrivee_choix = [valeur_defaut] + niveaux_arrivee_possibles
|
||||||
@ -45,7 +45,7 @@ def selectionner_niveaux():
|
|||||||
analyse_niveau_arrivee = st.selectbox("Niveau d'arrivée", niveaux_arrivee_choix, key="analyse_niveau_arrivee")
|
analyse_niveau_arrivee = st.selectbox("Niveau d'arrivée", niveaux_arrivee_choix, key="analyse_niveau_arrivee")
|
||||||
if analyse_niveau_arrivee == valeur_defaut:
|
if analyse_niveau_arrivee == valeur_defaut:
|
||||||
return niveau_depart_int, None
|
return niveau_depart_int, None
|
||||||
|
|
||||||
niveau_arrivee_int = inverse_niveau_labels[analyse_niveau_arrivee]
|
niveau_arrivee_int = inverse_niveau_labels[analyse_niveau_arrivee]
|
||||||
return niveau_depart_int, niveau_arrivee_int
|
return niveau_depart_int, niveau_arrivee_int
|
||||||
|
|
||||||
@ -66,7 +66,7 @@ def selectionner_minerais(G, niveau_depart, niveau_arrivee):
|
|||||||
minerais_nodes,
|
minerais_nodes,
|
||||||
key="analyse_minerais"
|
key="analyse_minerais"
|
||||||
)
|
)
|
||||||
|
|
||||||
return minerais_selection
|
return minerais_selection
|
||||||
|
|
||||||
|
|
||||||
@ -78,16 +78,16 @@ def selectionner_noeuds(G, niveaux_temp, niveau_depart, niveau_arrivee):
|
|||||||
depart_nodes = [n for n in G.nodes() if niveaux_temp.get(n) == niveau_depart]
|
depart_nodes = [n for n in G.nodes() if niveaux_temp.get(n) == niveau_depart]
|
||||||
arrivee_nodes = [n for n in G.nodes() if niveaux_temp.get(n) == niveau_arrivee]
|
arrivee_nodes = [n for n in G.nodes() if niveaux_temp.get(n) == niveau_arrivee]
|
||||||
|
|
||||||
noeuds_depart = st.multiselect("Filtrer par noeuds de départ (optionnel)",
|
noeuds_depart = st.multiselect("Filtrer par noeuds de départ (optionnel)",
|
||||||
sorted(depart_nodes),
|
sorted(depart_nodes),
|
||||||
key="analyse_noeuds_depart")
|
key="analyse_noeuds_depart")
|
||||||
noeuds_arrivee = st.multiselect("Filtrer par noeuds d'arrivée (optionnel)",
|
noeuds_arrivee = st.multiselect("Filtrer par noeuds d'arrivée (optionnel)",
|
||||||
sorted(arrivee_nodes),
|
sorted(arrivee_nodes),
|
||||||
key="analyse_noeuds_arrivee")
|
key="analyse_noeuds_arrivee")
|
||||||
|
|
||||||
noeuds_depart = noeuds_depart if noeuds_depart else None
|
noeuds_depart = noeuds_depart if noeuds_depart else None
|
||||||
noeuds_arrivee = noeuds_arrivee if noeuds_arrivee else None
|
noeuds_arrivee = noeuds_arrivee if noeuds_arrivee else None
|
||||||
|
|
||||||
return noeuds_depart, noeuds_arrivee
|
return noeuds_depart, noeuds_arrivee
|
||||||
|
|
||||||
|
|
||||||
@ -96,51 +96,62 @@ def configurer_filtres_vulnerabilite():
|
|||||||
st.markdown("---")
|
st.markdown("---")
|
||||||
st.markdown("## Sélection des filtres pour identifier les vulnérabilités")
|
st.markdown("## Sélection des filtres pour identifier les vulnérabilités")
|
||||||
|
|
||||||
filtrer_ics = st.checkbox("Filtrer les chemins contenant au moins minerai critique pour un composant (ICS > 66 %)",
|
filtrer_ics = st.checkbox("Filtrer les chemins contenant au moins minerai critique pour un composant (ICS > 66 %)",
|
||||||
key="analyse_filtrer_ics")
|
key="analyse_filtrer_ics")
|
||||||
filtrer_ivc = st.checkbox("Filtrer les chemins contenant au moins un minerai critique par rapport à la concurrence sectorielle (IVC > 30)",
|
filtrer_ivc = st.checkbox("Filtrer les chemins contenant au moins un minerai critique par rapport à la concurrence sectorielle (IVC > 30)",
|
||||||
key="analyse_filtrer_ivc")
|
key="analyse_filtrer_ivc")
|
||||||
filtrer_ihh = st.checkbox("Filtrer les chemins contenant au moins une opération critique par rapport à la concentration géographique ou industrielle (IHH pays ou acteurs > 25)",
|
filtrer_ihh = st.checkbox("Filtrer les chemins contenant au moins une opération critique par rapport à la concentration géographique ou industrielle (IHH pays ou acteurs > 25)",
|
||||||
key="analyse_filtrer_ihh")
|
key="analyse_filtrer_ihh")
|
||||||
|
|
||||||
ihh_type = "Pays"
|
ihh_type = "Pays"
|
||||||
if filtrer_ihh:
|
if filtrer_ihh:
|
||||||
ihh_type = st.radio("Appliquer le filtre IHH sur :",
|
ihh_type = st.radio("Appliquer le filtre IHH sur :",
|
||||||
["Pays", "Acteurs"],
|
["Pays", "Acteurs"],
|
||||||
horizontal=True,
|
horizontal=True,
|
||||||
key="analyse_ihh_type")
|
key="analyse_ihh_type")
|
||||||
|
|
||||||
filtrer_isg = st.checkbox("Filtrer les chemins contenant un pays instable (ISG ≥ 60)",
|
filtrer_isg = st.checkbox("Filtrer les chemins contenant un pays instable (ISG ≥ 60)",
|
||||||
key="analyse_filtrer_isg")
|
key="analyse_filtrer_isg")
|
||||||
logique_filtrage = st.radio("Logique de filtrage",
|
logique_filtrage = st.radio("Logique de filtrage",
|
||||||
["OU", "ET"],
|
["OU", "ET"],
|
||||||
horizontal=True,
|
horizontal=True,
|
||||||
key="analyse_logique_filtrage")
|
key="analyse_logique_filtrage")
|
||||||
|
|
||||||
return filtrer_ics, filtrer_ivc, filtrer_ihh, ihh_type, filtrer_isg, logique_filtrage
|
return filtrer_ics, filtrer_ivc, filtrer_ihh, ihh_type, filtrer_isg, logique_filtrage
|
||||||
|
|
||||||
|
|
||||||
def interface_analyse(G_temp):
|
def interface_analyse(G_temp):
|
||||||
|
st.markdown("# Analyse du graphe")
|
||||||
|
with st.expander("Comment utiliser cet onglet ?", expanded=False):
|
||||||
|
st.markdown("""
|
||||||
|
1. Sélectionnez le niveau de départ (produit final, composant ou minerai)
|
||||||
|
2. Choisissez le niveau d'arrivée souhaité
|
||||||
|
3. Affinez votre sélection en spécifiant soit un ou des minerais à cibler spécifiquement ou des items précis à chaque niveau (optionnel)
|
||||||
|
4. Définissez les critères d'analyse en sélectionnant les indices de vulnérabilité pertinents
|
||||||
|
5. Choisissez le mode de combinaison des indices (ET/OU) selon votre besoin d'analyse
|
||||||
|
6. Explorez le graphique généré en utilisant les contrôles de zoom et de déplacement ; vous pouvez basculer en mode plein écran pour le graphe
|
||||||
|
""")
|
||||||
|
st.markdown("---")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
st.markdown("# Analyse")
|
|
||||||
|
|
||||||
# Préparation du graphe
|
# Préparation du graphe
|
||||||
G_temp, niveaux_temp = preparer_graphe(G_temp)
|
G_temp, niveaux_temp = preparer_graphe(G_temp)
|
||||||
|
|
||||||
# Sélection des niveaux
|
# Sélection des niveaux
|
||||||
niveau_depart, niveau_arrivee = selectionner_niveaux()
|
niveau_depart, niveau_arrivee = selectionner_niveaux()
|
||||||
if niveau_depart is None or niveau_arrivee is None:
|
if niveau_depart is None or niveau_arrivee is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Sélection des minerais si nécessaire
|
# Sélection des minerais si nécessaire
|
||||||
minerais_selection = selectionner_minerais(G_temp, niveau_depart, niveau_arrivee)
|
minerais_selection = selectionner_minerais(G_temp, niveau_depart, niveau_arrivee)
|
||||||
|
|
||||||
# Sélection fine des noeuds
|
# Sélection fine des noeuds
|
||||||
noeuds_depart, noeuds_arrivee = selectionner_noeuds(G_temp, niveaux_temp, niveau_depart, niveau_arrivee)
|
noeuds_depart, noeuds_arrivee = selectionner_noeuds(G_temp, niveaux_temp, niveau_depart, niveau_arrivee)
|
||||||
|
|
||||||
# Configuration des filtres de vulnérabilité
|
# Configuration des filtres de vulnérabilité
|
||||||
filtrer_ics, filtrer_ivc, filtrer_ihh, ihh_type, filtrer_isg, logique_filtrage = configurer_filtres_vulnerabilite()
|
filtrer_ics, filtrer_ivc, filtrer_ihh, ihh_type, filtrer_isg, logique_filtrage = configurer_filtres_vulnerabilite()
|
||||||
|
|
||||||
# Lancement de l'analyse
|
# Lancement de l'analyse
|
||||||
st.markdown("---")
|
st.markdown("---")
|
||||||
if st.button("Lancer l'analyse", type="primary", key="analyse_lancer"):
|
if st.button("Lancer l'analyse", type="primary", key="analyse_lancer"):
|
||||||
|
|||||||
@ -5,6 +5,8 @@ import markdown
|
|||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from latex2mathml.converter import convert as latex_to_mathml
|
from latex2mathml.converter import convert as latex_to_mathml
|
||||||
from .utils.fiche_utils import render_fiche_markdown
|
from .utils.fiche_utils import render_fiche_markdown
|
||||||
|
import pypandoc
|
||||||
|
import streamlit as st
|
||||||
|
|
||||||
from .utils.dynamic import (
|
from .utils.dynamic import (
|
||||||
build_dynamic_sections,
|
build_dynamic_sections,
|
||||||
@ -113,13 +115,31 @@ def generer_fiche(md_source, dossier, nom_fichier, seuils):
|
|||||||
elif type_fiche == "minerai":
|
elif type_fiche == "minerai":
|
||||||
md_source = build_minerai_sections(md_source)
|
md_source = build_minerai_sections(md_source)
|
||||||
|
|
||||||
contenu_md = render_fiche_markdown(md_source, seuils)
|
contenu_md = render_fiche_markdown(md_source, seuils, license_path="assets/licence.md")
|
||||||
|
|
||||||
md_path = os.path.join("Fiches", dossier, nom_fichier)
|
md_path = os.path.join("Fiches", dossier, nom_fichier)
|
||||||
os.makedirs(os.path.dirname(md_path), exist_ok=True)
|
os.makedirs(os.path.dirname(md_path), exist_ok=True)
|
||||||
with open(md_path, "w", encoding="utf-8") as f:
|
with open(md_path, "w", encoding="utf-8") as f:
|
||||||
f.write(contenu_md)
|
f.write(contenu_md)
|
||||||
|
|
||||||
|
# Génération automatique du PDF
|
||||||
|
pdf_dir = os.path.join("static", "Fiches", dossier)
|
||||||
|
os.makedirs(pdf_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# Construire le chemin PDF correspondant (même nom que .md, mais .pdf)
|
||||||
|
nom_pdf = os.path.splitext(nom_fichier)[0] + ".pdf"
|
||||||
|
pdf_path = os.path.join(pdf_dir, nom_pdf)
|
||||||
|
|
||||||
|
try:
|
||||||
|
pypandoc.convert_file(
|
||||||
|
md_path,
|
||||||
|
to="pdf",
|
||||||
|
outputfile=pdf_path,
|
||||||
|
extra_args=["--pdf-engine=xelatex", "-V", "geometry:margin=2cm"]
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"[ERREUR] Génération PDF échouée pour {md_path}: {e}")
|
||||||
|
|
||||||
html_output = rendu_html(contenu_md)
|
html_output = rendu_html(contenu_md)
|
||||||
|
|
||||||
html_dir = os.path.join("HTML", dossier)
|
html_dir = os.path.join("HTML", dossier)
|
||||||
|
|||||||
@ -16,8 +16,21 @@ from .utils.fiche_utils import load_seuils, doit_regenerer_fiche
|
|||||||
from .generer import generer_fiche
|
from .generer import generer_fiche
|
||||||
|
|
||||||
def interface_fiches():
|
def interface_fiches():
|
||||||
st.markdown("# Affichage des fiches")
|
st.markdown("# Découverte des fiches")
|
||||||
st.markdown("Sélectionner d'abord l'opération que vous souhaitez examiner et ensuite choisisez la fiche à lire.")
|
with st.expander("Comment utiliser cet onglet ?", expanded=False):
|
||||||
|
st.markdown("""
|
||||||
|
1. Parcourez la liste des fiches disponibles par catégorie
|
||||||
|
2. Sélectionnez une fiche pour afficher son contenu complet
|
||||||
|
3. Consultez les données détaillées, graphiques et analyses supplémentaires
|
||||||
|
4. Utilisez ces informations pour approfondir votre compréhension des vulnérabilités identifiées
|
||||||
|
|
||||||
|
Les catégories sont les suivantes :
|
||||||
|
* Assemblage : opération d'assemblage des produits finaux à partir des composants
|
||||||
|
* Connexe : opérations diverses nécessaires pour fabriquer le numérique, mais n'entrant pas directement dans sa composition
|
||||||
|
* Criticités : indices utilisés pour identifier et évaluer les vulnérabilités
|
||||||
|
* Fabrication : opération de fabrication des composants à partir de minerais
|
||||||
|
* Minerai : description et opérations d'extraction et de traitement des minerais
|
||||||
|
""")
|
||||||
st.markdown("---")
|
st.markdown("---")
|
||||||
|
|
||||||
if "fiches_arbo" not in st.session_state:
|
if "fiches_arbo" not in st.session_state:
|
||||||
@ -29,7 +42,7 @@ def interface_fiches():
|
|||||||
return
|
return
|
||||||
|
|
||||||
dossiers = sorted(arbo.keys(), key=lambda x: x.lower())
|
dossiers = sorted(arbo.keys(), key=lambda x: x.lower())
|
||||||
dossier_choisi = st.selectbox("Choisissez un dossier", ["-- Sélectionner un dossier --"] + dossiers)
|
dossier_choisi = st.selectbox("Choisissez une catégorie de fiches", ["-- Sélectionner un dossier --"] + dossiers)
|
||||||
|
|
||||||
if dossier_choisi and dossier_choisi != "-- Sélectionner un dossier --":
|
if dossier_choisi and dossier_choisi != "-- Sélectionner un dossier --":
|
||||||
fiches = arbo.get(dossier_choisi, [])
|
fiches = arbo.get(dossier_choisi, [])
|
||||||
|
|||||||
@ -38,10 +38,11 @@ def _migrate_metadata(meta: Dict) -> Dict:
|
|||||||
return meta
|
return meta
|
||||||
|
|
||||||
|
|
||||||
def render_fiche_markdown(md_text: str, seuils: Dict) -> str:
|
def render_fiche_markdown(md_text: str, seuils: Dict, license_path: str = "assets/licence.md") -> str:
|
||||||
"""Renvoie la fiche rendue (Markdown) :
|
"""Renvoie la fiche rendue (Markdown) :
|
||||||
– placeholders Jinja2 remplacés ({{ … }})
|
– placeholders Jinja2 remplacés ({{ … }})
|
||||||
– table seuils injectée via dict 'seuils'.
|
– table seuils injectée via dict 'seuils'.
|
||||||
|
- licence ajoutée après le tableau de version et avant le premier titre de niveau 2
|
||||||
"""
|
"""
|
||||||
post = frontmatter.loads(md_text)
|
post = frontmatter.loads(md_text)
|
||||||
meta = _migrate_metadata(dict(post.metadata))
|
meta = _migrate_metadata(dict(post.metadata))
|
||||||
@ -64,6 +65,24 @@ def render_fiche_markdown(md_text: str, seuils: Dict) -> str:
|
|||||||
|
|
||||||
{rendered_body}"""
|
{rendered_body}"""
|
||||||
|
|
||||||
|
# Charger le contenu de la licence
|
||||||
|
try:
|
||||||
|
license_content = pathlib.Path(license_path).read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
# Insérer la licence après le tableau de version et avant le premier titre h2
|
||||||
|
# Trouver la position du premier titre h2
|
||||||
|
h2_match = re.search(r"^## ", rendered_body, flags=re.M)
|
||||||
|
|
||||||
|
if h2_match:
|
||||||
|
h2_position = h2_match.start()
|
||||||
|
rendered_body = f"{rendered_body[:h2_position]}\n\n{license_content}\n\n{rendered_body[h2_position:]}"
|
||||||
|
else:
|
||||||
|
# S'il n'y a pas de titre h2, ajouter la licence à la fin
|
||||||
|
rendered_body = f"{rendered_body}\n\n{license_content}"
|
||||||
|
except Exception as e:
|
||||||
|
# En cas d'erreur lors de la lecture du fichier de licence, continuer sans l'ajouter
|
||||||
|
pass
|
||||||
|
|
||||||
return rendered_body
|
return rendered_body
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@ -6,16 +6,17 @@ from .modification import modifier_produit
|
|||||||
from .import_export import importer_exporter_graph
|
from .import_export import importer_exporter_graph
|
||||||
|
|
||||||
def interface_personnalisation(G):
|
def interface_personnalisation(G):
|
||||||
st.markdown("""
|
st.markdown("# Personnalisation des produits finaux")
|
||||||
# Personnalisation des produits finaux
|
with st.expander("Comment utiliser cet onglet ?", expanded=False):
|
||||||
|
st.markdown("""
|
||||||
Dans cette section, vous pouvez ajouter des produits finaux qui ne sont pas présents dans la liste,
|
1. Cliquez sur "Ajouter un produit final" pour créer un nouveau produit
|
||||||
par exemple des produits que vous concevez vous-même.
|
2. Donnez un nom à votre produit
|
||||||
|
3. Sélectionnez une opération d'assemblage appropriée (si pertinent)
|
||||||
Vous pouvez aussi enregistrer ou recharger vos modifications.
|
4. Choisissez les composants qui constituent votre produit dans la liste proposée
|
||||||
|
5. Sauvegardez votre configuration pour une réutilisation future
|
||||||
---
|
6. Vous pourrez par la suite modifier ou supprimer vos produits personnalisés
|
||||||
""")
|
""")
|
||||||
|
st.markdown("---")
|
||||||
|
|
||||||
G = ajouter_produit(G)
|
G = ajouter_produit(G)
|
||||||
G = modifier_produit(G)
|
G = modifier_produit(G)
|
||||||
|
|||||||
178
app/visualisations/graphes.py
Normal file
178
app/visualisations/graphes.py
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
import streamlit as st
|
||||||
|
import altair as alt
|
||||||
|
import numpy as np
|
||||||
|
from collections import Counter
|
||||||
|
import pandas as pd
|
||||||
|
|
||||||
|
|
||||||
|
def afficher_graphique_altair(df):
|
||||||
|
ordre_personnalise = ['Assemblage', 'Fabrication', 'Traitement', 'Extraction']
|
||||||
|
categories = [cat for cat in ordre_personnalise if cat in df['categorie'].unique()]
|
||||||
|
for cat in categories:
|
||||||
|
st.markdown(f"### {cat}")
|
||||||
|
df_cat = df[df['categorie'] == cat].copy()
|
||||||
|
|
||||||
|
coord_pairs = list(zip(df_cat['ihh_pays'].round(1), df_cat['ihh_acteurs'].round(1)))
|
||||||
|
counts = Counter(coord_pairs)
|
||||||
|
|
||||||
|
offset_x = []
|
||||||
|
offset_y = {}
|
||||||
|
seen = Counter()
|
||||||
|
for pair in coord_pairs:
|
||||||
|
rank = seen[pair]
|
||||||
|
seen[pair] += 1
|
||||||
|
if counts[pair] > 1:
|
||||||
|
angle = rank * 1.5
|
||||||
|
radius = 0.8 + 0.4 * rank
|
||||||
|
offset_x.append(radius * np.cos(angle))
|
||||||
|
offset_y[pair] = radius * np.sin(angle)
|
||||||
|
else:
|
||||||
|
offset_x.append(0)
|
||||||
|
offset_y[pair] = 0
|
||||||
|
|
||||||
|
df_cat['ihh_pays'] += offset_x
|
||||||
|
df_cat['ihh_acteurs'] += [offset_y[p] for p in coord_pairs]
|
||||||
|
df_cat['ihh_pays_text'] = df_cat['ihh_pays'] + 0.5
|
||||||
|
df_cat['ihh_acteurs_text'] = df_cat['ihh_acteurs'] + 0.5
|
||||||
|
|
||||||
|
base = alt.Chart(df_cat).encode(
|
||||||
|
x=alt.X('ihh_pays:Q', title='IHH Pays (%)'),
|
||||||
|
y=alt.Y('ihh_acteurs:Q', title='IHH Acteurs (%)'),
|
||||||
|
size=alt.Size('criticite_cat:Q', scale=alt.Scale(domain=[1, 2, 3], range=[50, 500, 1000]), legend=None),
|
||||||
|
color=alt.Color('criticite_cat:N', scale=alt.Scale(domain=[1, 2, 3], range=['darkgreen', 'orange', 'darkred']))
|
||||||
|
)
|
||||||
|
|
||||||
|
points = base.mark_circle(opacity=0.6)
|
||||||
|
lines = alt.Chart(df_cat).mark_rule(strokeWidth=0.5, color='gray').encode(
|
||||||
|
x='ihh_pays:Q', x2='ihh_pays_text:Q',
|
||||||
|
y='ihh_acteurs:Q', y2='ihh_acteurs_text:Q'
|
||||||
|
)
|
||||||
|
|
||||||
|
labels = alt.Chart(df_cat).mark_text(
|
||||||
|
align='left', dx=3, dy=-3, fontSize=8, font='Arial', angle=335
|
||||||
|
).encode(
|
||||||
|
x='ihh_pays_text:Q',
|
||||||
|
y='ihh_acteurs_text:Q',
|
||||||
|
text='nom:N'
|
||||||
|
)
|
||||||
|
|
||||||
|
hline_15 = alt.Chart(df_cat).mark_rule(strokeDash=[2,2], color='green').encode(y=alt.datum(15))
|
||||||
|
hline_25 = alt.Chart(df_cat).mark_rule(strokeDash=[2,2], color='red').encode(y=alt.datum(25))
|
||||||
|
hline_100 = alt.Chart(df_cat).mark_rule(strokeDash=[2,2], color='white').encode(y=alt.datum(100))
|
||||||
|
vline_15 = alt.Chart(df_cat).mark_rule(strokeDash=[2,2], color='green').encode(x=alt.datum(15))
|
||||||
|
vline_25 = alt.Chart(df_cat).mark_rule(strokeDash=[2,2], color='red').encode(x=alt.datum(25))
|
||||||
|
vline_100 = alt.Chart(df_cat).mark_rule(strokeDash=[2,2], color='white').encode(x=alt.datum(100))
|
||||||
|
|
||||||
|
chart = (points + lines + labels + hline_15 + hline_25 + hline_100 + vline_15 + vline_25 + vline_100).properties(
|
||||||
|
width=500,
|
||||||
|
height=400,
|
||||||
|
title=f"Concentration et criticité – {cat}"
|
||||||
|
).interactive()
|
||||||
|
|
||||||
|
st.altair_chart(chart, use_container_width=True)
|
||||||
|
|
||||||
|
|
||||||
|
def creer_graphes(donnees):
|
||||||
|
if not donnees:
|
||||||
|
st.warning("Aucune donnée à afficher.")
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
df = pd.DataFrame(donnees)
|
||||||
|
df['ivc_cat'] = df['ivc'].apply(lambda x: 1 if x <= 15 else (2 if x <= 30 else 3))
|
||||||
|
|
||||||
|
from collections import Counter
|
||||||
|
coord_pairs = list(zip(df['ihh_extraction'].round(1), df['ihh_reserves'].round(1)))
|
||||||
|
counts = Counter(coord_pairs)
|
||||||
|
|
||||||
|
offset_x, offset_y = [], {}
|
||||||
|
seen = Counter()
|
||||||
|
for pair in coord_pairs:
|
||||||
|
rank = seen[pair]
|
||||||
|
seen[pair] += 1
|
||||||
|
if counts[pair] > 1:
|
||||||
|
angle = rank * 1.5
|
||||||
|
radius = 0.8 + 0.4 * rank
|
||||||
|
offset_x.append(radius * np.cos(angle))
|
||||||
|
offset_y[pair] = radius * np.sin(angle)
|
||||||
|
else:
|
||||||
|
offset_x.append(0)
|
||||||
|
offset_y[pair] = 0
|
||||||
|
|
||||||
|
df['ihh_extraction'] += offset_x
|
||||||
|
df['ihh_reserves'] += [offset_y[p] for p in coord_pairs]
|
||||||
|
df['ihh_extraction_text'] = df['ihh_extraction'] + 0.5
|
||||||
|
df['ihh_reserves_text'] = df['ihh_reserves'] + 0.5
|
||||||
|
|
||||||
|
base = alt.Chart(df).encode(
|
||||||
|
x=alt.X('ihh_extraction:Q', title='IHH Extraction (%)'),
|
||||||
|
y=alt.Y('ihh_reserves:Q', title='IHH Réserves (%)'),
|
||||||
|
size=alt.Size('ivc_cat:Q', scale=alt.Scale(domain=[1, 2, 3], range=[50, 500, 1000]), legend=None),
|
||||||
|
color=alt.Color('ivc_cat:N', scale=alt.Scale(domain=[1, 2, 3], range=['darkgreen', 'orange', 'darkred'])),
|
||||||
|
tooltip=['nom:N', 'ivc:Q', 'ihh_extraction:Q', 'ihh_reserves:Q']
|
||||||
|
)
|
||||||
|
|
||||||
|
points = base.mark_circle(opacity=0.6)
|
||||||
|
lines = alt.Chart(df).mark_rule(strokeWidth=0.5, color='gray').encode(
|
||||||
|
x='ihh_extraction:Q', x2='ihh_extraction_text:Q',
|
||||||
|
y='ihh_reserves:Q', y2='ihh_reserves_text:Q'
|
||||||
|
)
|
||||||
|
|
||||||
|
labels = alt.Chart(df).mark_text(
|
||||||
|
align='left', dx=10, dy=-10, fontSize=10, font='Arial', angle=335
|
||||||
|
).encode(
|
||||||
|
x='ihh_extraction_text:Q',
|
||||||
|
y='ihh_reserves_text:Q',
|
||||||
|
text='nom:N'
|
||||||
|
)
|
||||||
|
|
||||||
|
hline_15 = alt.Chart(df).mark_rule(strokeDash=[2,2], color='green').encode(y=alt.datum(15))
|
||||||
|
hline_25 = alt.Chart(df).mark_rule(strokeDash=[2,2], color='red').encode(y=alt.datum(25))
|
||||||
|
hline_100 = alt.Chart(df).mark_rule(strokeDash=[2,2], color='red').encode(y=alt.datum(100))
|
||||||
|
vline_15 = alt.Chart(df).mark_rule(strokeDash=[2,2], color='green').encode(x=alt.datum(15))
|
||||||
|
vline_25 = alt.Chart(df).mark_rule(strokeDash=[2,2], color='red').encode(x=alt.datum(25))
|
||||||
|
vline_100 = alt.Chart(df).mark_rule(strokeDash=[2,2], color='red').encode(x=alt.datum(100))
|
||||||
|
|
||||||
|
chart = (points + lines + labels + hline_15 + hline_25 + hline_100 + vline_15 + vline_25 + vline_100).properties(
|
||||||
|
width=600,
|
||||||
|
height=500,
|
||||||
|
title="Concentration des ressources critiques vs vulnérabilité IVC"
|
||||||
|
).interactive()
|
||||||
|
|
||||||
|
st.altair_chart(chart, use_container_width=True)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"Erreur lors de la création du graphique : {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def lancer_visualisation_ihh_criticite(graph):
|
||||||
|
try:
|
||||||
|
import networkx as nx
|
||||||
|
from utils.graph_utils import recuperer_donnees
|
||||||
|
|
||||||
|
niveaux = nx.get_node_attributes(graph, "niveau")
|
||||||
|
noeuds = [n for n, v in niveaux.items() if v == "10" and "Reserves" not in n]
|
||||||
|
noeuds.sort()
|
||||||
|
|
||||||
|
df = recuperer_donnees(graph, noeuds)
|
||||||
|
if df.empty:
|
||||||
|
st.warning("Aucune donnée à visualiser.")
|
||||||
|
else:
|
||||||
|
afficher_graphique_altair(df)
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"Erreur dans la visualisation IHH vs Criticité : {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def lancer_visualisation_ihh_ivc(graph):
|
||||||
|
try:
|
||||||
|
from utils.graph_utils import recuperer_donnees_2
|
||||||
|
noeuds_niveau_2 = [
|
||||||
|
n for n, data in graph.nodes(data=True)
|
||||||
|
if data.get("niveau") == "2" and "ivc" in data
|
||||||
|
]
|
||||||
|
if not noeuds_niveau_2:
|
||||||
|
return
|
||||||
|
data = recuperer_donnees_2(graph, noeuds_niveau_2)
|
||||||
|
creer_graphes(data)
|
||||||
|
except Exception as e:
|
||||||
|
st.error(f"Erreur dans la visualisation IHH vs IVC : {e}")
|
||||||
@ -1,185 +1,31 @@
|
|||||||
import streamlit as st
|
import streamlit as st
|
||||||
import altair as alt
|
|
||||||
import numpy as np
|
|
||||||
from collections import Counter
|
|
||||||
import pandas as pd
|
|
||||||
|
|
||||||
|
from .graphes import (
|
||||||
|
lancer_visualisation_ihh_criticite,
|
||||||
|
lancer_visualisation_ihh_ivc
|
||||||
|
)
|
||||||
|
|
||||||
def afficher_graphique_altair(df):
|
|
||||||
ordre_personnalise = ['Assemblage', 'Fabrication', 'Traitement', 'Extraction']
|
|
||||||
categories = [cat for cat in ordre_personnalise if cat in df['categorie'].unique()]
|
|
||||||
for cat in categories:
|
|
||||||
st.markdown(f"### {cat}")
|
|
||||||
df_cat = df[df['categorie'] == cat].copy()
|
|
||||||
|
|
||||||
coord_pairs = list(zip(df_cat['ihh_pays'].round(1), df_cat['ihh_acteurs'].round(1)))
|
|
||||||
counts = Counter(coord_pairs)
|
|
||||||
|
|
||||||
offset_x = []
|
|
||||||
offset_y = {}
|
|
||||||
seen = Counter()
|
|
||||||
for pair in coord_pairs:
|
|
||||||
rank = seen[pair]
|
|
||||||
seen[pair] += 1
|
|
||||||
if counts[pair] > 1:
|
|
||||||
angle = rank * 1.5
|
|
||||||
radius = 0.8 + 0.4 * rank
|
|
||||||
offset_x.append(radius * np.cos(angle))
|
|
||||||
offset_y[pair] = radius * np.sin(angle)
|
|
||||||
else:
|
|
||||||
offset_x.append(0)
|
|
||||||
offset_y[pair] = 0
|
|
||||||
|
|
||||||
df_cat['ihh_pays'] += offset_x
|
|
||||||
df_cat['ihh_acteurs'] += [offset_y[p] for p in coord_pairs]
|
|
||||||
df_cat['ihh_pays_text'] = df_cat['ihh_pays'] + 0.5
|
|
||||||
df_cat['ihh_acteurs_text'] = df_cat['ihh_acteurs'] + 0.5
|
|
||||||
|
|
||||||
base = alt.Chart(df_cat).encode(
|
|
||||||
x=alt.X('ihh_pays:Q', title='IHH Pays (%)'),
|
|
||||||
y=alt.Y('ihh_acteurs:Q', title='IHH Acteurs (%)'),
|
|
||||||
size=alt.Size('criticite_cat:Q', scale=alt.Scale(domain=[1, 2, 3], range=[50, 500, 1000]), legend=None),
|
|
||||||
color=alt.Color('criticite_cat:N', scale=alt.Scale(domain=[1, 2, 3], range=['darkgreen', 'orange', 'darkred']))
|
|
||||||
)
|
|
||||||
|
|
||||||
points = base.mark_circle(opacity=0.6)
|
|
||||||
lines = alt.Chart(df_cat).mark_rule(strokeWidth=0.5, color='gray').encode(
|
|
||||||
x='ihh_pays:Q', x2='ihh_pays_text:Q',
|
|
||||||
y='ihh_acteurs:Q', y2='ihh_acteurs_text:Q'
|
|
||||||
)
|
|
||||||
|
|
||||||
labels = alt.Chart(df_cat).mark_text(
|
|
||||||
align='left', dx=3, dy=-3, fontSize=8, font='Arial', angle=335
|
|
||||||
).encode(
|
|
||||||
x='ihh_pays_text:Q',
|
|
||||||
y='ihh_acteurs_text:Q',
|
|
||||||
text='nom:N'
|
|
||||||
)
|
|
||||||
|
|
||||||
hline_15 = alt.Chart(df_cat).mark_rule(strokeDash=[2,2], color='green').encode(y=alt.datum(15))
|
|
||||||
hline_25 = alt.Chart(df_cat).mark_rule(strokeDash=[2,2], color='red').encode(y=alt.datum(25))
|
|
||||||
vline_15 = alt.Chart(df_cat).mark_rule(strokeDash=[2,2], color='green').encode(x=alt.datum(15))
|
|
||||||
vline_25 = alt.Chart(df_cat).mark_rule(strokeDash=[2,2], color='red').encode(x=alt.datum(25))
|
|
||||||
|
|
||||||
chart = (points + lines + labels + hline_15 + hline_25 + vline_15 + vline_25).properties(
|
|
||||||
width=500,
|
|
||||||
height=400,
|
|
||||||
title=f"Concentration et criticité – {cat}"
|
|
||||||
).interactive()
|
|
||||||
|
|
||||||
st.altair_chart(chart, use_container_width=True)
|
|
||||||
|
|
||||||
|
|
||||||
def creer_graphes(donnees):
|
|
||||||
if not donnees:
|
|
||||||
st.warning("Aucune donnée à afficher.")
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
df = pd.DataFrame(donnees)
|
|
||||||
df['ivc_cat'] = df['ivc'].apply(lambda x: 1 if x <= 15 else (2 if x <= 30 else 3))
|
|
||||||
|
|
||||||
from collections import Counter
|
|
||||||
coord_pairs = list(zip(df['ihh_extraction'].round(1), df['ihh_reserves'].round(1)))
|
|
||||||
counts = Counter(coord_pairs)
|
|
||||||
|
|
||||||
offset_x, offset_y = [], {}
|
|
||||||
seen = Counter()
|
|
||||||
for pair in coord_pairs:
|
|
||||||
rank = seen[pair]
|
|
||||||
seen[pair] += 1
|
|
||||||
if counts[pair] > 1:
|
|
||||||
angle = rank * 1.5
|
|
||||||
radius = 0.8 + 0.4 * rank
|
|
||||||
offset_x.append(radius * np.cos(angle))
|
|
||||||
offset_y[pair] = radius * np.sin(angle)
|
|
||||||
else:
|
|
||||||
offset_x.append(0)
|
|
||||||
offset_y[pair] = 0
|
|
||||||
|
|
||||||
df['ihh_extraction'] += offset_x
|
|
||||||
df['ihh_reserves'] += [offset_y[p] for p in coord_pairs]
|
|
||||||
df['ihh_extraction_text'] = df['ihh_extraction'] + 0.5
|
|
||||||
df['ihh_reserves_text'] = df['ihh_reserves'] + 0.5
|
|
||||||
|
|
||||||
base = alt.Chart(df).encode(
|
|
||||||
x=alt.X('ihh_extraction:Q', title='IHH Extraction (%)'),
|
|
||||||
y=alt.Y('ihh_reserves:Q', title='IHH Réserves (%)'),
|
|
||||||
size=alt.Size('ivc_cat:Q', scale=alt.Scale(domain=[1, 2, 3], range=[50, 500, 1000]), legend=None),
|
|
||||||
color=alt.Color('ivc_cat:N', scale=alt.Scale(domain=[1, 2, 3], range=['darkgreen', 'orange', 'darkred'])),
|
|
||||||
tooltip=['nom:N', 'ivc:Q', 'ihh_extraction:Q', 'ihh_reserves:Q']
|
|
||||||
)
|
|
||||||
|
|
||||||
points = base.mark_circle(opacity=0.6)
|
|
||||||
lines = alt.Chart(df).mark_rule(strokeWidth=0.5, color='gray').encode(
|
|
||||||
x='ihh_extraction:Q', x2='ihh_extraction_text:Q',
|
|
||||||
y='ihh_reserves:Q', y2='ihh_reserves_text:Q'
|
|
||||||
)
|
|
||||||
|
|
||||||
labels = alt.Chart(df).mark_text(
|
|
||||||
align='left', dx=10, dy=-10, fontSize=10, font='Arial', angle=335
|
|
||||||
).encode(
|
|
||||||
x='ihh_extraction_text:Q',
|
|
||||||
y='ihh_reserves_text:Q',
|
|
||||||
text='nom:N'
|
|
||||||
)
|
|
||||||
|
|
||||||
hline_15 = alt.Chart(df).mark_rule(strokeDash=[2,2], color='green').encode(y=alt.datum(15))
|
|
||||||
hline_25 = alt.Chart(df).mark_rule(strokeDash=[2,2], color='red').encode(y=alt.datum(25))
|
|
||||||
vline_15 = alt.Chart(df).mark_rule(strokeDash=[2,2], color='green').encode(x=alt.datum(15))
|
|
||||||
vline_25 = alt.Chart(df).mark_rule(strokeDash=[2,2], color='red').encode(x=alt.datum(25))
|
|
||||||
|
|
||||||
chart = (points + lines + labels + hline_15 + hline_25 + vline_15 + vline_25).properties(
|
|
||||||
width=600,
|
|
||||||
height=500,
|
|
||||||
title="Concentration des ressources critiques vs vulnérabilité IVC"
|
|
||||||
).interactive()
|
|
||||||
|
|
||||||
st.altair_chart(chart, use_container_width=True)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
st.error(f"Erreur lors de la création du graphique : {e}")
|
|
||||||
|
|
||||||
|
|
||||||
def lancer_visualisation_ihh_criticite(graph):
|
|
||||||
try:
|
|
||||||
import networkx as nx
|
|
||||||
from utils.graph_utils import recuperer_donnees
|
|
||||||
|
|
||||||
niveaux = nx.get_node_attributes(graph, "niveau")
|
|
||||||
noeuds = [n for n, v in niveaux.items() if v == "10" and "Reserves" not in n]
|
|
||||||
noeuds.sort()
|
|
||||||
|
|
||||||
df = recuperer_donnees(graph, noeuds)
|
|
||||||
if df.empty:
|
|
||||||
st.warning("Aucune donnée à visualiser.")
|
|
||||||
else:
|
|
||||||
afficher_graphique_altair(df)
|
|
||||||
except Exception as e:
|
|
||||||
st.error(f"Erreur dans la visualisation IHH vs Criticité : {e}")
|
|
||||||
|
|
||||||
|
|
||||||
def lancer_visualisation_ihh_ivc(graph):
|
|
||||||
try:
|
|
||||||
from utils.graph_utils import recuperer_donnees_2
|
|
||||||
noeuds_niveau_2 = [
|
|
||||||
n for n, data in graph.nodes(data=True)
|
|
||||||
if data.get("niveau") == "2" and "ivc" in data
|
|
||||||
]
|
|
||||||
if not noeuds_niveau_2:
|
|
||||||
return
|
|
||||||
data = recuperer_donnees_2(graph, noeuds_niveau_2)
|
|
||||||
creer_graphes(data)
|
|
||||||
except Exception as e:
|
|
||||||
st.error(f"Erreur dans la visualisation IHH vs IVC : {e}")
|
|
||||||
|
|
||||||
def interface_visualisations(G_temp, G_temp_ivc):
|
def interface_visualisations(G_temp, G_temp_ivc):
|
||||||
st.markdown("# Visualisations")
|
st.markdown("# Analyse du graphe")
|
||||||
|
with st.expander("Comment utiliser cet onglet ?", expanded=False):
|
||||||
|
st.markdown("""
|
||||||
|
1. Explorez les graphiques présentant l'Indice de Herfindahl-Hirschmann (IHH)
|
||||||
|
2. Analysez sa relation avec la criticité moyenne des minerais ou leur Indice de Vulnérabilité Concurrentielle (IVC)
|
||||||
|
3. Zoomer dans les graphes pour mieux découvrir les informations
|
||||||
|
|
||||||
|
Il est important de se rappeler que l'IHH a deux seuils :
|
||||||
|
* en-dessous de 15, la concentration est considérée comme étant faible
|
||||||
|
* au-dessus de 25, elle est considérée comme étant forte
|
||||||
|
|
||||||
|
Ainsi plus le positionnement d'un point est en haut à droite des graphiques, plus les risques sont élevés.
|
||||||
|
Les graphiques présentent 2 droites horizontales et vetrticales pour matérialiser ces seuils.
|
||||||
|
""")
|
||||||
|
st.markdown("---")
|
||||||
|
|
||||||
st.markdown("""## Indice de Herfindahl-Hirschmann - IHH vs Criticité
|
st.markdown("""## Indice de Herfindahl-Hirschmann - IHH vs Criticité
|
||||||
|
|
||||||
Entre 0 et 15%, concentration faible, entre 15 et 25%, modérée, au-delà, forte.
|
La taille des points donne l'indication de la criticité de substituabilité du minerai.
|
||||||
|
|
||||||
Taille des points = criticité substituabilité du minerai
|
|
||||||
""")
|
""")
|
||||||
if st.button("Lancer", key="btn_ihh_criticite"):
|
if st.button("Lancer", key="btn_ihh_criticite"):
|
||||||
try:
|
try:
|
||||||
@ -189,9 +35,7 @@ Taille des points = criticité substituabilité du minerai
|
|||||||
|
|
||||||
st.markdown("""## Indice de Herfindahl-Hirschmann - IHH vs IVC
|
st.markdown("""## Indice de Herfindahl-Hirschmann - IHH vs IVC
|
||||||
|
|
||||||
Entre 0 et 15%, concentration faible, entre 15 et 25%, modérée, au-delà, forte.
|
La taille des points donne l'indication de la criticité concurrentielle du minerai.
|
||||||
|
|
||||||
Taille des points = criticité concurrentielle du minerai
|
|
||||||
""")
|
""")
|
||||||
|
|
||||||
if st.button("Lancer", key="btn_ihh_ivc"):
|
if st.button("Lancer", key="btn_ihh_ivc"):
|
||||||
|
|||||||
3
assets/licence.md
Normal file
3
assets/licence.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
Document généré avec [FabNum](https://fabnum.peccini.fr) par [Stéphan Peccini](mailto:stephan-pro@peccini.fr)
|
||||||
|
|
||||||
|
Licence : [CC BY-NC-ND 4.0](https://creativecommons.org/licenses/by-nc-nd/4.0/deed.fr) - Attribution - Utilisation non commerciale - Pas d’Œuvre dérivée
|
||||||
@ -10,7 +10,7 @@ def afficher_pied_de_page():
|
|||||||
<div role='contentinfo' aria-labelledby='footer-appli' class='wide-footer'>
|
<div role='contentinfo' aria-labelledby='footer-appli' class='wide-footer'>
|
||||||
<div class='info-footer'>
|
<div class='info-footer'>
|
||||||
<p id='footer-appli' class='info-footer'>
|
<p id='footer-appli' class='info-footer'>
|
||||||
Fabnum © 2025 – <a href='mailto:stephan-pro@peccini.fr'>Contact</a> – Licence <a href='https://creativecommons.org/licenses/by-nc-sa/4.0/' target='_blank'>CC BY-NC-SA</a>
|
Fabnum © 2025 – <a href='mailto:stephan-pro@peccini.fr'>Contact</a> – Licence <a href='https://creativecommons.org/licenses/by-nc-nd/4.0/deed.fr' target='_blank'>CC BY-NC-ND</a>
|
||||||
</p>
|
</p>
|
||||||
<p class='footer-note'>
|
<p class='footer-note'>
|
||||||
🌱 Calculs CO₂ via <a href='https://www.thegreenwebfoundation.org/' target='_blank'>The Green Web Foundation</a><br>
|
🌱 Calculs CO₂ via <a href='https://www.thegreenwebfoundation.org/' target='_blank'>The Green Web Foundation</a><br>
|
||||||
|
|||||||
43
fabnum.py
43
fabnum.py
@ -8,6 +8,47 @@ from utils.gitea import (
|
|||||||
charger_instructions_depuis_gitea
|
charger_instructions_depuis_gitea
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def afficher_instructions_avec_expanders(markdown_content):
|
||||||
|
"""
|
||||||
|
Affiche le contenu markdown avec les sections de niveau 2 (## Titre) dans des expanders
|
||||||
|
"""
|
||||||
|
# Extraction du titre principal (niveau 1)
|
||||||
|
titre_pattern = r'^# (.+)$'
|
||||||
|
titre_match = re.search(titre_pattern, markdown_content, re.MULTILINE)
|
||||||
|
|
||||||
|
# Affichage du titre principal
|
||||||
|
if titre_match:
|
||||||
|
titre_principal = titre_match.group(1)
|
||||||
|
st.markdown(f"# {titre_principal}")
|
||||||
|
|
||||||
|
# Division en sections de niveau 2
|
||||||
|
sections = re.split(r'^## ', markdown_content, flags=re.MULTILINE)
|
||||||
|
|
||||||
|
# Affichage du contenu avant la première section de niveau 2 (sans le titre principal)
|
||||||
|
introduction = sections[0]
|
||||||
|
if titre_match:
|
||||||
|
# Supprimer le titre principal de l'introduction car on l'a déjà affiché
|
||||||
|
introduction = re.sub(titre_pattern, '', introduction, flags=re.MULTILINE).strip()
|
||||||
|
|
||||||
|
if introduction:
|
||||||
|
st.markdown(introduction, unsafe_allow_html=True)
|
||||||
|
|
||||||
|
# Affichage des sections dans des expanders
|
||||||
|
for i, section in enumerate(sections[1:], 1):
|
||||||
|
# Extraction du titre de la section
|
||||||
|
lignes = section.split('\n', 1)
|
||||||
|
titre_section = lignes[0].strip()
|
||||||
|
|
||||||
|
# Garder le titre dans le contenu
|
||||||
|
contenu_section = f"## {titre_section}"
|
||||||
|
if len(lignes) > 1:
|
||||||
|
contenu_section += "\n\n" + lignes[1].strip()
|
||||||
|
|
||||||
|
# Affichage dans un expander
|
||||||
|
status = True if i == 1 else False
|
||||||
|
with st.expander(f"## {titre_section}", expanded=status):
|
||||||
|
st.markdown(contenu_section, unsafe_allow_html=True)
|
||||||
|
|
||||||
from utils.graph_utils import (
|
from utils.graph_utils import (
|
||||||
charger_graphe
|
charger_graphe
|
||||||
)
|
)
|
||||||
@ -105,7 +146,7 @@ dot_file_path = None
|
|||||||
if st.session_state.onglet == "Instructions":
|
if st.session_state.onglet == "Instructions":
|
||||||
markdown_content = charger_instructions_depuis_gitea(INSTRUCTIONS)
|
markdown_content = charger_instructions_depuis_gitea(INSTRUCTIONS)
|
||||||
if markdown_content:
|
if markdown_content:
|
||||||
st.markdown(markdown_content)
|
afficher_instructions_avec_expanders(markdown_content)
|
||||||
|
|
||||||
elif st.session_state.onglet == "Fiches":
|
elif st.session_state.onglet == "Fiches":
|
||||||
interface_fiches()
|
interface_fiches()
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user