feat: décrit les logements dans un référentiel saisi à la main
Les lots n'étaient connus que par l'extraction PDF : un numéro, un type souvent vide, et rien sur le bien lui-même. Cette table de caractéristiques (surface, étage, bâtiment, chauffage, DPE, rapprochement impôts) donne au référentiel une source de vérité indépendante des comptes rendus. Table séparée de `lots` à dessein : une ré-extraction ne peut alors pas écraser la saisie, et le désaccord sur le type de lot reste visible au lieu d'être arbitré en silence. La fiche gagne, le PDF comble les trous. Échéance du DPE et écart de surface ne sont pas stockés mais calculés : une colonne dérivée finirait par mentir après une correction. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
"""Pydantic models for API requests and responses."""
|
||||
|
||||
from datetime import date
|
||||
from datetime import date, datetime
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
from ...utils.logements import DPE_CLASSES
|
||||
|
||||
# ============================================================
|
||||
# Requests
|
||||
@@ -180,3 +182,82 @@ class DepensesSummary(BaseModel):
|
||||
by_tag: list[TagSummary]
|
||||
by_month: list[MonthlySummary]
|
||||
by_fournisseur: list[FournisseurSummary]
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Référentiel des logements
|
||||
# ============================================================
|
||||
|
||||
|
||||
class CaracteristiquesBody(BaseModel):
|
||||
"""Caractéristiques d'un logement telles que saisies.
|
||||
|
||||
Tous les champs sont optionnels : la fiche se remplit progressivement, et
|
||||
une fiche partielle vaut mieux qu'une fiche refusée.
|
||||
"""
|
||||
|
||||
bat: str | None = None
|
||||
etage: str | None = None
|
||||
type: str | None = None
|
||||
surface: float | None = Field(None, ge=0)
|
||||
surface_date_diag: date | None = None
|
||||
chauffage: str | None = None
|
||||
dpe_classe: str | None = None
|
||||
dpe_date_realisation: date | None = None
|
||||
numero_fiscal: str | None = None
|
||||
surface_impots: float | None = Field(None, ge=0)
|
||||
note_impots: str | None = None
|
||||
|
||||
@field_validator("dpe_classe")
|
||||
@classmethod
|
||||
def _classe_connue(cls, value: str | None) -> str | None:
|
||||
"""Refuse une classe hors A-G, faute de quoi les KPI DPE mentiraient."""
|
||||
if value is None or value == "":
|
||||
return None
|
||||
classe = value.strip().upper()
|
||||
if classe not in DPE_CLASSES:
|
||||
raise ValueError(f"Classe DPE inconnue : {value} (attendu A-G)")
|
||||
return classe
|
||||
|
||||
@field_validator(
|
||||
"bat", "etage", "type", "chauffage", "numero_fiscal", "note_impots"
|
||||
)
|
||||
@classmethod
|
||||
def _texte_vide_vaut_absent(cls, value: str | None) -> str | None:
|
||||
"""Un champ vide dans le tableau doit effacer la valeur, pas la figer."""
|
||||
if value is None:
|
||||
return None
|
||||
value = value.strip()
|
||||
return value or None
|
||||
|
||||
|
||||
class CaracteristiquesResponse(CaracteristiquesBody):
|
||||
"""Caractéristiques saisies, augmentées de leurs valeurs dérivées."""
|
||||
|
||||
#: Péremption du DPE, déduite de la date de réalisation (+10 ans).
|
||||
dpe_echeance: date | None = None
|
||||
#: Surface impôts moins surface mesurée ; None si une des deux manque.
|
||||
delta_surface: float | None = None
|
||||
updated_at: datetime | None = None
|
||||
|
||||
|
||||
class LotReferentielResponse(BaseModel):
|
||||
"""Un lot et sa fiche, tels qu'affichés dans le tableau du référentiel."""
|
||||
|
||||
id: int
|
||||
numero: str
|
||||
immeuble_id: int
|
||||
immeuble_code: str | None
|
||||
|
||||
#: Type de lot vu par l'extraction PDF, conservé tel quel.
|
||||
type_extrait: str | None
|
||||
#: Type retenu : celui de la fiche s'il existe, sinon celui du PDF.
|
||||
type_effectif: str | None
|
||||
#: Vrai quand les deux sources se contredisent (comparaison normalisée).
|
||||
type_ecart: bool = False
|
||||
|
||||
caracteristiques: CaracteristiquesResponse | None = None
|
||||
|
||||
#: Rattachements existants : un lot qui en a n'est pas supprimable.
|
||||
nb_revenus: int = 0
|
||||
nb_depenses: int = 0
|
||||
|
||||
Reference in New Issue
Block a user