Code/app/fiches/utils/fiche_utils.py
2025-06-05 09:28:49 +02:00

173 lines
5.6 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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.

"""
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