refactor(skin): replace trend with texture field on SkinConditionSnapshot

Remove the derived `trend` field (better computed from history by the MCP
agent) and add `texture: smooth|rough|flaky|bumpy` which LLM can reliably
assess from photos. Updates model, API, system prompt, tests, and frontend.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Piotr Oleszczyk 2026-02-28 13:25:57 +01:00
parent abf9593857
commit 4954d4f449
8 changed files with 30 additions and 31 deletions

View file

@ -20,7 +20,7 @@ from innercontext.models.enums import (
BarrierState,
OverallSkinState,
SkinConcern,
SkinTrend,
SkinTexture,
SkinType,
)
@ -39,8 +39,8 @@ class SnapshotCreate(SkinConditionSnapshotBase):
class SnapshotUpdate(SQLModel):
snapshot_date: Optional[date] = None
overall_state: Optional[OverallSkinState] = None
trend: Optional[SkinTrend] = None
skin_type: Optional[SkinType] = None
texture: Optional[SkinTexture] = None
hydration_level: Optional[int] = None
sebum_tzone: Optional[int] = None
@ -57,8 +57,8 @@ class SnapshotUpdate(SQLModel):
class SkinPhotoAnalysisResponse(SQLModel):
overall_state: Optional[OverallSkinState] = None
trend: Optional[SkinTrend] = None
skin_type: Optional[SkinType] = None
texture: Optional[SkinTexture] = None
hydration_level: Optional[int] = None
sebum_tzone: Optional[int] = None
sebum_cheeks: Optional[int] = None
@ -85,14 +85,13 @@ RULES:
- Omit any field you cannot confidently determine from the photos. Do not guess.
- All enum values must exactly match the allowed strings listed below.
- Numeric metrics use a 15 scale (1 = minimal, 5 = maximal).
- For trends: only populate if you have strong visual cues; otherwise omit.
- risks and priorities: short English phrases, max 10 words each.
- notes: 24 sentence paragraph describing key observations.
ENUM VALUES:
overall_state: "excellent" | "good" | "fair" | "poor"
trend: "improving" | "stable" | "worsening" | "fluctuating"
skin_type: "dry" | "oily" | "combination" | "sensitive" | "normal" | "acne_prone"
texture: "smooth" | "rough" | "flaky" | "bumpy"
barrier_state: "intact" | "mildly_compromised" | "compromised"
active_concerns: "acne" | "rosacea" | "hyperpigmentation" | "aging" | "dehydration" |
"redness" | "damaged_barrier" | "pore_visibility" | "uneven_texture" | "sebum_excess"
@ -104,7 +103,7 @@ sebum_cheeks: 1=very dry cheeks → 5=very oily cheeks
sensitivity_level: 1=no visible signs 5=severe redness/reactivity
OUTPUT (all fields optional):
{"overall_state":, "trend":, "skin_type":, "hydration_level":,
{"overall_state":, "skin_type":, "texture":, "hydration_level":,
"sebum_tzone":, "sebum_cheeks":, "sensitivity_level":,
"barrier_state":, "active_concerns":[], "risks":[], "priorities":[], "notes":}
"""

View file

@ -15,7 +15,7 @@ from .enums import (
ResultFlag,
RoutineRole,
SkinConcern,
SkinTrend,
SkinTexture,
SkinType,
StrengthLevel,
TextureType,
@ -59,7 +59,7 @@ __all__ = [
"ResultFlag",
"RoutineRole",
"SkinConcern",
"SkinTrend",
"SkinTexture",
"SkinType",
"StrengthLevel",
"TextureType",

View file

@ -186,11 +186,11 @@ class OverallSkinState(str, Enum):
POOR = "poor"
class SkinTrend(str, Enum):
IMPROVING = "improving"
STABLE = "stable"
WORSENING = "worsening"
FLUCTUATING = "fluctuating"
class SkinTexture(str, Enum):
SMOOTH = "smooth"
ROUGH = "rough"
FLAKY = "flaky"
BUMPY = "bumpy"
class BarrierState(str, Enum):

View file

@ -9,7 +9,7 @@ from sqlmodel import Field, SQLModel
from .base import utc_now
from .domain import Domain
from .enums import BarrierState, OverallSkinState, SkinConcern, SkinTrend, SkinType
from .enums import BarrierState, OverallSkinState, SkinConcern, SkinTexture, SkinType
# ---------------------------------------------------------------------------
# Base model (pure Python types, no sa_column, no id/created_at)
@ -20,8 +20,8 @@ class SkinConditionSnapshotBase(SQLModel):
snapshot_date: date
overall_state: OverallSkinState | None = None
trend: SkinTrend | None = None
skin_type: SkinType | None = None
texture: SkinTexture | None = None
# Metryki wizualne (1 = minimalne, 5 = maksymalne nasilenie)
hydration_level: int | None = Field(default=None, ge=1, le=5)

View file

@ -16,8 +16,8 @@ def test_create_snapshot_full(client):
json={
"snapshot_date": "2026-02-20",
"overall_state": "good",
"trend": "improving",
"skin_type": "combination",
"texture": "rough",
"hydration_level": 3,
"sebum_tzone": 4,
"sebum_cheeks": 2,
@ -32,7 +32,7 @@ def test_create_snapshot_full(client):
assert r.status_code == 201
data = r.json()
assert data["overall_state"] == "good"
assert data["trend"] == "improving"
assert data["texture"] == "rough"
assert "acne" in data["active_concerns"]
assert data["hydration_level"] == 3