diff --git a/.env b/.env index d08598c..c43e823 100644 --- a/.env +++ b/.env @@ -1,4 +1,3 @@ -ENV = "dev" ENV_CODE = "dev" PORT=8502 DOT_FILE = "schema.txt" diff --git a/.gitignore b/.gitignore index 4f059aa..fb52b72 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,8 @@ HTML/ # Ignorer données Fiches (adapté à ton projet) Instructions.md Fiches/ +HTML/ +static/Fiches/ # Autres spécifiques si besoin .DS_Store diff --git a/README.md b/README.md index 444d995..9c9f140 100644 --- a/README.md +++ b/README.md @@ -34,17 +34,33 @@ Le fichier **.env.local** qui contient GITEA_TOKEN n'est pas dans le dépôt car Pour l'environnement de pré-production, (https://fabnum-dev.peccini.fr)[https://fabnum-dev.peccini.fr] : - ENV=dev + ENV_CODE = "dev" PORT=8502 + DOT_FILE = "schema.txt" GITEA_URL = "https://fabnum-git.peccini.fr/api/v1" ORGANISATION = "fabnum" DEPOT_FICHES = "fiches" + DEPOT_CODE = "code" + ID_PROJET = "3" + INSTRUCTIONS = "Instructions.md" + FICHE_IHH = "Fiches/Criticités/Fiche technique IHH.md" + FICHE_ICS = "Fiches/Criticités/Fiche technique ICS.md" + FICHE_ISG = "Fiches/Criticités/Fiche technique ISG.md" + FICHE_IVC = "Fiches/Criticités/Fiche technique IVC.md" Pour l'environnement de production, (https://fabnum.peccini.fr)[https://fabnum.peccini.fr], le fichier est identique sauf pour : - ENV=public PORT=8501 +La différence entre les deux environnements se fait au travers de la configuration du proxy Nginx (ici celle de dev ; il suffit de changer dev en public pour l'environnement de production) : + + # Ajout d'un en-tête personnalisé pour indiquer l'environnement + add_header X-Environment "dev" always; + # Transmettre l'en-tête d'environnement au backend + proxy_set_header X-Environment "dev"; + +Cette configuration est utilisée par config.py. + dev et public sont les deux branches officielles du dépôt. L'application se lance simplement sous la forme : @@ -66,12 +82,10 @@ Pour automatiser le lancement, il est intégré dans systemd : [Service] User=fabnum WorkingDirectory=/home/fabnum/fabnum-dev - ExecStart=/home/fabnum/fabnum-dev/venv/bin/streamlit run /home/fabnum/fabnum-dev/fa - bnum.py + ExecStart=/home/fabnum/fabnum-dev/venv/bin/streamlit run /home/fabnum/fabnum-dev/fabnum.py --server.port 8502 Restart=always RestartSec=5 Environment=PYTHONUNBUFFERED=1 - SELinuxContext=system_u:system_r:httpd_t:s0 [Install] WantedBy=multi-user.target diff --git a/batch_generate_fiches.py b/batch_generate_fiches.py new file mode 100644 index 0000000..b271589 --- /dev/null +++ b/batch_generate_fiches.py @@ -0,0 +1,200 @@ +#!/usr/bin/env python3 +import os +import re +import yaml +import requests +import argparse +import logging + +# Import des fonctions de génération +from app.fiches.generer import ( + generer_fiche +) +from app.fiches.utils.fiche_utils import load_seuils +from utils.gitea import charger_arborescence_fiches +from config import GITEA_TOKEN, FICHES_CRITICITE + +# Configuration du logging +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(levelname)s - %(message)s', + handlers=[ + logging.FileHandler("batch_generation.log"), + logging.StreamHandler() + ] +) +logger = logging.getLogger(__name__) + +def get_fiche_type(md_source): + """Extrait le type de fiche depuis le frontmatter YAML.""" + front_match = re.match(r"(?s)^---\n(.*?)\n---\n", md_source) + if not front_match: + return "autre" + + context = yaml.safe_load(front_match.group(1)) + return context.get("type_fiche", "autre") + +def get_indice_court(md_source): + """Extrait l'indice court depuis le frontmatter YAML.""" + front_match = re.match(r"(?s)^---\n(.*?)\n---\n", md_source) + if not front_match: + return None + + context = yaml.safe_load(front_match.group(1)) + return context.get("indice_court") + +def batch_generate_fiches(output_dir="", force_regenerate=False): + """Génère toutes les fiches en batch en suivant un ordre de priorité.""" + # Assurer que les répertoires de sortie existent + if output_dir: + os.makedirs(output_dir, exist_ok=True) + + # Toujours créer les répertoires nécessaires + os.makedirs(os.path.join("Fiches"), exist_ok=True) + os.makedirs(os.path.join("HTML"), exist_ok=True) + os.makedirs(os.path.join("static", "Fiches"), exist_ok=True) + + # Charger les seuils + try: + seuils = load_seuils("assets/config.yaml") + logger.info("Seuils chargés avec succès") + except Exception as e: + logger.error(f"Erreur lors du chargement des seuils: {e}") + return + + # Charger l'arborescence des fiches + try: + arborescence = charger_arborescence_fiches() + logger.info(f"Arborescence chargée: {len(arborescence)} dossiers trouvés") + except Exception as e: + logger.error(f"Erreur lors du chargement de l'arborescence: {e}") + return + + # Créer une liste de toutes les fiches avec leurs informations + toutes_fiches = [] + for dossier, fiches in arborescence.items(): + for fiche in fiches: + toutes_fiches.append({ + "dossier": dossier, + "nom": fiche["nom"], + "download_url": fiche["download_url"], + "type": fiche.get("type", "autre") + }) + + logger.info(f"Total de {len(toutes_fiches)} fiches à traiter") + + # Organisation des fiches par type pour respecter l'ordre de priorité + fiches_par_type = { + "criticite": [], + "assemblage": [], + "fabrication": [], + "minerai": [], + "autre": [] + } + + # Télécharger et catégoriser les fiches + headers = {"Authorization": f"token {GITEA_TOKEN}"} + + # Création des listes spécifiques pour faciliter le traitement ordonné + fiches_criticite = [] + autres_fiches = [] + + # Première étape : identifier les fiches de criticité + logger.info("Identification des fiches de criticité...") + for fiche in toutes_fiches: + # Vérifier si c'est une fiche de criticité par deux méthodes + est_criticite = False + + # Méthode 1: vérifier par le nom du fichier + if fiche["nom"] in FICHES_CRITICITE: + est_criticite = True + logger.info(f"Fiche de criticité identifiée par nom: {fiche['nom']}") + + # Méthode 2: vérifier par le dossier + elif fiche["dossier"] == "Criticités": + est_criticite = True + logger.info(f"Fiche de criticité identifiée par dossier: {fiche['nom']}") + + if est_criticite: + fiches_criticite.append(fiche) + else: + autres_fiches.append(fiche) + + # Traiter d'abord les fiches de criticité + logger.info(f"Traitement prioritaire des fiches de criticité ({len(fiches_criticite)} fiches)") + criticite_count = 0 + + for fiche in fiches_criticite: + try: + # Télécharger la fiche de criticité + reponse = requests.get(fiche["download_url"], headers=headers) + reponse.raise_for_status() + md_source = reponse.text + + # Générer immédiatement la fiche de criticité + logger.info(f"Génération prioritaire de la fiche de criticité: {fiche['dossier']}/{fiche['nom']}") + generer_fiche(md_source, fiche["dossier"], fiche["nom"], seuils) + + # Ajouter à la liste pour le décompte + fiches_par_type["criticite"].append(fiche) + criticite_count += 1 + except Exception as e: + logger.error(f"Erreur lors de la génération de la fiche de criticité {fiche['nom']}: {e}") + + logger.info(f"{criticite_count} fiches de criticité générées") + + # Maintenant catégoriser et traiter les fiches restantes (non-criticité) + for fiche in autres_fiches: + try: + # Télécharger le contenu pour déterminer le type + reponse = requests.get(fiche["download_url"], headers=headers) + reponse.raise_for_status() + md_source = reponse.text + + # Déterminer le type + type_fiche = get_fiche_type(md_source) + if type_fiche in fiches_par_type: + fiches_par_type[type_fiche].append({**fiche, "md_source": md_source}) + else: + fiches_par_type["autre"].append({**fiche, "md_source": md_source}) + + except Exception as e: + logger.error(f"Erreur lors du traitement de {fiche['nom']}: {e}") + + # Ordre de traitement pour les fiches restantes + ordre_types = ["assemblage", "fabrication", "minerai", "autre"] + + # Générer les fiches restantes dans l'ordre spécifié + for type_fiche in ordre_types: + logger.info(f"Traitement des fiches de type '{type_fiche}' ({len(fiches_par_type[type_fiche])} fiches)") + + for fiche in fiches_par_type[type_fiche]: + try: + md_source = fiche["md_source"] + dossier = fiche["dossier"] + nom_fichier = fiche["nom"] + + # Vérifier si la génération est nécessaire + html_path = os.path.join("HTML", dossier, os.path.splitext(nom_fichier)[0] + ".html") + if force_regenerate or not os.path.exists(html_path): + logger.info(f"Génération de {dossier}/{nom_fichier}") + generer_fiche(md_source, dossier, nom_fichier, seuils) + else: + logger.info(f"Ignoré (déjà généré): {dossier}/{nom_fichier}") + + except Exception as e: + logger.error(f"Erreur lors de la génération de {fiche['nom']}: {e}") + + logger.info("Génération batch terminée") + +def main(): + parser = argparse.ArgumentParser(description="Générateur batch de fiches") + parser.add_argument("--output", "-o", default="", help="Répertoire de sortie (laissez vide pour utiliser les dossiers de l'application)") + parser.add_argument("--force", "-f", action="store_true", help="Forcer la régénération de toutes les fiches") + args = parser.parse_args() + + logger.info(f"Démarrage de la génération batch (output: {args.output if args.output else 'dossiers par défaut'}, force: {args.force})") + batch_generate_fiches(output_dir=args.output, force_regenerate=args.force) + +if __name__ == "__main__": + main() diff --git a/config.py b/config.py index 9128a24..14eb895 100644 --- a/config.py +++ b/config.py @@ -1,15 +1,35 @@ import os +import streamlit as st from dotenv import load_dotenv load_dotenv(".env") load_dotenv(".env.local", override=True) +# Fonction pour déterminer l'environnement à partir de l'en-tête X-Environment +def determine_environment(): + # Valeur par défaut (si aucun en-tête n'est détecté) + environment = "dev" + + # Si nous sommes dans une session Streamlit + if hasattr(st, 'context') and hasattr(st.context, 'headers'): + try: + # Lire directement l'en-tête X-Environment défini par Nginx : dev/public + nginx_env = st.context.headers.get("x-environment") + + if nginx_env: + environment = nginx_env.lower() + except Exception as e: + st.error(f"Erreur lors de la lecture de l'en-tête X-Environment: {e}\nEnvironnement dev par défaut") + + return environment + +ENV = determine_environment() + GITEA_URL = os.getenv("GITEA_URL", "https://fabnum-git.peccini.fr/api/v1") GITEA_TOKEN = os.getenv("GITEA_TOKEN", "") ORGANISATION = os.getenv("ORGANISATION", "fabnum") DEPOT_FICHES = os.getenv("DEPOT_FICHES", "fiches") DEPOT_CODE = os.getenv("DEPOT_CODE", "code") -ENV = os.getenv("ENV") ENV_CODE = os.getenv("ENV_CODE") DOT_FILE = os.getenv("DOT_FILE") INSTRUCTIONS = os.getenv("INSTRUCTIONS", "Instructions.md") diff --git a/fabnum.py b/fabnum.py index 9fb2499..cb1d0b3 100644 --- a/fabnum.py +++ b/fabnum.py @@ -1,4 +1,12 @@ import streamlit as st + +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 # Configuration Gitea @@ -69,13 +77,6 @@ from app.visualisations import interface_visualisations from app.personnalisation import interface_personnalisation from app.analyse import interface_analyse -st.set_page_config( - page_title="Fabnum – Analyse de chaîne", - page_icon="assets/weakness.png", # ajout - layout="centered", - initial_sidebar_state="expanded" -) - # Initialisation des traductions (langue française par défaut) init_translations()