Skip to content

[fix](backend): search/ingestion/reminder correctness fixes + offline RAPTOR test suite - #87

Open
SirQuackyy wants to merge 4 commits into
devfrom
search-test-improvements
Open

[fix](backend): search/ingestion/reminder correctness fixes + offline RAPTOR test suite#87
SirQuackyy wants to merge 4 commits into
devfrom
search-test-improvements

Conversation

@SirQuackyy

Copy link
Copy Markdown
Member

Description
Adds a deterministic, offline test suite for the journey-planning stack (RAPTOR core, prediction ingestion, /plan-journey, API handlers) built around a brute-force reference implementation, and fixes the correctness bugs that suite plus a full-workspace review surfaced. Covers routing, prediction ingestion, the reminder pipeline, the walking/street-graph layer, request validation, background jobs, and CI. Delayed ("DLY") buses are now surfaced to the frontend instead of being dropped.

Type of Change

  • Bug fix (fix)
  • New feature (feat)
  • Refactor / code improvement
  • Dependency / build update
  • Documentation
  • Other (explain) - test infrastructure, CI workflow

Related Issues
none

Changes Made

  • Backend (TypeScript) — tests:
    • new offline suite (npm run test:search): raptor-core, raptor-property, ingestion, journey-plan, api-handlers
    • test/helpers/oracle.ts — brute-force Pareto enumerator written independently of the algorithm, plus a per-journey validator that replays legs against the raw data (buffers, pickUp/dropOff, transfer windows, criteria)
    • test/helpers/network.ts / mockBusApi.ts — network DSL and realistic BusTime v3 payload builders (prd/ptr shapes, DUE, DLY, waypoints, loop passes)
    • raptor-property runs 500 seeded random networks (loops, overtaking expresses, minute-quantized ties, boarding restrictions, windowed transfers) against the oracle; a 5,000-seed sweep was run out-of-band
    • live-server tests (api/path/search-stress) now throw instead of process.exit(1), which was killing the vitest worker and masking other files' results
    • reminder.test.ts: every(allThere) short-circuited after index 0 (only the first prediction was ever validated) → forEach; ride clock assertion tightened from ±2 days to ±1 hour; live-pipeline test no longer fails when no vehicles are in service
  • Backend (TypeScript) — routing (raptor/, services/journey.ts):
    • transfer time windows are now enforced (previously ignored entirely)
    • trips are split into strict FIFO chains, so an overtaking express is actually boarded and tied-but-diverging trips can't tie-dominate each other out of the result set
    • pickUp: false / dropOff: false handled correctly; footpath relaxation snapshots labels so walks can't chain; on-board labels advance per stop
    • range search: seeds are latest-catchable departures (departure − walk − buffer) plus the window end, journeys are keyed by trip identity (a missing tatripid no longer misclassifies a bus journey as walking), and the full Pareto set per trip signature is kept
    • route index (FIFO chains + inverted stop→routes index) is built once per graph rebuild instead of once per request
    • departureTime is the first leg's start time (was arrival − Σ durations, wrong with any wait); walkingPenalty of 0 is respected; walkingDistance docs corrected (penalty-weighted seconds, not meters)
  • Backend (TypeScript) — ingestion (services/graphBuilder.ts, state/):
    • looping buses keep each pass as a separate stop event (matching on stop + countdown) instead of collapsing to the last one
    • DLY predictions are surfaced to the prediction endpoints (finite far-future prdtm, sorted last) and excluded from routing and reminder tracking
    • predictions missing tatripid from different vehicles no longer merge into one trip
    • timing cache merges instead of replacing (was clobbering the seeded CN/CS interlining entries); stop-sort comparator made antisymmetric; isExtrapolated carried into the graph
    • graph stop times are anchored to a recorded UTC midnight, so requests just after 00:00 UTC no longer see ~1444-minute countdowns
    • findNearestStops heap comparator fixed (it evicted the nearest stop, not the farthest) and now uses the shared haversine
    • transient feed failures keep previous data everywhere; routes dropped from the feed are evicted; both feeds are fetched concurrently
  • Backend (TypeScript) — API clients (services/bustimeClient.ts, mbus.ts, ride.ts):
    • ride.ts was a drifting copy of mbus.ts — both are now thin wrappers over one shared factory
    • BusTime reports failures as HTTP 200 with an error body; those (and any out-of-protocol 200 body, e.g. a proxy maintenance page) are now treated as failures instead of "no buses", which previously wiped the prediction caches and mass-fired false "Bus Disappeared" pushes
    • 15s request timeouts added
  • Backend (TypeScript) — walking / street graph:
    • A* never re-queued a node whose score improved while queued → suboptimal paths; now verified to match Dijkstra exactly on the real graph
    • parallel multigraph edges deduped at load (path reconstruction was summing the wrong edge)
    • walkingCache.json (224 MB) and landmark_dist.json are written atomically (temp + rename) with guarded parses — a kill mid-write used to make the server crash-loop on every boot
    • landmark cache invalidated against the map file's mtime (a stale cache makes the ALT heuristic inadmissible)
    • Dijkstra LRU bounded by retained size instead of entry count (the old bound permitted ~13 GB of heap)
    • cold-cache path computation yields to the event loop (was a 3–6 minute freeze); stop→node map rebuild skipped when the stop set is unchanged, so the distance cache survives longer than 60s
    • hand-rolled MinHeap replaced with the already-installed @datastructures-js/priority-queue
  • Backend (TypeScript) — reminders:
    • stage 1 tracks a pass by proximity to its expected arrival: an early-running bus stays matched (used to fire a false "disappeared"), a looping bus's next pass can't masquerade as a 20-minute delay, large single-tick jumps are followed as delays, and a DLY flip holds rather than guessing
    • vid-less (schedule-only) feed rows can't shadow a trackable bus or leak into stage 1
    • subscriptions expire after 3h (no-candidate zombies used to survive forever and fire a stale reminder the next service day); dead FCM tokens are pruned on registration-token-not-registered
    • swapToken dedups against the target token and is a no-op for identical tokens; errors log with real stacks (JSON.stringify(err) was printing {}); failure logs name the correct batch's tokens; Firebase init is idempotent
  • Backend (TypeScript) — API / jobs / app:
    • /plan-journey validates coordinates, walkingPenalty, and range (non-numeric input returned 500 before)
    • unknown route ids return a retryable 503 while either feed's route list is still loading, 400 only when the answer is knowable (clients were dropping valid reminders during the startup window)
    • thresh bounded to 1–120 minutes; modifyReminders validates the whole batch before applying anything; shared parseBody helper replaces four copies of the zod error block
    • legacy /getBusPredictions1/:busId no longer interpolates a user-controlled param into a query string carrying the API key, and honors MBUS_URL
    • prediction caches are prototype-less, so /getBusPredictions/constructor no longer breaks the response shape
    • min_supported_version restored to 2.0.2 (branch had it lowered to 2.0.0 for dev, which would re-admit clients main locked out)
    • vehicle-image fallback served with 200 so it actually renders; notifyMeLater returns a real 400 and restores its 10s delay; nearest-stops falls back to k=2 on garbage input
    • all asset paths resolve relative to the module instead of process.cwd(); boot jobs share the interval overlap guards; startBackgroundJobs is skipped under vitest
    • removed dead src/types.ts and the unused ts-array-utils dependency; documented_design.md status corrected from "Implemented" (the files it references don't exist on this branch)
  • Firebase / Shared:
    • FCM sends: dead-token pruning, per-token failure logs fixed, unhandled-rejection guard on sendEachForMulticast (a rejection could take down the server), idempotent initializeApp
    • CI: docs are deployed only on pushes to main (fork PRs previously always failed the deploy step for lack of secrets, and any branch push published docs); test job waits on real data readiness instead of a no-op curl loop that exited on the server's 404; npx tsc with a pinned typescript devDependency instead of the runner's global one

Testing Done
99 automated tests pass (77 offline + 22 reminder), tsc --noEmit clean. The offline suite runs in ~0.5s with no network and no live server. A 5,000-seed differential fuzz against the brute-force oracle passes on the final routing code, and A* was re-verified to match Dijkstra to sub-second rounding on the real Ann Arbor graph. Also smoke-tested against a locally running server with the live feed: journey planning, range mode, validation paths, and error handling all behave correctly, and the working tree stays clean. Every fix in this PR was additionally put through an adversarial review pass that specifically hunted for regressions and incomplete fixes in the diff; that pass found 13 issues with the first round of fixes, all of which are fixed here and covered by tests.

Screenshots / Demo (if UI or notification change)
n/a

Checklist

  • Commit messages follow Conventional Commits
  • PR title follows [type](scope): short description
  • PR target branch is not main and is our current working update branch (e.g. maizebus2.1)
  • No print() / debugPrint() / console.log() left in production code
  • Secrets / keys not committed

@SirQuackyy SirQuackyy changed the title fixes to raptor/reminders/walking + cleanup + full suite of offline t… [fix](backend): search/ingestion/reminder correctness fixes + offline RAPTOR test suite Aug 27, 2026
@SirQuackyy
SirQuackyy requested a review from jumpy-cat August 27, 2026 18:11
@SirQuackyy
SirQuackyy marked this pull request as draft August 27, 2026 18:14
@SirQuackyy
SirQuackyy marked this pull request as ready for review August 27, 2026 19:21
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.

1 participant