Add ProductBase, ProductPublic, ProductWithInventory and SkinConditionSnapshotBase, SkinConditionSnapshotPublic. Table models now inherit from their Base counterpart and override JSON fields with sa_column. All field_serializer hacks removed; FastAPI response models use the non-table Public classes so Pydantic coerces raw DB dicts → typed models cleanly. ProductCreate and SnapshotCreate now simply inherit their respective Base classes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
84 lines
3 KiB
Python
84 lines
3 KiB
Python
from __future__ import annotations
|
||
|
||
from datetime import date, datetime
|
||
from typing import ClassVar
|
||
from uuid import UUID, uuid4
|
||
|
||
from sqlalchemy import JSON, Column, UniqueConstraint
|
||
from sqlmodel import Field, SQLModel
|
||
|
||
from .base import utc_now
|
||
from .domain import Domain
|
||
from .enums import BarrierState, OverallSkinState, SkinConcern, SkinTrend, SkinType
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Base model (pure Python types, no sa_column, no id/created_at)
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class SkinConditionSnapshotBase(SQLModel):
|
||
snapshot_date: date
|
||
|
||
overall_state: OverallSkinState | None = None
|
||
trend: SkinTrend | None = None
|
||
skin_type: SkinType | None = None
|
||
|
||
# Metryki wizualne (1 = minimalne, 5 = maksymalne nasilenie)
|
||
hydration_level: int | None = Field(default=None, ge=1, le=5)
|
||
sebum_tzone: int | None = Field(default=None, ge=1, le=5)
|
||
sebum_cheeks: int | None = Field(default=None, ge=1, le=5)
|
||
sensitivity_level: int | None = Field(default=None, ge=1, le=5)
|
||
|
||
barrier_state: BarrierState | None = None
|
||
|
||
# Aktywne troski — podzbiór SkinConcern widoczny na zdjęciach lub wynikający z rutyny
|
||
active_concerns: list[SkinConcern] = Field(default_factory=list)
|
||
|
||
# Wolny tekst — LLM wypełnia na podstawie analizy
|
||
risks: list[str] = Field(default_factory=list)
|
||
priorities: list[str] = Field(default_factory=list)
|
||
notes: str | None = None
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Table model
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class SkinConditionSnapshot(SkinConditionSnapshotBase, table=True):
|
||
"""
|
||
Tygodniowy snapshot kondycji skóry wypełniany przez LLM na podstawie zdjęć
|
||
i kontekstu rutyny. Wszystkie metryki numeryczne w skali 1–5.
|
||
"""
|
||
|
||
__tablename__ = "skin_condition_snapshots"
|
||
__domains__: ClassVar[frozenset[Domain]] = frozenset({Domain.SKINCARE})
|
||
__table_args__ = (UniqueConstraint("snapshot_date", name="uq_skin_snapshot_date"),)
|
||
|
||
id: UUID = Field(default_factory=uuid4, primary_key=True)
|
||
|
||
# Override: add index for table context
|
||
snapshot_date: date = Field(index=True)
|
||
|
||
# Override 3 JSON fields with sa_column (only in table model)
|
||
active_concerns: list[SkinConcern] = Field(
|
||
default_factory=list, sa_column=Column(JSON, nullable=False)
|
||
)
|
||
risks: list[str] = Field(
|
||
default_factory=list, sa_column=Column(JSON, nullable=False)
|
||
)
|
||
priorities: list[str] = Field(
|
||
default_factory=list, sa_column=Column(JSON, nullable=False)
|
||
)
|
||
|
||
created_at: datetime = Field(default_factory=utc_now, nullable=False)
|
||
|
||
|
||
# ---------------------------------------------------------------------------
|
||
# Public response model
|
||
# ---------------------------------------------------------------------------
|
||
|
||
|
||
class SkinConditionSnapshotPublic(SkinConditionSnapshotBase):
|
||
id: UUID
|
||
created_at: datetime
|