refactor: retire les parseurs texte de repli

Les parseurs géométriques (par cellules de tableau) traitent les 5 PDF du
corpus sans jamais déclencher le repli : les parseurs texte étaient 586
lignes de regex fragiles, non testées, à maintenir à chaque évolution du
format de sortie. Les références golden sont inchangées après leur
suppression — l'extraction produit exactement la même chose.

Le parseur géométrique devient donc le seul chemin : ce qu'il ne lit pas
est perdu. L'orchestrateur distingue maintenant les deux cas :

- aucun lot lu -> ExtractionError (422 côté API). Un compte rendu sans
  lot n'existe pas : c'est le tableau qui n'a pas été reconnu, et mieux
  vaut échouer que d'enregistrer un document vide découvert bien plus
  tard, au moment de relire les chiffres ;
- aucune opération -> accepté. Un mois sans dépense reste plausible.

_extract_lot_code_from_description était la seule fonction encore
utilisée : elle rejoint utils.lots sous le nom
extract_lot_numero_from_description, à côté de normalize_lot_numero.
extract_text_from_pdf, sans appelant, disparaît au passage.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-26 09:44:20 +02:00
parent 1b319dff4e
commit ea5bdac18a
9 changed files with 122 additions and 635 deletions

View File

@@ -48,6 +48,36 @@ def normalize_lot_numero(value: str | int | None) -> str | None:
return significant.zfill(LOT_NUMERO_WIDTH)
def extract_lot_numero_from_description(description: str) -> str | None:
"""Extrait le numéro de lot depuis la description d'une opération.
Les codes lots suivent le format {Lettre}{Numéro} où la lettre identifie
l'immeuble (M=Marietton, S=Servient, B=Bloch…) et le numéro le lot.
Exemples:
- "M06 - Commande moteur pompe" -> "06"
- "S05 - Mise en service" -> "05"
- "B01 - Plaques" -> "01"
Args:
description: Description de l'opération
Returns:
Numéro sur 2 chiffres (ex: "06") ou None si non trouvé
"""
if not description:
return None
# Pattern: lettre majuscule + (espace optionnelle) + 1-2 chiffres, terminé par
# un espace, un tiret ou la fin. Gère "S10 - ...", "S 17 - ..." (espace dans le
# code) et "S01 SOLDE ..." (code lot non suivi d'un tiret).
match = re.search(r"\b[A-Z]\s*(\d{1,2})(?=[\s-]|$)", description)
if match:
return normalize_lot_numero(match.group(1))
return None
def normalize_extraction_lots(data: dict[str, Any]) -> dict[str, Any]:
"""Normalise, sur place, tous les numéros de lot d'une extraction.