From 4954d4f4491f2922b91f4cd2b60f260d2ff62080 Mon Sep 17 00:00:00 2001 From: Piotr Oleszczyk Date: Sat, 28 Feb 2026 13:25:57 +0100 Subject: [PATCH] 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 --- backend/innercontext/api/skincare.py | 11 +++++------ backend/innercontext/models/__init__.py | 4 ++-- backend/innercontext/models/enums.py | 10 +++++----- backend/innercontext/models/skincare.py | 4 ++-- backend/tests/test_skincare.py | 4 ++-- frontend/src/lib/types.ts | 4 ++-- frontend/src/routes/skin/+page.server.ts | 4 ++-- frontend/src/routes/skin/+page.svelte | 20 ++++++++++---------- 8 files changed, 30 insertions(+), 31 deletions(-) diff --git a/backend/innercontext/api/skincare.py b/backend/innercontext/api/skincare.py index 1606cce..8e2cf9c 100644 --- a/backend/innercontext/api/skincare.py +++ b/backend/innercontext/api/skincare.py @@ -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 1–5 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: 2–4 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":…} """ diff --git a/backend/innercontext/models/__init__.py b/backend/innercontext/models/__init__.py index 50b164a..1ffe287 100644 --- a/backend/innercontext/models/__init__.py +++ b/backend/innercontext/models/__init__.py @@ -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", diff --git a/backend/innercontext/models/enums.py b/backend/innercontext/models/enums.py index 1441bf5..e610a03 100644 --- a/backend/innercontext/models/enums.py +++ b/backend/innercontext/models/enums.py @@ -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): diff --git a/backend/innercontext/models/skincare.py b/backend/innercontext/models/skincare.py index de5eebf..9eed99c 100644 --- a/backend/innercontext/models/skincare.py +++ b/backend/innercontext/models/skincare.py @@ -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) diff --git a/backend/tests/test_skincare.py b/backend/tests/test_skincare.py index db5f63a..5bf9db5 100644 --- a/backend/tests/test_skincare.py +++ b/backend/tests/test_skincare.py @@ -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 diff --git a/frontend/src/lib/types.ts b/frontend/src/lib/types.ts index d1f3607..b30dbd5 100644 --- a/frontend/src/lib/types.ts +++ b/frontend/src/lib/types.ts @@ -55,7 +55,7 @@ export type SkinConcern = | 'uneven_texture' | 'hair_growth' | 'sebum_excess'; -export type SkinTrend = 'improving' | 'stable' | 'worsening' | 'fluctuating'; +export type SkinTexture = 'smooth' | 'rough' | 'flaky' | 'bumpy'; export type SkinType = 'dry' | 'oily' | 'combination' | 'sensitive' | 'normal' | 'acne_prone'; export type StrengthLevel = 1 | 2 | 3; export type TextureType = 'watery' | 'gel' | 'emulsion' | 'cream' | 'oil' | 'balm' | 'foam' | 'fluid'; @@ -250,8 +250,8 @@ export interface SkinConditionSnapshot { id: string; snapshot_date: string; overall_state?: OverallSkinState; - trend?: SkinTrend; skin_type?: SkinType; + texture?: SkinTexture; hydration_level?: number; sebum_tzone?: number; sebum_cheeks?: number; diff --git a/frontend/src/routes/skin/+page.server.ts b/frontend/src/routes/skin/+page.server.ts index 01accb9..f11c131 100644 --- a/frontend/src/routes/skin/+page.server.ts +++ b/frontend/src/routes/skin/+page.server.ts @@ -13,7 +13,7 @@ export const actions: Actions = { const form = await request.formData(); const snapshot_date = form.get('snapshot_date') as string; const overall_state = form.get('overall_state') as string; - const trend = form.get('trend') as string; + const texture = form.get('texture') as string; const notes = form.get('notes') as string; const hydration_level = form.get('hydration_level') as string; const sensitivity_level = form.get('sensitivity_level') as string; @@ -35,7 +35,7 @@ export const actions: Actions = { const body: Record = { snapshot_date, active_concerns }; if (overall_state) body.overall_state = overall_state; - if (trend) body.trend = trend; + if (texture) body.texture = texture; if (notes) body.notes = notes; if (hydration_level) body.hydration_level = Number(hydration_level); if (sensitivity_level) body.sensitivity_level = Number(sensitivity_level); diff --git a/frontend/src/routes/skin/+page.svelte b/frontend/src/routes/skin/+page.svelte index 3f4bbc3..14b5777 100644 --- a/frontend/src/routes/skin/+page.svelte +++ b/frontend/src/routes/skin/+page.svelte @@ -12,7 +12,7 @@ let { data, form }: { data: PageData; form: ActionData } = $props(); const states = ['excellent', 'good', 'fair', 'poor']; - const trends = ['improving', 'stable', 'worsening', 'fluctuating']; + const skinTextures = ['smooth', 'rough', 'flaky', 'bumpy']; const barrierStates = ['intact', 'mildly_compromised', 'compromised']; const skinTypes = ['dry', 'oily', 'combination', 'sensitive', 'normal', 'acne_prone']; @@ -28,7 +28,7 @@ // Form state (bound to inputs so AI can pre-fill) let snapshotDate = $state(new Date().toISOString().slice(0, 10)); let overallState = $state(''); - let trend = $state(''); + let texture = $state(''); let barrierState = $state(''); let skinType = $state(''); let hydrationLevel = $state(''); @@ -64,7 +64,7 @@ try { const r = await analyzeSkinPhotos(selectedFiles); if (r.overall_state) overallState = r.overall_state; - if (r.trend) trend = r.trend; + if (r.texture) texture = r.texture; if (r.skin_type) skinType = r.skin_type; if (r.barrier_state) barrierState = r.barrier_state; if (r.hydration_level != null) hydrationLevel = String(r.hydration_level); @@ -178,12 +178,12 @@
- - - +