Skip to content

BayesianBahn 0.3.0

Choose a tag to compare

@github-actions github-actions released this 30 Aug 17:55
· 34 commits to main since this release

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, and ConnectionModel.propagate — rather than a description
    of it.