Skip to content

feat: Seal of reliability infrastructure and official criterion - #1795

Open
jcpitre wants to merge 6 commits into
mainfrom
feat/seal_logic_and_official_criterion-1783
Open

feat: Seal of reliability infrastructure and official criterion#1795
jcpitre wants to merge 6 commits into
mainfrom
feat/seal_logic_and_official_criterion-1783

Conversation

@jcpitre

@jcpitre jcpitre commented Aug 5, 2026

Copy link
Copy Markdown
Collaborator

Closes #1783

Implements the seal or reliability algorithm as defined in #1761, but uses only official as the first criterion implemented.

The code is placed in functions-python/task_executor as the seal_of_reliability task.

By default, the task will extract the list of feeds to process. The query to extract the feeds is defined in functions-python/tasks_executor/src/tasks/seal_of_reliability/context.py and is currently:

db_session.query(Gtfsfeed).filter(
        Feed.data_type == "gtfs",
        Feed.status.notin_(["deprecated", "development"]),
        Feed.operational_status == "published",
    )

The payload also allows work on specific feeds (as listed in the stable_feed_ids parameter).
Note that the filtering (deprecated, development, etc) is also applied to the list of feeds passed in the payload.

From our AI friend:

What this does

Implements the Seal of Reliability evaluation logic and the Official criterion, per #1783. The logic is described in #1761; the sealcriterion and feedreliabilityseal tables come from #1760, which is the base branch of this PR.

A new tasks_executor task, update_seal_of_reliability, evaluates every eligible GTFS feed and writes the two seal tables. The source tables (feed, gtfsdataset, validationreport, gtfs_feed_availability_check) are only ever read.

The remaining five criteria are #1784 and #1782. The framework here is what they slot into: each is a new evaluator subclass plus whatever data it needs on the context.

How to run it

{
  "task": "update_seal_of_reliability",
  "payload": { "dry_run": true }
}

dry_run defaults to true, so an accidental invocation evaluates everything and writes nothing. Other parameters: stable_feed_ids, limit, criteria, batch_size, now. All documented in functions-python/tasks_executor/README.md, including a local curl walkthrough.

No Cloud Scheduler job is defined yet — the task is invoked manually for now. When it is scheduled it needs to run after the daily check_gtfs_feed_availability job, since the Available criterion (#1784) will read the availability rows recorded for that day.

Structure

File Role
criteria.py SealCriterionName enum (all six values, mirroring the DB type) and the 6-month reliability window
context.py Feed eligibility query and the per-feed FeedSealContext, built in bulk per batch
evaluators/base.py CriterionEvaluator base class, RawEvaluation
evaluators/official.py The Official criterion
state_machine.py transition() — steps 2-3 of the logic: failure tracking and grace_failing
seal_updater.py update_seals() — orchestration, the has_seal roll-up, and persistence
update_seal_of_reliability.py Task entry point: payload parsing only

Evaluators are pure functions over a pre-loaded context and never query, which is what keeps the query count proportional to the number of criteria rather than the number of feeds, and what makes the unit tests dataclass-shaped.

Decisions worth a reviewer's attention

A criterion that can't be evaluated is not a failing criterion. RawEvaluation.failing is tri-state; None means "no verdict this run" and leaves the stored row untouched. Without this, an outage in an upstream pipeline (no availability check recorded, no validation report yet) would look like every feed failing and would revoke seals across the catalogue once the grace period expired.

A grace period protects a seal a feed already holds; it cannot be used to earn one. Granting requires every criterion to pass now; keeping only requires that no failure has been confirmed. Otherwise a feed evaluated for the first time while already failing would be handed the seal for the length of the grace period, which is the opposite of "14 days to fix it before disqualification".

inactive and future feeds are deliberately evaluated. Excluding a feed does not make it neutral, it freezes its stored rows — an inactive feed should fail Fresh (future coverage) and lose the seal rather than keep displaying one because we stopped looking at it. Only deprecated and development are excluded, alongside anything not published.

Official has no grace period and no reliability window. It is a point-in-time state check that clears as soon as a feed is flagged official again. The state machine's grace and window logic is generic and ships now, so it is tested against a synthetic criterion that has both — Official alone would leave those branches unexercised for #1784 to discover.

now is a parameter, not a call to the clock. Runs are replayable and idempotent: re-evaluating for the same instant does not restart a failure streak, and a historical timestamp can be passed for backfills.

Both seal tables are written with Core statements against __table__. feedreliabilityseal.feed_id is simultaneously its primary key and a foreign key to feed(id), which sqlacodegen maps as joined-table inheritance (class Feedreliabilityseal(Feed), a sibling of Gtfsfeed). Persisting an ORM instance would attempt to insert a new feed. A surrogate id with UNIQUE (feed_id) in #1760 would remove the quirk if preferred — worth a decision, since it affects the base branch.

The report describes changes, not state. evaluations holds one entry per criterion whose verdict moved, rather than one per feed per criterion — the latter grows with the catalogue (~424 bytes each, megabytes for a full run) and is what sealcriterion is for. This follows failures in check_gtfs_feed_availability and dispatched in backfill_changelog. evaluations is also excluded from the log line, since Cloud Logging drops a LogEntry over 256 KB.

Seal counts are reported as seals_before_run / seals_after_run / seals_granted / seals_revoked, with before + granted - revoked == after. On a dry run, after is what would be stored.

Tests

54 tests, 94% branch coverage of the new package.

  • test_seal_evaluators.py — the Official criterion (true / false / NULL) and the base class contract. No database.
  • test_seal_state_machine.py — grace-period expiry, streak reset and restart, reliability-window boundaries at day 179 and 180, idempotency. No database.
  • test_seal_updater_db.py — eligibility, context loading, grant / deny / revoke / recover, count balance, against the test DB.
  • test_seal_end_to_end_db.py — seeds feeds, invokes tasks_executor with a hand-built flask.Request, inspects both tables, modifies official, runs again, and inspects the transitions. Covers dispatch and payload parsing, including that dry_run defaults to true through the payload layer.

test_seal_updater_db.py and test_seal_end_to_end_db.py need the local Postgres test database, like test_check_gtfs_feed_availability_db.py. CI already provides it.

Not in this PR

Summary:

Summarize the changes in the pull request including how it relates to any issues (include the #number, or link them).

Expected behavior:

Explain and/or show screenshots for how you expect the pull request to work in your testing (in case other devices exhibit different behavior).

Testing tips:

Provide tips, procedures and sample files on how to test the feature.
Testers are invited to follow the tips AND to try anything they deem relevant outside the bounds of the testing tips.

Please make sure these boxes are checked before submitting your pull request - thanks!

  • Run the unit tests with ./scripts/api-tests.sh to make sure you didn't break anything
  • Add or update any needed documentation to the repo
  • Format the title like "feat: [new feature short description]". Title must follow the Conventional Commit Specification(https://www.conventionalcommits.org/en/v1.0.0/).
  • Linked all relevant issues
  • Include screenshot(s) showing how this pull request works and fixes the issue(s)

@jcpitre jcpitre changed the title Seal of reliability first commit feat: Seal of reliability infrastructure and official criterion Aug 6, 2026
Base automatically changed from feat/1760 to main August 6, 2026 13:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Seal of reliability: Implement logic and Official criterion

2 participants