Ostrava buses (DPO): peak vypravenost sheet
Output: Ostrava Bus Vypravenost.html in the project root, published at https://claude.ai/artifact/6rNwavbbnG4H2aJVZPNx8R
(also config.json → artifact_url; republish
to the same URL). Shared rules for all cities are in ../README.md. The sister sheets are ../ostrava/ (trams) and
../ostrava-trolleybus/. This pipeline imports their helpers (scripts/lib.py): the tram lib for the parsers, JDF
reader, version rule, blocks, runfit, plans and make_nm, and the trolleybus lib for cycle_need_peak.
State on 30 Sep 2026: observed at the peak
- The roster is observed: these are the buses DPO's live feed showed on their lines in 15:50–16:15 on Wed 30 Sep 2026 (
live.window). 167 buses were observed against a need of 173. 10 are "not needed" surplus (22, 45, 48 ×2, 49 ×2, 54 ×2, 61, 68), and 16 are designed with real car numbers from the line history. - Coverage: the poller's machine slept for parts of 30 Sep. Snapshots exist for 14:06–14:14, 16:01–16:15 and 18:xx. The peak window has 15 snapshots of about 120 buses each (
live.coverage_note). - Branch rebalance (
04_roster.pystep 3a): the live trip names match the branch termini only loosely, so extra buses on one branch move to a short branch of the same line (line 34: 7208 and 5017 → 34A). This happens before the surplus is marked. - Before 30 Sep: a provisional roster (buses seen 17:40–17:50 on 29 Sep plus history-designed ones) stood in;
live.provisionalstill describes it. 03_fleet.py switches to mode "peak" as soon as the vehicle day has snapshots inlive.window.
Sources
| What | URL | Notes |
|---|---|---|
| Timetable | https://portal.cisjr.cz/pub/jdf/JDF.zip | The bus export (~107 MB, ~13,000 inner zips), not the city export that holds the trams and trolleybuses. DPO bus lines are 915021–915099 plus 915201; 02_timetable.py finds them by prefix and IČO. |
| Fleet register | seznam-autobusu.cz/seznam?prov=1&trakce=autobus&iddopravce=133&vcetneSluzebnich=1 (3 pages) and …trakce=elektrobus… (1 page) |
Electric buses are a separate traction (elektrobus). Depot cell: AH = Garáže Hranečník, AP = Garáže Poruba (a trailing * is dropped). WAF headers as for trams. |
| Live feed | POST https://dashboard.dpo.cz/api/server (body in 01b_live_poll.py) |
vehicleType "bus"; all rows are DPO (agencyID dpo). Line, course, trip start and final stop. No history. |
| Day records | seznam-autobusu ODIS vypravenost | Almost no DPO bus rows (3 on 25 Sep); fetched only as a cross-check. |
| Disruptions | https://www.dpo.cz/aktualne/vyluky.html | config.network_state: Na Karolině (21/25/81/92), Náměstí Republiky (50/73/99, stop C), line 67 bridge; line 22 in October is not modelled. |
| pak128 | local repo C:\Users\vojte\Developer\simutrans-custom-czech-pak128\build\VZ-DPOstrava-bus\ (main b84c15c) |
Urbino 12/18 in s3 (blue-yellow) and s4 (turquoise), Urbino 12 III CNG / 18 III CNG own objects (s3_CNG; 12 III CNG and 18 III also turquoise), Urbino 10 (blue-yellow only; the turquoise one was removed on 20 Oct, pak main 2cdf3f3), SOR EBN 10,5, Dekstra LE 37, Rošero First, Scania Urbis DD, Ekova Electron 12. The electric Urbino 12 IV has its own object, SolarisUrbino12_s4_electric (dpotyrkysova; engine_type battery, 85 pax, length 8, intro 3/2022; installed since 10 Oct 2026, confirmed by the pak session on 20 Oct). The other objects have engine_type diesel. |
Steps (scripts/, stdlib-only Python)
01_fetch.py [--jdf] [--fleet] [--records]fetches the raw inputs.01b_live_poll.pypolls the live feed: every minute from 13:00 to 19:30, otherwise every 10 min.02_timetable.pyfinds DPO's lines, picks one version each and classifies them: peak, morning, night, or none on the day.03_fleet.pybuilds the fleet (type rules, depot), the records, and live.json for the vehicle day.04_roster.pyfinds branches automatically, sizes their needs and builds the observed roster and any designed buses. The rules are in its docstring:- A terminus pair is its own Simutrans line if it has ≥ 6 trips in 15:00–18:00, a 16:xx departure and no garage end.
- The branch need is
cycle_need_peak, with max_wait = max(30, 1.5 × headway). - The line need is the larger of the branch sum and the buses on the road (Prague bus rule): each non-depot trip holds a bus until arrival + the cycle layover, the most at once in 16:00–16:59. The difference goes to the main branch (23, 56, 59 …).
- Sparse lines get at least 1 bus.
05_schedule.pyruns runfit for every terminus. Departures are set 1 min early everywhere, as agreed on 29 Sep for all sheets, block turns included.06_render.pyrenders the pending state or the observed state.
Decisions
- Vehicle day Wed 30 Sep, observed live. The tram and trolleybus sheets use 25 and 29 Sep; buses could not be observed on those days.
- All 57 DPO bus lines are scanned. 47 run in the afternoon and are on the sheet. The rest (25, 43, 74, 92, 201 morning only; 71, 73, 77 night; 72 and 21x none on a weekday) are listed under "Not in the afternoon peak".
- Branches by rule, not by hand (config
branch_rule).terminus_aliasmerges Přívoz, sad Boženy Němcové into Moravská Ostrava, Vozovna trolejbusů: lines 49 and 54 arrive at one stop and leave from the next. - Stop names: the tram and trolleybus expansions, plus bus ones such as rozcestí, autobusové nádraží, průmyslová zóna, Vysoká škola báňská – Technická univerzita Ostrava and Nábřeží Svazu protifašistických bojovníků. Company names (OKD, OZO, TAXI, AVION, STAV-IZOL) stay.
How to update
- Start
python scripts/01b_live_poll.pybefore 15:00 on a normal weekday.T19:45 - Set
snapshot(for new raw data),vehicle_day, both labels,network_state_*andlive.labelinconfig.json. python scripts/01_fetch.py(JDF ~107 MB, registers, records).- Re-read the výluky and update
network_state. Checkdepot_stopsandterminus_aliasif termini changed. - After the peak,
python scripts/run.py. Check that no branch has many designed buses. If one does, the branch matching of live trip names may need a look (04_roster.py, step 3). - Republish.
Schedule fit and sheet changes of 20 Oct 2026
Both ends at the same rate:
plansin the tram lib (../ostrava/scripts/lib.py) is shared. Where the two ends of a branch differ in departures per hour, or either end's exact run misses the peak band (15:30–17:30, overlap < 30 min, fewer than 4 departures (3 if hourly), interval not dividing 1440), every rate either end offers is tried exactly on both ends. Otherwise both ends get one even slot at the averaged rate, marked "approximate, up to N min off", with the real band minutes in the Fit column. A direction whose opposite direction is scheduled is never merged away (bus 27, Proskovice → Hrabová).Rows to check: an approximate row with more slots in 15:30–17:30 than real departures is marked on its card and listed under "Approximate schedules to check", with the slower alternative (
altin schedule.json). It is the owner's choice; switch both ends of a line together.Display: as on Prague, the typed Offset/Also at values carry the real minute under them (worked out from each typed value + 1 at loops), plus a Fit column. The Headway column shows the fitted pattern's gaps.
Only 05_schedule and 06_render were rerun on 20 Oct; the roster (vehicles) is unchanged and identical to the published sheet.
Network state checked 20 Oct 2026 (dpo.cz výluky): added Klimkovice, centrum (lines 59, 64, 76, 20 Feb – 12 Dec, modelled; the CIS JŘ export still lists the regular stops) and line 45 Most Mládeže (stop moved 160 m from 1 Oct, not modelled). Line 22's Dědičná repair has ended. Line 67 Vrablovec ends 20 Nov.
Line overrides (
config.line_overrides): the owner can set a line's model by hand. 04_roster then uses the given branch and need, 05_schedule the given rows (loop −1 only whereloopis true), and 06_render shows the timetable table of the override window. 41 (20 Oct 2026, owner): modelled as at 14:00, half-hourly from Hrabůvka, Poliklinika, alternating to Vratimov, náměstí (:22) and Kartonážka (:52); back from Kartonážka :15 and náměstí :48. It is one circuit of 180 min with 3 buses: 7267 as seen, plus designed 7872 and 7864 (Urbino 18, line history). The real 14:xx hour sends both trips to Kartonážka (náměstí trips at 13:22 and 15:25); the alternation is the owner's simplification.Offsets count from 01:00 (fixed 20 Oct 2026): the fork's offset is (real − 60) mod interval.
loop_shiftin the shared lib now applies it. It changes nothing for intervals dividing 60, but the every-40 rows (28, 40, 47, 56) had typed offsets 20 min off before. The real subtitle shows the first clock time from 15:30 for such intervals.Schedule overrides (
config.schedule_overrides): single rows set by hand. 21 and 81 (owner, 20 Oct 2026): from 16:00 both run every 40, interleaved on the shared Hranečník – Vratimov, náměstí trunk. 21 leaves Hranečník at 16:05/16:45/17:25 and 81 at 16:23/17:03/17:43. Back, 21 leaves Horní Datyně at :09/:49/:29 and 81 leaves Řepiště at :29/:09/:49, which gives every 20 at Vratimov, náměstí. 59 (owner, 21 Oct 2026: "so the two branches don't meet at Polanka at the same time"): the Svinov branch runs through Klimkovice, centrum, so both branches share Klimkovice – Polanka – Stará Bělá – Vítkovice (26 stops). The automatic far-end fit had Svinov at :55 (passing Klimkovice :11) and Klimkovice at :15, so they ran 4 min apart over the whole trunk. The two inbound rows are now set by hand: Klimkovice :00 (real 15:00) and Svinov :09 (real 14:05 + 4), 25/35 apart, the same split as outbound (Vítkovice :08/:33). The needs stay 2 + 3 (59B can take any Svinov minute: its two waits always sum to 56). A row may give its ownwindow. The Fit cell now gives how far each real departure in the window is from a slot and lists those more than 3 min off; overridden rows lose theirinterleavetag. The old 21/81 cell gave the worst slot deviation instead.Shared-trunk scan (21 Oct 2026): the
passingrule only keeps a far end ≥ 3 min from a bus passing that terminus, and a 4-min gap at Klimkovice let 59 through.scripts/check_trunks.py(within a line) andscripts/check_cross_lines.py(between lines, ≥ 8 shared stops) are checks run by hand, not run.py steps. Compare every flag with the real times at the junction: only model artefacts are fixed. v33 (owner: 'continue', and a screenshot of 39/35/39 leaving Dům energetiky within 4 min): 34 Hlučín AN :05 → :00 (every 10 with Bobrovníky at Bobrovníky; 34B 4 → 3 buses, as seen live, with the timetable's own 1–2 min turns; designed 7771 dropped). 39 statek :27/:47 → :21/:41 and Paskov :23 → :50, interleaved with 35 (every 20, PZ jih :10) from Hrabůvka, Benzina to Otakara Jeremiáše, 7–13 min apart (about 10 in reality); 4 + 3 buses unchanged. 40A every 40 (:19/:36, :15/:38) → every 20 as in reality (Opavská :19, Alšovo :04), between the radnice buses; 2 → 3 buses, 7124 (seen live, was not needed) added. v34 (owner, 22 Oct: 'solve the problems that are only in the model, not in reality'): set withscripts/space_corridor.py(hand tool: shifts rows within their interval, keeps every branch's need viapattern_need, scores each shared stop's closest gap against even spacing, worst stop first, then fewest rows moved, then closeness to the real minutes). 35 PZ jih :10 → :13 (35+39 9–11 min apart at every shared stop both ways); 26 Dubina :19 → :07 (12 min from the 59 at Dubina, was 1); 44 Svinov, Dubí :46 → :42 and 46 Krásné Pole :14 → :16, Polanka → Krásné Pole :14 → :12 (closest 4–5 min, as in reality, was 0–2); 56 Ludgeřovice :03/:50 → :11/:49 and Darkovičky :50 → :18 (kostel :49, :59, :11, :24, :36). No need or roster changed. Not fixable by offsets: 56/67/68 on Hlučínská: 68 is every 80 in the model, 56/67 every 60, so over 240 min some buses coincide whatever the offsets (the tool's best was 13 rows moved for a 2-min worst gap); the real timetable is coordinated trip by trip. A fix needs a change of model (e.g. 68 on a 60-min pattern), the owner's choice. Left: 27/41 at Poliklinika (41 is the owner's hand line modelled as at 14:00, 27 at 16:10; the only fixes move 27's exact rows 20+ min); 27/78 on the Hrabůvka – Zábřeh trunk, which with 48 and 78 has a bus every 4–5 min (gains 1 → 2 min only).Not needed buses are collapsed per card (
details.nn); the vehicle search opens the group of a hit.Needs come from the suggested pattern (user rule, 20 Oct 2026, all lines):
pattern_needinostrava/scripts/lib.pyfollows a vehicle round the sheet's own schedule slots (real running times, at least the terminus layover) and counts the slots of one round. 05_schedule therefore runs before 04_roster. Lines without a timed pattern at both ends keep the timetable sizing. The variant extra (buses of unmodelled variants) is dropped. Bus, 20 Oct 2026: need 174 → 171 (21: 4 → 2 at every 40; 81: 3 → 2; 26, 34, 54, 58B, 68 up).keep_roster(data/published/roster_2026-10-20_v7.json) keeps designed buses on their line and swaps branch moves back, so 7 units change in the game.
All lines at Hranečník, with ODIS lines (owner, 20 Oct 2026)
- Section at the end of the sheet ("All lines at Hranečník"): a table of every bus line with a stop at Hranečník (10 DPO lines, linking to their cards, plus 8 ODIS lines), then cards for the ODIS lines in the DPO style. The page totals, depot tables and nav stay DPO-only.
01c_fetch_odis.py(config.odis) scans the national JDF for non-DPO lines with a stop matchinghub_stop_matchinhub_town. On 20 Oct that was 8 lines, all Transdev Slezsko (IČO 45192081, seznam carrier 28): 383, 387, 440, 441, 442, 531, 534, 535 (ODIS number = last 3 digits, confirmed by the records). It also fetches each line's seznam-autobusu ODIS records, the vehicle day's records and the Transdev register intodata/raw/2026-10-20/odis/.07_odis.pybuildsderived/odis.jsonwith the same rules as the DPO lines: version for the vehicle day, branch rule, shared schedule fit,pattern_need. 383, 387 and 535 are too sparse for a pattern at both ends, so they are sized by turns at 16:00. Vehicles: no record of these lines exists for 30 Sep (their records are mostly weekends and holidays), so all are designed. They are real buses from the line's records, active, covering its types, one line each. If the records run short, the line's main type comes from the same operating area (442: 4th SOR CNG 12).- Pak: no Transdev objects or livery.
config.odis.typesmaps each type to the nearest DPO object (≈): Crossway LE, SOR CNG 12 and Citywide 12M/15M → Urbino 12 CNG; Dekstra LE 37 L → Dekstra LE 37. A request for real objects would go to the pak session. - Pak lengths updated 20 Oct 2026 (pak main d28795c): Urbino 18 rear section 6, Urbino 10 = 7.
Afternoon branches (owner, 20 Oct 2026)
Branch rule (
lib.branch_pairs, shared by 04, 05 and 07): the peak rule as before, plus an afternoon rule. A terminus pair with at least 3 trips in each direction in 13:00–19:00 (branch_rule.afternoon,afternoon_min) is its own Simutrans line, even if it is thin at 16:00. Example: 23 to Bohumín, Záblatí, střed, every 60–80 min all afternoon. New branches on 20 Oct: 23 Záblatí, 27 Hrabová–Proskovice, 44 Svinov, Dubí, 56 Ludgeřovice / Hlučín, sídliště OKD / Hlučín, Darkovičky, 59 Svinov, mosty – Vítkovice, 68 Šilheřovice, Paseky.Schedule: every branch gets its own rows (
plans(keep=…)); its trips are no longer merged into the main row. A direction with fewer than 2 departures in the peak band is fitted over 13:00–19:00, with intervals up to 120 (40, 80, 90 and 120 included). Approximate patterns choose both ends' offsets together: each end within a few minutes of its best fit (a third of the interval on sparse branches), with the shortest round, using the longest running time 15:00–18:00. That is the shortest-wait rule, and it stops a bus waiting a whole interval at each end.Effect: total need 192 → 197 (DPO + ODIS), and 19 units added or moved. Sparse lines that had no pattern at both ends now have one, so they are sized from it (e.g. 535: every 60 with 64-min trips → 3; the slower option is on the To check list).
23 (owner, 20 Oct 2026): separate fits gave 23A every 30 and 23B every 80, bunching 7 min apart at Hranečník. In reality the two alternate every 40 from 16:10, so
schedule_overridessets both to every 80, 40 apart: Hranečník → Záblatí 16:10, 17:30 and → Rychvald 16:50, 18:10 (exact); back from Rychvald 16:10, 17:30, 18:50 (exact) and from Záblatí 16:57, 18:17 (real 18:07). One bus per branch, so 3 → 2 (5011 and 7252 not needed). Still bunched at a shared terminus on 20 Oct: 34, 44, 56, 68 (check with the shared-terminus scan in the session notes).
Each line at its own afternoon peak; branches interleaved (owner, 20 Oct 2026)
- Why: the owner saw every 20 as 23's afternoon pattern while the sheet showed every 40. The reader was right (it keeps every working-day trip), but the sheet modelled every line at 16:00, and 23 drops from every 20 to every 40 at 16:10. About 25 lines peak earlier (13:00–16:00, Ostrava's shift change) and were modelled in their wind-down.
lib.line_anchor: all departures of the line, both directions. If its busiest 2 hours in 13:00–18:00 have at most one departure more than 15:00–17:00, it stays at 16:00, where the live roster was observed. Otherwise the line is anchored 30 min into that busiest stretch, so every fit band covers it exactly. Anchors are inderived/peaks.json(DPO) andodis.json['peaks']. The fit window, branch rule (peak part), sizing (pattern_need), timetable table and card labels all follow the anchor. The afternoon branch rule keeps its absolute 13:00–19:00. On 20 Oct, 24 lines moved (21, 24, 27, 31, 34, 38, 39, 41, 45, 47, 48, 52, 56, 58, 59, 67, 68, 76, 78, 79, 81, 96, 98, ODIS 534).lib.interleave: at every terminus shared by two or more branches, the branches are fitted together on one common interval. Each branch's departures cluster into its own slots (within 5 min), the slot count matches its trips, no two branches share a slot, and the window covers at least 1.5 periods. The far end of each branch gets the same interval, shifted for the shortest round within tolerance (3 min if the far end is itself a shared terminus). Merged variants count with the branch they share most stops with (34: Rovniny → Hlučín AN). A row already set at its own shared terminus is not overwritten. Examples: 23 at Hranečník: Záblatí :10, Rychvald :30/:50 (every 20, R-R-Z); 34 at Křižíkova: Bobrovníky :01, Hlučín AN :11 (every 10); 44 at Otakara Jeremiáše: Tesco :07/:27/:46, průmyslová zóna :36/:56, Dubí :16 (every 10); 27 at Poliklinika: Výškovice :07/:37, Proskovice :22. The shared-terminus scan finds no bunching.- Approximate fits now count 5 min against a candidate for every slot without a real trip, so a sparse branch no longer gets a busier phantom pattern.
- Retired overrides: 21/81 every 40 and 23 every 80 fitted the wind-down; see
schedule_overrides_retired. Line 41's override stays. - Effect against v12: total need 196 → 212, with 39 units added or moved and 9 parked. Rows to check went from 26 to 7.
- Open: 23B needs 2 buses (Záblatí hourly, a 63-min round). Real buses interwork the two branches at Hranečník. A cross-branch circuit, as the tram 1+2 circuit was built, would bring 23 to 3 but is not modelled.
- Circuits across branches (
lib.circuitize, owner 20 Oct 2026: "where it significantly shortens wait, do it"): for lines whose branches all share one terminus, every assignment of a returning bus to a next departure slot at that terminus is tried (≤ 8 slots per period). Each job is out on its branch, back on the first real departure after the layover, and in again. The assignment with the least total waiting splits into cycles; each cycle is one Simutrans line, a circuit or a single branch. It is used only when it saves at least one bus. The far-end slot is the first slot of the far row's fitted pattern after the layover. Those are the same rate-matched slots the separate rows use. The v14 circuits on raw far-end residues put legs off the real departures and were dropped. Only 58 remains: one circuit Plesná / Martinov, 5 → 4, every leg on a real departure. 34 and 56 are back to separate branch lines (7 each). The renderer counts each leg's slots on a real departure (±3 min) and lists any leg with a miss under To check, "Circuit legs off the timetable". 23 stays 4: in its every-20 peak the three slots an hour need 240 min of bus time, whatever the order. The 3 buses seen live at 16:00 were in the every-40 wind-down. Lines whose branches meet at two termini (27) are not tried. - Two paths between the same termini (
lib.path_variants; owner 20 Oct 2026, line 28): 28 runs Šenov – Václavovice alternately via U Slívy / U Blažka and via Václavovice střed, each every 40. A merged row would let Simutrans buses take one path only. A direction counts when 2+ paths (trip stop sets differing by at most one stop are one path) have 2+ trips each in the anchor −60..+120. The old rule (each path needs 2+ stops of its own) missed 32, whose Hlučínská path has only one; corrected on 20 Oct at the owner's request, to serve Přívoz, Hálkova. Such a single-branch line goes throughcircuitizewith the far end free to take either path: the first fitted far slot after arrival, with each slot's own running time. It is kept even without saving a bus (28: 3, as observed), as one Simutrans line H → V (via střed) → H (via U Slívy) → V (via U Slívy) → H (via střed). Each path is labelled by a stop only it has, the same in both directions. The card lists the stops of each path. Where the X row runs every I with one slot but the paths alternate (32: every 30 at Křižíkova), the period is widened to a multiple of I in which both paths get their own slots (32: every 60, :15 via Hlučínská, :45 via Hálkova). Path label: the path's own stop nearest the end; matched to the reverse path where one exists (28). A path with no stop of its own skips stops and is labelled "non-stop to" (ODIS 531's :40 from Hranečník). On 20 Oct: 28 (one 3-bus circuit), 32 (two 1-bus lines, one per path, tie with a circuit), ODIS 531 (two 2-bus lines). Bus counts unchanged. - Far ends that other branches pass through (
lib.interleave,passing; owner 20 Oct 2026, screenshot of 23 at Rychvald, rozcestí): a far terminus that another branch's buses pass on their way to the shared terminus counts as a junction. The minutes those buses pass come from their set far row plus the median running time to that stop. If the usual far-end choice leaves within 2 min of a passing bus, the shift with the widest gap is taken instead. The new shift may be at most 3 min further from the real departures, and sparse rows (fewer than 2 real departures per slot) are not moved. On 20 Oct only 59 Klimkovice, centrum moved (:12 → :15; the Svinov bus passes at :11). A scan of every line's junctions shows no departure within 2 min of a passing bus. - Anchor override (
config.anchor_overrides): 23 is modelled at 14:50 (window 14:20–16:20), its every-20 peak. The rule kept it at 16:00, because the busiest stretch beat 16:00 by only one departure. At 16:00 the line winds down to every 40 and the Záblatí bus runs at :57 instead of :27, which put rozcestí and Záblatí departures in the same minute at Rychvald, rozcestí. Now Hranečník is every 20: Záblatí :10 and rozcestí :30 / :50 / :70 in an 80-min cycle, so one bus in four goes to Záblatí, as in the real 14:00–16:00 timetable. Rozcestí departs :10 / :30 / :70 with the Záblatí bus passing at :60. Still 4 buses. - Evenly spaced slots fold to a plain interval (
lib.simplify_rows/even_interval; owner 20 Oct 2026, 27A: "why is this not just departure every 30 minutes?"): interleaved branches share one interval at their terminus. 27A runs every 30 beside 27B every 60, and was written as every 60 with Offset 17 and Also at 47. Every timed row (DPO and ODIS) whose slots are evenly spaced is now written at the plain interval: every 30, Offset 17. Circuit legs are folded the same way on the page.circuitizeexpands rows of different intervals to the longest one. The folding never changes a departure or a need. On 20 Oct only 27A's two rows changed. - Both ends matched across aliased termini (05_schedule; owner 20 Oct 2026, "31 does have uneven departures"):
plansis now fitted on aliased terminus names, so a line whose two ends have different stop names is rate-matched like any other. The rows then get back their real stop names. 31 runs out to Nová huť jižní brána and back from Nová huť garáže, one loop at the steelworks; new alias garáže → jižní brána. It had been two unmatched branches: back every 60 at :07 / :15 / :27 / :47, sized by turns. Now it is every 20 both ways (:10 out, :07 back), 4 buses. 49's sad B. Němcové / Vozovna trolejbusů branch is every 30 both ways, 3 → 2 buses. - Near-duplicate slots merged (
simplify_rows): slots 1–2 min apart are one departure whose minute drifts. 54 runs every 10 at :x3, then :x2 from 16:02, and had been written as every 60 with 7 slots (7 an hour against a real 6). The member kept is the one that leaves an even pattern, then the row folds: 54 is every 10 at :03 and :09, 9 → 7 buses. That equals the timetable's 7 buses on the road at once and the 7 seen live. - Rows thinned to what the timetable supports (
thin_rows, aftersimplify_rows): a row whose slots in its window (the model window widened to the row's own run) exceed its real departures by 2 or more, with fewer than two departures per slot, becomes one slot, approximate. 62 has two shift trips, 14:10 and 15:02, and had been written as every 60 at :02 and :10. It is now every 60 at :06, up to 4 min off. Only 62 changed. - DPO need after these: 181.
- Stop coverage (owner rule via the Havířov/Karviná/Orlová sheet, 20 Oct 2026: "501 is the only line running to Dětmarovice, how can we not model that?"): every stop the sheet's lines (DPO and the Hranečník ODIS lines) serve 13:00–19:00 must be on a modelled Simutrans line, or be listed in the page's Coverage section with the reason. It is computed in 06_render.py: the main stop sequence of every modelled direction, every path leg, and the owner's 41 circuit. Fixes it needed:
path_variants(..., keep_stops): a stop only one line serves is never treated as a "skipped stop". 49 runs every second bus from Poruba to Michálkovice via Hrušov, Růžová, which is now its own path.circuitizehandles paths on any branch of a star line, with the period the LCM of the row intervals (49: every 20 and every 30, so 60). At equal bus counts it keeps each branch's buses on their branch, and cycles that stay on one plain branch merge into one Simutrans line (49A Vozovna, 2 buses). 49B is a 5-bus circuit: via Růžová, then twice not via Růžová.- Path labels: a path that only skips 1–2 stops is "not via
". A path skipping a stretch is "non-stop to ". Circuit legs show the stops the trips really start and end at, and are matched to their real departures through the aliases. - ODIS 441: alias Havířov, Město, nemocnice → žel. st. for the hourly Havířov Město loop (out ends at žel. st., back starts at nemocnice via obchodní dům). 07_odis now fits on aliased termini like 05_schedule. 441 is two branches, Podlesí (2) and Havířov Město (2).
- Left listed (single trips): Heřmanice, Dolina and Parcelní (22, one evening round trip at 18:30; trolleybus 106 also serves them but is ended on this day), and Vítkovice, U chladičů (57, one shift trip at 14:08). A daily line (every 1440) would need its own bus each; not done without the owner's say.
- 49A (Vozovna): the branch runs every 20 until 16:45 and then stops. In the 15:30–17:30 window it is averaged to every 30 (2 buses), and its legs are in To check (every 20 would need 3).
- ODIS buses on VZ-ODIS-bus (pak session, main 6f7b639, 20 Oct 2026; owner had said to wait for its recommendation):
config.pak.odis_objectsmaps real type and real paint toVZ-ODIS-. 3ČSAD white-green →- - 3csadbilozelena; white & MSK logo →bilazelenykruh(Havířovsko). The map: Crossway LE CITY 12M → Crossway_LE_City_12M; Crossway LE LINE 12M NP → Crossway_LE_Line_12M_NP; SOR CNG 12 → SOR_CNG_12; Citywide LE 12M → Citywide_LE_12M_CNG; Citywide LE 15M → Citywide_LE_15M_CNG, or ≈ Citywide_Suburban_LE_15M_CNG in white-green; Dekstra LE 37 → Dekstra_LE_37_CNG. Lengths come from the pak's dat files: 8, except 15 m = 10 and Dekstra = 5. CZ-ODIS / CZR-ODIS objects are only for the old save and are not used. - Exact runs win over approximations (
lib.exactify, 05_schedule; found with the Havířov/Karviná/Orlová sheet, 20 Oct 2026): a direction pair counts when a row is approximate (≥ 5 min off), has variant minutes folded in, or was refitted to the other end. If both rows' own trips keep an exact pattern at the same rate (≥ 6 departures, or 3 for an hourly single slot as in plans' ok(), over ≥ 2 h, every slot taken, ±1 min, covering at least half the model window), both rows take it and folded variant minutes are dropped. On 20 Oct:- 49 to sad B. Němcové: every 20 until 16:45; had been averaged to every 30 over a window where it winds down. 2 → 3 buses.
- 76: hourly both ways (Svinov :41, Klimkovice :15). 2 Svinov → Polanka trips folded into the row had made both ends every 30. 3 → 2 buses.
- 54: every 10, as before.
- DPO need unchanged at 181.
- Checked and left as they are, because the real timetable is irregular there: 26, 29, 38, 52, 64, 67, 83, 383, 534. No hourly line had been pushed to every 120 (79 and 83 really are two-trip lines).
- Whole-day reading: regional lines only. The owner said on 20 Oct 2026 that the whole-day rules are for regional lines, not local ones. Local DPO lines are modelled at their afternoon peak, with their unique stops. So 05_schedule no longer passes
day=toexactify(96 is approximate again, :38 / :06). The ODIS lines use the method throughconfig.hand_lines. - Was (withdrawn the same day) — whole-day reading (owner, 20 Oct 2026, forwarded by the Havířov/Karviná/Orlová session from the save game's Ex4: "I defined it in core points, created a repeating interval and it suddenly made sense"): when the 13:00–19:00 reading finds no exact pattern for a pair,
exactifyreads the same pair over the whole operating day (05:00–23:00,day=argument). One departure in six may sit up to 5 min off its slot, and no other departure may fall inside the run. Such rows say "read over the whole day as it repeats" and name the displaced departure. On 20 Oct:- 96: hourly all day, Poliklinika :38 and Korýtko :03; only 13:43 and 14:08 are 5 min off. It had been approximate (:38 / :06). Still 1 bus.
- Read over the whole day and still without a repeating interval: 26, 29, 38, 52, 64, 67 (and 34 / 67 together on the Hlučín corridor), 83, 383, 534.
- ODIS 442 repeats hourly: Hranečník :05, :10, :20, :40, :50; Podlesí :21, :36, :51. The :05 and :10 run one way only, so they wait for the 20 Oct live log to show where those buses go back.
ODIS lines set by hand with the bus schedule method (owner, 20 Oct 2026: "You are good to go")
The Hranečník ODIS lines were checked step by step against ../bus-schedule-method.md (v2). The check is in method-check-odis-2026-10-20.md, and the Havířov/Karviná/Orlová session reviewed it.
- Config:
config.odis_hand(rule text inodis_hand_rule). - Spec: for each line, its core points, a card note, and the Simutrans lines.
- Each Simutrans line has
every, the terminiX/Y, andout/backslots: minutes for every 60, elseH:MM. back: nullis an empty, untimed return.- It also names the JDF lines whose trips run each leg (
out_lines/back_lines) and an optional path label (out_via/back_via).
- Each Simutrans line has
- Builder:
07_odis.hand_linesturns each line into a branch with legs, so the card, fit cells, coverage check and roster read it like a circuit.- Real departures per leg: the line's own trips, plus another line's trips only where they run in this line's slots (534 in 535's :25), plus the line's own short turns in a slot (535 17:25 to Lutyně).
- Running time: the line's own trips to the far terminus.
- Need:
lib.slot_needmatches each terminal's arrivals to its departures.- Minimum wait: 3 min, or the real turnaround where it is shorter (never under 1). The wait before an empty return is 0.
- This is exact for uneven slots.
- Lines set on 20 Oct:
- 442 + 441:
- 442 sends 5 an hour to Podlesí in 14:00–17:30 and brings 3 back; 441 sends 1 and brings 3. So 442's :05 and :10 come back as 441's :32 and :47 (method Step 5b).
- Simutrans line 442: every 60, :20 :40 :50 / :21 :36 :51, 4 buses.
- 442/441 circuit: out :05 :10 on 442's express path, back :32 :47 on 441's path via Šumbark, 3 buses.
- 441: :30 / :17, 2 buses.
- 441 Město: :00 / nemocnice :05, 2 buses.
- 8 → 11. The sheet had dropped 441's :17 and :47.
- 534 + 535: one corridor, every 30 at Hranečník (out :25 / :55, in :03 / :33).
- 534: :55 / Karviná :20, 3 buses.
- 535: :25 / Karviná :01, 3 buses. 534's 13:25–15:25 run in this slot.
- 9 → 6. They meet alternately at Orlová, Poruba, rest. Horník.
- 387: out only in the afternoon (14:45, 15:45), every 60 :45 with an empty untimed return. 1 → 2.
- 383: every 72 (any divisor of 1440, method v2): Hranečník 15:28 slot, Řepiště 14:32 slot. Out within 3 min, back within 9 (16:47 against 16:56); on the To check list. 2 buses.
- Unchanged: 531 (two path circuits, 4 buses) and 440 (every 30, 3 buses). 440's Kartonážka trips are exactly every 480 but only listed, because DPO 41 serves Kartonážka (method Step 8).
- Total: ODIS need 27 → 28. Coverage unchanged: 565 of 568 stops; the 3 left are DPO single trips.
- 442 + 441:
- No every 1440 / 720 (owner rule forwarded 20 Oct 2026; at most 480) applies to regional lines. Local lines get no long-interval lines for single trips. Depot runs are never modelled. Line 22's evening run to Heřmanice, Důl Heřmanice (hourly from 18:30) is not modelled; its 2 stops are listed in Coverage as outside the afternoon peak.
- 57 exception (owner approved this case only, 20 Oct 2026; further cases are to be asked with supporting info): Kunčice, Nová huť garáže (a public stop, not a DPO depot) → Hulváky via Vítkovice, U chladičů runs at 6:08, 14:08 and 22:13. These are the stop's only service. It is
config.hand_lines['57'](modeadd): every 480 from Nová huť at 14:08, with an empty untimed return, 1 bus. 22:13 is 5 min off its slot. DPO need 181 → 182; coverage 566 of 568.
Owner's choice of vehicle type (20 Oct 2026)
config.owner_types puts named buses on a Simutrans line in place of buses seen there (04_roster step 3c): the owner asked for the
Solaris Urbino 10 III, which were not out at the 30 Sep peak ("Definitely 50, do not do 23 - do some from Poruba too").
50 runs the three Hranečník cars (7521, 7523, 7524) with one seen 12 m bus; 40C (Poruba, radnice – Plesná, Globus) runs the two
Poruba cars (7518, 7519). The replaced buses are listed as not needed with the car that took their place. Needs are unchanged.
ODIS cards: buses seen on the ODIS live map (20 Oct 2026)
The Hranečník ODIS cards (383, 387, 440–442, 531, 534, 535) show the buses seen on the ODIS live map (mpvnet.cz) on Tue 20 Oct
2026, polled by the Havířov/Karviná/Orlová pipeline (one snapshot a minute 13:05–19:30, pipeline/havirov-karvina-orlova/data/raw/2026-10-20/live/).
scripts/01e_odis_live.py (run by hand once; it keeps the file when the raw folder is gone) writes the compact
data/raw/2026-10-20/odis_live.json {plate: [[first, last minute, line, trip]]}; config.odis.live points to it and to that
pipeline's fleet.json (plate → seznam-autobusu vid, joined to this sheet's register). 07_odis.py: the feed's trip number is the
JDF trip number, so each bus at the line's anchor gives its exact trip and goes to the Simutrans line whose slot that trip runs
(hand-line legs' out_lines/back_lines, ±3 min): 1TY6417 on 441's 15:32 is in the 442/441 circuit, 1TJ7099 on 534's 15:25 on 535.
Buses of a card's lines that match no slot fill the branch with the largest shortfall. 16:00 cards first, then 534 (14:40); one bus
on one card; surplus by the shared rule. The rest is designed from records, from buses not seen on the map that afternoon.
Result: 26 seen + 2 designed (387, 440) for a need of 28; no bus is on the Havířov/Karviná/Orlová sheet too (checked with session a4).
All lines at sad Boženy Němcové (owner, 25 Oct 2026: "similarly how we did the Hranečník, do the regional lines from Sad B.Němcové")
- Section after the Hranečník one ("All lines at sad Boženy Němcové", nav link "Sad B. Němcové"): a table of every bus line with a stop at Přívoz, sad Boženy Němcové (10 afternoon DPO lines linking to their cards, DPO 72 and 201 outside the afternoon, 8 ODIS lines), then a card per modelled ODIS line, then the ODIS lines no sheet models, with their reasons.
- Why not modelled here, as Hranečník was: Hranečník's ODIS lines were modelled on this sheet on 20 Oct, before the regional sheets existed, and those sheets link to them. Every ODIS line at sad B. Němcové was modelled, with its buses, on a regional sheet on 21–22 Oct: 281, 282, 290, 292 on Opavsko/Vítkovsko/Hlučínsko; 543, 553 on Havířovsko/Karvinsko/Orlovsko (they pass sad B. Němcové on their way to hlavní nádraží). 283 (two trips a day) and 331 (Mošnov airport shift line, Z-Group bus) are listed as not modelled on Opavsko and Novojičínsko. Lines shared between sheets are listed and linked, not repeated, and no bus is on two sheets. Moving the lines here instead would change three sheets and two in-game line sets; it is the owner's call.
01c_fetch_odis.py --hubsscans the national JDF for every line (DPO too) with a stop matching eachconfig.hubs[].matchintown, in the same pass as the Hranečník scan, intodata/raw/. No records or fleets are fetched for them./odis/hub_ .json 08_hubs.py(run.py, before 06_render) →derived/hubs.json. Each ODIS line's home is the first ofconfig.regional_sheets(build order) whose derivedschedule.jsonmodels it, else the first whoseconfig.unmodelledlists it. It reads the home sheet's Simutrans lines (typed values, real minutes, fit, slots without a real departure), its buses (roster.json: seen on the ODIS live map on 20 Oct, or designed) and its trips. The minute each leg leaves, passes or reaches the hub is its slot plus the median time from the leg's terminal to the hub on its own trips, shown against the real times in the model hours. Trams and trolleybuses at the hub come from the sister sheets' trips (config.hubs_other_modes: trolleybuses 101, 102, 103, 106, 108; no tram).- 06_render: the cards (dashed) show the home sheet's values and buses with a link; pak objects stay on the home sheet. Their buses are not in this sheet's totals, depot tables, line bar or coverage check. The DPO part of the page is byte-identical to v34. Rebuild after any regional sheet changes:
python scripts/08_hubs.py && python scripts/06_render.py. - Opavsko rows checked and fixed (25 Oct 2026, owner: "I want you to check"): the first version of this section showed that
281, 282, 290 and 292 had slots without real trips on the Opavsko sheet (281 Třebom :16 against real 15:01 / 15:56; 290 every
40 against about every 25; empty 292 and 282 slots). They were set by hand there with the bus schedule method
(
../odis-regional-opavsko/method-check-2026-10-25.md) and the cards here were rebuilt from that sheet. The cards label each typed value with its own real time (multi-slot rows such as 290's four slots).- The model's DPO/ODIS departures 0–1 min apart at sad B. Němcové (towards Důl Odra) mostly copy the real timetables, which are not coordinated either (292 15:33 / DPO 34 15:34; 282 16:54 / 34 16:54; 281 :18 / 32 :18).
Method check of 25 Oct 2026 (owner: "verify all sheets with this level of detail")
Every approximate DPO row and every short exact row was checked by hand against its real departures; the evidence is in
method-check-city-2026-10-25.md (it also covers the tram, trolleybus and Havířov/Karviná/Orlová sheets). No timetable
value and no bus changed.
- 21: both rows were the real timetable within 1 min (Hranečník every 20 :16, Horní Datyně :19) but marked approximate by
the rate-matching fallback.
schedule_overrides['21']keeps the same values with alabel(new, optional:05_schedulepasses it on and06_renderprints it instead of "set by the owner … spaced from the other branch"). - To check list: every approximate DPO row is now listed (5 → 18), not only those with more slots than real departures; the card note says how far the slots are off, and the table shows each row's own window. The Hranečník ODIS cards keep their own list.
config.check_alt(new, render only): a slower option found by hand, shown on the To check list and the card in place of the computedalt. 38: every 40 (Hranečník :24, Bartovice :25; the line's own every 40 from 13:00 to 17:00), 2 buses.- 41's ORIGIN flag in
audit/city_systemic.pyis a false alarm: its Poliklinika entries areloop: falsethrough stops. - The published
odis.json/hubs.jsonwere kept for this render, and04_roster.pywas run withPYTHONHASHSEED=4:07_odis.pyand04_roster.pywrite hand-set lines' waits (five ODIS cards, 57) in a hash-dependent order (lib.slot_neediterates a set), and08_hubs.pywould pick up a newer Opavsko 290 card text. To reproduce the published sheet:run.py, copyodis.jsonandhubs.jsonfrom../_snapshots/2026-10-25/derived/ostrava-bus/intodata/derived/, thenPYTHONHASHSEED=4 python 04_roster.py && python 06_render.py. Lasting fix (shared lib, not done): iterate a sorted list inslot_need.