Suite 45 — ingestion pipeline correctness fixes (45.1-45.4, 45.7-45.10) - #43
Merged
Conversation
….7-45.10) Nine of ten suite-45 tickets. 752 tests green, ruff clean, migrations linear (0019 -> 0020 -> 0021). 45.1 prompt_suffix never applied in any scheduled run — the batch functions already resolved it correctly; the bug was purely that tasks.py and ingest_events.py called them bare. New ingestion/prompt_dispatch.py loops per source (all sources, not just active=True, so deactivated sources' unprocessed rows aren't stranded) and sweeps leftovers. 45.2 TIME_ZONE UTC -> America/New_York. Reproduced first: a 9:00 PM ET event rendered "Thursday, August 6 - 1:00 AM" — wrong time and wrong weekday. USE_TZ=True keeps storage UTC, so no data migration. Swept for double-correction sites; found none. 45.3 Reject cancelled events pipeline-wide at the standardizer chokepoint, matching title only (a description reading "rain date if cancelled" must not trip it). Guard runs before the Gemini call. `cancelled` is deliberately excluded from CANDIDATE_STATUSES: organizers un-cancel and resubmit, so anchoring would silently suppress the legitimate resubmission. 45.4 EventSource.default_town, resolved per staged event inside the publish loop rather than threaded from the task — a batch spans multiple sources. resolve_town keeps its fallback-not-override semantics (PR #42 regression). 45.7 EventSource.blocked_reason/blocked_since + a `quarantined` health level. Quarantined sources keep polling; refused -> ok is how we learn 45.6 landed. 45.8 manage.py probe_sources — fetch-only reachability from the real prod egress path, which the DEBUG-gated devtools playground structurally cannot test. Surfaces WAF headers; non-2xx is always `refused` so a captcha page can't parse to 0 items and misreport as ok. 45.9 scrape_all_sources_task docstring stated a stale roster; now states the rule, plus the scrape-worker vs backend container distinction. 45.10 Internal-only rename raw_start/raw_end -> raw_start_datetime/ raw_end_datetime, removing the third naming. Event.date untouched — no API break. Staging->published boundary now documented in ingestion/models.py. 45.5 was executed directly against prod (source id 8 deactivated; evidence showed it a strict subset of id 6, inverting the ticket's guess). 45.6 is not built and needs an owner decision — see STATE.md. Co-Authored-By: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Nine of ten suite-45 tickets, orchestrated in four batches. 752 tests green, ruff clean, migrations linear (0019 → 0020 → 0021).
What's here
prompt_suffixnever applied in any scheduled run — newingestion/prompt_dispatch.pyTIME_ZONEUTC →America/New_York— digest emails showed every event 4–5h lateEventSource.default_townto recoverskipped_no_townlossesblocked_reason/blocked_since+ aquarantinedhealth levelmanage.py probe_sources— reachability from the real prod egress pathscrape_all_sources_taskdocstringraw_start→raw_start_datetimeFindings that changed the tickets
45.2's premise was unverified in the ticket — I reproduced it first. A 9:00 PM ET event rendered
Thursday, August 6 · 1:00 AM: wrong time and wrong weekday.USE_TZ=Truemeans storage stays UTC, so no data migration. Swept for double-correction sites; found none.45.1's root cause was narrower than described. The batch functions already resolved
prompt_suffixcorrectly — the bug was purely thattasks.pyandingest_events.pycalled them bare. Also:RawEvent.sourceis non-nullable, so the "orphaned rows" concern was theoretical; the sweep is in as defense-in-depth.45.3:
cancelledis deliberately excluded fromCANDIDATE_STATUSES. Unlikeskipped_no_town(structural, won't reverse), organizers un-cancel and resubmit. As a dedupe anchor it would silently match the legitimate resubmission as a duplicate and permanently suppress a real event — so it follows therejectedprecedent instead.45.4 keeps
resolve_town's fallback-not-override semantics (the PR #42 regression). The fallback resolves per staged event inside the publish loop rather than being threaded from the task, since a batch spans multiple sources.45.8 hardens beyond spec: a WAF captcha page isn't empty, so it would parse to 0 items and misreport as
ok. Any non-2xx is nowrefused.Not in this PR
source_uids vs id 6's 9, making id 8 the strict subset. Deactivated, not deleted, soSourceRunhistory survives.events/searchAPI was discontinued Dec 2019 (org-owned events only), so an official integration covers 0 of 4 sources, not 3 of 4. Their WAFcaptchaalso plausibly enforces their own anti-scraping ToS.Deploy notes
Migration
0021is aRenameField, and deploy runsmigratebeforeup -d— so for ~30–60s the old containers run old code against the new column./api/events/direct-submitand the admin RawEvent list would 500 in that window; the public/events/API doesn't touchRawEvent. A pre-migratepg_dumpis taken automatically.Post-deploy ops (Neon data — no migration ships these):
default_townon prod sources, thenmanage.py reopen_skipped_towns(18 stranded rows).blocked_reasonon the 4 WAF-blocked sources.probe_sourcesinscrape-worker—backendhas no Chromium.🤖 Generated with Claude Code