Cette mise a jour complete ameliore significativement la qualite et la maintenabilite du projet. 1. Extension de la couverture de tests Couverture globale passee de 8% a 16% (+100%) - Ajout de 25 nouveaux tests (total: 67 tests, 100% passent) - Nouveaux fichiers de tests: * tests/unit/test_gitea.py (17 tests) * tests/unit/test_fiches_tickets.py (8 tests) Etat de la couverture par module: - utils/gitea.py: 100% - utils/widgets.py: 100% - utils/logger.py: 94% - app/fiches/utils/tickets/core.py: 77% - utils/graph_utils.py: 59% 2. Documentation d'architecture complete Creation de 3 nouveaux documents (30 Ko total): - docs/ARCHITECTURE.md (15 Ko) * Architecture complete du projet * Flux de donnees detailles * Indices de vulnerabilite (IHH, ISG, ICS, IVC) * Structure du graphe NetworkX - docs/MODULES.md (15 Ko) * Guide des 11 modules principaux * Exemples de code (15+ snippets) * Bonnes pratiques * Guide de depannage - docs/README.md (4 Ko) * Index de toute la documentation Contenu documente: - 5 modules applicatifs - 6 modules utilitaires - 4 indices de vulnerabilite avec formules et seuils - Conventions de code 3. Reorganisation de la documentation Structure finale optimisee: - Racine: README.md (mis a jour) + Instructions.md - docs/: 11 documents organises par categorie Fichiers deplaces vers docs/: - README_connexion.md -> docs/CONNEXION.md - GUIDE_LOGS.md -> docs/ - GUIDE_RUFF.md -> docs/ - RAPPORT_RUFF.md -> docs/ - RAPPORT_CORRECTIONS_AUTO.md -> docs/ - REFACTORING_REPORT.md -> docs/ - VERIFICATION_LOGS.md -> docs/ - TODO_IA_BATCH.md -> docs/ 4. Ajout de docstrings 52 fonctions documentees en style Google (100%) Documentation en francais avec Args, Returns, Raises 5. Corrections automatiques Ruff Application de 347 corrections automatiques: - Formatage du code (line-length: 120) - Organisation des imports - Simplifications syntaxiques - Suppressions de code mort - Ameliorations de performance 6. Configuration qualite du code Nouveaux fichiers: - pyproject.toml: configuration Ruff complete - .vscode/settings.json: integration Ruff avec formatOnSave - GUIDE_RUFF.md: documentation du linter - GUIDE_LOGS.md: documentation du logging - .gitignore: ajout htmlcov/ pour rapports de couverture Etat final du projet: - Linter: Ruff configure (15 regles actives) - Tests: 67 tests (100% passent) - Couverture de code: 16% - Docstrings: 52/52 (100%) - Documentation: 11 fichiers organises Impact: - Tests plus robustes et maintenables - Documentation technique complete - Meilleure organisation des fichiers - Workflow optimise avec Ruff - Code pret pour integration continue References: - Architecture: docs/ARCHITECTURE.md - Guide modules: docs/MODULES.md - Tests: tests/unit/ - Configuration: pyproject.toml Co-Authored-By: Claude <noreply@anthropic.com>
176 lines
5.6 KiB
Python
176 lines
5.6 KiB
Python
"""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 pathlib
|
||
import re
|
||
from datetime import datetime, timezone
|
||
|
||
import frontmatter
|
||
import jinja2
|
||
import yaml
|
||
|
||
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)
|
||
|
||
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
|