|
| 1 | +# BusAnalysis — related work on the same data |
| 2 | + |
| 3 | +[`lihay7/BusAnalysis`](https://github.com/lihay7/BusAnalysis) is an independent |
| 4 | +reconstruction of Israel's planned-vs-actual bus record, built on the same |
| 5 | +hasadna `stride` data this hackathon uses. It is worth knowing about here for |
| 6 | +two reasons: it answers "did the scheduled bus run?" at national scale, and it |
| 7 | +found five defects in the upstream data that affect anyone querying the same |
| 8 | +tables — including our analyses. |
| 9 | + |
| 10 | +**Access:** the repo is **private** (a plain `curl` or unauthenticated fetch |
| 11 | +404s — that is permission, not a missing repo). Licence is MIT, but it is not |
| 12 | +our code: anything ported across needs `lihay7`'s sign-off first. Read it with |
| 13 | +an authenticated `gh`: |
| 14 | + |
| 15 | +```bash |
| 16 | +gh repo view lihay7/BusAnalysis |
| 17 | +gh api repos/lihay7/BusAnalysis/git/trees/main?recursive=1 --jq '.tree[].path' |
| 18 | +``` |
| 19 | + |
| 20 | +Scope: 1,306 service days (2023-01 → 2026-07), 117.2M schedule links, ~7 GB |
| 21 | +local warehouse rebuilt from GTFS + SIRI. Created and pushed 2026-07-31. |
| 22 | +Languages are Python (~1 MB of source) and HTML (the committed dashboards). |
| 23 | + |
| 24 | +--- |
| 25 | + |
| 26 | +## The four findings |
| 27 | + |
| 28 | +| # | Finding | Fix direction | |
| 29 | +|---|---|---| |
| 30 | +| 1 | **5.2%** of scheduled departures never appear in the national tracking record (±5 min, 116.4M rides, tracked operators). Widening to ±60 min only moves it to 4.9% — so these are cancellations, not delays. | Publish the rate per line and per month **with its window stated**. | |
| 31 | +| 2 | **~1.7×** — the sparsest lines (under 8 departures/day) lose **7.8%** of departures against **4.6–4.8%** on mid-frequency lines, after the control that kills the naive version of the claim. | Protect thin lines first: one missing bus on a two-a-day line is the whole service that day. | |
| 32 | +| 3 | **5.0% vs 1.5%** — reconstructed non-execution for 2024-H1 against the figure the ministry's own electronic control published for the same period, both at ±30 min. | Compute the violation rate from the open record; the enforcement basis is already electronic. | |
| 33 | +| 4 | **4.2 minutes** median *extra* waiting beyond what the published timetable promises, nationally, every month. 11.8% of line-months run 10+ minutes over promise. | Publish a timetable the service can keep — an honest every-20 beats a broken every-10. | |
| 34 | + |
| 35 | +Each number is reproducible from committed aggregates in `report/metrics/` |
| 36 | +(parquet, ~9 MB) without rebuilding the warehouse. |
| 37 | + |
| 38 | +--- |
| 39 | + |
| 40 | +## Five upstream data defects — relevant to our analyses |
| 41 | + |
| 42 | +Written up as paste-ready issues in `handoff/issues/`. These describe the |
| 43 | +`stride` database we query through [stride.py](../openbus_hack/stride.py), so |
| 44 | +they constrain what our own cards can honestly claim. |
| 45 | + |
| 46 | +| ID | Defect | Why it matters to us | |
| 47 | +|---|---|---| |
| 48 | +| **F1** | The SIRI→GTFS ride-matching job has written **zero matches since October 2024**. All four match columns (`gtfs_ride_id`, `route_gtfs_ride_id`, `scheduled_time_gtfs_ride_id`, `journey_gtfs_ride_id`) fail together; the raw feed is healthy (~2.9–3.1M `siri_ride` rows/month). | Any analysis joining actual→planned via the stored link gets nothing for the last ~21 months. You have to match yourself. | |
| 49 | +| **F6** | **2.6%** of `siri_ride` rows are surplus duplicate journeys (3.93% sit in a duplicated group); rate has ~quadrupled since 2023 and is 66× skewed across operators. Downstream of the `scheduled_start_time` drift bug (hasadna #390). | Raw counts of tracking rows overstate real service. Affects anything counting rides rather than deduplicating journeys. | |
| 50 | +| **F7** | `first_vehicle_location_id` is **100% null for 18 consecutive months** (2024-12 → 2026-05, 49.4M rides); `duration_minutes` tracks it. | It is a **processing-state flag, not a property of the bus** — it encodes whether stride's enrichment job ran, not whether a vehicle transmitted. Easy to misread as a per-operator transmission metric. Don't. | |
| 51 | +| **F8** | The GTFS import sometimes keeps the previous release alongside the current one under a single `gtfs_route.date`, **doubling planned counts** on affected dates. 50 days exceed 1.5×, 36 exceed 1.8×, scattered with no era pattern. | Planned-ride denominators are ~2× too high on those dates. | |
| 52 | +| **F9** | **Five operators never reach the tracking feed** — three absent entirely across 3.5 years, two under 1% covered. 2.74M scheduled rides, ~2.3% of national planned volume. | They are unmeasurable, not failing. Counting them as cancellations is most of how 5.2% becomes 7.4%. | |
| 53 | + |
| 54 | +Fixing all five moves the national figure from **7.4% → 5.2%** (and to 42% on |
| 55 | +the worst days), which is the case for treating them as defects rather than |
| 56 | +noise. |
| 57 | + |
| 58 | +--- |
| 59 | + |
| 60 | +## How to read numbers off this data honestly |
| 61 | + |
| 62 | +Four rules the project holds itself to. They apply to our cards too: |
| 63 | + |
| 64 | +1. **Every rate names its time window.** There is no tolerance-free answer to |
| 65 | + "did the bus run?" A figure without a window can't be compared to one that |
| 66 | + has it. |
| 67 | +2. **These are claims about the tracking record, not about the road.** A ride |
| 68 | + the feed never saw counts as missing. No independent national bound exists |
| 69 | + on the feed's own completeness — so the phrasing is "never appears in the |
| 70 | + record", never "the bus didn't run". |
| 71 | +3. **Time differences here are scheduled-against-scheduled, not punctuality.** |
| 72 | + The record holds no observed departure time. Their M2 is a *waiting* |
| 73 | + measure; actual lateness needs stop-level data. |
| 74 | +4. **Five operators have no tracking feed at all** — excluded from every |
| 75 | + performance rate and reported as a finding in their own right (F9). |
| 76 | + |
| 77 | +--- |
| 78 | + |
| 79 | +## How it relates to this repo |
| 80 | + |
| 81 | +Same data, opposite architecture: |
| 82 | + |
| 83 | +| | this repo | BusAnalysis | |
| 84 | +|---|---|---| |
| 85 | +| Data access | live paginating API client with a disk cache | ~7 GB local mirror (4 slim tables, 245M rows), not committed | |
| 86 | +| Unit of work | one `@analysis` function per person → card | a batch pipeline: mirror → per-day matcher → marts → metrics → figures | |
| 87 | +| Matching | not attempted; we lean on what the API returns | own greedy 1:1 matcher, 6 time windows, in `src/busanalysis/matching/greedy.py` | |
| 88 | +| Output | live React dashboard over FastAPI | committed self-contained HTML (`report/dashboard/index.html`, 11.6 MB; `brief.html`, one screen) | |
| 89 | +| Registry | `openbus_hack/registry.py` discovers `analyses/*` | has its own `src/busanalysis/registry.py` for the same job | |
| 90 | + |
| 91 | +Layout of theirs, briefly: |
| 92 | + |
| 93 | +``` |
| 94 | +src/busanalysis/ |
| 95 | + pipeline/ mirror, extract_day, marts, backfill |
| 96 | + matching/ greedy 1:1 matcher + validation |
| 97 | + quality/ filters |
| 98 | + metrics/ departure_fidelity, enforcement_gap, gap_series, |
| 99 | + record_integrity, city_profiles |
| 100 | + viz/ charts, dashboard, brief, city_map, heatmap |
| 101 | +tests/ substantial — test_extract_day.py alone is 42 KB |
| 102 | +report/ dashboards, figures, committed parquet aggregates |
| 103 | +handoff/ the five defect issues + QUESTIONS_FOR_HASADNA.md |
| 104 | +assistant/ research record: findings ledger, verification memos, |
| 105 | + DECISIONS.md, ATTEMPTS_AND_FAILURES.md (the dead ends) |
| 106 | +plans/ the plans each phase was executed from |
| 107 | +``` |
| 108 | + |
| 109 | +## Directions worth taking from it |
| 110 | + |
| 111 | +- **Port finding #2 as an analysis card.** "Sparse lines lose disproportionately |
| 112 | + more service" maps cleanly onto `@analysis` + `bar_chart` over |
| 113 | + `gtfs_rides_agg`, and it is the finding that most needs a live, per-line view |
| 114 | + rather than a static national figure. |
| 115 | +- **Cross-check.** They built from an offline mirror, we query the API live. If |
| 116 | + our numbers diverge from their 5.2%, the divergence itself is informative — |
| 117 | + most likely F6/F8 territory. |
| 118 | +- **Carry the defect list upstream.** The five `handoff/issues/` write-ups are |
| 119 | + directly relevant to the consolidation this hackathon feeds into |
| 120 | + (`open-bus-stride-api`, `open-bus-pipelines`). F1 in particular claims the |
| 121 | + national "rides that didn't run" statistic has had no computable basis since |
| 122 | + October 2024. |
0 commit comments