Spaces:
Sleeping
Sleeping
File size: 13,474 Bytes
ee86836 2a87f5b de9192c f54bb20 de9192c ee86836 652752d 0b09377 2a2fef0 d641f6e cc53ead 9011070 ee86836 9011070 0864c88 4287328 9011070 6361fbb ad8d926 60816b1 99bd437 8f6b234 46bb905 052fb51 8f6b234 a2bea75 de9192c 823fb32 2720506 27d155d 2720506 e60a30b 7d68969 dd0db4e d4d4112 3300273 e45d507 46bb905 503d263 c813aa1 ee838b2 de9192c 05c538b de9192c 0864c88 503d263 46bb905 253292a cc53ead 9011070 1e8b84c 9dadaf7 9011070 9dadaf7 9011070 fb1d823 4255304 fb1d823 9011070 4255304 9011070 ee86836 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 | """Garde-fou contre la croissance silencieuse des fichiers.
Chaque fichier listé dans :data:`FILE_BUDGETS` a un budget en lignes.
Si un fichier dépasse son budget, le test échoue et la PR est forcée
à choisir entre :
1. **Refactor** pour rentrer dans le budget (extraire un sous-module,
factoriser, supprimer du code mort).
2. **Relever le budget délibérément** : modifier la valeur dans ce
fichier en l'expliquant dans le message de commit. La hausse devient
un acte conscient, plus une dérive silencieuse.
Calibration : snapshot v1.0.0 (2026-05-02), ``current + ~15 %`` de marge
pour l'évolution naturelle. Les god-modules historiques (statistics,
generator, runner) gardent un budget proche de leur taille actuelle ; le
choix de les dégonfler est une décision dédiée à un sprint de refactor,
pas un sous-produit de l'invariant.
Re-calibrer à chaque release tag.
"""
from __future__ import annotations
from pathlib import Path
import pytest
REPO_ROOT = Path(__file__).resolve().parents[2]
# Format : chemin relatif → max_lines.
# Seuls les fichiers ≥ 400 lignes sont surveillés (les petits fichiers
# n'ont pas besoin de budget — leur croissance est gérée par les tests
# de couverture, pas par un seuil dur).
FILE_BUDGETS: dict[str, int] = {
# Sprint D.1 (plan v2.0) — adapter de compat run_benchmark legacy
# → BenchmarkService rewrite. Module qui présente l'API
# historique mono-call ``run_benchmark(corpus, engines, ...)``
# consommée par les interfaces CLI/web.
# Sprint D.2.b a ajouté ~260 LOC pour la branche resumable.
# Sprint D.2.c-f a ajouté ~190 LOC : NER attach + over_normalization
# + validate_profile.
# Sprint H.2.c a retiré ``_ocr_only_to_spec`` legacy + simplifié
# ``build_adapter_resolver`` (canonique uniquement).
# Sprint H.4 — module renommé ``_legacy_runner_adapter`` →
# ``benchmark_runner`` (drop le préfixe legacy : c'est l'entry
# point canonique des interfaces vers ``BenchmarkService``).
"picarones/app/services/benchmark_runner.py": 1700, # actuel ~1450
# --- God-modules : budget actuel + 15 % de marge.
# Le rétrécissement sera l'objet d'un sprint de refactor dédié.
# statistics.py (1128 lignes) a été éclaté en sous-package
# ``picarones/measurements/statistics/`` lors du sprint
# « découpage de statistics.py » (2026-05-02). Plus aucun fichier
# de la famille ne dépasse 350 lignes, donc aucune entrée requise.
# runner.py (1019 lignes) a été éclaté en sous-package
# ``picarones/measurements/runner/`` lors du sprint
# « découpage de runner.py » (2026-05-03). Le sous-package a été
# supprimé en Sprint D.6.b du plan v2.0 — son entrée dans
# ``FILE_BUDGETS`` a été retirée.
# --- Refactor (sprint « découpage de generator.py ») : passé de
# 1063 à 431 lignes via extraction vers picarones/report/assets.py
# et le sous-package picarones/report/report_data/. Budget serré
# à 500 pour verrouiller le gain ; toute croissance > 500 sera
# un signal pour redécouper.
# Phase 5.E : ``report/generator.py`` est désormais un shim ;
# canonique dans ``reports/html/generator.py``.
"picarones/reports/html/generator.py": 550, # actuel 471
# --- Fichiers métier larges.
# (Phase 7.D — ``reports/html/renderers/pipeline.py`` supprimé.)
# Phase 4-ter : ``core/results.py`` est désormais un shim
# (≤ 25 l). Le contenu canonique vit dans ``evaluation/`` ;
# même budget pour la même raison historique (modèles
# BenchmarkResult/EngineReport/DocumentResult).
"picarones/evaluation/benchmark_result.py": 750, # actuel 702
# Phase 5.C : ``report/philological_render.py`` est désormais
# un shim (≤ 25 l). Le contenu canonique vit dans
# ``reports/html/renderers/philological.py``.
"picarones/reports/html/renderers/philological.py": 700, # actuel 601
# Sprint E.1 du plan v2.0 — module migré vers ``evaluation/metrics/``.
"picarones/evaluation/metrics/modern_archives.py": 700, # actuel 599
# Sprint E.4 du plan v2.0 — migré vers ``evaluation/metrics/``.
"picarones/evaluation/metrics/builtin_hooks.py": 700, # actuel 590
# Sprint E.5 du plan v2.0 — modules ``history`` et ``robustness``
# migrés depuis ``measurements/`` vers la couche canonique.
"picarones/evaluation/metrics/history.py": 720, # actuel 615
"picarones/evaluation/metrics/robustness.py": 850, # actuel 742
# (Phase 7.D — ``pipeline/legacy_runner.py`` et
# ``pipeline/legacy_pipeline_benchmark.py`` supprimés.)
# Phase 8 — importers IIIF/Gallica déplacés vers ``adapters/corpus/``.
"picarones/adapters/corpus/iiif.py": 675, # actuel 567
"picarones/adapters/corpus/gallica.py": 675, # actuel 563
# Sprint A14-S10 + Lot D — déplacés depuis measurements/.
# L'ancien emplacement (shim) a été supprimé au Lot D ; seul le
# canonique reste dans evaluation/metrics/.
"picarones/evaluation/metrics/levers.py": 675, # actuel 561
"picarones/evaluation/metrics/inter_engine.py": 575, # actuel 484
"picarones/adapters/corpus/escriptorium.py": 650, # actuel 553 (Phase 8)
# Sprint A14-S1 — A.I.0 P0 : ajout de validated_path,
# validated_prompt_filename, safe_report_name et compute_workspace_roots.
# Ces helpers seront extraits dans ``picarones/web/path_security.py``
# lors du Sprint S20 du rewrite ciblé (création couche app/services/).
# Sprint F du plan v2.0 — déplacé vers ``interfaces/web/``.
"picarones/interfaces/web/security.py": 850, # actuel 751
# Sprint A14-S8 — CorpusRunner introduit pour orchestrer les
# pipelines composées sur un corpus avec backpressure / timeout
# réel / annulation propre. Budget stable, l'extension
# ProcessPoolExecutor (S11) restera dans cette enveloppe.
"picarones/pipeline/runner.py": 550, # actuel 462
# Sprint A14-S28 — PipelineExecutor refondu pour consommer un
# ExecutionPlan (run_plan) tout en gardant run(spec) comme sucre.
# PipelinePlanner introduit pour transformer une PipelineSpec en
# plan immuable (validation + bindings + jonctions de métriques).
# Sprint A14-S47 — branchement ArtifactStore : +60 lignes (lookup
# cache avant exec, persistance après succès, helpers privés).
"picarones/pipeline/executor.py": 600, # actuel 541
"picarones/pipeline/planner.py": 465, # actuel 403
# Sprint A14-S29 — ArtifactStore (ABC + 2 implémentations) avec
# hash multi-paramètres pour adresser la critique d'audit n° 14
# « hash multi-paramètres + reprise par hash ».
"picarones/adapters/storage/artifact_store.py": 580, # actuel 504
# Sprint A14-S37 + S52 + S56 — JobStore SQLite : POST/GET/DELETE,
# JobStoreError, schema_version table (S56) + busy_timeout 30s +
# WAL mode pour les jobs concurrents.
"picarones/adapters/storage/job_store.py": 500, # actuel 421
# Sprint A14-S41 — artifacts_index.jsonl séparé.
"picarones/app/services/benchmark_service.py": 470, # actuel 400
# Sprint A14-S44 — BaseLLMAdapter implémente le contrat StepExecutor
# (input_types, output_types, execute) en plus de complete().
# S59 ajout du descripteur ``_DeprecatedAttribute`` + alias rétrocompat
# ``DEFAULT_CORRECTION_PROMPT`` + warning lang fallback (M6).
"picarones/adapters/llm/base.py": 560, # actuel 486
# Phase 4-quater : ``core/corpus.py`` est désormais un shim
# (≤ 30 l). Le contenu canonique vit dans ``evaluation/`` ;
# même budget pour la même raison historique
# (Document/Corpus/GTLevel + 5 payloads + load_corpus_from_directory).
"picarones/evaluation/corpus.py": 600, # actuel 533
# Sprint H.1 du plan v2.0 — ``fixtures.py`` migré vers
# ``evaluation/synthetic.py``.
"picarones/evaluation/synthetic.py": 600, # actuel 510
# Phase 5.C.batch7 + Lot D : le shim
# ``measurements/roman_numerals.py`` a été supprimé. Seul le
# canonique ``evaluation/metrics/roman_numerals.py`` reste.
"picarones/evaluation/metrics/roman_numerals.py": 575, # actuel 484
# Sprint A14-S11 + Lot I — déplacés depuis extras/importers/.
# Les shims ``extras/importers/{htr_united, huggingface,
# _fallback_log}`` ont été supprimés au Lot I (mai 2026).
"picarones/adapters/corpus/htr_united.py": 575, # actuel 473
"picarones/adapters/corpus/huggingface.py": 550, # actuel 464
# Sprint G du plan v2.0 — déplacé vers ``interfaces/cli/``.
"picarones/interfaces/cli/_workflows.py": 550, # actuel 469
# ``__init__.py`` du legacy CLI — plus gros que les autres car il
# contient les commandes ``info``, ``engines``, ``metrics``,
# ``report``, ``demo``.
"picarones/interfaces/cli/__init__.py": 500, # actuel 396
# Phase 4-ter : ``core/metric_hooks.py`` est désormais un shim
# (≤ 80 l). Le contenu canonique vit dans ``evaluation/`` ;
# même budget pour la même raison historique (centralise les
# hooks document/corpus, croissance maîtrisée).
"picarones/evaluation/metric_hooks.py": 500, # actuel 427
# Phase 5.C.batch7 : ``measurements/numerical_sequences.py`` est
# désormais un shim ; canonique dans
# ``evaluation/metrics/numerical_sequences.py``.
"picarones/evaluation/metrics/numerical_sequences.py": 500, # actuel 428
# Sprint A14-S9 + Lot D — déplacé depuis measurements/normalization.py.
# Le shim a été supprimé au Lot D ; seul le canonique reste.
"picarones/formats/text/normalization.py": 500, # actuel 420
# Phase 5.E : ``report/comparison.py`` est désormais un shim ;
# canonique dans ``reports/html/comparison.py``.
"picarones/reports/html/comparison.py": 500, # actuel 414
# --- Module mutualisé créé par le sprint des render helpers
# (Sprint « consolidation des renderers » 2026-05-02). Budget
# calibré sur la taille post-documentation des conventions.
# Phase 5 : ``report/render_helpers.py`` est désormais un shim
# (≤ 25 l). Le contenu canonique vit dans
# ``reports/_helpers/`` ; même budget pour la même raison
# historique (consolidation des 25 helpers de couleur).
"picarones/reports/_helpers/render_helpers.py": 480, # actuel 428
# --- Services applicatifs et orchestration du rewrite ciblé.
# Budgets calibrés à current + 15 % de marge. La CLI elle-même
# reste mince (~110 lignes) — toute logique métier vit dans
# ``app/services/``.
"picarones/app/services/corpus_service.py": 625, # actuel 541
"picarones/app/services/path_security.py": 470, # actuel 410
"picarones/app/services/run_orchestrator.py": 500, # actuel 432
# Le rendu HTML vit en couche ``reports/`` (cible documentée
# du rewrite — un rapport est un format de sortie, pas un
# service métier).
"picarones/reports/html/render.py": 700, # actuel 615
}
def _line_count(path: Path) -> int:
"""Compte les lignes physiques (y compris vides)."""
return len(path.read_text(encoding="utf-8").splitlines())
@pytest.mark.parametrize(
("rel_path", "budget"),
sorted(FILE_BUDGETS.items()),
)
def test_file_size_within_budget(rel_path: str, budget: int) -> None:
"""Chaque fichier surveillé doit rester ≤ budget."""
path = REPO_ROOT / rel_path
assert path.exists(), (
f"Fichier disparu : {rel_path}. "
"Retire l'entrée de FILE_BUDGETS dans "
"tests/architecture/test_file_budgets.py."
)
actual = _line_count(path)
assert actual <= budget, (
f"\n{rel_path} a {actual} lignes (budget {budget}).\n\n"
"Soit refactor pour rentrer dans le budget, soit relève le budget "
"consciemment dans tests/architecture/test_file_budgets.py "
"avec une justification dans le message de commit."
)
def test_no_orphaned_budget_entries() -> None:
"""Toute entrée de FILE_BUDGETS doit pointer vers un fichier existant."""
missing = [p for p in FILE_BUDGETS if not (REPO_ROOT / p).exists()]
assert not missing, (
f"Entrées orphelines dans FILE_BUDGETS : {missing}. "
"Le fichier a été déplacé/supprimé — retire l'entrée."
)
def test_budget_table_covers_all_large_files() -> None:
"""Tout fichier ≥ 400 lignes doit avoir une entrée dans FILE_BUDGETS.
Empêche un fichier nouveau ou subitement gros d'échapper à la
surveillance. Si un fichier dépasse 400 lignes, ajoute-le à
FILE_BUDGETS avec son budget (current + 15 %).
"""
threshold = 400
untracked: list[tuple[str, int]] = []
for path in (REPO_ROOT / "picarones").rglob("*.py"):
rel = path.relative_to(REPO_ROOT).as_posix()
if rel in FILE_BUDGETS:
continue
count = _line_count(path)
if count >= threshold:
untracked.append((rel, count))
assert not untracked, (
f"\nFichiers ≥ {threshold} lignes non surveillés :\n"
+ "\n".join(f" {p} ({n} lignes)" for p, n in sorted(untracked))
+ "\n\nAjoute-les à FILE_BUDGETS dans "
"tests/architecture/test_file_budgets.py avec budget = current + ~15 %."
)
|