innercontext/backend/innercontext/models/skincare.py
Piotr Oleszczyk c09acc7c81 refactor: split table models into Base/Table/Public for proper FastAPI serialization
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>
2026-02-27 15:37:46 +01:00

84 lines
3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 15.
"""
__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