Nobody has started this yet — be first.
Business impact
Every event whose client doesn't supply occurred_at explicitly gets timestamped with datetime.now() — a naive datetime with no timezone attached, representing whatever the server process's local clock says. Events that do supply occurred_at get a timezone-aware datetime. These two kinds of timestamps end up mixed together in the same occurred_at::timestamptz[] array sent to Postgres on every batch flush; Postgres silently interprets a naive timestamp using the server's session timezone, not necessarily UTC and not necessarily the same timezone the process's wall clock is actually running in. The result is a systematic, silent timestamp skew for exactly the events a client trusted the server to timestamp correctly — and it's the kind of bug that can pass every test running in a UTC CI environment and only show up once a deployment lands somewhere that isn't UTC.
Problem
Every event whose client doesn't supply occurred_at gets timestamped with a naive (timezone-less) local datetime, while events that do supply occurred_at get a timezone-aware one from Pydantic's ISO-8601 parsing. The two kinds end up mixed in the same array cast to timestamptz[] on every batch flush, and Postgres silently interprets a naive value using its own session timezone rather than raising an error.
Current behavior
EventRecord.from_event_in builds the default timestamp with event.occurred_at or datetime.now() — datetime.now() with no argument returns a naive value (tzinfo=None), unlike the timezone-aware values the client-supplied path produces.
Expected behavior
Every EventRecord.occurred_at, whether client-supplied or server-defaulted, must be timezone-aware and in UTC; client-supplied values must continue to pass through unchanged.
Steps to reproduce
cd fastapi/ingest_service && .venv/bin/python -c "from app.schemas import EventIn, EventRecord; e = EventIn(device_id='d1', event_type='t', sequence=1); r = EventRecord.from_event_in(e); print(r.occurred_at, r.occurred_at.tzinfo)"Why this matters
datetime.now() and datetime.now(timezone.utc) differ by exactly one argument and both "look correct" if you're not specifically checking for timezone-awareness — the bug is invisible in any environment where the machine's local time already happens to be UTC, which is exactly what makes it a production-only surprise. This is a genuine cross-component contract mismatch: Python's naive-vs-aware datetime distinction meets Postgres's "silently assume session timezone" behavior for naive input to a timestamptz column — neither side raises an error, they just disagree quietly.
Suggested approach
Look at EventRecord.from_event_in in app/schemas.py. Compare the occurred_at = event.occurred_at or datetime.now() line against how the rest of this codebase already reaches for UTC elsewhere.
Acceptance criteria
Verification
pytest practice_tickets/tests/test_ticket_16_naive_datetime.py -v
Hints (0/2)
Try it without hints first — the reading is the exercise.
Working on this ticket
Work on a branch named for the ticket — that's what you'll submit.
Branch off your fork
Sign in to create it automatically, or do it yourself:
$git checkout -b fix/naive-datetime-timestampFix it and commit
Meet every acceptance criterion, and add a test that would have caught this.
Push the branch
$git push -u origin fix/naive-datetime-timestampSubmit it below
Paste your fork URL and the branch name, with a short write-up of the root cause.
Questions
Ask about anything unclear in the ticket — the maintainer and anyone who has solved it can answer. Please don't post full solutions.
Sign in to ask a question or reply.
Sign inhttp://localhost:8000localhost:5432curl localhost:8000/healthzcurl localhost:8000/readyzWork the tickets in practice_tickets/tickets/ (ordered easy → hard); each names one dedicated test in practice_tickets/tests/:
pytest practice_tickets/tests/test_ticket_01_persisted_count.py -v # a single ticket
./practice_tickets/run_tickets.sh # all 20, clean pass/fail summary
TICKET-17 needs a real Postgres to run at all (it checks which column a value lands in, which no fake/in-memory DB backend can meaningfully verify) — set TEST_DATABASE_URL=postgresql://<you>@localhost/ingest_test before running it or the full suite; without it, that one test is skipped, not failed.
Level 1
Fix a bug
Read existing behaviour, correct it.