feat(api): include 7-day upcoming grooming context in routine suggestions

This commit is contained in:
Piotr Oleszczyk 2026-03-07 01:39:31 +01:00
parent 5d69a976c4
commit 1c457d62a3
3 changed files with 73 additions and 2 deletions

View file

@ -327,6 +327,48 @@ def _build_grooming_context(
return "\n".join(lines) + "\n" return "\n".join(lines) + "\n"
def _build_upcoming_grooming_context(
session: Session,
start_date: date,
days: int = 7,
) -> str:
entries = session.exec(
select(GroomingSchedule).order_by(col(GroomingSchedule.day_of_week))
).all()
if not entries:
return f"UPCOMING GROOMING (next {days} days): none\n"
entries_by_weekday: dict[int, list[GroomingSchedule]] = {}
for entry in entries:
entries_by_weekday.setdefault(entry.day_of_week, []).append(entry)
lines = [f"UPCOMING GROOMING (next {days} days):"]
for offset in range(days):
target_date = start_date + timedelta(days=offset)
day_entries = entries_by_weekday.get(target_date.weekday(), [])
if not day_entries:
continue
if offset == 0:
relative_label = "dzisiaj"
elif offset == 1:
relative_label = "jutro"
else:
relative_label = f"za {offset} dni"
day_name = _DAY_NAMES[target_date.weekday()]
actions = ", ".join(
f"{_ev(entry.action)}" + (f" ({entry.notes})" if entry.notes else "")
for entry in day_entries
)
lines.append(f" {relative_label} ({target_date}, {day_name}): {actions}")
if len(lines) == 1:
lines.append(" (no entries in this window)")
return "\n".join(lines) + "\n"
def _build_recent_history(session: Session) -> str: def _build_recent_history(session: Session) -> str:
cutoff = date.today() - timedelta(days=7) cutoff = date.today() - timedelta(days=7)
routines = session.exec( routines = session.exec(
@ -632,7 +674,11 @@ def suggest_routine(
weekday = data.routine_date.weekday() weekday = data.routine_date.weekday()
skin_ctx = _build_skin_context(session) skin_ctx = _build_skin_context(session)
profile_ctx = build_user_profile_context(session, reference_date=data.routine_date) profile_ctx = build_user_profile_context(session, reference_date=data.routine_date)
grooming_ctx = _build_grooming_context(session, weekdays=[weekday]) upcoming_grooming_ctx = _build_upcoming_grooming_context(
session,
start_date=data.routine_date,
days=7,
)
history_ctx = _build_recent_history(session) history_ctx = _build_recent_history(session)
day_ctx = _build_day_context(data.leaving_home) day_ctx = _build_day_context(data.leaving_home)
available_products = _get_available_products( available_products = _get_available_products(
@ -675,7 +721,7 @@ def suggest_routine(
f"na {data.routine_date} ({day_name}).\n\n" f"na {data.routine_date} ({day_name}).\n\n"
f"{mode_line}\n" f"{mode_line}\n"
"INPUT DATA:\n" "INPUT DATA:\n"
f"{profile_ctx}\n{skin_ctx}\n{grooming_ctx}\n{history_ctx}\n{day_ctx}\n{products_ctx}\n{objectives_ctx}" f"{profile_ctx}\n{skin_ctx}\n{upcoming_grooming_ctx}\n{history_ctx}\n{day_ctx}\n{products_ctx}\n{objectives_ctx}"
"\nNARZEDZIA:\n" "\nNARZEDZIA:\n"
"- Masz dostep do funkcji: get_product_details.\n" "- Masz dostep do funkcji: get_product_details.\n"
"- Wywoluj narzedzia tylko, gdy potrzebujesz detali do decyzji klinicznej/bezpieczenstwa.\n" "- Wywoluj narzedzia tylko, gdy potrzebujesz detali do decyzji klinicznej/bezpieczenstwa.\n"

View file

@ -249,6 +249,7 @@ def test_suggest_routine(client, session):
assert data["reasoning"] == "because" assert data["reasoning"] == "because"
kwargs = mock_gemini.call_args.kwargs kwargs = mock_gemini.call_args.kwargs
assert "USER PROFILE:" in kwargs["contents"] assert "USER PROFILE:" in kwargs["contents"]
assert "UPCOMING GROOMING (next 7 days):" in kwargs["contents"]
assert "function_handlers" in kwargs assert "function_handlers" in kwargs
assert "get_product_details" in kwargs["function_handlers"] assert "get_product_details" in kwargs["function_handlers"]

View file

@ -10,6 +10,7 @@ from innercontext.api.routines import (
_build_products_context, _build_products_context,
_build_recent_history, _build_recent_history,
_build_skin_context, _build_skin_context,
_build_upcoming_grooming_context,
_contains_minoxidil_text, _contains_minoxidil_text,
_ev, _ev,
_extract_active_names, _extract_active_names,
@ -121,6 +122,29 @@ def test_build_grooming_context(session: Session):
assert "(no entries for specified days)" in ctx2 assert "(no entries for specified days)" in ctx2
def test_build_upcoming_grooming_context(session: Session):
assert (
_build_upcoming_grooming_context(session, start_date=date(2026, 3, 2), days=7)
== "UPCOMING GROOMING (next 7 days): none\n"
)
monday = GroomingSchedule(
id=uuid.uuid4(), day_of_week=0, action="shaving_oneblade", notes="Morning"
)
wednesday = GroomingSchedule(id=uuid.uuid4(), day_of_week=2, action="dermarolling")
session.add_all([monday, wednesday])
session.commit()
ctx = _build_upcoming_grooming_context(
session,
start_date=date(2026, 3, 2),
days=7,
)
assert "UPCOMING GROOMING (next 7 days):" in ctx
assert "dzisiaj (2026-03-02, poniedziałek): shaving_oneblade (Morning)" in ctx
assert "za 2 dni (2026-03-04, środa): dermarolling" in ctx
def test_build_recent_history(session: Session): def test_build_recent_history(session: Session):
assert _build_recent_history(session) == "RECENT ROUTINES: none\n" assert _build_recent_history(session) == "RECENT ROUTINES: none\n"