Skip to content
Esc
L train: Canarsie Tube closedRun the controller
Lexington Ave express suspendedRun the controller
A and C: Cranberry Street Tunnel closedRun the controller
Aisha's tripArchitect, uses a wheelchair
Dev's tripLine cook, breakfast service
Grace's tripParalegal, downtown
Kwame's tripBank operations lead
Lena's tripGraduate student
Maya's tripICU nurse, day shift
Sam's tripAnalyst, Financial District
Tomás's tripHigh-school teacher
HomeThe L closure, chapter by chapter
Control roomClose a line and watch the agents
RidersRemembered riders and their routes
MemoryFacts over time, episodes, handoffs
Agency brainWho owns it, who knows it, and why
ProofFalkorDB against NetworkX
Who owns the Myrtle-Wyckoff elevator outage?Ask the agency brain
Why do L tube bus bridges get 27% more buses?Ask the agency brain
Who knows the most about the G at Metropolitan Av?Ask the agency brain
What changed in the last 7 days?Ask the agency brain
Who it hitsriders by stops from the closure
Where they gothe G overload
What it can carrymax flow
The choke pointmin cut
The fixa shuttle tested on a fork
The ridersadvisories after approval
FalkorDB vs NetworkXmax flow on every closure
Many agents at oncetenant isolation under load
What each tool costsqueries and milliseconds
Data and assumptionsMTA sources
Every querythe Cypher behind every tool

Proof

Every network number is computed twice, by two different programs.

FalkorDB does the work: the agents' tools run Cypher and graph algorithms on your copy of the subway graph. A separate Python program built on NetworkX reads the same data files, follows the same written spec, and recomputes the answers. Every max flow also carries a certificate that proves it maximal. Where a number would differ, the checks that compare them stop with an error: pnpm judge re-measures every README number on a FalkorDB and fails on any mismatch. Memory, the agent and the brain cannot be recomputed that way, so they are measured against baselines instead, with their caveats beside them.

The entry submitted before the deadline is v1.0.0. Checks 2 to 7 were added after the deadline, on October 19.

Check 1

FalkorDB and NetworkX agree on every closure.

Max flow from a super source to a super sink over spare capacity, per direction of travel. FalkorDB runs algo.maxFlow on a forked graph; the checker runs NetworkX's Edmonds-Karp on the same network. Both follow docs/model.md.

Riders an hour to move and max flow, FalkorDB against NetworkX
ClosureDirectionTo move · FalkorDBNetworkXMax flow · FalkorDBNetworkXAgree
L train: Canarsie Tube closedforward18,43218432.417,16717166.7✓ yes
L train: Canarsie Tube closedbackward2,4232423.12,0332033.4✓ yes
Lexington Ave express suspendedforward15,02215021.715,02215021.7✓ yes
Lexington Ave express suspendedbackward14,48714486.614,48714486.6✓ yes
A and C: Cranberry Street Tunnel closedforward14,26714267.514,26714267.5✓ yes
A and C: Cranberry Street Tunnel closedbackward2,0012000.62,0012000.6✓ yes

Riders hit, by stops from the L closure

The affected_riders tool walks upstream one hop at a time in Cypher and joins the 2025 origin-destination flows whose paths cross the closure. NetworkX counts the same thing by breadth-first search.

WithinFalkorDBNetworkXAgree
boards in the closed stretch7,8237823.2✓
1 stop10,14210142.4✓
2 stops12,29212291.6✓
3 stops13,40013399.8✓
4 stops14,71314713.0✓
5 stops15,97515975.3✓
6 stops17,27217272.3✓

How the check runs

  • Same inputs. Both read data/nyc/network.json and demand.json, built from MTA files by scripts/build-network.ts.
  • Different code. FalkorDB tools in TypeScript and Cypher; verify.py in Python with NetworkX 3.7, which also checks that the min cut's capacity equals the max flow.
  • Run by hand, not yet in CI. run-scenarios.ts seeds FalkorDB, runs every tool on all three closures and exits with an error when a number drifts from NetworkX by more than rounding. A GitHub workflow for it is written but not switched on, so each run is started by hand before results are committed.
  • Where the min cut sits. On the L, the min cut holds no open segment: the open network has room everywhere the riders can reach. Only stations the closure cuts off limit the flow, 1 Av as an origin and 3 Av as a destination, both served only by the L and with no walking link. That is why the recommended shuttle starts at 1 Av.

Check 2

Every max flow is certified before anyone reads it.

FalkorDB's algo.maxFlow is fast but not always right, so its answer is never taken on its word. After each call the tool reads back the flows and checks three things: every edge within capacity and flow conserved at every station; no augmenting path left in the residual graph; and the value equal to the capacity of the cut it leaves. If any check fails, Detour's own Dinic solver recomputes the flow on the same edges and the tool says so in its result. Code in src/lib/graph/flow.ts.

Max flow, cut capacity, solver, headroom and cut-off stations for each closure and direction
ClosureDirectionMax flowCut capacityAnswer fromHeadroomWhat limits it
L train: Canarsie Tube closedforward17,166.717,166.7✓ FalkorDB, certified1.77×No open segment; only cut-off stations: 1 Av (origin), 3 Av (destination)
L train: Canarsie Tube closedbackward2,033.42,033.4✓ FalkorDB, certified> 20×No open segment; only cut-off stations: 1 Av (destination), 3 Av (destination)
Lexington Ave express suspendedforward15,021.715,021.7✓ FalkorDB, certified10.61×Nothing today; first to fill: J 111 St → Woodhaven Blvd
Lexington Ave express suspendedbackward14,486.614,486.6✓ FalkorDB, certified11.14×Nothing today; first to fill: G Greenpoint Av → 21 St
A and C: Cranberry Street Tunnel closedforward14,267.514,267.5✓ FalkorDB, certified7.12×Nothing today; first to fill: FS Park Pl → Franklin Av-Medgar Evers College/Botanic Garden
A and C: Cranberry Street Tunnel closedbackward2,000.62,000.6✓ FalkorDB, certified> 20×Nothing within 20× today's demand

Why the check is there

Another Graph Hacks team, Chokepoint, found algo.maxFlow returning wrong answers on node-split networks and published an 11-edge reproduction. On FalkorDB 4.22.1, the engine Detour runs on, it returns 300 where the maximum is 453, with flows that are not even conserved. The certificate rejects it. On 100 random node-split networks FalkorDB returned a value below the maximum or a flow that breaks conservation 13 times; the certificate caught 13 and let none through.

scripts/judge/maxflow-repro.ts · pnpm judge:maxflow

On Detour's own networks

The defect has not fired on the subway: 90 of 90 max flows on 48 held-out closures and all 254 headroom solves passed the certificate as FalkorDB returned them (0 needed Dinic). Headroom is how far displaced demand can grow before an open segment saturates: an exact parametric max flow, a few certified solves on the fork per direction.

docs/model.md, “Checking max flow” and “Headroom”

Check 3

48 closures nobody tuned for: the two programs still agree.

The three demo closures could have been fitted by hand. These were not: 48 stretches of 2 to 6 segments on 25 lines, drawn with a fixed seed (20261019), each closed on FalkorDB through the controller's own procedure and recomputed by the NetworkX verifier.

FalkorDB against NetworkX

Share of held-out closures on which FalkorDB and NetworkX agree, by measure
MeasureAgreeRate
Closed segments48 of 48100%
Displaced riders48 of 48100%
Max flow, each direction48 of 48100%
Max flow certified48 of 48100%
Cut-off stations48 of 48100%
Headroom multiplier48 of 48100%
Riders with no path48 of 48100%
Fastest-path overflow48 of 48100%

Rider counts within 1.5 an hour; closed segments and cut-off stations identical; headroom within 0.1%. Overflow: the same value on 42 closures. On 9 the fastest path is not unique, two routes take the same minutes, and which one each program picks moves the overflow (by up to 1,248 riders an hour on 7: 42 St-Bryant Pk/5 Av to 33 St-Rawson St); there it is checked against the range NetworkX finds across 8 random tie-breaks, and lies inside it on 6 more.

The controller's plan against an oracle

15 of the 48 closures leave riders with no path or a max-flow shortfall, so the controller tests shuttles. For each, an oracle simulated every candidate leg the controller saw on its own fork and kept the best gain per bus (riders with no path plus shortfall cleared, per bus on the road).

Best choice
11 of 15
Mean gap
4%
Worst gap
33%
  • Q: 72 St to 57 St-7 Av: the controller cleared 93 riders per bus with 12 buses; the best single leg clears 140 with 8.
  • 7: Court Sq-23 St to Jackson Hts-Roosevelt Av/74 St-Broadway: the controller cleared 326 riders per bus with 22 buses; the best single leg clears 402 with 6.
  • L: Grand St to DeKalb Av: the controller cleared 354 riders per bus with 5 buses; the best single leg clears 368 with 4.
  • The controller simulates only the first candidate for riders with no line and the first for relief; the oracle tries them all.

scripts/eval/closures-eval.ts · data/eval/closures.json · FalkorDB 4.22.1

Check 4

What the verifier recomputes, and what it cannot.

Beyond max flow, the NetworkX program rebuilds the platform layer with the same costs (3 minutes and half the headway to board, the ride, a free exit, walks) and sends every displaced rider down their fastest open path, so the overloaded G, the riders with no path and the shuttle's effect are recomputed too. 8 of 8 L-closure numbers below match. The last rows have no second implementation, and say so.

Numbers for the L closure from FalkorDB and from the NetworkX verifier
L closureFalkorDBNetworkXAgree
Displaced riders23,44523,445✓ yes
Overloaded segments on fastest paths (%)190, 198, 201, 244190, 198, 201, 244✓ yes
Riders with no open path4,6894,689✓ yes
Fastest-path overflow24,07824,078✓ yes
Cut-off stations toward Manhattan1 Av (origin), 3 Av (destination)1 Av (origin), 3 Av (destination)✓ yes
Headroom toward Manhattan1.77×1.77×✓ yes
Shuttle 1 Av ↔ Astor Pl: riders with no path after623623✓ yes
Shuttle 1 Av ↔ Astor Pl: max-flow shortfall after315315✓ yes
Memory: past shuttle carried 79%, so 27% more busesread from graph memorynot recomputedn/a
Concurrency: reads and writes a second7,195 reads/snot recomputedn/a
Step-free routes for remembered ridersk fastest paths, filterednot recomputedn/a

