Files
babymonitor/plan_3_prompt.md
T
2026-06-24 15:30:47 +02:00

4.2 KiB
Raw Blame History

Plan 3 — Performance

Items covered: #4 (missing database indexes), #2 (O(n³) timetable loop), #3 (in-Python aggregation in other stats endpoints)


Context

You are working on a FastAPI backend for a baby monitor / sleep tracker app. The codebase lives under app/. This plan replaces expensive Python loops in app/routers/stats.py with SQL queries, and adds database indexes to make those queries fast. The index fix (#4) must be done first because the SQL queries depend on it being effective.

Do one task at a time. After each file change, re-read the file to verify it is correct. Run the tests between each task.


Tasks

1. Add database indexes (app/models.py)

Step 1 — Read the file first

Read app/models.py in full to understand the SleepSession model structure and current field definitions.

Step 2 — Add indexes

Add indexes on start_time and end_time. The preferred approach is inline with sa_column:

from sqlalchemy import Column, DateTime
from sqlmodel import Field, SQLModel

class SleepSession(SQLModel, table=True):
    ...
    start_time: datetime = Field(sa_column=Column(DateTime, index=True))
    end_time: Optional[datetime] = Field(default=None, sa_column=Column(DateTime, index=True, nullable=True))

If the fields already use sa_column, just add index=True to the existing Column(...) calls. Do not duplicate field definitions.

Step 3 — Verify

Run the tests to make sure the model change doesn't break anything:

pytest

2. Fix the O(n³) timetable query (app/routers/stats.py:77-99)

Step 1 — Read the file first

Read app/routers/stats.py in full before making any changes. Understand the full timetable endpoint: its parameters, what it returns, and the structure of the current triple-nested loop.

Step 2 — Rewrite the loop

The current code iterates over every day, then every 15-minute slot, then every session in memory — ~2.88 million comparisons for 30 days × 1,000 sessions.

Replace it with a per-day SQL query approach:

For each day in the date range:

  1. Query the database for sessions that overlap that day: WHERE start_time < day_end AND (end_time IS NULL OR end_time > day_start)
  2. Build the 96 fifteen-minute slots comparing only against the small set of sessions returned for that day.

This keeps the slot-building logic in Python (which is fine) but moves the session filtering into SQL (which uses the index). The number of sessions compared per day drops from ALL sessions to only the ones active that day.

Keep the return format identical to the current implementation — do not change the shape of the response.

Step 3 — Verify

Run the tests:

pytest

All timetable tests must pass. If there is only one happy-path test, that is expected — new tests are added in Plan 5.


3. Fix in-Python aggregation in the other two stats endpoints (app/routers/stats.py:28-47 and 119-134)

Read the file again (it may have changed from the previous step) before editing.

daily-summary endpoint (around lines 28-47)

The current code loads all sessions for a date range and loops through them in Python to compute per-day totals. Replace with a single SQL GROUP BY query:

SELECT DATE(start_time), SUM(<duration_expression>), COUNT(*)
FROM sleep_session
WHERE start_time >= :from_date AND start_time < :to_date
GROUP BY DATE(start_time)

Use SQLAlchemy's query API or a text() query to express this. Map the results back to the same response format the endpoint currently returns.

wake-sleep-times endpoint (around lines 119-134)

The current code loads all sessions and loops per day to find the first wakeup and last bedtime. Replace with:

SELECT DATE(start_time), MIN(start_time), MAX(end_time)
FROM sleep_session
WHERE start_time >= :from_date AND start_time < :to_date
GROUP BY DATE(start_time)

Map the results back to the same response format.

Important

Do not change the response shape of either endpoint. The goal is identical output, produced more efficiently.

Step — Verify

Run the full test suite:

pytest

All tests must pass before declaring this plan complete. Do not move on to Plan 4 until the suite is green.