Releases: SAY-5/rideloop
Release list
v5.0.0
Observability and deterministic replay.
- Prometheus
GET /metricson all three services (prometheus-client, aggregated across uvicorn workers viaPROMETHEUS_MULTIPROC_DIR). Counters:rideloop_matches_total,rideloop_match_latency_seconds(histogram),rideloop_matches_per_minute(trailing-minute gauge from the sweep loop),rideloop_unmatched_total,rideloop_claim_conflicts_total,rideloop_ttl_expiries_total,rideloop_offers_total{outcome}, sweep/position/ride counters, and per-route HTTP request counts and latency. - TTL expiries are now observable: the nearby read filters
ttlclient-side and deletes an expired row on first sight with a conditional delete (ttlunchanged), so each expiry is counted once and stale rows stop costing reads while DynamoDB's own sweep lags. sim/replay.py: JSON-lines ride streams (driver positions and ride requests with relative timestamps) replayed through the matcher on a virtual clock. Same file, same matches, same rider-to-driver assignments; the summary carries a fingerprint and a laterrunexits 1 on drift.make replaysynthesizes a stream, replays it twice and compares;sim.demo --recordcaptures a live run.- Tests:
/metricson every service with request counting, matcher counters after a sweep, claim conflicts under a 8-thread race, an expiry counted once, replay determinism across a wiped store, the CLI round trip including a tampered summary. 94 tests total. - Also in this release: the simulated fleet runs in worker processes (a single-process load generator starved its own pings and made offers expire), expired driver rows are stamped rather than deleted so a resumed driver keeps its status, and the compose file waits for DynamoDB Local to answer before starting the services.
make demo on this release: 600 rides submitted at 10/s, 600 matched and completed, 586 matches per minute, match latency p50 62 ms / p95 1101 ms (the p95 is the one ride in ten whose driver declined and that was rematched), 51 declines re-queued with 0 timeouts, 14305 position posts from 300 drivers with 3 errors, and the silenced driver gone from the map once its TTL passed.
Correction — 2026-09-29
The figures above are an unverified historical documentation transcript, not
a retained benchmark. No raw run log, UTC execution timestamp or machine
metadata was retained for it. Documentation commit time does not establish
run time. The p95 attribution to declined rides was not established by retained
latency samples and should not be read as a measured causal explanation.
The earlier 601/min and 59/101 ms figures describe a different documented,
pre-offer/decline workload and are also unverified. The portfolio's former
“p50 54 ms” headline has no supporting retained record. None of these figures
is a valid comparison with the browser's virtual-clock model or a backend
capacity claim. See measurement provenance.
The TTL paragraph above also contains a superseded description: the release
stamps expired rows using a conditional update; it does not delete them on
read. This preserves driver status until DynamoDB's own expiry processing.
The later “Also in this release” paragraph describes the shipped behavior.
This dated correction preserves the original release text as history and
makes no new throughput claim.
v4.0.0
Driver accept, decline and rematch.
- A match is now an offer: trips carry
offered_atandaccepted_at, drivers carryoffers,accepts,declines(Alembic0004; existing matched trips are backfilled as accepted). POST /rides/{id}/accept?driver_id=takes the trip; only after acceptance do the driver's pings move it through en_route, arrived, in_trip and completed.POST /rides/{id}/decline?driver_id=puts the trip back torequestedwith the driver appended todeclined_by, then releases the DynamoDB claim with a conditional update (status = busy AND trip_id = this trip) so a driver already claimed elsewhere is untouched. The next sweep rematches the trip to the nearest driver not indeclined_by.- Offers nobody answers within
DISPATCH_OFFER_TIMEOUT_S(15 s) are expired at the start of each dispatcher sweep (FOR UPDATE SKIP LOCKED) and handled as declines with the eventoffer_timeout. GET /drivers/{id}/acceptancereports offers, accepts, declines and the acceptance rate;GET /dispatch/statsaddsoffers_declinedandoffers_timed_out.- Simulated drivers accept offers (and decline a configurable share, 10% in
make demo); simulated riders wait for an accepted match. The demo summary reports accepted, declined, timed out and rematched rides. - Tests: acceptance gating pings, decline releasing the claim and rematching without the decliner, a trip waiting when its only driver declined, offer timeout in the sweep, the conditional release, migration 0004 up and down. 85 tests total.
v3.0.0
Surge pricing per geohash cell.
- Trips record
pickup_cell(precision-5 geohash) and thesurge_multiplierthey were priced at (Alembic0003, indexed on(pickup_cell, requested_at)). - Demand per cell is a half-life-decayed sum of recent requests (60 s half-life by default); supply is the count of available, unexpired drivers in the cell. The multiplier is 1.0 while supply covers demand and otherwise
1 + 0.5 * (demand/supply - 1), capped at 3.0. Nothing is counted incrementally, both numbers are read from PostgreSQL and DynamoDB at request time, so replicas cannot drift. POST /ridesprices the trip against the demand already queued in its cell;GET /rides/surge?lat&lngquotes a point;GET /dispatch/heatmaplists every cell with recent demand or available supply, hottest first.- Configuration:
SURGE_HALF_LIFE_S,SURGE_STEP,SURGE_MAX_MULTIPLIER. - Tests: multiplier curve, decay arithmetic, a burst of requests in one cell driving the multiplier up through the HTTP service and cooling off ten minutes later, heatmap ordering through the dispatch service, migration 0003 up and down. 79 tests total.
v2.0.0
Trip lifecycle and pickup ETA.
- New trip states
arrivedandin_tripbetweenen_routeandcompleted(Alembic0002; the downgrade folds them back intoen_routeand rebuilds the enum type). - The driver_location service advances a busy driver's trip from its position reports: first ping sets
en_route, within 40 m (L1 route distance) of the pickup setsarrived, 120 m away again setsin_trip, within 40 m of the dropoff setscompletedand releases the driver. Each hop is timestamped and recorded inevents. - Driver items carry a smoothed
speed_mpscomputed from consecutive pings. GET /rides/{id}returnsdriver_position(lat, lng, heading, speed, distance to pickup, updated_at) andpickup_eta_s= route distance / observed speed + pull-over time.- Simulated drivers drive to the pickup, wait, and continue to the dropoff; simulated riders follow the trip and only force completion after the ride window.
- Tests: full state progression through the HTTP service, ETA within 15% on a simulated grid drive, migration 0002 up and down, 75 tests total.
v1.0.0
First tagged release of the ride dispatch platform. Three FastAPI services (driver_location, ride_request, dispatch), geohash-partitioned DynamoDB driver positions with TTL expiry, a PostgreSQL trip schema managed by Alembic, and a nearest-driver matcher with atomic claims and an expanding search radius. 67 tests cover the store, the matcher, the trip lifecycle, migrations and a 500 matches per minute throughput floor.