Every README number, with the script and test behind it: CLAIMS.md. One command re-measures them all on a FalkorDB and fails on any difference: pnpm judge.

Check 5

With memory, a step-free rider gets the working elevator 66% of the time. Without it, 50%.

Every check below runs twice, and only memory changes between the two runs. The first replays real MTA elevator and escalator records month by month, so memory never sees a record from after the decision it informs; a placebo memory that keeps every record forever shows what Detour's validity windows and SUPERSEDES add. The rest run on FalkorDB with Detour's own tools.

A step-free rider can use one of two elevators (or escalators) at the same station. Which one is more likely to be running this month?

  1. No memorya coin picks · 95% interval 49.4% to 51.0%50.2%
  2. Placebo memoryevery record kept forever, no validity windows · 95% interval 57.3% to 66.8%61.8%
  3. Detour's memoryrecords valid for a year, superseded after an overhaul · 95% interval 61.3% to 72.4%66.3%
Decisions
9,360
115 station complexes, 8 months held out
Over no memory
+16.1 pts
95% interval +10.9 to +22.4
Over the placebo
+4.5 pts
95% interval +1.5 to +7.7

Where memory falls short

  • It still picks the wrong unit in 34% of decisions (3,153). Memory predicts from the past; a unit that breaks for the first time, or one just repaired, still fools it. These are its largest misses.
  • Listing the 50 units that will break down most often in the months held out: 60% of Detour's list was right, against 10% by chance, but the placebo's all-time record did better, 72%. For chronic failures a long memory helps, and the one-year window throws that away.
  • Elevators alone: 63.4% with memory, 50.3% without (3,657 decisions); escalators: 68.2%.

The validity window, varied

Detour keeps a unit's monthly record for twelve months. Windows from three months to two years land within a point of it, so the result does not hang on that choice.

WindowRight
3 months66.3%
6 months67.0%
12 months66.3%
24 months66.7%
How it was measured
  1. Source: MTA NYCT Subway Elevator and Escalator Availability: Beginning 2015 (Metropolitan Transportation Authority, via data.ny.gov), retrieved 2026-10-19; 698 units, 140 months from Jan 2015.
  2. Replay in time order. Each month's records reach memory only at the start of the next month; every decision reads only records valid before it (a check that throws on any later record found none).
  3. Decisions: every month from Jan 2026 to Aug 2026, every pair of units of one kind at one station complex whose availability that month differs by more than half a point. Right means memory picked the unit that ran more.
  4. No memory: a seeded coin. Placebo: every record ever read, kept forever (no validity window, no SUPERSEDES). Time-aware (Detour): records valid for 12 months, and a unit back from two months mostly out of service supersedes its older records, the way a superseded Finding is closed.
  5. 95% intervals: 2000 seeded bootstrap resamples of whole station complexes.

