From 1c457d62a32faa1361c5ca0198b32b7b45e9a555 Mon Sep 17 00:00:00 2001 From: Piotr Oleszczyk Date: Sat, 7 Mar 2026 01:39:31 +0100 Subject: [PATCH] feat(api): include 7-day upcoming grooming context in routine suggestions --- backend/innercontext/api/routines.py | 50 ++++++++++++++++++++++++-- backend/tests/test_routines.py | 1 + backend/tests/test_routines_helpers.py | 24 +++++++++++++ 3 files changed, 73 insertions(+), 2 deletions(-) diff --git a/backend/innercontext/api/routines.py b/backend/innercontext/api/routines.py index 5535a1e..5c33536 100644 --- a/backend/innercontext/api/routines.py +++ b/backend/innercontext/api/routines.py @@ -327,6 +327,48 @@ def _build_grooming_context( 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: cutoff = date.today() - timedelta(days=7) routines = session.exec( @@ -632,7 +674,11 @@ def suggest_routine( weekday = data.routine_date.weekday() skin_ctx = _build_skin_context(session) 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) day_ctx = _build_day_context(data.leaving_home) available_products = _get_available_products( @@ -675,7 +721,7 @@ def suggest_routine( f"na {data.routine_date} ({day_name}).\n\n" f"{mode_line}\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" "- Masz dostep do funkcji: get_product_details.\n" "- Wywoluj narzedzia tylko, gdy potrzebujesz detali do decyzji klinicznej/bezpieczenstwa.\n" diff --git a/backend/tests/test_routines.py b/backend/tests/test_routines.py index b993eeb..ee1060c 100644 --- a/backend/tests/test_routines.py +++ b/backend/tests/test_routines.py @@ -249,6 +249,7 @@ def test_suggest_routine(client, session): assert data["reasoning"] == "because" kwargs = mock_gemini.call_args.kwargs assert "USER PROFILE:" in kwargs["contents"] + assert "UPCOMING GROOMING (next 7 days):" in kwargs["contents"] assert "function_handlers" in kwargs assert "get_product_details" in kwargs["function_handlers"] diff --git a/backend/tests/test_routines_helpers.py b/backend/tests/test_routines_helpers.py index 7619425..64562dd 100644 --- a/backend/tests/test_routines_helpers.py +++ b/backend/tests/test_routines_helpers.py @@ -10,6 +10,7 @@ from innercontext.api.routines import ( _build_products_context, _build_recent_history, _build_skin_context, + _build_upcoming_grooming_context, _contains_minoxidil_text, _ev, _extract_active_names, @@ -121,6 +122,29 @@ def test_build_grooming_context(session: Session): 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): assert _build_recent_history(session) == "RECENT ROUTINES: none\n"