feat(profile): add profile settings and LLM user context
This commit is contained in:
parent
db3d9514d5
commit
b99b9ed68e
25 changed files with 472 additions and 9 deletions
23
backend/tests/test_llm_profile_context.py
Normal file
23
backend/tests/test_llm_profile_context.py
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
from datetime import date
|
||||
|
||||
from sqlmodel import Session
|
||||
|
||||
from innercontext.api.llm_context import build_user_profile_context
|
||||
from innercontext.models import SexAtBirth, UserProfile
|
||||
|
||||
|
||||
def test_build_user_profile_context_without_data(session: Session):
|
||||
ctx = build_user_profile_context(session, reference_date=date(2026, 3, 5))
|
||||
assert ctx == "USER PROFILE: no data\n"
|
||||
|
||||
|
||||
def test_build_user_profile_context_with_data(session: Session):
|
||||
profile = UserProfile(birth_date=date(1990, 3, 20), sex_at_birth=SexAtBirth.FEMALE)
|
||||
session.add(profile)
|
||||
session.commit()
|
||||
|
||||
ctx = build_user_profile_context(session, reference_date=date(2026, 3, 5))
|
||||
assert "USER PROFILE:" in ctx
|
||||
assert "Age: 35" in ctx
|
||||
assert "Birth date: 1990-03-20" in ctx
|
||||
assert "Sex at birth: female" in ctx
|
||||
|
|
@ -11,15 +11,26 @@ from innercontext.api.products import (
|
|||
_build_shopping_context,
|
||||
_extract_requested_product_ids,
|
||||
)
|
||||
from innercontext.models import Product, ProductInventory, SkinConditionSnapshot
|
||||
from innercontext.models import (
|
||||
Product,
|
||||
ProductInventory,
|
||||
SexAtBirth,
|
||||
SkinConditionSnapshot,
|
||||
)
|
||||
from innercontext.models.profile import UserProfile
|
||||
|
||||
|
||||
def test_build_shopping_context(session: Session):
|
||||
# Empty context
|
||||
ctx = _build_shopping_context(session)
|
||||
ctx = _build_shopping_context(session, reference_date=date.today())
|
||||
assert "USER PROFILE: no data" in ctx
|
||||
assert "(brak danych)" in ctx
|
||||
assert "POSIADANE PRODUKTY" in ctx
|
||||
|
||||
profile = UserProfile(birth_date=date(1990, 1, 10), sex_at_birth=SexAtBirth.MALE)
|
||||
session.add(profile)
|
||||
session.commit()
|
||||
|
||||
# Add snapshot
|
||||
snap = SkinConditionSnapshot(
|
||||
id=uuid.uuid4(),
|
||||
|
|
@ -54,7 +65,10 @@ def test_build_shopping_context(session: Session):
|
|||
session.add(inv)
|
||||
session.commit()
|
||||
|
||||
ctx = _build_shopping_context(session)
|
||||
ctx = _build_shopping_context(session, reference_date=date(2026, 3, 5))
|
||||
assert "USER PROFILE:" in ctx
|
||||
assert "Age: 36" in ctx
|
||||
assert "Sex at birth: male" in ctx
|
||||
assert "Typ skóry: combination" in ctx
|
||||
assert "Nawilżenie: 3/5" in ctx
|
||||
assert "Wrażliwość: 4/5" in ctx
|
||||
|
|
@ -91,6 +105,7 @@ def test_suggest_shopping(client, session):
|
|||
assert data["suggestions"][0]["product_type"] == "cleanser"
|
||||
assert data["reasoning"] == "Test shopping"
|
||||
kwargs = mock_gemini.call_args.kwargs
|
||||
assert "USER PROFILE:" in kwargs["contents"]
|
||||
assert "function_handlers" in kwargs
|
||||
assert "get_product_inci" in kwargs["function_handlers"]
|
||||
assert "get_product_safety_rules" in kwargs["function_handlers"]
|
||||
|
|
@ -111,7 +126,7 @@ def test_shopping_context_medication_skip(session: Session):
|
|||
session.add(p)
|
||||
session.commit()
|
||||
|
||||
ctx = _build_shopping_context(session)
|
||||
ctx = _build_shopping_context(session, reference_date=date.today())
|
||||
assert "Epiduo" not in ctx
|
||||
|
||||
|
||||
|
|
|
|||
35
backend/tests/test_profile.py
Normal file
35
backend/tests/test_profile.py
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
def test_get_profile_empty(client):
|
||||
r = client.get("/profile")
|
||||
assert r.status_code == 200
|
||||
assert r.json() is None
|
||||
|
||||
|
||||
def test_upsert_profile_create_and_get(client):
|
||||
create = client.patch(
|
||||
"/profile", json={"birth_date": "1990-01-15", "sex_at_birth": "male"}
|
||||
)
|
||||
assert create.status_code == 200
|
||||
body = create.json()
|
||||
assert body["birth_date"] == "1990-01-15"
|
||||
assert body["sex_at_birth"] == "male"
|
||||
|
||||
fetch = client.get("/profile")
|
||||
assert fetch.status_code == 200
|
||||
fetched = fetch.json()
|
||||
assert fetched is not None
|
||||
assert fetched["id"] == body["id"]
|
||||
|
||||
|
||||
def test_upsert_profile_updates_existing_row(client):
|
||||
first = client.patch(
|
||||
"/profile", json={"birth_date": "1992-06-10", "sex_at_birth": "female"}
|
||||
)
|
||||
assert first.status_code == 200
|
||||
first_id = first.json()["id"]
|
||||
|
||||
second = client.patch("/profile", json={"sex_at_birth": "intersex"})
|
||||
assert second.status_code == 200
|
||||
second_body = second.json()
|
||||
assert second_body["id"] == first_id
|
||||
assert second_body["birth_date"] == "1992-06-10"
|
||||
assert second_body["sex_at_birth"] == "intersex"
|
||||
|
|
@ -248,6 +248,7 @@ def test_suggest_routine(client, session):
|
|||
assert data["steps"][0]["action_type"] == "shaving_razor"
|
||||
assert data["reasoning"] == "because"
|
||||
kwargs = mock_gemini.call_args.kwargs
|
||||
assert "USER PROFILE:" in kwargs["contents"]
|
||||
assert "function_handlers" in kwargs
|
||||
assert "get_product_inci" in kwargs["function_handlers"]
|
||||
assert "get_product_safety_rules" in kwargs["function_handlers"]
|
||||
|
|
@ -279,6 +280,8 @@ def test_suggest_batch(client, session):
|
|||
assert len(data["days"]) == 1
|
||||
assert data["days"][0]["date"] == "2026-03-03"
|
||||
assert data["overall_reasoning"] == "batch test"
|
||||
kwargs = mock_gemini.call_args.kwargs
|
||||
assert "USER PROFILE:" in kwargs["contents"]
|
||||
|
||||
|
||||
def test_suggest_batch_invalid_date_range(client):
|
||||
|
|
|
|||
|
|
@ -128,3 +128,33 @@ def test_delete_snapshot(client):
|
|||
def test_delete_snapshot_not_found(client):
|
||||
r = client.delete(f"/skincare/{uuid.uuid4()}")
|
||||
assert r.status_code == 404
|
||||
|
||||
|
||||
def test_analyze_photos_includes_user_profile_context(client, monkeypatch):
|
||||
from innercontext.api import skincare as skincare_api
|
||||
|
||||
captured: dict[str, object] = {}
|
||||
|
||||
class _FakeResponse:
|
||||
text = "{}"
|
||||
|
||||
def _fake_call_gemini(**kwargs):
|
||||
captured.update(kwargs)
|
||||
return _FakeResponse()
|
||||
|
||||
monkeypatch.setattr(skincare_api, "call_gemini", _fake_call_gemini)
|
||||
|
||||
profile = client.patch(
|
||||
"/profile", json={"birth_date": "1991-02-10", "sex_at_birth": "female"}
|
||||
)
|
||||
assert profile.status_code == 200
|
||||
|
||||
r = client.post(
|
||||
"/skincare/analyze-photos",
|
||||
files={"photos": ("face.jpg", b"fake-bytes", "image/jpeg")},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
|
||||
parts = captured["contents"]
|
||||
assert isinstance(parts, list)
|
||||
assert any("USER PROFILE:" in str(part) for part in parts)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue