BayesianBahn 0.3.0
Fixed
- Picking a date without touching the time searched from 06:00. The
departure button said "now", and on today it meant it — but choosing another
day quietly replaced it with six in the morning, so a search for Saturday made
at lunchtime answered with the first train of the Saturday morning. Nothing in
the app showed that time or offered a way to see it. "Now" is now the current
time of day on whichever date is picked, which is what the button has been
saying all along. - A change at a busy station offered six trains that had already left. For
a journey with one change the app lists up to six connecting trains, and it
deliberately includes ones leaving shortly before the feeder is due —
usually missed, but a delayed one is occasionally exactly the connection that
works. It took the first six by departure time, so where a station has a
service every few minutes all six were in the past before the feeder even
arrived: six impossible trains and no possible one, each honestly reported as
unreachable. Nothing inside the app could notice, because every number it
showed was right. At most two of the six may now be trains already gone and
the rest is filled forward, with the reverse allowance late in the day when
there is only one train left ahead. Found by the two-leg evaluation, where it
accounted for 31% of the journeys with a change on a single collected day. - A predicted arrival could be a day late. Where a journey ends, the IRIS
board gives a departure and nothing beyond it, so the arrival was recovered
by taking the destination's most recent time of day from history and
hanging it on today's date — rolling the date forward whenever that landed
before the departure. A timetable that had shifted by half an hour was enough
to trigger it: a 28-minute leg was published as 24 hours and 28 minutes.
Departure and arrival are now read from the same historical run and the leg
between them applied to today's departure, so a schedule that shifts moves
both; the median run sets it, and a leg longer than 14 hours is declined
rather than published. This affected journeys with a change and direct
journeys alike — both recovered the arrival the same way. Found by the
two-leg evaluation on its first end-to-end run, where four candidates in six
landed a day out on one collected day.
Changed
-
The search for a change no longer spends its budget on the first trains to
leave. A journey with one change costs a live board request per change
evaluated, and eight are affordable. Those eight used to be handed out feeder
by feeder in departure order, two stations each, so at a station like Ulm Hbf
the first four departures used the whole budget while forty other trains were
never looked at — and when a train leaves says nothing about whether changing
off it works. The origin's board is a single fetch and every train on it
arrives with its own route, so all the candidate changes are known before any
attempt is spent; they are now ranked together, nearest the destination first,
and the budget goes to the best of them wherever they sit in the board. No
extra requests: measured over 4,000 archived journeys the mean spend is
unchanged at 5.8 attempts. Journeys found rise from 82% to 86% overall and
from 57% to 73% at the biggest origins, and the itinerary found arrives 5
minutes earlier on average. A station is opened once, and a train that has
already yielded an itinerary is not changed off a second time — that is the
same departure by another route, not a second option. -
Reading a train's delay history is about ten times faster, and drawing its
histogram far more. A history shard was decoded by walking a JSON tree and
asking each field for its number, which is a string parse per field per
run — a median shard holds nine hundred runs across ten stations, so opening
one train cost several thousand of them. Decoding straight into typed fields
cuts parsing 600 shards from 1.4 seconds to 0.14; building the tree had never
been the expensive part, at 88 milliseconds of that. Separately, the
distribution behind the histogram and the 80% interval answered every
question by scanning its whole list of points — one per past run per
connecting train, so hundreds to thousands — where it now binary-searches a
running total it builds once. Both run on the phone: the first on every shard
the app reads, the second on every histogram it draws. The evaluation, which
asks for a cumulative probability 661 times per scored journey, went from an
hour to six minutes for nine days. -
The evaluation waits for the next train that really runs. A journey with
a change was scored only if the passenger boarded one of the six trains the
app had offered; if they missed all six and took the seventh, the journey was
dropped. That excused the answers the app got most wrong — 30% of the two-leg
journeys on one collected day — so the walk to the train actually boarded now
runs to the end of the day, while both forecasters still answer over the
app's own six. The published margin over DB on these journeys falls from
3.001 to 2.216 minutes of CRPS as a result, and the page states outright how
often the passenger boarded past the list.
Added
- An itinerary now says when DB reports a disruption on it. DB states
trouble in more than one place and the app read only one of them. A blocked
section is not a cancellation: the trains keep their times, the cancellation
flag stays unset, and the journey is impossible anyway — DB reports it in a
notice element that both the app and the collector parsed straight past. One
Memmingen document carries 180 of them, 21 marked as a disruption. So an
evening where no passenger could travel was recorded as an evening where
every train ran to time, and the app would have shown a confident prediction
for a trip DB was publishing as impossible. An itinerary whose feeder or
connecting train carries one now says so above the times rather than below
them, because it is the reason not to trust them; the prediction is still
shown, as DB's own apps show the times too, but no longer silently. Only
notices categorised as a disruption count — roadworks are attached to half
the stops in Germany and say nothing about today, so warning on those would
warn on everything. The validity window and timestamps are not stored: a
construction notice valid for three months would otherwise ride along on
every poll of every stop it touches. The evaluation records the flag per
event without acting on it yet — it is a different failure from a
cancellation and deserves its own count first. - The evaluation scores a complete two-leg journey, not only its parts: the
predicted arrival at the far end of a change against the arrival that
happened, in the same units as a direct journey, so for the first time the
two kinds of journey can be compared with each other rather than each only
with DB. Both forecasters answer over the same candidate trains and from the
same moment, and neither is shown a delay the other was denied: DB's answer
is the arrival of whichever train its own forecasts say the passenger
catches. The harness drives the shipping code —CandidateBuilder, which the
app itself uses, andConnectionModel.propagate— rather than a description
of it.