Skip to content

Releases: SAY-5/rideloop

v5.0.0

Choose a tag to compare

@SAY-5 SAY-5 released this 08 Sep 22:19

Observability and deterministic replay.

  • Prometheus GET /metrics on all three services (prometheus-client, aggregated across uvicorn workers via PROMETHEUS_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 ttl client-side and deletes an expired row on first sight with a conditional delete (ttl unchanged), 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 later run exits 1 on drift. make replay synthesizes a stream, replays it twice and compares; sim.demo --record captures a live run.
  • Tests: /metrics on 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

Choose a tag to compare

@SAY-5 SAY-5 released this 05 Sep 22:36

Driver accept, decline and rematch.

  • A match is now an offer: trips carry offered_at and accepted_at, drivers carry offers, accepts, declines (Alembic 0004; 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 to requested with the driver appended to declined_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 in declined_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 event offer_timeout.
  • GET /drivers/{id}/acceptance reports offers, accepts, declines and the acceptance rate; GET /dispatch/stats adds offers_declined and offers_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

Choose a tag to compare

@SAY-5 SAY-5 released this 05 Sep 22:24

Surge pricing per geohash cell.

  • Trips record pickup_cell (precision-5 geohash) and the surge_multiplier they were priced at (Alembic 0003, 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 /rides prices the trip against the demand already queued in its cell; GET /rides/surge?lat&lng quotes a point; GET /dispatch/heatmap lists 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

Choose a tag to compare

@SAY-5 SAY-5 released this 05 Sep 08:15

Trip lifecycle and pickup ETA.

  • New trip states arrived and in_trip between en_route and completed (Alembic 0002; the downgrade folds them back into en_route and 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 sets arrived, 120 m away again sets in_trip, within 40 m of the dropoff sets completed and releases the driver. Each hop is timestamped and recorded in events.
  • Driver items carry a smoothed speed_mps computed from consecutive pings.
  • GET /rides/{id} returns driver_position (lat, lng, heading, speed, distance to pickup, updated_at) and pickup_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

Choose a tag to compare

@SAY-5 SAY-5 released this 03 Sep 20:12

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.