Modifications mineures

This commit is contained in:
Fabrication du Numérique 2025-05-11 22:48:13 +02:00
parent 91bb36fb8b
commit 120f5a7af8
12 changed files with 357 additions and 225 deletions

3
.gitignore vendored
View File

@ -19,7 +19,8 @@ __pycache__/
venv/
.venv/
Local/
HTML
HTML/
static/
# Ignorer données Fiches (adapté à ton projet)
Instructions.md

View File

@ -152,6 +152,7 @@ fabnum-dev/
│ │ └── README.md # Documentation du module
│ └── visualisations/ # Visualisations graphiques
│ ├── interface.py # Interface des visualisations
│ ├── graphes.py # Gestion des graphes à visualiser
│ └── README.md # Documentation du module
├── components/ # Composants d'interface réutilisables
│ ├── sidebar.py # Barre latérale de navigation

View File

@ -121,8 +121,19 @@ def configurer_filtres_vulnerabilite():
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:
st.markdown("# Analyse")
# Préparation du graphe
G_temp, niveaux_temp = preparer_graphe(G_temp)

View File

@ -5,6 +5,8 @@ import markdown
from bs4 import BeautifulSoup
from latex2mathml.converter import convert as latex_to_mathml
from .utils.fiche_utils import render_fiche_markdown
import pypandoc
import streamlit as st
from .utils.dynamic import (
build_dynamic_sections,
@ -113,13 +115,31 @@ def generer_fiche(md_source, dossier, nom_fichier, seuils):
elif type_fiche == "minerai":
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)
os.makedirs(os.path.dirname(md_path), exist_ok=True)
with open(md_path, "w", encoding="utf-8") as f:
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_dir = os.path.join("HTML", dossier)

View File

@ -16,8 +16,21 @@ from .utils.fiche_utils import load_seuils, doit_regenerer_fiche
from .generer import generer_fiche
def interface_fiches():
st.markdown("# Affichage des fiches")
st.markdown("Sélectionner d'abord l'opération que vous souhaitez examiner et ensuite choisisez la fiche à lire.")
st.markdown("# Découverte des fiches")
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("---")
if "fiches_arbo" not in st.session_state:
@ -29,7 +42,7 @@ def interface_fiches():
return
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 --":
fiches = arbo.get(dossier_choisi, [])

View File

@ -38,10 +38,11 @@ def _migrate_metadata(meta: Dict) -> Dict:
return meta
def render_fiche_markdown(md_text: str, seuils: Dict) -> str:
"""Renvoie la fiche rendue (Markdown) :
 placeholders Jinja2 remplacés ({{ }})
 table seuils injectée via dict 'seuils'.
def render_fiche_markdown(md_text: str, seuils: Dict, license_path: str = "assets/licence.md") -> str:
"""Renvoie la fiche rendue (Markdown) :
placeholders Jinja2 remplacés ({{ }})
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)
meta = _migrate_metadata(dict(post.metadata))
@ -64,6 +65,24 @@ def render_fiche_markdown(md_text: str, seuils: Dict) -> str:
{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

View File

@ -6,16 +6,17 @@ from .modification import modifier_produit
from .import_export import importer_exporter_graph
def interface_personnalisation(G):
st.markdown("""
# Personnalisation des produits finaux
Dans cette section, vous pouvez ajouter des produits finaux qui ne sont pas présents dans la liste,
par exemple des produits que vous concevez vous-même.
Vous pouvez aussi enregistrer ou recharger vos modifications.
---
""")
st.markdown("# Personnalisation des produits finaux")
with st.expander("Comment utiliser cet onglet ?", expanded=False):
st.markdown("""
1. Cliquez sur "Ajouter un produit final" pour créer un nouveau produit
2. Donnez un nom à votre produit
3. Sélectionnez une opération d'assemblage appropriée (si pertinent)
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 = modifier_produit(G)

View 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}")

View File

@ -1,185 +1,31 @@
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):
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é
Entre 0 et 15%, concentration faible, entre 15 et 25%, modérée, au-delà, forte.
Taille des points = criticité substituabilité du minerai
La taille des points donne l'indication de la criticité de substituabilité du minerai.
""")
if st.button("Lancer", key="btn_ihh_criticite"):
try:
@ -189,9 +35,7 @@ Taille des points = criticité substituabilité du minerai
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.
Taille des points = criticité concurrentielle du minerai
La taille des points donne l'indication de la criticité concurrentielle du minerai.
""")
if st.button("Lancer", key="btn_ihh_ivc"):

3
assets/licence.md Normal file
View 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

View File

@ -10,7 +10,7 @@ def afficher_pied_de_page():
<div role='contentinfo' aria-labelledby='footer-appli' class='wide-footer'>
<div 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 class='footer-note'>
🌱 Calculs CO₂ via <a href='https://www.thegreenwebfoundation.org/' target='_blank'>The Green Web Foundation</a><br>

View File

@ -8,6 +8,47 @@ from utils.gitea import (
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 (
charger_graphe
)
@ -105,7 +146,7 @@ dot_file_path = None
if st.session_state.onglet == "Instructions":
markdown_content = charger_instructions_depuis_gitea(INSTRUCTIONS)
if markdown_content:
st.markdown(markdown_content)
afficher_instructions_avec_expanders(markdown_content)
elif st.session_state.onglet == "Fiches":
interface_fiches()