SYMCA bus punctuality — Power BI project
========================================

Open "SYMCA Punctuality.pbip" in Power BI Desktop. It will load the model,
connect to the four feeds and refresh. Choose Anonymous if it asks about
credentials.


WHAT IS IN IT
-------------
Six tables, all ordinary queries. Four read the feeds; Date and
Operators derive from the first of those:

  Punctuality   one row per day, operator, line and route -- the fact table
  Periods       SYMCA's four-weekly financial periods, by operator
  DailyTotals   network totals by day
  Rebadges      one row per time the feed moved a bus to its next journey
  Date          the range Punctuality actually holds, so it cannot offer
                a day that was never recorded
  Operators     exactly the operators with data, so one slicer covers every
                table

22 measures, each with a description and in a display folder. Hover any of
them in the field list to see what it means before you use it.

Three parameters, under Transform data > Manage parameters:

  AreaCode   "370" -- South Yorkshire. Required: the feed rejects an
             unscoped request above 400,000 rows.
  FromDate   4 August 2026, when recording began. Nothing exists before it
             and no backfill is possible.
  ToDate     today. The query then drops today itself, a part day.


THE ONE MEASURE TO GET RIGHT
----------------------------
Use "OTP %", not "OTP % including terminus".

A bus arriving early at the end of its journey is not a punctuality failure:
the last leg of a timetable carries slack, an interchange is where a bus is
meant to wait, and nobody misses a bus that got there early. Counted in, it
runs early on roughly three arrivals in ten against fewer than three in a
hundred along the route -- a different measurement, not different driving --
and it holds the headline about two points low.

"OTP % including terminus" is there only to reconcile against figures
produced elsewhere. Do not report it.


TWO EARLIER BUILDS WOULD NOT OPEN
---------------------------------
The first had a measure called "Observations" against a column called
"observations" on the same table. Names within a table are case-insensitive to
the model engine, so it refused the file. The measure is now "Stops recorded".

The second built Date and Operators as DAX calculated tables, whose columns
have names the engine infers from the expression -- and a relationship
pointing at one of those was rejected: "relationship 'Operators-Punctuality'
uses an invalid column ID". Both are now ordinary queries with ordinary
columns, deriving from the Punctuality query rather than fetching anything
again. The construct that broke is gone rather than adjusted.

The generator now refuses to write a model whose measures collide with
columns, whose relationships or measures name something absent, whose sort
columns are missing, or whose declared columns are not produced by their own
query.


THE PAGES ARE BUILT, BUT UNTESTED
---------------------------------
Five pages, nineteen visuals. Every one was checked against the model -- no
visual names a measure or column that does not exist, every field referenced
is projected, every query alias is declared and used.

What could not be checked is whether Power BI Desktop likes the layout,
because there is no Power BI where this was generated. If a visual comes up
blank or errors, delete it and drag the fields back: the measure behind it is
right, and the field list tells you which to use.

If the project refuses to open at all, that is the report and not the model.
Delete "SYMCA Punctuality.Report/report.json", reopen, and Power BI will
rebuild an empty report over the same model -- you lose the layout and keep
everything that took the work.

What is on each page:

  Overview
    Four cards, on-time performance by day, and two bars by operator --
    performance worst first, and volume so a small operator's figure is not
    read as if it carried the same weight.

  By operator and period
    Periods down, operators across, using "Period OTP % (complete only)".
    Part periods are excluded by the measure rather than by a report filter,
    so a visual cannot show one by having a filter forgotten. The table
    underneath gives days held against period length, so you can see which
    periods are still filling.

  By service
    Every service, busiest first, with an operator slicer.

  Start time adherence
    Started early against started late by operator, early first. An early
    departure is the worse failure: someone who arrives on time for a bus
    that has already gone has missed it, where a late bus is only late.

  Record quality
    "Unfinished, nearly home" is the only one of the four charted as a
    fault -- a bus past halfway that lost its last stops. The other three
    cards are context: "on arrival" is about seven cases in eight and is
    normal, and "barely begun" is a tracking artefact rather than an
    operator problem.


WHAT THE NUMBERS DO AND DO NOT SAY
----------------------------------
Put these on the report somewhere, or they will be lost:

  * Recording began 4 August 2026. No earlier data exists.
  * Figures count stops where a bus was tracked. A journey nobody tracked
    and a journey that did not run are indistinguishable here, so this is a
    ceiling on performance, not a count of cancellations.
  * The on-time window is 1 minute early to 5 minutes 59 late, the national
    standard. It is the same window Vix uses -- checked against their own
    export, so a difference between the two is not a difference of
    definition.
  * Today is excluded everywhere. It is a part day.
  * DailyTotals[sta_pct] is empty before 7 August 2026, when start time
    adherence was not yet recorded. Leave it null; coercing it to zero draws
    a collapse that never happened.


COLOURS
-------
SYMCA.json in this folder is the report theme: the orange the rest of the
platform uses, dark grey for everything ordinary, red kept for a fault.

Apply it with View > Themes > Browse for themes, and pick SYMCA.json.

It is a separate file rather than baked into the project on purpose. Embedding
a custom theme means a resource package inside report.json, and a report.json
this project could not open was already three of the four failures getting
here. A one-click import cannot break the file you now have working.

Out of the box the report uses Power BI's own CY24SU10, which is the teal-blue
default -- that is what you are seeing before the theme is applied.


REFRESH
-------
After 05:00 UK. The nightly rollup runs at 02:20 and the exports finish by
04:10.

Leave Rebadges out of any scheduled refresh: it takes about 14 seconds and
changes only as buses run.


IF THE FEEDS ARE BLOCKED
------------------------
The data/ folder holds the same four files as they were on 14 September 2026.
If Web.Contents cannot reach the site from your network, point each query at
the local file instead: in the Power Query editor, replace

    Csv.Document(Web.Contents("https://performance.networkpulse.info", ...

with

    Csv.Document(File.Contents("C:\path\to\data\punctuality_daily.csv"), ...

and leave the rest of each query alone. Nothing else changes.
