diff --git a/backend/innercontext/api/health.py b/backend/innercontext/api/health.py index 5f04434..b0da062 100644 --- a/backend/innercontext/api/health.py +++ b/backend/innercontext/api/health.py @@ -3,7 +3,7 @@ from datetime import datetime from typing import Optional from uuid import UUID, uuid4 -from fastapi import APIRouter, Depends, HTTPException +from fastapi import APIRouter, Depends from pydantic import field_validator from sqlmodel import Session, SQLModel, col, select @@ -72,8 +72,11 @@ class LabResultCreate(SQLModel): @classmethod def check_test_code_format(cls, v: str) -> str: if not re.fullmatch(r"\d+-\d", v): - raise ValueError("test_code must match LOINC format: digits-digit (e.g. 718-7)") + raise ValueError( + "test_code must match LOINC format: digits-digit (e.g. 718-7)" + ) return v + test_name_original: Optional[str] = None test_name_loinc: Optional[str] = None @@ -142,9 +145,7 @@ def list_medications( @router.post("/medications", response_model=MedicationEntry, status_code=201) -def create_medication( - data: MedicationCreate, session: Session = Depends(get_session) -): +def create_medication(data: MedicationCreate, session: Session = Depends(get_session)): entry = MedicationEntry(record_id=uuid4(), **data.model_dump()) session.add(entry) session.commit() @@ -274,9 +275,7 @@ def list_lab_results( @router.post("/lab-results", response_model=LabResult, status_code=201) -def create_lab_result( - data: LabResultCreate, session: Session = Depends(get_session) -): +def create_lab_result(data: LabResultCreate, session: Session = Depends(get_session)): result = LabResult(record_id=uuid4(), **data.model_dump()) session.add(result) session.commit() diff --git a/backend/innercontext/api/inventory.py b/backend/innercontext/api/inventory.py index 85fdce3..9d50034 100644 --- a/backend/innercontext/api/inventory.py +++ b/backend/innercontext/api/inventory.py @@ -1,12 +1,12 @@ from uuid import UUID -from fastapi import APIRouter, Depends, HTTPException +from fastapi import APIRouter, Depends from sqlmodel import Session from db import get_session +from innercontext.api.products import InventoryUpdate from innercontext.api.utils import get_or_404 from innercontext.models import ProductInventory -from innercontext.api.products import InventoryUpdate router = APIRouter() diff --git a/backend/innercontext/api/products.py b/backend/innercontext/api/products.py index d7fc443..86b5979 100644 --- a/backend/innercontext/api/products.py +++ b/backend/innercontext/api/products.py @@ -2,30 +2,33 @@ from datetime import date from typing import Optional from uuid import UUID, uuid4 -from fastapi import APIRouter, Depends, HTTPException, Query +from fastapi import APIRouter, Depends, Query from sqlmodel import Session, SQLModel, select from db import get_session from innercontext.api.utils import get_or_404 from innercontext.models import ( Product, + ProductBase, ProductCategory, ProductInventory, + ProductPublic, + ProductWithInventory, SkinConcern, ) +from innercontext.models.enums import ( + AbsorptionSpeed, + DayTime, + PriceTier, + SkinType, + TextureType, +) from innercontext.models.product import ( ActiveIngredient, ProductContext, ProductEffectProfile, ProductInteraction, ) -from innercontext.models.enums import ( - AbsorptionSpeed, - DayTime, - PriceTier, - TextureType, - SkinType, -) router = APIRouter() @@ -35,57 +38,8 @@ router = APIRouter() # --------------------------------------------------------------------------- -class ProductCreate(SQLModel): - name: str - brand: str - line_name: Optional[str] = None - sku: Optional[str] = None - url: Optional[str] = None - barcode: Optional[str] = None - - category: ProductCategory - recommended_time: DayTime - - texture: Optional[TextureType] = None - absorption_speed: Optional[AbsorptionSpeed] = None - leave_on: bool - - price_tier: Optional[PriceTier] = None - size_ml: Optional[float] = None - pao_months: Optional[int] = None - - inci: list[str] = [] - actives: Optional[list[ActiveIngredient]] = None - - recommended_for: list[SkinType] = [] - - targets: list[SkinConcern] = [] - contraindications: list[str] = [] - usage_notes: Optional[str] = None - - fragrance_free: Optional[bool] = None - essential_oils_free: Optional[bool] = None - alcohol_denat_free: Optional[bool] = None - pregnancy_safe: Optional[bool] = None - - product_effect_profile: ProductEffectProfile = ProductEffectProfile() - - ph_min: Optional[float] = None - ph_max: Optional[float] = None - - incompatible_with: Optional[list[ProductInteraction]] = None - synergizes_with: Optional[list[str]] = None - context_rules: Optional[ProductContext] = None - - min_interval_hours: Optional[int] = None - max_frequency_per_week: Optional[int] = None - - is_medication: bool = False - is_tool: bool = False - needle_length_mm: Optional[float] = None - - personal_tolerance_notes: Optional[str] = None - personal_repurchase_intent: Optional[bool] = None +class ProductCreate(ProductBase): + pass class ProductUpdate(SQLModel): @@ -159,17 +113,12 @@ class InventoryUpdate(SQLModel): notes: Optional[str] = None -# --------------------------------------------------------------------------- -# Helper -# --------------------------------------------------------------------------- - - # --------------------------------------------------------------------------- # Product routes # --------------------------------------------------------------------------- -@router.get("", response_model=list[Product]) +@router.get("", response_model=list[ProductPublic]) def list_products( category: Optional[ProductCategory] = None, brand: Optional[str] = None, @@ -205,7 +154,7 @@ def list_products( return products -@router.post("", response_model=Product, status_code=201) +@router.post("", response_model=ProductPublic, status_code=201) def create_product(data: ProductCreate, session: Session = Depends(get_session)): product = Product( id=uuid4(), @@ -217,16 +166,18 @@ def create_product(data: ProductCreate, session: Session = Depends(get_session)) return product -@router.get("/{product_id}") +@router.get("/{product_id}", response_model=ProductWithInventory) def get_product(product_id: UUID, session: Session = Depends(get_session)): product = get_or_404(session, Product, product_id) - inventory = session.exec(select(ProductInventory).where(ProductInventory.product_id == product_id)).all() - data = product.model_dump(mode="json") - data["inventory"] = [item.model_dump(mode="json") for item in inventory] - return data + inventory = session.exec( + select(ProductInventory).where(ProductInventory.product_id == product_id) + ).all() + result = ProductWithInventory.model_validate(product, from_attributes=True) + result.inventory = list(inventory) + return result -@router.patch("/{product_id}", response_model=Product) +@router.patch("/{product_id}", response_model=ProductPublic) def update_product( product_id: UUID, data: ProductUpdate, session: Session = Depends(get_session) ): @@ -252,9 +203,7 @@ def delete_product(product_id: UUID, session: Session = Depends(get_session)): @router.get("/{product_id}/inventory", response_model=list[ProductInventory]) -def list_product_inventory( - product_id: UUID, session: Session = Depends(get_session) -): +def list_product_inventory(product_id: UUID, session: Session = Depends(get_session)): get_or_404(session, Product, product_id) stmt = select(ProductInventory).where(ProductInventory.product_id == product_id) return session.exec(stmt).all() diff --git a/backend/innercontext/api/routines.py b/backend/innercontext/api/routines.py index ad8b25c..e20156b 100644 --- a/backend/innercontext/api/routines.py +++ b/backend/innercontext/api/routines.py @@ -2,7 +2,7 @@ from datetime import date from typing import Optional from uuid import UUID, uuid4 -from fastapi import APIRouter, Depends, HTTPException +from fastapi import APIRouter, Depends from sqlmodel import Session, SQLModel, select from db import get_session @@ -105,7 +105,9 @@ def list_grooming_schedule(session: Session = Depends(get_session)): @router.get("/{routine_id}") def get_routine(routine_id: UUID, session: Session = Depends(get_session)): routine = get_or_404(session, Routine, routine_id) - steps = session.exec(select(RoutineStep).where(RoutineStep.routine_id == routine_id)).all() + steps = session.exec( + select(RoutineStep).where(RoutineStep.routine_id == routine_id) + ).all() data = routine.model_dump(mode="json") data["steps"] = [step.model_dump(mode="json") for step in steps] return data @@ -206,9 +208,7 @@ def update_grooming_schedule( @router.delete("/grooming-schedule/{entry_id}", status_code=204) -def delete_grooming_schedule( - entry_id: UUID, session: Session = Depends(get_session) -): +def delete_grooming_schedule(entry_id: UUID, session: Session = Depends(get_session)): entry = get_or_404(session, GroomingSchedule, entry_id) session.delete(entry) session.commit() diff --git a/backend/innercontext/api/skincare.py b/backend/innercontext/api/skincare.py index 19979bc..0bed948 100644 --- a/backend/innercontext/api/skincare.py +++ b/backend/innercontext/api/skincare.py @@ -2,12 +2,16 @@ from datetime import date from typing import Optional from uuid import UUID, uuid4 -from fastapi import APIRouter, Depends, HTTPException +from fastapi import APIRouter, Depends from sqlmodel import Session, SQLModel, select from db import get_session from innercontext.api.utils import get_or_404 -from innercontext.models import SkinConditionSnapshot +from innercontext.models import ( + SkinConditionSnapshot, + SkinConditionSnapshotBase, + SkinConditionSnapshotPublic, +) from innercontext.models.enums import ( BarrierState, OverallSkinState, @@ -24,23 +28,8 @@ router = APIRouter() # --------------------------------------------------------------------------- -class SnapshotCreate(SQLModel): - snapshot_date: date - overall_state: Optional[OverallSkinState] = None - trend: Optional[SkinTrend] = None - skin_type: Optional[SkinType] = None - - hydration_level: Optional[int] = None - sebum_tzone: Optional[int] = None - sebum_cheeks: Optional[int] = None - sensitivity_level: Optional[int] = None - - barrier_state: Optional[BarrierState] = None - - active_concerns: list[SkinConcern] = [] - risks: list[str] = [] - priorities: list[str] = [] - notes: Optional[str] = None +class SnapshotCreate(SkinConditionSnapshotBase): + pass class SnapshotUpdate(SQLModel): @@ -62,17 +51,12 @@ class SnapshotUpdate(SQLModel): notes: Optional[str] = None -# --------------------------------------------------------------------------- -# Helper -# --------------------------------------------------------------------------- - - # --------------------------------------------------------------------------- # Routes # --------------------------------------------------------------------------- -@router.get("", response_model=list[SkinConditionSnapshot]) +@router.get("", response_model=list[SkinConditionSnapshotPublic]) def list_snapshots( from_date: Optional[date] = None, to_date: Optional[date] = None, @@ -89,10 +73,8 @@ def list_snapshots( return session.exec(stmt).all() -@router.post("", response_model=SkinConditionSnapshot, status_code=201) -def create_snapshot( - data: SnapshotCreate, session: Session = Depends(get_session) -): +@router.post("", response_model=SkinConditionSnapshotPublic, status_code=201) +def create_snapshot(data: SnapshotCreate, session: Session = Depends(get_session)): snapshot = SkinConditionSnapshot(id=uuid4(), **data.model_dump()) session.add(snapshot) session.commit() @@ -100,12 +82,12 @@ def create_snapshot( return snapshot -@router.get("/{snapshot_id}", response_model=SkinConditionSnapshot) +@router.get("/{snapshot_id}", response_model=SkinConditionSnapshotPublic) def get_snapshot(snapshot_id: UUID, session: Session = Depends(get_session)): return get_or_404(session, SkinConditionSnapshot, snapshot_id) -@router.patch("/{snapshot_id}", response_model=SkinConditionSnapshot) +@router.patch("/{snapshot_id}", response_model=SkinConditionSnapshotPublic) def update_snapshot( snapshot_id: UUID, data: SnapshotUpdate, diff --git a/backend/innercontext/models/__init__.py b/backend/innercontext/models/__init__.py index 07355e7..50b164a 100644 --- a/backend/innercontext/models/__init__.py +++ b/backend/innercontext/models/__init__.py @@ -25,13 +25,20 @@ from .health import LabResult, MedicationEntry, MedicationUsage from .product import ( ActiveIngredient, Product, + ProductBase, ProductContext, ProductEffectProfile, ProductInteraction, ProductInventory, + ProductPublic, + ProductWithInventory, ) from .routine import GroomingSchedule, Routine, RoutineStep -from .skincare import SkinConditionSnapshot +from .skincare import ( + SkinConditionSnapshot, + SkinConditionSnapshotBase, + SkinConditionSnapshotPublic, +) __all__ = [ # domain @@ -64,14 +71,19 @@ __all__ = [ # product "ActiveIngredient", "Product", + "ProductBase", "ProductContext", "ProductEffectProfile", "ProductInteraction", "ProductInventory", + "ProductPublic", + "ProductWithInventory", # routine "GroomingSchedule", "Routine", "RoutineStep", # skincare "SkinConditionSnapshot", + "SkinConditionSnapshotBase", + "SkinConditionSnapshotPublic", ] diff --git a/backend/innercontext/models/enums.py b/backend/innercontext/models/enums.py index 53ddaa5..1441bf5 100644 --- a/backend/innercontext/models/enums.py +++ b/backend/innercontext/models/enums.py @@ -1,6 +1,5 @@ from enum import Enum - # --------------------------------------------------------------------------- # Shared / Product # --------------------------------------------------------------------------- diff --git a/backend/innercontext/models/product.py b/backend/innercontext/models/product.py index 88d9cf1..42a1755 100644 --- a/backend/innercontext/models/product.py +++ b/backend/innercontext/models/product.py @@ -21,7 +21,6 @@ from .enums import ( TextureType, ) - # --------------------------------------------------------------------------- # Value objects # --------------------------------------------------------------------------- @@ -83,16 +82,11 @@ def _ev(v: object) -> str: # --------------------------------------------------------------------------- -# Table models +# Base model (pure Python types, no sa_column, no id/timestamps) # --------------------------------------------------------------------------- -class Product(SQLModel, table=True): - __tablename__ = "products" - __domains__: ClassVar[frozenset[Domain]] = frozenset({Domain.SKINCARE}) - - id: UUID = Field(default_factory=uuid4, primary_key=True) - +class ProductBase(SQLModel): name: str brand: str line_name: str | None = Field(default=None, max_length=128) @@ -107,10 +101,61 @@ class Product(SQLModel, table=True): absorption_speed: AbsorptionSpeed | None = None leave_on: bool - price_tier: PriceTier | None = Field(default=None, index=True) + price_tier: PriceTier | None = None size_ml: float | None = Field(default=None, gt=0) pao_months: int | None = Field(default=None, ge=1, le=60) + inci: list[str] = Field(default_factory=list) + actives: list[ActiveIngredient] | None = None + + recommended_for: list[SkinType] = Field(default_factory=list) + + targets: list[SkinConcern] = Field(default_factory=list) + contraindications: list[str] = Field(default_factory=list) + usage_notes: str | None = None + + fragrance_free: bool | None = None + essential_oils_free: bool | None = None + alcohol_denat_free: bool | None = None + pregnancy_safe: bool | None = None + + product_effect_profile: ProductEffectProfile = Field( + default_factory=ProductEffectProfile + ) + + ph_min: float | None = Field(default=None, ge=0, le=14) + ph_max: float | None = Field(default=None, ge=0, le=14) + + incompatible_with: list[ProductInteraction] | None = None + synergizes_with: list[str] | None = None + context_rules: ProductContext | None = None + + min_interval_hours: int | None = Field(default=None, ge=0) + max_frequency_per_week: int | None = Field(default=None, ge=1, le=14) + + is_medication: bool = Field(default=False) + is_tool: bool = Field(default=False) + needle_length_mm: float | None = Field(default=None, gt=0) + + personal_tolerance_notes: str | None = None + personal_repurchase_intent: bool | None = None + + +# --------------------------------------------------------------------------- +# Table models +# --------------------------------------------------------------------------- + + +class Product(ProductBase, table=True): + __tablename__ = "products" + __domains__: ClassVar[frozenset[Domain]] = frozenset({Domain.SKINCARE}) + + id: UUID = Field(default_factory=uuid4, primary_key=True) + + # Override: add index for table context + price_tier: PriceTier | None = Field(default=None, index=True) + + # Override 9 JSON fields with sa_column (only in table model) inci: list[str] = Field( default_factory=list, sa_column=Column(JSON, nullable=False) ) @@ -128,21 +173,12 @@ class Product(SQLModel, table=True): contraindications: list[str] = Field( default_factory=list, sa_column=Column(JSON, nullable=False) ) - usage_notes: str | None = None - - fragrance_free: bool | None = None - essential_oils_free: bool | None = None - alcohol_denat_free: bool | None = None - pregnancy_safe: bool | None = None product_effect_profile: ProductEffectProfile = Field( default_factory=ProductEffectProfile, sa_column=Column(JSON, nullable=False), ) - ph_min: float | None = Field(default=None, ge=0, le=14) - ph_max: float | None = Field(default=None, ge=0, le=14) - incompatible_with: list[ProductInteraction] | None = Field( default=None, sa_column=Column(JSON, nullable=True) ) @@ -153,16 +189,6 @@ class Product(SQLModel, table=True): default=None, sa_column=Column(JSON, nullable=True) ) - min_interval_hours: int | None = Field(default=None, ge=0) - max_frequency_per_week: int | None = Field(default=None, ge=1, le=14) - - is_medication: bool = Field(default=False) - is_tool: bool = Field(default=False) - needle_length_mm: float | None = Field(default=None, gt=0) - - personal_tolerance_notes: str | None = None - personal_repurchase_intent: bool | None = None - created_at: datetime = Field(default_factory=utc_now, nullable=False) updated_at: datetime = Field( default_factory=utc_now, @@ -264,13 +290,12 @@ class Product(SQLModel, table=True): ctx["ph_max"] = self.ph_max ep = self.product_effect_profile - if ep is not None: - if isinstance(ep, dict): - nonzero = {k: v for k, v in ep.items() if v >= 2} - else: - nonzero = {k: v for k, v in ep.model_dump().items() if v >= 2} - if nonzero: - ctx["effect_profile"] = nonzero + if isinstance(ep, dict): + nonzero = {k: v for k, v in ep.items() if v >= 2} + else: + nonzero = {k: v for k, v in ep.model_dump().items() if v >= 2} + if nonzero: + ctx["effect_profile"] = nonzero if self.incompatible_with: parts = [] @@ -307,7 +332,12 @@ class Product(SQLModel, table=True): ctx["max_frequency_per_week"] = self.max_frequency_per_week safety = {} - for flag in ("fragrance_free", "essential_oils_free", "alcohol_denat_free", "pregnancy_safe"): + for flag in ( + "fragrance_free", + "essential_oils_free", + "alcohol_denat_free", + "pregnancy_safe", + ): val = getattr(self, flag) if val is not None: safety[flag] = val @@ -358,3 +388,18 @@ class ProductInventory(SQLModel, table=True): created_at: datetime = Field(default_factory=utc_now, nullable=False) product: Optional["Product"] = Relationship(back_populates="inventory") + + +# --------------------------------------------------------------------------- +# Public response models +# --------------------------------------------------------------------------- + + +class ProductPublic(ProductBase): + id: UUID + created_at: datetime + updated_at: datetime + + +class ProductWithInventory(ProductPublic): + inventory: list[ProductInventory] = [] diff --git a/backend/innercontext/models/skincare.py b/backend/innercontext/models/skincare.py index 8a90609..de5eebf 100644 --- a/backend/innercontext/models/skincare.py +++ b/backend/innercontext/models/skincare.py @@ -11,19 +11,13 @@ 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 SkinConditionSnapshot(SQLModel, 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) - snapshot_date: date = Field(index=True) +class SkinConditionSnapshotBase(SQLModel): + snapshot_date: date overall_state: OverallSkinState | None = None trend: SkinTrend | None = None @@ -38,17 +32,53 @@ class SkinConditionSnapshot(SQLModel, table=True): 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) ) - - # Wolny tekst — LLM wypełnia na podstawie analizy 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) ) - notes: str | None = None created_at: datetime = Field(default_factory=utc_now, nullable=False) + + +# --------------------------------------------------------------------------- +# Public response model +# --------------------------------------------------------------------------- + + +class SkinConditionSnapshotPublic(SkinConditionSnapshotBase): + id: UUID + created_at: datetime diff --git a/backend/main.py b/backend/main.py index 993ca93..d996bd7 100644 --- a/backend/main.py +++ b/backend/main.py @@ -4,11 +4,11 @@ from dotenv import load_dotenv load_dotenv() # load .env before db.py reads DATABASE_URL -from fastapi import FastAPI -from fastapi.middleware.cors import CORSMiddleware +from fastapi import FastAPI # noqa: E402 +from fastapi.middleware.cors import CORSMiddleware # noqa: E402 -from db import create_db_and_tables -from innercontext.api import health, inventory, products, routines, skincare +from db import create_db_and_tables # noqa: E402 +from innercontext.api import health, inventory, products, routines, skincare # noqa: E402 @asynccontextmanager diff --git a/backend/tests/test_health.py b/backend/tests/test_health.py index 8c98b1a..29a5331 100644 --- a/backend/tests/test_health.py +++ b/backend/tests/test_health.py @@ -1,6 +1,5 @@ import uuid - # --------------------------------------------------------------------------- # Medications # --------------------------------------------------------------------------- @@ -32,9 +31,7 @@ def test_list_filter_kind(client): client.post( "/health/medications", json={"kind": "prescription", "product_name": "A"} ) - client.post( - "/health/medications", json={"kind": "supplement", "product_name": "B"} - ) + client.post("/health/medications", json={"kind": "supplement", "product_name": "B"}) r = client.get("/health/medications?kind=supplement") assert r.status_code == 200 data = r.json() @@ -46,9 +43,7 @@ def test_list_filter_product_name(client): client.post( "/health/medications", json={"kind": "otc", "product_name": "Epiduo Forte"} ) - client.post( - "/health/medications", json={"kind": "otc", "product_name": "Panoxyl"} - ) + client.post("/health/medications", json={"kind": "otc", "product_name": "Panoxyl"}) r = client.get("/health/medications?product_name=epid") assert r.status_code == 200 data = r.json() @@ -113,7 +108,11 @@ def test_create_usage(client, created_medication): mid = created_medication["record_id"] r = client.post( f"/health/medications/{mid}/usages", - json={"valid_from": "2026-01-01T08:00:00", "dose_value": 1.0, "dose_unit": "pea"}, + json={ + "valid_from": "2026-01-01T08:00:00", + "dose_value": 1.0, + "dose_unit": "pea", + }, ) assert r.status_code == 201 data = r.json() @@ -156,7 +155,9 @@ def test_update_usage(client, created_medication): ) uid = r.json()["record_id"] - r2 = client.patch(f"/health/usages/{uid}", json={"dose_value": 2.5, "dose_unit": "mg"}) + r2 = client.patch( + f"/health/usages/{uid}", json={"dose_value": 2.5, "dose_unit": "mg"} + ) assert r2.status_code == 200 assert r2.json()["dose_value"] == 2.5 assert r2.json()["dose_unit"] == "mg" @@ -247,8 +248,14 @@ def test_list_filter_flag(client): def test_list_filter_date_range(client): - client.post("/health/lab-results", json={**LAB_RESULT_DATA, "collected_at": "2026-01-01T00:00:00"}) - client.post("/health/lab-results", json={**LAB_RESULT_DATA, "collected_at": "2026-06-01T00:00:00"}) + client.post( + "/health/lab-results", + json={**LAB_RESULT_DATA, "collected_at": "2026-01-01T00:00:00"}, + ) + client.post( + "/health/lab-results", + json={**LAB_RESULT_DATA, "collected_at": "2026-06-01T00:00:00"}, + ) r = client.get("/health/lab-results?from_date=2026-05-01T00:00:00") assert r.status_code == 200 data = r.json() @@ -271,7 +278,9 @@ def test_get_lab_result_not_found(client): def test_update_lab_result(client): r = client.post("/health/lab-results", json=LAB_RESULT_DATA) rid = r.json()["record_id"] - r2 = client.patch(f"/health/lab-results/{rid}", json={"notes": "Recheck in 3 months"}) + r2 = client.patch( + f"/health/lab-results/{rid}", json={"notes": "Recheck in 3 months"} + ) assert r2.status_code == 200 assert r2.json()["notes"] == "Recheck in 3 months" diff --git a/backend/tests/test_product_model.py b/backend/tests/test_product_model.py index 735b1a7..f2076b3 100644 --- a/backend/tests/test_product_model.py +++ b/backend/tests/test_product_model.py @@ -1,9 +1,7 @@ """Unit tests for Product.to_llm_context() — no database required.""" -from uuid import uuid4 from typing import Any - -import pytest +from uuid import uuid4 from innercontext.models import Product from innercontext.models.enums import ( diff --git a/backend/tests/test_products.py b/backend/tests/test_products.py index f550a69..1fae538 100644 --- a/backend/tests/test_products.py +++ b/backend/tests/test_products.py @@ -55,7 +55,9 @@ def test_list_filter_category(client, client_and_data=None): "recommended_time": "both", "leave_on": True, } - r1 = client.post("/products", json={**base, "name": "Moist", "category": "moisturizer"}) + r1 = client.post( + "/products", json={**base, "name": "Moist", "category": "moisturizer"} + ) r2 = client.post("/products", json={**base, "name": "Ser", "category": "serum"}) assert r1.status_code == 201 assert r2.status_code == 201 @@ -96,7 +98,12 @@ def test_list_filter_is_medication(client): # is_medication=True requires usage_notes (model validator) client.post( "/products", - json={**base, "name": "Med", "is_medication": True, "usage_notes": "Apply pea-sized amount"}, + json={ + **base, + "name": "Med", + "is_medication": True, + "usage_notes": "Apply pea-sized amount", + }, ) r = client.get("/products?is_medication=true") diff --git a/backend/tests/test_routines.py b/backend/tests/test_routines.py index 0df2565..b240079 100644 --- a/backend/tests/test_routines.py +++ b/backend/tests/test_routines.py @@ -1,6 +1,5 @@ import uuid - # --------------------------------------------------------------------------- # Routines # --------------------------------------------------------------------------- diff --git a/backend/tests/test_skincare.py b/backend/tests/test_skincare.py index 2d922b9..db5f63a 100644 --- a/backend/tests/test_skincare.py +++ b/backend/tests/test_skincare.py @@ -112,9 +112,7 @@ def test_update_snapshot_concerns(client): def test_update_snapshot_not_found(client): - r = client.patch( - f"/skincare/{uuid.uuid4()}", json={"overall_state": "good"} - ) + r = client.patch(f"/skincare/{uuid.uuid4()}", json={"overall_state": "good"}) assert r.status_code == 404