feat: normalise les numéros de lot sur 2 chiffres

Les PDF et les saisies manuelles écrivent le même lot de plusieurs façons
("6", "06", "0006"), et chaque forme créait jusqu'ici un lot distinct en
base. utils/lots.py fixe la forme canonique sur 2 chiffres et sert de point
d'entrée unique pour la normalisation.

Elle est appliquée à la source dans les parseurs (locataires texte et
tableau, codes lot des opérations), et en dernier recours dans
get_or_create_lot et save_document, pour couvrir les extractions éditées à
la main via l'API. Les numéros à plus de 2 chiffres significatifs ne sont
pas tronqués.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-25 11:46:33 +02:00
parent 0e30ed8501
commit 420a856e41
9 changed files with 232 additions and 18 deletions

View File

@@ -53,7 +53,7 @@ def sample_data():
},
"situation_locataires": [
{
"lot": {"numero": "001", "type": "Appartement"},
"lot": {"numero": "01", "type": "Appartement"},
"locataire": {"nom": "DUPONT"},
"lignes": [
{

View File

@@ -27,7 +27,7 @@ def test_save_document_creates_full_graph(db_session, sample_data):
# Immeuble / lot / locataire créés
assert db_session.query(Immeuble).filter_by(code="IMM1").count() == 1
assert db_session.query(Lot).filter_by(numero="001").count() == 1
assert db_session.query(Lot).filter_by(numero="01").count() == 1
assert db_session.query(Locataire).filter_by(nom="DUPONT").count() == 1
# Revenu et dépense rattachés

121
tests/test_lots.py Normal file
View File

@@ -0,0 +1,121 @@
"""Tests de la normalisation des numéros de lot."""
import pytest
from plesna_gerance.parsers.operations import _extract_lot_code_from_description
from plesna_gerance.utils.lots import normalize_extraction_lots, normalize_lot_numero
@pytest.mark.parametrize(
"raw,expected",
[
("0006", "06"),
("06", "06"),
("6", "06"),
(6, "06"),
("0020", "20"),
("20", "20"),
("Lot 6", "06"),
(" 7 ", "07"),
("0000", "00"),
("0", "00"),
# Plus de 2 chiffres significatifs : on ne tronque pas
("0123", "123"),
# Rien d'exploitable
(None, None),
("", None),
("abc", None),
],
)
def test_normalize_lot_numero(raw, expected):
assert normalize_lot_numero(raw) == expected
@pytest.mark.parametrize(
"description,expected",
[
("M06 - Commande moteur pompe", "06"),
("S05 - Mise en service", "05"),
("B01 - Plaques", "01"),
("S10 - Reparation", "10"),
("S 17 - Espace dans le code", "17"),
("S01 SOLDE LOCATAIRE", "01"),
("Nettoyage immeuble", None),
("", None),
],
)
def test_extract_lot_code_from_description(description, expected):
assert _extract_lot_code_from_description(description) == expected
def test_normalize_extraction_lots():
data = {
"situation_locataires": [
{"lot": {"numero": "0006", "type": "Appartement T3"}},
{"lot": {"numero": "7", "type": "Studio"}},
{"lot": {"numero": None, "type": "Garage"}},
],
"recapitulatif_operations": [
{"lot_numero": "0013"},
{"lot_numero": "1"},
{"lot_numero": None},
],
}
normalize_extraction_lots(data)
assert [s["lot"]["numero"] for s in data["situation_locataires"]] == [
"06",
"07",
None,
]
assert [o["lot_numero"] for o in data["recapitulatif_operations"]] == [
"13",
"01",
None,
]
def test_normalize_extraction_lots_tolerates_missing_sections():
# Une extraction partielle ne doit pas lever
assert normalize_extraction_lots({}) == {}
assert normalize_extraction_lots({"situation_locataires": None}) == {
"situation_locataires": None
}
def test_get_or_create_lot_deduplicates_formats(db_session):
""""0006", "06" et "6" désignent le même lot : un seul enregistrement."""
from plesna_gerance.database.models import Immeuble, Lot
from plesna_gerance.database.service import DatabaseService
service = DatabaseService(db_session)
immeuble = service.get_or_create_immeuble(code="IMM1")
lots = [
service.get_or_create_lot(immeuble_id=immeuble.id, numero=numero)
for numero in ("0006", "06", "6")
]
assert {lot.id for lot in lots} == {lots[0].id}
assert lots[0].numero == "06"
assert db_session.query(Lot).filter_by(immeuble_id=immeuble.id).count() == 1
assert db_session.query(Immeuble).count() == 1
def test_save_document_normalizes_lot_numero(db_session, sample_data):
from plesna_gerance.database.models import Lot
from plesna_gerance.database.service import DatabaseService
sample_data["situation_locataires"][0]["lot"]["numero"] = "0001"
sample_data["recapitulatif_operations"][0]["lot_numero"] = "1"
service = DatabaseService(db_session)
doc = service.save_document(data=sample_data)
# Le lot du locataire et celui de l'opération sont le même
assert db_session.query(Lot).count() == 1
assert db_session.query(Lot).first().numero == "01"
# Le JSON persisté est normalisé lui aussi
assert '"numero": "01"' in doc.json_data
assert '"lot_numero": "01"' in doc.json_data