""" fiche_utils.py – outils de lecture / rendu des fiches Markdown (indices et opérations) Dépendances : pip install python-frontmatter pyyaml jinja2 Usage : from fiche_utils import load_seuils, render_fiche_markdown seuils = load_seuils("config/indices_seuils.yaml") markdown_rendered = render_fiche_markdown(raw_md_text, seuils) """ from __future__ import annotations import os import frontmatter, yaml, jinja2, re, pathlib from typing import Dict from datetime import datetime, timezone from utils.gitea import recuperer_date_dernier_commit def load_seuils(path: str | pathlib.Path = "config/indices_seuils.yaml") -> Dict: """Charge le fichier YAML des seuils et renvoie le dict 'seuils'. Args: path (str | pathlib.Path, optional): Chemin vers le fichier des seuils. Defaults to "config/indices_seuils.yaml". Returns: Dict: Dictionnaire contenant les seuils. """ data = yaml.safe_load(pathlib.Path(path).read_text(encoding="utf-8")) return data.get("seuils", {}) def _migrate_metadata(meta: Dict) -> Dict: """Normalise les clés YAML (ex : sheet_type → type_fiche). Args: meta (Dict): Métadonnées à normaliser. Returns: Dict: Métadonnées normalisées. """ keymap = { "sheet_type": "type_fiche", "indice_code": "indice_court", # si besoin } for old, new in keymap.items(): if old in meta and new not in meta: meta[new] = meta.pop(old) return meta def render_fiche_markdown( md_text: str, seuils: Dict, license_path: str = "assets/licence.md" ) -> str: """Renvoie la fiche rendue (Markdown) avec les placeholders remplacés et le tableau de version. Args: md_text (str): Contenu Markdown brut. seuils (Dict): Tableau des versions. license_path (str, optional): Chemin vers le fichier de licence. Defaults to "assets/licence.md". Returns: str: Fiche Markdown rendue avec les placeholders remplacés et la table de version. Note: - Les licences sont ajoutées après le tableau de version. - Les titres de niveau 2 doivent être présents pour l'insertion automatique de licence. """ post = frontmatter.loads(md_text) meta = _migrate_metadata(dict(post.metadata)) body_template = post.content # Instancie Jinja2 en 'StrictUndefined' pour signaler les placeholders manquants. env = jinja2.Environment( undefined=jinja2.StrictUndefined, autoescape=False, trim_blocks=True, lstrip_blocks=True, ) tpl = env.from_string(body_template) rendered_body = tpl.render(**meta, seuils=seuils) # Option : ajoute automatiquement titre + tableau version si absent. header = f"# {meta.get('indice', meta.get('titre',''))} ({meta.get('indice_court','')})" if not re.search(r"^# ", rendered_body, flags=re.M): rendered_body = f"""{header} {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 import streamlit as st st.error(e) pass return rendered_body def fichier_plus_recent( chemin_fichier: str|None, reference: datetime ) -> bool: """Vérifie si un fichier est plus récent que la référence donnée. Args: chemin_fichier (str): Chemin vers le fichier à vérifier. reference (datetime): Référence temporelle de comparaison. Returns: bool: True si le fichier est plus récent, False sinon. """ try: modif = datetime.fromtimestamp(os.path.getmtime(chemin_fichier), tz=timezone.utc) return modif > reference except Exception: return False def doit_regenerer_fiche( html_path: str, fiche_type: str, fiche_choisie: str, commit_url: str, fichiers_criticite: Dict[str, str] ) -> bool: """Détermine si une fiche doit être regénérée. Args: html_path (str): Chemin vers le fichier HTML. fiche_type (str): Type de la fiche. fiche_choisie (str): Nom choisi pour la fiche. commit_url (str): URL du dernier commit. fichiers_criticite (Dict[str, str]): Dictionnaire des fichiers critiques. Returns: bool: True si la fiche doit être regénérée, False sinon. """ if not os.path.exists(html_path): return True local_mtime = datetime.fromtimestamp(os.path.getmtime(html_path), tz=timezone.utc) remote_mtime = recuperer_date_dernier_commit(commit_url) if remote_mtime is None or remote_mtime > local_mtime: return True if fichier_plus_recent(fichiers_criticite.get("IHH"), local_mtime): return True if fiche_type == "minerai" or "minerai" in fiche_choisie.lower(): if fichier_plus_recent(fichiers_criticite.get("IVC"), local_mtime): return True if fichier_plus_recent(fichiers_criticite.get("ICS"), local_mtime): return True return False