feat: affiche la date de derniere extraction et revient aux documents
La liste des documents montrait la date d'import, figee au premier enregistrement : c'est la date de la derniere extraction ayant produit les donnees affichees qui renseigne, surtout apres un balayage de re-extraction. La colonne `extracted_at` la porte, mise a jour a chaque enregistrement, et la date d'import reste consultable en infobulle. Le projet n'ayant pas d'outil de migration (`create_all` laisse intactes les tables existantes), `init_db` rattrape les colonnes ajoutees apres coup : sans cela une base deja installee cesserait de fonctionner. Les documents deja presents recoivent leur date d'import, qui est bien celle de leur extraction. Une fois la selection appliquee sans echec, le balayage renvoie vers la liste des documents ; en cas d'echec on reste sur place, les lignes concernees portant leur message. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -195,6 +195,7 @@ async def list_documents(
|
||||
solde_montant=doc.solde_montant,
|
||||
solde_type=doc.solde_type,
|
||||
created_at=doc.created_at.isoformat() if doc.created_at else None,
|
||||
extracted_at=doc.extracted_at.isoformat() if doc.extracted_at else None,
|
||||
has_pdf=doc.pdf_path is not None,
|
||||
has_json=doc.json_path is not None,
|
||||
)
|
||||
@@ -245,6 +246,9 @@ async def get_document(
|
||||
"json_data": json.loads(document.json_data) if document.json_data else None,
|
||||
"depenses_tags": db_service.get_depenses_tags(document_id),
|
||||
"created_at": document.created_at.isoformat() if document.created_at else None,
|
||||
"extracted_at": document.extracted_at.isoformat()
|
||||
if document.extracted_at
|
||||
else None,
|
||||
"has_pdf": document.pdf_path is not None,
|
||||
"has_json": document.json_path is not None,
|
||||
}
|
||||
|
||||
@@ -53,6 +53,9 @@ class DocumentSummary(BaseModel):
|
||||
solde_montant: float | None
|
||||
solde_type: str | None
|
||||
created_at: str
|
||||
#: Derniere extraction ayant produit les donnees (absente sur une base
|
||||
#: anterieure a ce champ : l'appelant retombe alors sur `created_at`).
|
||||
extracted_at: str | None = None
|
||||
has_pdf: bool = False
|
||||
has_json: bool = False
|
||||
|
||||
|
||||
@@ -87,6 +87,7 @@ def init_db(db_path: Path | None = None) -> Path:
|
||||
# Build the engine (reuses the shared configuration) and create tables
|
||||
engine = get_engine(db_path)
|
||||
Base.metadata.create_all(bind=engine)
|
||||
_apply_schema_updates(engine)
|
||||
|
||||
# Seed predefined tags if the table is empty
|
||||
_seed_tags_if_empty(engine)
|
||||
@@ -94,6 +95,34 @@ def init_db(db_path: Path | None = None) -> Path:
|
||||
return db_path
|
||||
|
||||
|
||||
#: Colonnes ajoutees apres coup, par table : nom -> (definition SQL, valeur de
|
||||
#: rattrapage pour les lignes existantes). `create_all` ne modifie pas une table
|
||||
#: deja presente, et le projet n'utilise pas d'outil de migration : sans ce
|
||||
#: rattrapage, une base installee cesserait de fonctionner apres mise a jour.
|
||||
_ADDED_COLUMNS = {
|
||||
"documents": {
|
||||
# Les documents deja en base ont ete extraits lors de leur import.
|
||||
"extracted_at": ("DATETIME", "created_at"),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _apply_schema_updates(engine):
|
||||
"""Ajoute a une base existante les colonnes apparues depuis sa creation."""
|
||||
with engine.begin() as conn:
|
||||
for table, columns in _ADDED_COLUMNS.items():
|
||||
existing = {
|
||||
row[1] for row in conn.execute(text(f"PRAGMA table_info({table})"))
|
||||
}
|
||||
if not existing: # table absente : create_all vient de la creer
|
||||
continue
|
||||
for name, (definition, backfill) in columns.items():
|
||||
if name in existing:
|
||||
continue
|
||||
conn.execute(text(f"ALTER TABLE {table} ADD COLUMN {name} {definition}"))
|
||||
conn.execute(text(f"UPDATE {table} SET {name} = {backfill}"))
|
||||
|
||||
|
||||
def _seed_tags_if_empty(engine):
|
||||
"""Insert predefined tags if the tags table is empty."""
|
||||
from ..scripts.seed_tags import PREDEFINED_TAGS
|
||||
|
||||
@@ -161,6 +161,10 @@ class Document(Base):
|
||||
solde_date_arrete = Column(Date, nullable=True)
|
||||
|
||||
created_at = Column(DateTime, default=_utcnow)
|
||||
#: Date de la derniere extraction ayant produit les donnees stockees. Une
|
||||
#: re-extraction validee la met a jour, contrairement a `created_at` qui
|
||||
#: reste la date du premier import.
|
||||
extracted_at = Column(DateTime, default=_utcnow)
|
||||
|
||||
# Chemins vers les fichiers stockés (relatifs à PLESNA_STORAGE_PATH)
|
||||
pdf_path = Column(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Database service for saving and querying extracted data."""
|
||||
|
||||
import json
|
||||
from datetime import date, datetime
|
||||
from datetime import date, datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import select
|
||||
@@ -249,6 +249,8 @@ class DatabaseService:
|
||||
"solde_date_arrete": self._parse_date(solde_info.get("date_arrete")),
|
||||
"pdf_path": pdf_path,
|
||||
"json_path": json_path,
|
||||
# Ces donnees viennent de l'extraction qu'on est en train d'enregistrer.
|
||||
"extracted_at": datetime.now(timezone.utc),
|
||||
}
|
||||
if reused is not None:
|
||||
document = reused
|
||||
|
||||
Reference in New Issue
Block a user