Tracker de vie auto-hébergé : suivi poids/calories/sport avec planning de pesées, sevrage tabac (vape) avec modèle de coût DIY et économies, et finances personnelles avec import de relevés bancaires. Architecture : FastAPI + SQLAlchemy 2.0 + PostgreSQL 16, React 18 + TS + Vite + Tailwind + ECharts, déploiement Docker Compose. Modules auto-découverts des deux côtés (pkgutil / import.meta.glob) et framework de connecteurs à deux voies (importeurs de fichiers + ingestion JSON) pour brancher de nouvelles sources sans toucher au noyau. Validé : 292 tests pytest, tsc + vite build, contrat API/web vérifié contre le schéma OpenAPI, et déploiement Docker réel sur PostgreSQL 16 (28 tables, SPA servie par nginx, wizard de premier démarrage). Documentation : README.md, docs/GUIDE.md, CONVENTIONS.md, et les documents de conception et de recherche dans docs/. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
30 lines
1.3 KiB
Python
30 lines
1.3 KiB
Python
from datetime import datetime
|
|
from typing import Any
|
|
|
|
from sqlalchemy import DateTime, ForeignKey, String
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
|
|
from app.core.database import Base
|
|
from app.core.mixins import JSONB_V, TimestampMixin
|
|
|
|
|
|
class ImportRun(TimestampMixin, Base):
|
|
__tablename__ = "import_runs"
|
|
|
|
id: Mapped[int] = mapped_column(primary_key=True)
|
|
user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), index=True)
|
|
importer_id: Mapped[str] = mapped_column(String(50)) # ex: "foodvisor_csv"
|
|
domain: Mapped[str] = mapped_column(String(20)) # "health" | "vape" | "finance"
|
|
filename: Mapped[str] = mapped_column(String(255))
|
|
file_size: Mapped[int] = mapped_column()
|
|
status: Mapped[str] = mapped_column(String(20)) # "completed" | "failed"
|
|
rows_total: Mapped[int] = mapped_column(default=0)
|
|
rows_inserted: Mapped[int] = mapped_column(default=0)
|
|
rows_updated: Mapped[int] = mapped_column(default=0)
|
|
rows_duplicates: Mapped[int] = mapped_column(default=0)
|
|
rows_errors: Mapped[int] = mapped_column(default=0)
|
|
# [{row, message}] capped at 100
|
|
error_details: Mapped[list[dict[str, Any]]] = mapped_column(JSONB_V, default=list)
|
|
started_at: Mapped[datetime] = mapped_column(DateTime(timezone=True))
|
|
finished_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|