On the graph, with memory switched off and on

  • Step-free routes past a broken elevator

    A wheelchair user asks for a step-free route. An elevator on the way is out of service, and memory knows it.

    Memory off
    100% through it
    Memory on
    0% through it

    56 trips at 40 platforms. With memory, 59% found another step-free route (median 6.3 more minutes) and 41% were told there is none.

    How it was measured
    1. 40 accessible station-and-line platforms, taken at even steps from the 264 in the network (sorted by station and line); 0 already had an outage in memory and were skipped.
    2. For each: report_elevator writes the outage as a Finding, then two step-free trips to and from an accessible station 3 to 8 stops along the line are routed with route_rider, once with memory off (outages ignored) and once on. The outage is then closed by a report that the elevator works again.
    3. A trip counts when, with memory off, its route boards or leaves the train at the broken platform.
  • Bus bridges sized from what past shuttles carried

    A bus bridge is planned for so many riders an hour. How close does what it carries come to that?

    Memory off
    off by 255 riders/h
    Memory on
    off by 107 riders/h

    Mean error over 32 past incidents, left out one at a time; 95% interval for the gain 72 to 233. Bridges within 5% of plan: 28% without, 50% with. Fed only earlier incidents: off by 135. Worse with memory on the A/C (115 → 126) and the J/Z (35 → 121): buses there run close to plan, and sizing for the lowest share on record adds buses after one weak run.

    How it was measured
    1. 32 past shuttle incidents: 2 seeded (the L and A/C bridges) and 30 synthetic, six on each of 5 corridors whose buses carry a known share of plan (L 79%, A/C 93%, G 72%, 7 86%, J/Z 100%) give or take 6 points, from a seeded generator.
    2. Leave one out: each incident is hidden, recall reads the rest from FalkorDB, and the bridge is sized the way the scripted controller sizes it: buses an hour divided by the lowest share of plan a recalled shuttle carried, when under 97%.
    3. Without memory the buses run at the raw estimate. Carried = buses × 70 riders × the share that incident's buses really achieved; the error is its distance from the riders planned.
    4. Online: the same rule fed only incidents that came earlier. 95% interval: seeded bootstrap over incidents.
  • A fact from one session, used in the next

    Does a fact written in one session change what a later session answers?

    Memory off
    0 of 38 change
    Memory on
    4 of 38 change

    Aisha before: “Aisha: about 33 min, 0 transfers, step-free: M Myrtle-Wyckoff Avs → 14 St/6 Av (11 stops). Skipped a faster path because memory says: Elevator to the L platform at Myrtle-Wyckoff Avs out of service; M platform elevator works.” After: “Aisha: about 24 min, 0 transfers, step-free: L Myrtle-Wyckoff Avs → 14 St/6 Av (12 stops).”

    How it was measured
    1. Session A writes 6 facts through report_elevator: the L platform elevator at Myrtle-Wyckoff Avs works again (Aisha's), and outages at the busiest accessible stations (Grand Central-42 St 4, Times Sq-42 St/Port Authority Bus Terminal 1, Fulton St 2, 34 St-Herald Sq B, 34 St-Penn Station A).
    2. Session B, a later request with a fresh context and another agent, asks 38 questions: every remembered rider's commute and 30 seeded step-free trips between accessible stations. Each answer is compared with the same question asked before session A.
    3. Control: the same questions with memory off change nothing.
  • Reports that arrive at the same moment

    Many reports about one elevator arrive at once. Does memory end with exactly one current fact?

    Memory off
    –
    Memory on
    1 open fact, every time

    10 writers: 1 open after the same report, 1 after mixed reports; 50 writers: 1 open after the same report, 1 after mixed reports; 100 writers: 1 open after the same report, 1 after mixed reports

    How it was measured
    1. For 10, 50 and 100 writers: all report the same outage at once, then half report it fixed and half report it broken, all at once. Each writer is its own context; the open elevator facts for the platform are counted after each burst.
    2. remember() writes the new finding and closes the one it supersedes in one query, and FalkorDB runs one write at a time per graph.

Run Oct 18, 2026, seeded, so a rerun gives the same numbers. scripts/eval/memory-eval.ts

Check 6

With the graph, the agent answers 93% of the 28 held-out questions run so far. The same model without it: 7%.

144 questions with answers computed without FalkorDB: plain TypeScript over the MTA data for closures, trips and step-free access, the NetworkX verifier for max flow and min cut, and the seeded memory for dates. 101 are held out and 43 were for development; the split was fixed with seed 20261018 and committed before any run, and questions about one closure or trip never straddle it. About one in five should be declined: a name or a relation that is not in the graph.

Running since October 18, 2026 on the deployment, on a sandbox graph of its own, with the free models gemini-flash-lite-latest, gemini-2.5-flash and openai/gpt-oss-120b. The baseline gets the model the graph arm ended on, the same question, and no tools. The run is partial: 28 held-out questions have run with the graph and 28 without it, 27 of them on both. The rest wait for the free models’ daily quotas, which the eval shares with visitors and never takes the last of; 231 arms that met a spent quota never ran and are left out, not counted wrong. It ran in two parts: the free database restarted empty in between, so the second run skipped the questions the first had answered on every arm, and each question’s model arms come from one run. scripts/eval

LLM with the graph
93%
26 of 28 held out · 95% CI 82%–100% · dev –
Same model, no graph
7%
2 of 28 held out · 95% CI 0%–18% · dev –
Rules only, no model
100%
101 of 101 held out · 95% CI 100%–100% · dev 98%

Paired on the same 27 held-out questions: with the graph minus without it, +85 points (95% CI +70 to +96); with the graph minus rules alone, −7 points (−18 to 0). Seeded bootstrap, 2,000 resamples.

Held-out accuracy by kind of question: the share right, then right of those run
Held-out questionsnLLM with the graphSame model, no graphRules only, no model
Riders a closure hits3394% 17/180% 0/18100% 33/33
Max flow683% 5/617% 1/6100% 6/6
Min cut1100% 1/10% 0/1100% 1/1
Cut off by a closure6100% 1/1100% 1/1100% 6/6
Fastest trip14–0% 0/1100% 14/14
Step-free trips6100% 1/10% 0/1100% 6/6
Lines at a station7100% 1/1–100% 7/7
Memory, with dates9––100% 9/9
Not in the graph8––100% 8/8
Unknown names11––100% 11/11
Declines what is not in the graph (graph · baseline)
–
Wrongly declines an answerable question
0% · 0%
Riders within 1 stop of a closure, then 4 (graph)
100% · 83%
The same pairs without the graph
0% · 0%
Called the tools a question needs, in a valid order
96% (27 of 28)
Answers whose every figure a tool returned
95% (19 of 20)

A valid order means names were resolved before their ids were used and a line was closed before anything was measured on it. The rules-only arm is a router written against these question templates, which dev and held-out share, so it is an optimistic floor: what plain code on the same graph tools gets when it knows exactly how questions are worded. The model never writes Cypher in any arm; every tool call is recorded with its arguments, query time and rows.

Where we lost

No held-out question went right without the graph and wrong with it.

Check 7

The brain's graph tools answer 100.0% of 769 held-out questions; keyword retrieval 84.4%.

1,261 questions are generated from the committed snapshot of the MTA's records, and their answers are computed in plain code from the raw feed JSON, never through FalkorDB. 340 of them have no answer in the records (units, alerts, events and projects that do not exist, units with no outage or no published alternative, quarters the complaint data does not cover), so refusing is scored too. Whole stations, alerts, events and projects are held out by a fixed seed (20261019); the brain was tuned on the 492 dev questions, with one exception noted below.

Each question goes to three arms with no model in the loop: the brain's standard procedure (it picks a graph tool from the question's words and answers from the tool's facts), a CONTAINS search over every record's text with the answer extracted from the top hits, and the same graph tools holding only the newest feed state. Scores are exact match and set F1 with 95% bootstrap intervals.

Graph tools
100.0%
exact match, 95% CI 100.0% to 100.0% · F1 100.0%
refused 100.0% of 222 unanswerable · wrongly refused 0.0% of 547
Keyword retrieval
84.4%
exact match, 95% CI 81.5% to 86.9% · F1 85.3%
refused 97.7% of 222 unanswerable · wrongly refused 11.9% of 547
Latest snapshot only
83.1%
exact match, 95% CI 80.4% to 85.6% · F1 85.1%
refused 100.0% of 222 unanswerable · wrongly refused 20.7% of 547
Held-out exact match by question type for each arm
Question typeHeld outGraph toolsKeyword retrievalLatest snapshot only
Is a unit in service now105 (25 no answer)100%100%100%
Outage's estimated return62 (38 no answer)100%100%100%
The MTA's alternative route95 (42 no answer)100%100%100%
Who maintains a unit80 (18 no answer)100%100%100%
Lines an alert affects67 (12 no answer)100%100%100%
Stations an alert affects28 (11 no answer)100%39%100%
Units out at an alert's stations (3 hops)38 (5 no answer)100%13%100%
Units out at a station, by name39 (5 no answer)100%62%100%
Stations on a line with outages16 (5 no answer)100%6%100%
Outages begun since a time19100%0%16%
Outages resolved since a time1100%100%0%
Capital project stage18 (9 no answer)100%100%100%
Capital project milestone date37 (7 no answer)100%100%100%
Outage complaints per station and quarter72 (27 no answer)100%79%38%
Updates in an alert event50 (8 no answer)100%100%16%
An alert event's last update32 (6 no answer)100%100%19%
What a decision rests on (sample org)5 (2 no answer)100%40%100%
Who made a decision (sample org)5 (2 no answer)100%40%100%

What the baselines miss

  • Joins. Which stations an alert names, which of them have a unit out, which stations on a line have outages: the answer is spread over several records linked by edges, not written in any one of them.
  • Time. Outages that began or left the feed since a moment, an alert event's chain of updates, complaints per quarter: a brain that keeps only the newest feed cannot answer them, and refuses.
  • Where keyword search is enough. One-record facts named by an id (a unit's status, maintainer or alternative route, a project's stage) are found by CONTAINS too, so those rows tie.

Read it with care

  • The questions are templates in several wordings over the same records the tools read, so a high score says the tools answer these forms, not open questions.
  • The keyword arm is given the question's identifiers and parameters and an extraction rule per record format; a plainer retriever would score lower.
  • Fixes made while building it: the planner's wordings for stations along a line came from dev misses; reading full station names before partial ones was fixed after a held-out run, which scored 99.4% before it.

Held-out questions, with every arm's answer

Is a unit in service now105
  1. Is ES258X working right now?

    Answerout of service

    • ✓Graph tools: out of service
    • ✓Keyword retrieval: out of service
    • ✓Latest snapshot only: out of service
  2. Is EL135 working right now?

    Answerout of service

    • ✓Graph tools: out of service
    • ✓Keyword retrieval: out of service
    • ✓Latest snapshot only: out of service
  3. Is the escalator ES117 running at the moment?

    Answerout of service

    • ✓Graph tools: out of service
    • ✓Keyword retrieval: out of service
    • ✓Latest snapshot only: out of service
Outage's estimated return62
  1. How long will escalator ES258X be out?

    Answer2026-10-20 16:00

    • ✓Graph tools: 2026-10-20 16:00
    • ✓Keyword retrieval: 2026-10-20 16:00
    • ✓Latest snapshot only: 2026-10-20 16:00
  2. When will EL135 be fixed?

    Answer2027-01-31 23:00

    • ✓Graph tools: 2027-01-31 23:00
    • ✓Keyword retrieval: 2027-01-31 23:00
    • ✓Latest snapshot only: 2027-01-31 23:00
  3. How long will escalator ES117 be out?

    Answer2026-10-18 23:00

    • ✓Graph tools: 2026-10-18 23:00
    • ✓Keyword retrieval: 2026-10-18 23:00
    • ✓Latest snapshot only: 2026-10-18 23:00
The MTA's alternative route95
  1. If elevator EL277 is down, what route does the MTA suggest instead?

    AnswerIf you are on the mezzanine: For downtown 1, proceed to the elevator at the other side of the mezzanine to reach the platform and use the e…

    • ✓Graph tools: If you are on the mezzanine: For downtown 1, proceed to the elevator at the other side of the mezzanine to reach the platform and use the e…
    • ✓Keyword retrieval: If you are on the mezzanine: For downtown 1, proceed to the elevator at the other side of the mezzanine to reach the platform and use the e…
    • ✓Latest snapshot only: If you are on the mezzanine: For downtown 1, proceed to the elevator at the other side of the mezzanine to reach the platform and use the e…
  2. What's the step-free alternative when EL622 is broken?

    AnswerIf you are on the mezzanine: For downtown 1, proceed to the elevator at the other side of the mezzanine to reach the platform and use the e…

    • ✓Graph tools: If you are on the mezzanine: For downtown 1, proceed to the elevator at the other side of the mezzanine to reach the platform and use the e…
    • ✓Keyword retrieval: If you are on the mezzanine: For downtown 1, proceed to the elevator at the other side of the mezzanine to reach the platform and use the e…
    • ✓Latest snapshot only: If you are on the mezzanine: For downtown 1, proceed to the elevator at the other side of the mezzanine to reach the platform and use the e…
  3. What is the MTA's alternative route when elevator EL118 is out of service?

    AnswerUse an adjacent elevator.

    • ✓Graph tools: Use an adjacent elevator.
    • ✓Keyword retrieval: Use an adjacent elevator.
    • ✓Latest snapshot only: Use an adjacent elevator.
Who maintains a unit80
  1. Who maintains elevator EL454?

    AnswerNYCT

    • ✓Graph tools: NYCT
    • ✓Keyword retrieval: NYCT
    • ✓Latest snapshot only: NYCT
  2. Which organization maintains EL201?

    AnswerNYCT

    • ✓Graph tools: NYCT
    • ✓Keyword retrieval: NYCT
    • ✓Latest snapshot only: NYCT
  3. Which organization maintains EL199?

    AnswerNYCT

    • ✓Graph tools: NYCT
    • ✓Keyword retrieval: NYCT
    • ✓Latest snapshot only: NYCT
Lines an alert affects67
  1. What lines are hit by MTA alert lmm:planned_work:35560?

    AnswerC

    • ✓Graph tools: C
    • ✓Keyword retrieval: C
    • ✓Latest snapshot only: C
  2. Which subway lines does MTA alert lmm:planned_work:34550 affect?

    AnswerA

    • ✓Graph tools: A
    • ✓Keyword retrieval: A
    • ✓Latest snapshot only: A
  3. Alert lmm:planned_work:35718: which routes does it cover?

    AnswerM

    • ✓Graph tools: M
    • ✓Keyword retrieval: M
    • ✓Latest snapshot only: M
Stations an alert affects28
  1. Where does MTA alert lmm:planned_work:35562 apply?

    Answer167, 618, 165, 164, 611, 162

    • ✓Graph tools: 167, 618, 165, 164, 611, 162
    • ✗Keyword retrieval: refused
    • ✓Latest snapshot only: 167, 618, 165, 164, 611, 162
  2. Where does MTA alert lmm:planned_work:35355 apply?

    Answer181, 180, 179, 627, 177, 176, 175

    • ✓Graph tools: 181, 180, 179, 627, 177, 176, 175
    • ✗Keyword retrieval: refused
    • ✓Latest snapshot only: 181, 180, 179, 627, 177, 176, 175
  3. Where does MTA alert lmm:planned_work:35563 apply?

    Answer153, 154, 155, 156, 157, 158, 159, 160, 614, 162, 611, 164, 165, 618, 167, 168

    • ✓Graph tools: 153, 154, 155, 156, 157, 158, 159, 160, 614, 162, 611, 164, 165, 618, 167, 168
    • ✗Keyword retrieval: refused
    • ✓Latest snapshot only: 153, 154, 155, 156, 157, 158, 159, 160, 614, 162, 611, 164, 165, 618, 167, 168
Units out at an alert's stations (3 hops)38
  1. Which stations named in MTA alert lmm:planned_work:35124 have an elevator or escalator out of service right now?

    Answer306

    • ✓Graph tools: 306
    • ✗Keyword retrieval: refused
    • ✓Latest snapshot only: 306
  2. Do any stations in alert lmm:planned_work:33366 have a broken elevator or escalator right now?

    Answer276

    • ✓Graph tools: 276
    • ✗Keyword retrieval: refused
    • ✓Latest snapshot only: 276
  3. Which stations named in MTA alert lmm:planned_work:34174 have an elevator or escalator out of service right now?

    Answer612, 414, 628, 602

    • ✓Graph tools: 612, 414, 628, 602
    • ✗Keyword retrieval: refused
    • ✓Latest snapshot only: 612, 414, 628, 602
Units out at a station, by name39
  1. 57 St-7 Av: which elevators or escalators are down?

    AnswerEL281

    • ✓Graph tools: EL281
    • ✗Keyword retrieval: none
    • ✓Latest snapshot only: EL281
  2. Which elevators and escalators at Coney Island-Stillwell Av are out of service right now?

    AnswerEL701

    • ✓Graph tools: EL701
    • ✓Keyword retrieval: EL701
    • ✓Latest snapshot only: EL701
  3. Are any elevators or escalators broken at Livonia Av?

    AnswerEL771

    • ✓Graph tools: EL771
    • ✓Keyword retrieval: EL771
    • ✓Latest snapshot only: EL771
Stations on a line with outages16
  1. Which stations on the Q have an elevator or escalator out of service right now?

    Answer617, 9, 602

    • ✓Graph tools: 602, 9, 617
    • ✗Keyword retrieval: none
    • ✓Latest snapshot only: 602, 9, 617
  2. Which R stations have a broken elevator or escalator?

    Answer9, 35, 602

    • ✓Graph tools: 602, 9, 35
    • ✗Keyword retrieval: none
    • ✓Latest snapshot only: 602, 9, 35
  3. Which stations on the W have an elevator or escalator out of service right now?

    Answer9

    • ✓Graph tools: 9
    • ✗Keyword retrieval: none
    • ✓Latest snapshot only: 9
Outages begun since a time19
  1. What elevator or escalator outages have started since 2026-10-18 16:29?

    AnswerES117

    • ✓Graph tools: ES117
    • ✗Keyword retrieval: none
    • ✓Latest snapshot only: ES117
  2. Which elevator and escalator outages began since 2026-10-18 16:06?

    AnswerES117, ES310, ES411

    • ✓Graph tools: ES117, ES310, ES411
    • ✗Keyword retrieval: none
    • ✓Latest snapshot only: ES117, ES310, ES411
  3. Which elevator and escalator outages began since 2026-10-18 15:35?

    AnswerES117, ES310, ES411, EL281, EL388

    • ✓Graph tools: ES117, ES310, ES411, EL281, EL388
    • ✗Keyword retrieval: EL388
    • ✓Latest snapshot only: ES117, ES310, ES411, EL281, EL388
Outages resolved since a time1
  1. Which outages left the feed since 2026-10-18 16:30?

    AnswerEL427

    • ✓Graph tools: EL427
    • ✓Keyword retrieval: EL427
    • ✗Latest snapshot only: none
Capital project stage18
  1. Where does MTA capital project 11097 stand?

    AnswerConstruction

    • ✓Graph tools: Construction
    • ✓Keyword retrieval: Construction
    • ✓Latest snapshot only: Construction
  2. Which stage has capital project 11134 reached?

    AnswerConstruction

    • ✓Graph tools: Construction
    • ✓Keyword retrieval: Construction
    • ✓Latest snapshot only: Construction
  3. Where does MTA capital project 12040 stand?

    AnswerConstruction

    • ✓Graph tools: Construction
    • ✓Keyword retrieval: Construction
    • ✓Latest snapshot only: Construction
Capital project milestone date37
  1. Capital project 12338: when was "Contract Solicitation"?

    Answer2021-04-01

    • ✓Graph tools: 2021-04-01
    • ✓Keyword retrieval: 2021-04-01
    • ✓Latest snapshot only: 2021-04-01
  2. When did MTA capital project 12197 reach the milestone "Bid Opening Date"?

    Answer2020-01-01

    • ✓Graph tools: 2020-01-01
    • ✓Keyword retrieval: 2020-01-01
    • ✓Latest snapshot only: 2020-01-01
  3. Capital project 11097: when was "Financial Commitment and Contract Award"?

    Answer2022-12-01

    • ✓Graph tools: 2022-12-01
    • ✓Keyword retrieval: 2022-12-01
    • ✓Latest snapshot only: 2022-12-01
Outage complaints per station and quarter72
  1. How many outage complaints did riders make about 14 St-Union Sq in 2025 Q2?

    Answer3

    • ✓Graph tools: 3
    • ✗Keyword retrieval: 1
    • ✗Latest snapshot only: refused
  2. In 2026 Q3, how many elevator or escalator complaints came in for Atlantic Av-Barclays Ctr?

    Answer7

    • ✓Graph tools: 7
    • ✗Keyword retrieval: 4
    • ✗Latest snapshot only: refused
  3. How many outage complaints did riders make about Sheepshead Bay in 2026 Q3?

    Answer2

    • ✓Graph tools: 2
    • ✓Keyword retrieval: 2
    • ✗Latest snapshot only: refused
Updates in an alert event50
  1. Count the updates to subway alert event 257127.

    Answer2

    • ✓Graph tools: 2
    • ✓Keyword retrieval: 2
    • ✗Latest snapshot only: refused
  2. Count the updates to subway alert event 257535.

    Answer2

    • ✓Graph tools: 2
    • ✓Keyword retrieval: 2
    • ✗Latest snapshot only: refused
  3. How many updates did MTA subway alert event 261553 have?

    Answer5

    • ✓Graph tools: 5
    • ✓Keyword retrieval: 5
    • ✗Latest snapshot only: refused
An alert event's last update32
  1. What was the final update for alert event 261893?

    AnswerDowntown 6/6X trains are running with delays after we addressed a mechanical problem on a train at 23 St-Baruch College.

    • ✓Graph tools: Downtown 6/6X trains are running with delays after we addressed a mechanical problem on a train at 23 St-Baruch College.
    • ✓Keyword retrieval: Downtown 6/6X trains are running with delays after we addressed a mechanical problem on a train at 23 St-Baruch College.
    • ✗Latest snapshot only: refused
  2. How did MTA alert event 257802 end?

    AnswerUptown R/W trains are running with delays after we removed a train with a mechanical problem from service at Whitehall St-South Ferry.

    • ✓Graph tools: Uptown R/W trains are running with delays after we removed a train with a mechanical problem from service at Whitehall St-South Ferry.
    • ✓Keyword retrieval: Uptown R/W trains are running with delays after we removed a train with a mechanical problem from service at Whitehall St-South Ferry.
    • ✗Latest snapshot only: refused
  3. What was the final update for alert event 259711?

    AnswerUptown 4/6 trains are running with delays after a rail replacement at 96 St concluded.

    • ✓Graph tools: Uptown 4/6 trains are running with delays after a rail replacement at 96 St concluded.
    • ✓Keyword retrieval: Uptown 4/6 trains are running with delays after a rail replacement at 96 St concluded.
    • ✗Latest snapshot only: refused
What a decision rests on (sample org)5
  1. What is decision DEC-0616-SIZE based on?

    AnswerDOC-AAR-0612, MSG-0612-3, BUS-0612-07, F-0612-1

    • ✓Graph tools: F-0612-1, DOC-AAR-0612, BUS-0612-07, MSG-0612-3
    • ✗Keyword retrieval: refused
    • ✓Latest snapshot only: F-0612-1, DOC-AAR-0612, BUS-0612-07, MSG-0612-3
  2. What is decision DEC-0620-BUSES based on?

    AnswerDEC-0616-SIZE, F-0612-1, MSG-0612-3

    • ✓Graph tools: DEC-0616-SIZE, MSG-0612-3, F-0612-1
    • ✗Keyword retrieval: refused
    • ✓Latest snapshot only: DEC-0616-SIZE, MSG-0612-3, F-0612-1
  3. What does decision DEC-1014-SF rest on?

    AnswerEE-1207, DOC-POLICY-SF, F-ELEV-2

    • ✓Graph tools: F-ELEV-2, DOC-POLICY-SF, EE-1207
    • ✗Keyword retrieval: refused
    • ✓Latest snapshot only: F-ELEV-2, DOC-POLICY-SF, EE-1207
Who made a decision (sample org)5
  1. Whose decision was DEC-0616-SIZE?

    AnswerRenee Alvarez

    • ✓Graph tools: Renee Alvarez
    • ✗Keyword retrieval: refused
    • ✓Latest snapshot only: Renee Alvarez
  2. Who decided DEC-0620-BUSES?

    AnswerTunde Okafor

    • ✓Graph tools: Tunde Okafor
    • ✗Keyword retrieval: refused
    • ✓Latest snapshot only: Tunde Okafor
  3. Who made decision DEC-1014-SF?

    AnswerJordan Lee

    • ✓Graph tools: Jordan Lee
    • ✗Keyword retrieval: refused
    • ✓Latest snapshot only: Jordan Lee

Snapshot: outage feed read 4 times, last Oct 18, 2026, 4:42 PM ET; run Oct 18, 2026, 4:43 PM ET. Every held-out question, its answer and each arm's answer are in data/eval/brain.json; scripts/eval/brain-eval.ts reproduces it on any FalkorDB.

Check 8

7,195 reads and 719 writes a second, no leaks between tenants.

Every visitor gets a graph of their own. The benchmark runs 48 reader agents and 6 writer agents for 15 seconds across 6 tenant graphs over 12 connections, then checks that a closure written in one tenant is invisible in the others.

Reads
107,961
p50 5.4 ms · p95 15.5 ms
Writes
10,795
p50 7.4 ms · p95 17.1 ms
Errors
0
across both
Isolation
Held
closed segments per tenant: 10, 0, 0, 0, 0, 0

FalkorDB 4.22.1 module on Redis 8.0.5, Vercel Sandbox, 4 vCPU. scripts/ci/bench.ts

Check 9

What each tool costs on the L closure.

Summed query time per call, from the same run. The expensive tools fork the graph, rebuild the flow network and reroute two thousand trips.

  1. Resolve names1 ms · 2q
  2. Resolve names1 ms · 2q
  3. Close the line47 ms · 7q
  4. Who rides through it246 ms · 10q
  5. Recall memory6 ms · 3q
  6. Measure capacity1.3 s · 21q
  7. Measure headroom971 ms · 31q
  8. Find shuttle bridges402 ms · 16q
  9. Simulate a plan2.1 s · 24q
  10. Simulate a plan2.2 s · 24q
  11. Publish the plan43 ms · 8q
  12. Steer riders, no buses2 ms · 3q
  13. Remembered riders hit3 ms · 2q
  14. Route a rideraisha33 ms · 5q
  15. Route a riderdev9 ms · 5q
  16. Route a ridermaya9 ms · 5q
  17. Route a ridertomas8 ms · 5q
  18. Route a rideraisha-stepfree21 ms · 5q
  19. Route a riderstaten-island5 ms · 3q
  20. Remember a finding3 ms · 2q
  21. Remember a finding4 ms · 2q
  22. Resolve the incident60 ms · 12q

Inputs

The data, and what was assumed.

445 station complexes, 26 routes, 1,459 scheduled segments, 190 walking links and 30,205 origin-destination pairs carrying 404,040 of the 446,230 riders the MTA estimates for a weekday 8 a.m. hour.

Peak hour
08:00-08:59 weekday
Riders per car (A division / 60 ft / 75 ft)
110 / 145 / 175
Transfer penalty
3 min + half the headway
Walking links
under 400 m at 75 m/min
Shuttle bus
70 riders, 4 min + 3 min per km
Smallest flow kept
2 riders an hour

Baseline paths come from an all-or-nothing assignment (fastest route by in-vehicle time, half-headway wait and transfer penalty). Riders and past incidents in memory are example data, labeled as such.

Every query

110 Cypher queries and graph commands behind 14 tools.

The agents never write Cypher. They pick from this fixed catalogue; each call returns its answer shape, a summary, and the exact queries below with their timings, which the control room shows step by step.

Resolve namesresolve_places1
  1. resolve "…"on detour:sbx:<visitor>

    CALL db.idx.fulltext.queryNodes('Station', $q) YIELD node, score
    RETURN node.id AS id, node.name AS name, node.label AS label, node.routes AS routes, node.ada AS ada, score
    ORDER BY score DESC LIMIT 6
Close the lineclose_line7
  1. reopen closed segmentson detour:sbx:<visitor>

    MATCH ()-[r:RUNS {closed: true}]->()
    SET r.closed = false, r.minutes = r.baseMinutes,
    r.spare = CASE WHEN r.capacity > r.load THEN r.capacity - r.load ELSE 0 END
    RETURN count(r) AS n
  2. reopen platform rideson detour:sbx:<visitor>

    MATCH ()-[r:RIDE {closed: true}]->() SET r.closed = false, r.minutes = r.baseMinutes
  3. clear crowding costson detour:sbx:<visitor>

    MATCH ()-[r:BOARD|ALIGHT|RIDE|WALK|BUS]->() WHERE r.cost IS NOT NULL SET r.cost = null, r.crowd = null
  4. close out incidenton detour:sbx:<visitor>

    MATCH (i:Incident {status: 'open'}) SET i.status = $status, i.closedMs = timestamp()
  5. close segmentson detour:sbx:<visitor>

    MATCH (a:Station)-[r:RUNS]->(b:Station)
    WHERE r.route IN $routes AND a.id IN $stretch AND b.id IN $stretch
    SET r.closed = true, r.minutes = $closedMinutes, r.spare = 0
    RETURN r.eid AS eid, r.route AS route, a.id AS from, b.id AS to, r.load AS load
    ORDER BY eid
  6. close the same rides on the platform layeron detour:sbx:<visitor>

    MATCH ()-[r:RIDE]->() WHERE r.eid IN $eids SET r.closed = true, r.minutes = $closedMinutes
  7. record incidenton detour:sbx:<visitor>

    MATCH (ag:Agent {id: $agent})
    CREATE (ag)-[:OPENED {atMs: timestamp()}]->(i:Incident {id: $id, title: $title, report: $report, routes: $routes,
    stretch: $stretch, closedEids: $closedEids, status: 'open', openedMs: timestamp()})
    WITH i
    MATCH (s:Station) WHERE s.id IN $stretch
    CREATE (i)-[:CLOSES]->(s)
Who rides through itaffected_riders3
  1. open incidenton detour:sbx:<visitor>

    MATCH (i:Incident {status: 'open'})
    RETURN i.id AS id, i.title AS title, i.report AS report, i.routes AS routes, i.stretch AS stretch,
    i.closedEids AS closedEids, i.status AS status, i.openedMs AS openedMs
    ORDER BY i.openedMs DESC LIMIT 1
  2. displaced riderson detour:demand

    MATCH (o:Station)-[d:DEMAND]->(x:Station)
    WHERE any(e IN d.via WHERE e IN $closed)
    RETURN o.id AS o, x.id AS d, d.riders AS riders, d.via AS via
  3. stations k stops upstreamon detour:sbx:<visitor>

    MATCH (o:Station)-[:RUNS]->(t:Station)
    WHERE t.id IN $frontier AND NOT o.id IN $seen
    RETURN DISTINCT o.id AS id
Recall memoryrecall3
  1. findings about these stations and routeson detour:sbx:<visitor>

    MATCH (f:Finding)-[:ABOUT]->(n)
    WHERE (n:Station AND n.id IN $stations) OR (n:Route AND n.id IN $routes)
    WITH DISTINCT f
    OPTIONAL MATCH (f)-[:FROM]->(i:Incident)
    OPTIONAL MATCH (ag:Agent)-[:WROTE]->(f)
    RETURN f.id AS id, f.text AS text, f.kind AS kind, f.confidence AS confidence, f.validFromMs AS validFromMs,
    f.validToMs AS validToMs, f.example AS example, i.id AS incident, ag.name AS agent
    ORDER BY validFromMs DESC LIMIT 12
  2. past incidents on these routeson detour:sbx:<visitor>

    MATCH (i:Incident)
    WHERE i.status <> 'open' AND any(r IN i.routes WHERE r IN $routes)
    OPTIONAL MATCH (s:Shuttle)-[:FOR]->(i)
    OPTIONAL MATCH (s)-[:FROM]->(a:Station)
    OPTIONAL MATCH (s)-[:TO]->(b:Station)
    WITH i, collect(CASE WHEN s IS NULL THEN null ELSE {from: a.name, to: b.name, bph: s.busesPerHour, planned: s.planned, observed: s.observed} END) AS shuttles
    RETURN i.id AS id, i.title AS title, i.openedMs AS openedMs, i.example AS example, shuttles
    ORDER BY openedMs DESC LIMIT 5
  3. playbooks for these routeson detour:sbx:<visitor>

    MATCH (p:Playbook)-[:FOR]->(r:Route) WHERE r.id IN $routes
    RETURN DISTINCT p.id AS id, p.title AS title, p.steps AS steps, p.uses AS uses, p.lastUsedMs AS lastUsedMs
Measure capacitymeasure_capacity17
  1. open incidenton detour:sbx:<visitor>

    MATCH (i:Incident {status: 'open'})
    RETURN i.id AS id, i.title AS title, i.report AS report, i.routes AS routes, i.stretch AS stretch,
    i.closedEids AS closedEids, i.status AS status, i.openedMs AS openedMs
    ORDER BY i.openedMs DESC LIMIT 1
  2. fork the sandboxon detour:fork:<visitor>:<fork>

    GRAPH.COPY detour:sbx:<visitor> detour:fork:<visitor>:<fork>
  3. drop closed segments in the forkon detour:fork:<visitor>:<fork>

    MATCH ()-[r:RUNS {closed: true}]->() DELETE r
  4. drop closed rides in the forkon detour:fork:<visitor>:<fork>

    MATCH ()-[r:RIDE {closed: true}]->() DELETE r
  5. displaced riderson detour:demand

    MATCH (o:Station)-[d:DEMAND]->(x:Station)
    WHERE any(e IN d.via WHERE e IN $closed)
    RETURN o.id AS o, x.id AS d, d.riders AS riders, d.via AS via
  6. free displaced riders' capacityon detour:fork:<visitor>:<fork>

    UNWIND $freed AS f
    MATCH ()-[r:RUNS {eid: f.eid}]->()
    SET r.freed = f.riders,
    r.spare = CASE WHEN r.capacity - r.load + f.riders > 0 THEN r.capacity - r.load + f.riders ELSE 0 END
  7. forward source and sinkon detour:fork:<visitor>:<fork>

    CREATE (src:Hub {id: $src}), (snk:Hub {id: $snk})
    WITH src, snk
    UNWIND $sources AS s
    MATCH (x:Station {id: s.id})
    CREATE (src)-[:FLOW {spare: s.cap, kind: $kind, ref: -1}]->(x)
  8. forward sinkson detour:fork:<visitor>:<fork>

    MATCH (snk:Hub {id: $snk})
    UNWIND $sinks AS s
    MATCH (x:Station {id: s.id})
    CREATE (x)-[:FLOW {spare: s.cap, kind: $kind, ref: -1}]->(snk)
  9. clear corridor flow edgeson detour:fork:<visitor>:<fork>

    MATCH (:Station)-[f:FLOW]->(:Station) DELETE f
  10. aggregate spare capacity by corridoron detour:fork:<visitor>:<fork>

    MATCH (a:Station)-[r]->(b:Station)
    WHERE type(r) IN $kinds
    WITH a, b, sum(r.spare) AS spare,
    collect(CASE type(r) WHEN 'RUNS' THEN 'R' + toString(r.eid) WHEN 'WALK' THEN 'W' + toString(r.wid) ELSE 'B' + toString(r.bid) END) AS refs
    CREATE (a)-[:FLOW {spare: spare, kind: 'NET', refs: refs}]->(b)
  11. forward max flowon detour:fork:<visitor>:<fork>

    MATCH (src:Hub {id: $src}), (snk:Hub {id: $snk})
    CALL algo.maxFlow({sourceNodes: [src], targetNodes: [snk], relationshipTypes: ['FLOW'], capacityProperty: 'spare'})
    YIELD maxFlow, edges, edgeFlows
    RETURN maxFlow, [e IN edges | [e.kind, startNode(e).id, endNode(e).id]] AS es, edgeFlows AS flows
  12. forward residual graphon detour:fork:<visitor>:<fork>

    MATCH (a)-[r:FLOW]->(b) WHERE r.kind IN $kinds
    RETURN r.kind AS kind, a.id AS a, b.id AS b, r.spare AS cap, r.refs AS refs
  13. reroute displaced riders on fastest open pathson detour:fork:<visitor>:<fork>

    UNWIND $pairs AS p
    MATCH (a:Station {id: p.o}), (b:Station {id: p.d})
    CALL algo.SPpaths({sourceNode: a, targetNode: b, relTypes: $types, weightProp: 'minutes'})
    YIELD path, pathWeight
    RETURN p.o AS o, p.d AS d, pathWeight AS minutes,
    [r IN relationships(path) WHERE type(r) = 'RIDE' | r.eid] AS eids,
    [r IN relationships(path) WHERE type(r) = 'BUS' | [r.bid, startNode(r).id]] AS bids,
    [r IN relationships(path) WHERE type(r) = 'WALK' | r.wid] AS walks
  14. drop the forkon detour:fork:<visitor>:<fork>

    GRAPH.DELETE detour:fork:<visitor>:<fork>
  15. remember the capacity pictureon detour:sbx:<visitor>

    MATCH (i:Incident {id: $id})
    SET i.displaced = $displaced, i.overflow = $overflow, i.stranded = $stranded,
    i.required = $required, i.absorbed = $absorbed, i.unmet = $unmet, i.measuredMs = timestamp()
  16. routing cost is travel timeon detour:sbx:<visitor>

    MATCH ()-[r:BOARD|ALIGHT|RIDE|WALK|BUS]->() SET r.cost = r.minutes, r.crowd = null
  17. crowded rides cost moreon detour:sbx:<visitor>

    UNWIND $segs AS c
    MATCH ()-[r:RIDE {eid: c.eid}]->()
    SET r.crowd = c.pct, r.cost = r.minutes * c.factor
    RETURN count(r) AS n
Measure headroommeasure_headroom15
  1. open incidenton detour:sbx:<visitor>

    MATCH (i:Incident {status: 'open'})
    RETURN i.id AS id, i.title AS title, i.report AS report, i.routes AS routes, i.stretch AS stretch,
    i.closedEids AS closedEids, i.status AS status, i.openedMs AS openedMs
    ORDER BY i.openedMs DESC LIMIT 1
  2. fork the sandboxon detour:fork:<visitor>:<fork>

    GRAPH.COPY detour:sbx:<visitor> detour:fork:<visitor>:<fork>
  3. drop closed segments in the forkon detour:fork:<visitor>:<fork>

    MATCH ()-[r:RUNS {closed: true}]->() DELETE r
  4. drop closed rides in the forkon detour:fork:<visitor>:<fork>

    MATCH ()-[r:RIDE {closed: true}]->() DELETE r
  5. displaced riderson detour:demand

    MATCH (o:Station)-[d:DEMAND]->(x:Station)
    WHERE any(e IN d.via WHERE e IN $closed)
    RETURN o.id AS o, x.id AS d, d.riders AS riders, d.via AS via
  6. free displaced riders' capacityon detour:fork:<visitor>:<fork>

    UNWIND $freed AS f
    MATCH ()-[r:RUNS {eid: f.eid}]->()
    SET r.freed = f.riders,
    r.spare = CASE WHEN r.capacity - r.load + f.riders > 0 THEN r.capacity - r.load + f.riders ELSE 0 END
  7. forward source and sinkon detour:fork:<visitor>:<fork>

    CREATE (src:Hub {id: $src}), (snk:Hub {id: $snk})
    WITH src, snk
    UNWIND $sources AS s
    MATCH (x:Station {id: s.id})
    CREATE (src)-[:FLOW {spare: s.cap, kind: $kind, ref: -1}]->(x)
  8. forward sinkson detour:fork:<visitor>:<fork>

    MATCH (snk:Hub {id: $snk})
    UNWIND $sinks AS s
    MATCH (x:Station {id: s.id})
    CREATE (x)-[:FLOW {spare: s.cap, kind: $kind, ref: -1}]->(snk)
  9. clear corridor flow edgeson detour:fork:<visitor>:<fork>

    MATCH (:Station)-[f:FLOW]->(:Station) DELETE f
  10. aggregate spare capacity by corridoron detour:fork:<visitor>:<fork>

    MATCH (a:Station)-[r]->(b:Station)
    WHERE type(r) IN $kinds
    WITH a, b, sum(r.spare) AS spare,
    collect(CASE type(r) WHEN 'RUNS' THEN 'R' + toString(r.eid) WHEN 'WALK' THEN 'W' + toString(r.wid) ELSE 'B' + toString(r.bid) END) AS refs
    CREATE (a)-[:FLOW {spare: spare, kind: 'NET', refs: refs}]->(b)
  11. forward remember feed capacitieson detour:fork:<visitor>:<fork>

    MATCH ()-[f:FLOW {kind: $feed}]->() SET f.base = f.spare
  12. forward max flow at 1× demandon detour:fork:<visitor>:<fork>

    MATCH (src:Hub {id: $src}), (snk:Hub {id: $snk})
    CALL algo.maxFlow({sourceNodes: [src], targetNodes: [snk], relationshipTypes: ['FLOW'], capacityProperty: 'spare'})
    YIELD maxFlow, edges, edgeFlows
    RETURN maxFlow, [e IN edges | [e.kind, startNode(e).id, endNode(e).id]] AS es, edgeFlows AS flows
  13. forward residual graphon detour:fork:<visitor>:<fork>

    MATCH (a)-[r:FLOW]->(b) WHERE r.kind IN $kinds
    RETURN r.kind AS kind, a.id AS a, b.id AS b, r.spare AS cap, r.refs AS refs
  14. forward scale demand to 20.000×on detour:fork:<visitor>:<fork>

    MATCH ()-[f:FLOW {kind: $feed}]->() SET f.spare = f.base * $lambda
  15. drop the forkon detour:fork:<visitor>:<fork>

    GRAPH.DELETE detour:fork:<visitor>:<fork>
Find shuttle bridgesshuttle_candidates14
  1. open incidenton detour:sbx:<visitor>

    MATCH (i:Incident {status: 'open'})
    RETURN i.id AS id, i.title AS title, i.report AS report, i.routes AS routes, i.stretch AS stretch,
    i.closedEids AS closedEids, i.status AS status, i.openedMs AS openedMs
    ORDER BY i.openedMs DESC LIMIT 1
  2. fork the sandboxon detour:fork:<visitor>:<fork>

    GRAPH.COPY detour:sbx:<visitor> detour:fork:<visitor>:<fork>
  3. drop closed segments in the forkon detour:fork:<visitor>:<fork>

    MATCH ()-[r:RUNS {closed: true}]->() DELETE r
  4. drop closed rides in the forkon detour:fork:<visitor>:<fork>

    MATCH ()-[r:RIDE {closed: true}]->() DELETE r
  5. displaced riderson detour:demand

    MATCH (o:Station)-[d:DEMAND]->(x:Station)
    WHERE any(e IN d.via WHERE e IN $closed)
    RETURN o.id AS o, x.id AS d, d.riders AS riders, d.via AS via
  6. free displaced riders' capacityon detour:fork:<visitor>:<fork>

    UNWIND $freed AS f
    MATCH ()-[r:RUNS {eid: f.eid}]->()
    SET r.freed = f.riders,
    r.spare = CASE WHEN r.capacity - r.load + f.riders > 0 THEN r.capacity - r.load + f.riders ELSE 0 END
  7. forward source and sinkon detour:fork:<visitor>:<fork>

    CREATE (src:Hub {id: $src}), (snk:Hub {id: $snk})
    WITH src, snk
    UNWIND $sources AS s
    MATCH (x:Station {id: s.id})
    CREATE (src)-[:FLOW {spare: s.cap, kind: $kind, ref: -1}]->(x)
  8. forward sinkson detour:fork:<visitor>:<fork>

    MATCH (snk:Hub {id: $snk})
    UNWIND $sinks AS s
    MATCH (x:Station {id: s.id})
    CREATE (x)-[:FLOW {spare: s.cap, kind: $kind, ref: -1}]->(snk)
  9. clear corridor flow edgeson detour:fork:<visitor>:<fork>

    MATCH (:Station)-[f:FLOW]->(:Station) DELETE f
  10. aggregate spare capacity by corridoron detour:fork:<visitor>:<fork>

    MATCH (a:Station)-[r]->(b:Station)
    WHERE type(r) IN $kinds
    WITH a, b, sum(r.spare) AS spare,
    collect(CASE type(r) WHEN 'RUNS' THEN 'R' + toString(r.eid) WHEN 'WALK' THEN 'W' + toString(r.wid) ELSE 'B' + toString(r.bid) END) AS refs
    CREATE (a)-[:FLOW {spare: spare, kind: 'NET', refs: refs}]->(b)
  11. forward max flowon detour:fork:<visitor>:<fork>

    MATCH (src:Hub {id: $src}), (snk:Hub {id: $snk})
    CALL algo.maxFlow({sourceNodes: [src], targetNodes: [snk], relationshipTypes: ['FLOW'], capacityProperty: 'spare'})
    YIELD maxFlow, edges, edgeFlows
    RETURN maxFlow, [e IN edges | [e.kind, startNode(e).id, endNode(e).id]] AS es, edgeFlows AS flows
  12. forward residual graphon detour:fork:<visitor>:<fork>

    MATCH (a)-[r:FLOW]->(b) WHERE r.kind IN $kinds
    RETURN r.kind AS kind, a.id AS a, b.id AS b, r.spare AS cap, r.refs AS refs
  13. shuttle candidates within reachon detour:fork:<visitor>:<fork>

    UNWIND $sources AS s
    MATCH (a:Station {id: s.id}), (b:Station)
    WHERE b.id <> a.id AND NOT b.id IN $stretch AND distance(a.loc, b.loc) <= $maxMeters
    WITH a, b, s, distance(a.loc, b.loc) AS meters
    MATCH (b)-[r:RUNS]->()
    WITH a, b, s, meters, sum(r.spare) AS outSpare, collect(DISTINCT r.route) AS lines
    WHERE size([x IN lines WHERE NOT x IN $closedRoutes]) > 0
    RETURN a.id AS fromId, a.name AS fromName, b.id AS toId, b.name AS toName, lines,
    s.net AS supply, s.reason AS reason, meters, outSpare
    ORDER BY reason DESC, supply DESC, meters ASC
  14. drop the forkon detour:fork:<visitor>:<fork>

    GRAPH.DELETE detour:fork:<visitor>:<fork>
Simulate a plansimulate_plan17
  1. open incidenton detour:sbx:<visitor>

    MATCH (i:Incident {status: 'open'})
    RETURN i.id AS id, i.title AS title, i.report AS report, i.routes AS routes, i.stretch AS stretch,
    i.closedEids AS closedEids, i.status AS status, i.openedMs AS openedMs
    ORDER BY i.openedMs DESC LIMIT 1
  2. fork the sandboxon detour:fork:<visitor>:<fork>

    GRAPH.COPY detour:sbx:<visitor> detour:fork:<visitor>:<fork>
  3. drop closed segments in the forkon detour:fork:<visitor>:<fork>

    MATCH ()-[r:RUNS {closed: true}]->() DELETE r
  4. drop closed rides in the forkon detour:fork:<visitor>:<fork>

    MATCH ()-[r:RIDE {closed: true}]->() DELETE r
  5. displaced riderson detour:demand

    MATCH (o:Station)-[d:DEMAND]->(x:Station)
    WHERE any(e IN d.via WHERE e IN $closed)
    RETURN o.id AS o, x.id AS d, d.riders AS riders, d.via AS via
  6. free displaced riders' capacityon detour:fork:<visitor>:<fork>

    UNWIND $freed AS f
    MATCH ()-[r:RUNS {eid: f.eid}]->()
    SET r.freed = f.riders,
    r.spare = CASE WHEN r.capacity - r.load + f.riders > 0 THEN r.capacity - r.load + f.riders ELSE 0 END
  7. forward source and sinkon detour:fork:<visitor>:<fork>

    CREATE (src:Hub {id: $src}), (snk:Hub {id: $snk})
    WITH src, snk
    UNWIND $sources AS s
    MATCH (x:Station {id: s.id})
    CREATE (src)-[:FLOW {spare: s.cap, kind: $kind, ref: -1}]->(x)
  8. forward sinkson detour:fork:<visitor>:<fork>

    MATCH (snk:Hub {id: $snk})
    UNWIND $sinks AS s
    MATCH (x:Station {id: s.id})
    CREATE (x)-[:FLOW {spare: s.cap, kind: $kind, ref: -1}]->(snk)
  9. clear corridor flow edgeson detour:fork:<visitor>:<fork>

    MATCH (:Station)-[f:FLOW]->(:Station) DELETE f
  10. aggregate spare capacity by corridoron detour:fork:<visitor>:<fork>

    MATCH (a:Station)-[r]->(b:Station)
    WHERE type(r) IN $kinds
    WITH a, b, sum(r.spare) AS spare,
    collect(CASE type(r) WHEN 'RUNS' THEN 'R' + toString(r.eid) WHEN 'WALK' THEN 'W' + toString(r.wid) ELSE 'B' + toString(r.bid) END) AS refs
    CREATE (a)-[:FLOW {spare: spare, kind: 'NET', refs: refs}]->(b)
  11. forward max flowon detour:fork:<visitor>:<fork>

    MATCH (src:Hub {id: $src}), (snk:Hub {id: $snk})
    CALL algo.maxFlow({sourceNodes: [src], targetNodes: [snk], relationshipTypes: ['FLOW'], capacityProperty: 'spare'})
    YIELD maxFlow, edges, edgeFlows
    RETURN maxFlow, [e IN edges | [e.kind, startNode(e).id, endNode(e).id]] AS es, edgeFlows AS flows
  12. forward residual graphon detour:fork:<visitor>:<fork>

    MATCH (a)-[r:FLOW]->(b) WHERE r.kind IN $kinds
    RETURN r.kind AS kind, a.id AS a, b.id AS b, r.spare AS cap, r.refs AS refs
  13. reroute displaced riders on fastest open pathson detour:fork:<visitor>:<fork>

    UNWIND $pairs AS p
    MATCH (a:Station {id: p.o}), (b:Station {id: p.d})
    CALL algo.SPpaths({sourceNode: a, targetNode: b, relTypes: $types, weightProp: 'minutes'})
    YIELD path, pathWeight
    RETURN p.o AS o, p.d AS d, pathWeight AS minutes,
    [r IN relationships(path) WHERE type(r) = 'RIDE' | r.eid] AS eids,
    [r IN relationships(path) WHERE type(r) = 'BUS' | [r.bid, startNode(r).id]] AS bids,
    [r IN relationships(path) WHERE type(r) = 'WALK' | r.wid] AS walks
  14. fork for this planon detour:fork:<visitor>:<fork>

    GRAPH.COPY detour:fork:<visitor>:<fork> detour:fork:<visitor>:<fork>
  15. add shuttle legson detour:fork:<visitor>:<fork>

    UNWIND $legs AS l
    MATCH (a:Station {id: l.from}), (b:Station {id: l.to})
    CREATE (a)-[:BUS {bid: l.bid, capacity: l.capacity, spare: l.capacity, minutes: l.minutes, busesPerHour: l.busesPerHour}]->(b),
    (b)-[:BUS {bid: l.bid, capacity: l.capacity, spare: l.capacity, minutes: l.minutes, busesPerHour: l.busesPerHour}]->(a)
  16. bus legson detour:fork:<visitor>:<fork>

    MATCH (a)-[r:BUS]->(b) RETURN r.bid AS bid, a.id AS a, b.id AS b, r.capacity AS cap
  17. record the planon detour:sbx:<visitor>

    MATCH (i:Incident {id: $incident}), (ag:Agent {id: $agent})
    CREATE (ag)-[:PROPOSED {atMs: timestamp()}]->(p:Plan {id: $id, label: $label, status: 'simulated', legs: $legsJson,
    vehicles: $vehicles, overflowBefore: $ob, overflowAfter: $oa, unmetBefore: $ub, unmetAfter: $ua,
    strandedBefore: $sb, strandedAfter: $sa, crowding: $crowding, createdMs: timestamp()})-[:FOR]->(i)
Publish the planpublish_plan8
  1. open incidenton detour:sbx:<visitor>

    MATCH (i:Incident {status: 'open'})
    RETURN i.id AS id, i.title AS title, i.report AS report, i.routes AS routes, i.stretch AS stretch,
    i.closedEids AS closedEids, i.status AS status, i.openedMs AS openedMs
    ORDER BY i.openedMs DESC LIMIT 1
  2. plans for the incidenton detour:sbx:<visitor>

    MATCH (p:Plan)-[:FOR]->(i:Incident)
    WHERE $incident = '' OR i.id = $incident
    RETURN p.id AS id, p.label AS label, p.status AS status, p.legs AS legs, p.vehicles AS vehicles,
    p.overflowBefore AS overflowBefore, p.overflowAfter AS overflowAfter, p.unmetBefore AS unmetBefore,
    p.unmetAfter AS unmetAfter, p.strandedBefore AS strandedBefore, p.strandedAfter AS strandedAfter,
    p.createdMs AS createdMs, p.publishedMs AS publishedMs, i.id AS incident
    ORDER BY createdMs DESC LIMIT 12
  3. remove earlier shuttleson detour:sbx:<visitor>

    MATCH ()-[b:BUS]->() DELETE b
  4. add the plan's shuttle legson detour:sbx:<visitor>

    UNWIND $legs AS l
    MATCH (a:Station {id: l.from}), (b:Station {id: l.to})
    CREATE (a)-[:BUS {bid: l.bid, plan: $plan, capacity: l.capacity, spare: l.capacity, minutes: l.minutes, cost: l.minutes, busesPerHour: l.busesPerHour}]->(b),
    (b)-[:BUS {bid: l.bid, plan: $plan, capacity: l.capacity, spare: l.capacity, minutes: l.minutes, cost: l.minutes, busesPerHour: l.busesPerHour}]->(a)
  5. what overflows with the plan runningon detour:sbx:<visitor>

    MATCH (p:Plan {id: $plan}) RETURN p.crowding AS crowding
  6. routing cost is travel timeon detour:sbx:<visitor>

    MATCH ()-[r:BOARD|ALIGHT|RIDE|WALK|BUS]->() SET r.cost = r.minutes, r.crowd = null
  7. crowded rides cost moreon detour:sbx:<visitor>

    UNWIND $segs AS c
    MATCH ()-[r:RIDE {eid: c.eid}]->()
    SET r.crowd = c.pct, r.cost = r.minutes * c.factor
    RETURN count(r) AS n
  8. publish and hand off to the rider deskon detour:sbx:<visitor>

    MATCH (p:Plan {id: $plan}), (c:Agent {id: 'controller'}), (r:Agent {id: 'rider-desk'})
    SET p.status = 'published', p.publishedMs = timestamp(), p.approvedBy = $approvedBy
    CREATE (c)-[:HANDED_OFF {atMs: timestamp(), incident: $incident, plan: $plan,
    note: 'Plan published. Tell every remembered rider whose trip crosses the closure what to take instead.'}]->(r)
Steer riders, no busespropose_steering3
  1. open incidenton detour:sbx:<visitor>

    MATCH (i:Incident {status: 'open'})
    RETURN i.id AS id, i.title AS title, i.report AS report, i.routes AS routes, i.stretch AS stretch,
    i.closedEids AS closedEids, i.status AS status, i.openedMs AS openedMs
    ORDER BY i.openedMs DESC LIMIT 1
  2. the measured capacity pictureon detour:sbx:<visitor>

    MATCH (i:Incident {id: $id}) RETURN i.overflow AS overflow, i.stranded AS stranded, i.unmet AS unmet
  3. record the planon detour:sbx:<visitor>

    MATCH (i:Incident {id: $incident}), (ag:Agent {id: $agent})
    CREATE (ag)-[:PROPOSED {atMs: timestamp()}]->(p:Plan {id: $id, label: $label, status: 'simulated', legs: '[]',
    vehicles: 0, overflowBefore: $o, overflowAfter: $o, unmetBefore: $u, unmetAfter: $u,
    strandedBefore: $s, strandedAfter: $s, createdMs: timestamp()})-[:FOR]->(i)
Remembered riders hitriders_affected2
  1. open incidenton detour:sbx:<visitor>

    MATCH (i:Incident {status: 'open'})
    RETURN i.id AS id, i.title AS title, i.report AS report, i.routes AS routes, i.stretch AS stretch,
    i.closedEids AS closedEids, i.status AS status, i.openedMs AS openedMs
    ORDER BY i.openedMs DESC LIMIT 1
  2. riders and what they prefer nowon detour:sbx:<visitor>

    MATCH (r:Rider)-[:STARTS_AT]->(a:Station), (r)-[:ENDS_AT]->(b:Station)
    OPTIONAL MATCH (r)-[p:PREFERS]->(pref:Preference)
    WHERE p.validToMs IS NULL
    WITH r, a, b, collect(CASE WHEN pref IS NULL THEN null ELSE {key: pref.key, value: pref.value, sinceMs: p.validFromMs} END) AS prefs
    RETURN r.id AS id, r.name AS name, r.persona AS persona, r.departs AS departs, r.stepFree AS stepFree,
    a.id AS from, b.id AS to, r.usualEids AS usualEids, r.usualStations AS usualStations,
    r.usualMinutes AS usualMinutes, prefs AS preferences
    ORDER BY name
Route a riderroute_rider5
  1. riders and what they prefer nowon detour:sbx:<visitor>

    MATCH (r:Rider)-[:STARTS_AT]->(a:Station), (r)-[:ENDS_AT]->(b:Station)
    OPTIONAL MATCH (r)-[p:PREFERS]->(pref:Preference)
    WHERE p.validToMs IS NULL
    WITH r, a, b, collect(CASE WHEN pref IS NULL THEN null ELSE {key: pref.key, value: pref.value, sinceMs: p.validFromMs} END) AS prefs
    RETURN r.id AS id, r.name AS name, r.persona AS persona, r.departs AS departs, r.stepFree AS stepFree,
    a.id AS from, b.id AS to, r.usualEids AS usualEids, r.usualStations AS usualStations,
    r.usualMinutes AS usualMinutes, prefs AS preferences
    ORDER BY name
  2. current accessibility outages (memory)on detour:sbx:<visitor>

    MATCH (f:Finding {kind: 'accessibility'})-[:ABOUT]->(s:Station)
    WHERE f.validToMs IS NULL
    OPTIONAL MATCH (f)-[:ABOUT]->(r:Route)
    RETURN s.id AS id, f.text AS text, f.validFromMs AS sinceMs, collect(r.id) AS routes
  3. rides over capacity right nowon detour:sbx:<visitor>

    MATCH ()-[r:RIDE]->() WHERE r.crowd > 100 RETURN count(r) AS n
  4. paths weighed by minutes and crowdingon detour:sbx:<visitor>

    MATCH (a:Station {id: $from}), (b:Station {id: $to})
    CALL algo.SPpaths({sourceNode: a, targetNode: b, relTypes: ['BOARD', 'RIDE', 'ALIGHT', 'WALK', 'BUS'], weightProp: 'cost', pathCount: $k})
    YIELD path, pathWeight
    RETURN [n IN nodes(path) | {station: coalesce(n.station, n.id), route: n.route, platform: hasLabels(n, ['Platform'])}] AS nodes,
    [r IN relationships(path) | {t: type(r), eid: r.eid, minutes: r.minutes, crowd: r.crowd}] AS rels,
    pathWeight AS weight
    ORDER BY weight
  5. fastest paths, ignoring crowdingon detour:sbx:<visitor>

    MATCH (a:Station {id: $from}), (b:Station {id: $to})
    CALL algo.SPpaths({sourceNode: a, targetNode: b, relTypes: ['BOARD', 'RIDE', 'ALIGHT', 'WALK', 'BUS'], weightProp: 'minutes', pathCount: $k})
    YIELD path, pathWeight
    RETURN [n IN nodes(path) | {station: coalesce(n.station, n.id), route: n.route, platform: hasLabels(n, ['Platform'])}] AS nodes,
    [r IN relationships(path) | {t: type(r), eid: r.eid, minutes: r.minutes, crowd: r.crowd}] AS rels,
    pathWeight AS weight
    ORDER BY weight
Remember a findingremember3
  1. open incidenton detour:sbx:<visitor>

    MATCH (i:Incident {status: 'open'})
    RETURN i.id AS id, i.title AS title, i.report AS report, i.routes AS routes, i.stretch AS stretch,
    i.closedEids AS closedEids, i.status AS status, i.openedMs AS openedMs
    ORDER BY i.openedMs DESC LIMIT 1
  2. write findingon detour:sbx:<visitor>

    MATCH (ag:Agent {id: $agent})
    WITH ag, [] AS current
    CREATE (ag)-[:WROTE]->(f:Finding {id: $id, text: $text, kind: $kind, confidence: $confidence,
    validFromMs: timestamp(), learnedMs: timestamp()})
    FOREACH (c IN current | SET c.validToMs = timestamp() CREATE (f)-[:SUPERSEDES]->(c))
    WITH f, [c IN current | c.id] AS superseded
    OPTIONAL MATCH (n) WHERE (n:Station AND n.id IN $stations) OR (n:Route AND n.id IN $routes)
    WITH f, superseded, collect(n) AS about
    FOREACH (x IN about | CREATE (f)-[:ABOUT]->(x))
    WITH f, superseded
    OPTIONAL MATCH (i:Incident {id: $incident})
    FOREACH (_ IN CASE WHEN i IS NULL THEN [] ELSE [1] END | CREATE (f)-[:FROM]->(i))
    RETURN f.id AS id, superseded
  3. write finding and close the one it supersedeson detour:sbx:<visitor>

    MATCH (ag:Agent {id: $agent})
    OPTIONAL MATCH (old:Finding {id: $old})
    WITH ag, old
    WHERE old IS NULL OR old.validToMs IS NULL
    WITH ag, CASE WHEN old IS NULL THEN [] ELSE [old] END AS current
    CREATE (ag)-[:WROTE]->(f:Finding {id: $id, text: $text, kind: $kind, confidence: $confidence,
    validFromMs: timestamp(), learnedMs: timestamp()})
    FOREACH (c IN current | SET c.validToMs = timestamp() CREATE (f)-[:SUPERSEDES]->(c))
    WITH f, [c IN current | c.id] AS superseded
    OPTIONAL MATCH (n) WHERE (n:Station AND n.id IN $stations) OR (n:Route AND n.id IN $routes)
    WITH f, superseded, collect(n) AS about
    FOREACH (x IN about | CREATE (f)-[:ABOUT]->(x))
    WITH f, superseded
    OPTIONAL MATCH (i:Incident {id: $incident})
    FOREACH (_ IN CASE WHEN i IS NULL THEN [] ELSE [1] END | CREATE (f)-[:FROM]->(i))
    RETURN f.id AS id, superseded
Resolve the incidentresolve_incident12
  1. open incidenton detour:sbx:<visitor>

    MATCH (i:Incident {status: 'open'})
    RETURN i.id AS id, i.title AS title, i.report AS report, i.routes AS routes, i.stretch AS stretch,
    i.closedEids AS closedEids, i.status AS status, i.openedMs AS openedMs
    ORDER BY i.openedMs DESC LIMIT 1
  2. the published planon detour:sbx:<visitor>

    MATCH (p:Plan {status: 'published'})-[:FOR]->(:Incident {id: $incident})
    RETURN p.id AS id, p.legs AS legs, p.publishedMs AS publishedMs
    ORDER BY publishedMs DESC LIMIT 1
  3. remove shuttleson detour:sbx:<visitor>

    MATCH ()-[b:BUS]->() DELETE b
  4. reopen closed segmentson detour:sbx:<visitor>

    MATCH ()-[r:RUNS {closed: true}]->()
    SET r.closed = false, r.minutes = r.baseMinutes,
    r.spare = CASE WHEN r.capacity > r.load THEN r.capacity - r.load ELSE 0 END
    RETURN count(r) AS n
  5. reopen platform rideson detour:sbx:<visitor>

    MATCH ()-[r:RIDE {closed: true}]->() SET r.closed = false, r.minutes = r.baseMinutes
  6. clear crowding costson detour:sbx:<visitor>

    MATCH ()-[r:BOARD|ALIGHT|RIDE|WALK|BUS]->() WHERE r.cost IS NOT NULL SET r.cost = null, r.crowd = null
  7. close out incidenton detour:sbx:<visitor>

    MATCH (i:Incident {status: 'open'}) SET i.status = $status, i.closedMs = timestamp()
  8. playbooks for these routeson detour:sbx:<visitor>

    MATCH (p:Playbook)-[:FOR]->(r:Route) WHERE r.id IN $routes
    WITH p, collect(r.id) AS routes
    RETURN p.id AS id, p.title AS title, p.steps AS steps, p.stations AS stations, coalesce(p.revision, 1) AS revision, routes
  9. count the playbooks this incident appliedon detour:sbx:<visitor>

    MATCH (p:Playbook), (i:Incident {id: $incident}) WHERE p.id IN $ids
    SET p.uses = p.uses + 1, p.lastUsedMs = timestamp()
    CREATE (p)-[:APPLIED {atMs: timestamp()}]->(i)
  10. findings about these stations and routeson detour:sbx:<visitor>

    MATCH (f:Finding)-[:ABOUT]->(n)
    WHERE (n:Station AND n.id IN $stations) OR (n:Route AND n.id IN $routes)
    WITH DISTINCT f
    OPTIONAL MATCH (f)-[:FROM]->(i:Incident)
    OPTIONAL MATCH (ag:Agent)-[:WROTE]->(f)
    RETURN f.id AS id, f.text AS text, f.kind AS kind, f.confidence AS confidence, f.validFromMs AS validFromMs,
    f.validToMs AS validToMs, f.example AS example, i.id AS incident, ag.name AS agent
    ORDER BY validFromMs DESC LIMIT 12
  11. past incidents on these routeson detour:sbx:<visitor>

    MATCH (i:Incident)
    WHERE i.status <> 'open' AND any(r IN i.routes WHERE r IN $routes)
    OPTIONAL MATCH (s:Shuttle)-[:FOR]->(i)
    OPTIONAL MATCH (s)-[:FROM]->(a:Station)
    OPTIONAL MATCH (s)-[:TO]->(b:Station)
    WITH i, collect(CASE WHEN s IS NULL THEN null ELSE {from: a.name, to: b.name, bph: s.busesPerHour, planned: s.planned, observed: s.observed} END) AS shuttles
    RETURN i.id AS id, i.title AS title, i.openedMs AS openedMs, i.example AS example, shuttles
    ORDER BY openedMs DESC LIMIT 5
  12. playbooks for these routeson detour:sbx:<visitor>

    MATCH (p:Playbook)-[:FOR]->(r:Route) WHERE r.id IN $routes
    RETURN DISTINCT p.id AS id, p.title AS title, p.steps AS steps, p.uses AS uses, p.lastUsedMs AS lastUsedMs