Code/fabnum.py
Stéphan Peccini 6d2e877341
feat(audit): audit qualité complet — 907→0 erreurs ruff + fix multiselect labels
- Correction des 907 erreurs ruff (pathlib, imports, nommage, simplifications, docstrings)
- Fix déduplication labels dans multiselect nœuds d'arrivée (analyse)
- Expansion 1→N label→IDs pour le Sankey (Pays d'opération)
- Ajout CLAUDE.md et document de design de l'audit
- Mise à jour .gitignore (artefacts tests exploratoires)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-02 11:52:01 +01:00

212 lines
7.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import utils.persistance
utils.persistance.update_session_paths()
import streamlit as st
from utils.persistance import get_champ_statut, get_session_id
st.set_page_config(
page_title="Fabnum Analyse de chaîne",
page_icon="assets/weakness.png", # ajout
layout="centered",
initial_sidebar_state="expanded"
)
import re
from pathlib import Path
# Configuration Gitea
from config import ENV, INSTRUCTIONS
from utils.gitea import charger_instructions_depuis_gitea
# Import du module de traductions
from utils.translations import _, init_translations, set_language
from utils.widgets import html_expander
def afficher_instructions_avec_expanders(markdown_content):
"""Affiche le contenu markdown avec les sections de niveau 2 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 = i == 1
# with st.expander(f"## {titre_section}", expanded=status):
html_expander(f"{titre_section}", content=contenu_section, open_by_default=status, details_class="details_introduction")
from app.analyse import interface_analyse
from app.fiches import interface_fiches
from app.ia_nalyse import interface_ia_nalyse
from app.personnalisation import interface_personnalisation
from app.plan_d_action.interface import interface_plan_d_action
from app.visualisations import interface_visualisations
from components.footer import afficher_pied_de_page
from components.header import afficher_entete
from components.sidebar import afficher_impact, afficher_menu
from utils.graph_utils import charger_graphe
# Initialisation des traductions (langue française par défaut)
init_translations()
# Pour tester d'autres langues, décommenter cette ligne :
set_language("fr")
#
# Important
# Avec Selinux, il faut mettre les bons droits :
#
# sudo semanage fcontext -a -t var_log_t '/var/log/nginx/fabnum-public\.access\.log'
# sudo restorecon -v /var/log/nginx/fabnum-public.access.log
#
session_id = get_session_id()
def get_total_bytes_for_session(session_id):
"""Calcule le volume total d'octets transférés pour une session Nginx."""
total_bytes = 0
try:
with Path(f"/var/log/nginx/fabnum-{ENV}.access.log").open() as f:
for line in f:
if session_id in line:
match = re.search(r'"GET.*?" \d+ (\d+)', line)
if match:
bytes_sent = int(match.group(1))
total_bytes += bytes_sent
except Exception as e:
st.error(f"Erreur lecture log: {e}")
return total_bytes
def charger_theme():
"""Charge et injecte les fichiers CSS du thème sélectionné."""
# Chargement des fichiers CSS (une seule fois)
if "base_css_content" not in st.session_state:
with Path("assets/styles/base.css").open() as f:
st.session_state["base_css_content"] = f.read()
if "theme_css_content_light" not in st.session_state:
with Path("assets/styles/theme-light.css").open() as f:
st.session_state["theme_css_content_light"] = f.read()
if "theme_css_content_dark" not in st.session_state:
with Path("assets/styles/theme-dark.css").open() as f:
st.session_state["theme_css_content_dark"] = f.read()
# Mappage des noms traduits vers les noms internes
theme_mapping = {
"clair": "light",
"sombre": "dark",
"light": "light",
"dark": "dark"
}
# Thème en cours (conversion du nom traduit vers l'identifiant interne)
st.session_state["theme_mode"] = get_champ_statut("theme_mode")
current_theme_display = st.session_state.get("theme_mode").lower()
current_theme = theme_mapping.get(current_theme_display, "light") # Par défaut light si non trouvé
theme_css = st.session_state[f"theme_css_content_{current_theme}"]
# Injection des CSS dans le bon ordre
# 1. D'abord les variables du thème
st.markdown(f"<style>{theme_css}</style>", unsafe_allow_html=True)
# 2. Ensuite le CSS de base
st.markdown(f"<style>{st.session_state['base_css_content']}</style>", unsafe_allow_html=True)
def ouvrir_page():
"""Initialise la page avec le thème, l'en-tête et le menu latéral."""
charger_theme()
afficher_entete()
afficher_menu()
st.markdown("""
<main role="main">
""", unsafe_allow_html=True)
def fermer_page():
"""Ferme les balises HTML de la page et affiche le pied de page."""
st.markdown("</div>", unsafe_allow_html=True)
st.markdown("""</section>""", unsafe_allow_html=True)
st.markdown("</main>", unsafe_allow_html=True)
total_bytes = get_total_bytes_for_session(get_session_id())
afficher_pied_de_page()
afficher_impact(total_bytes)
ouvrir_page()
dot_file_path = None
# Obtenir les noms traduits des onglets
instructions_tab = _("navigation.instructions")
fiches_tab = _("navigation.fiches")
personnalisation_tab = _("navigation.personnalisation")
analyse_tab = _("navigation.analyse")
ia_nalyse_tab = _("navigation.ia_nalyse")
plan_d_action_tab = _("navigation.plan_d_action")
visualisations_tab = _("navigation.visualisations")
navigation_onglet = get_champ_statut("navigation_onglet")
if navigation_onglet == instructions_tab:
markdown_content = charger_instructions_depuis_gitea(INSTRUCTIONS)
if markdown_content:
afficher_instructions_avec_expanders(markdown_content)
elif navigation_onglet == fiches_tab:
interface_fiches()
else:
# Charger le graphe une seule fois
# Le graphe n'est pas nécessaire pour Instructions ou Fiches
dot_file_path = charger_graphe()
if dot_file_path and navigation_onglet == analyse_tab:
G_temp = st.session_state["G_temp"]
interface_analyse(G_temp)
elif dot_file_path and navigation_onglet == ia_nalyse_tab:
G_temp = st.session_state["G_temp"]
interface_ia_nalyse(G_temp)
elif dot_file_path and navigation_onglet == plan_d_action_tab:
G_temp = st.session_state["G_temp"]
interface_plan_d_action(G_temp)
elif dot_file_path and navigation_onglet == visualisations_tab:
G_temp = st.session_state["G_temp"]
G_temp_ivc = st.session_state["G_temp_ivc"]
interface_visualisations(G_temp, G_temp_ivc)
elif dot_file_path and navigation_onglet == personnalisation_tab:
G_temp = st.session_state["G_temp"]
G_temp = interface_personnalisation(G_temp)
fermer_page()