Analysis: read a rarely-read meter as coarse, not absent; newest rows first
ci / build-test (push) Successful in 2m41s
ci / build-test (push) Successful in 2m41s
Three things a reported Heizoel page got wrong at once. Its tank is dipped a few times a year and its burner read every few months, which is exactly the shape the coverage rules had not been walked through. "No data" for data that exists. A tank books nothing until the next dipstick closes the interval, so the stretch after the last dipstick is covered by no run at all, and a bucket no run covers was reported missing. The burner, whose run reaches into the window, said "only coarser data" -- the honest answer -- so one card claimed there was nothing while the coverage panel beside it listed years of data. A bucket that no run covers, no gap overlaps and no opening balance explains now reports the meter's resolution when its preceding coverage is within one interval of its own class: it is not silent, it is read rarely. A meter that does book its own buckets and stops -- a dead hourly source, a sheet asked about a later month -- still reads missing. Auto answering twelve months with one bar. Coarse only means "longer than a month", so a dipstick taken each autumn straddles a New Year as surely as a month start: coarsening the chart to years bought nothing and cost every point. The planning resolution now caps coarse at month when a run crosses a local year edge, and a series that cannot resolve the natural size no longer coarsens the whole chart -- it is drawn at that size with its buckets marked, which the chart and table already explain. A page contradicting itself. The comparison line above the ranking was fed the leading measure's matched coverage but worded as if it spoke for the page, directly above a burner row that did compare. It now names the figure it is about. Alongside: the "largest changes" ranking no longer drops a meter whose change is not comparable. It ranks what can be ranked, then lists the rest with their values and the reason -- the tank had been vanishing from its own energy type. And every dated table now reads newest first, as lists are read; charts stay chronological left to right, and the CSV export stays ascending for spreadsheets. A-41 to A-43 in the note record the three rules.
This commit is contained in:
@@ -9,6 +9,7 @@ The note was kept current through Phase 5:
|
||||
- §12: amendments from the acceptance review.
|
||||
- §13: decisions recorded with the final documentation.
|
||||
- §14: amendments from the performance measurement.
|
||||
- §15: amendments from the first reports of a live instance.
|
||||
- §9 and §10: extended with what the implementation measured and changed.
|
||||
|
||||
The outcome is in [ANALYSIS_REPORT.md](ANALYSIS_REPORT.md), the user-facing changes in
|
||||
@@ -738,3 +739,82 @@ value changes; only what a request reads does.
|
||||
and passes that snapshot to all three (`CostReader.ReadAsync(db, catalog, …)`), so every figure answers from the
|
||||
same snapshot and the load is not repeated. The cards, the change table and the composition are derived from those
|
||||
results and are never priced again. `OverviewReadBudgetTests` asserts the statement count of one load.
|
||||
|
||||
## 15. Amendments from the first reports of a live instance
|
||||
|
||||
Three findings from one real instance: imported history only, a Heizöl type whose tank is dipped with a stick
|
||||
a few times a year and whose burner hours are noted every few months. Two of them are about what a figure says
|
||||
when the measurement is coarser than the question, one is about the order and the completeness of the lists
|
||||
beside it. No figure, golden reconciliation number or seeded bill total changes.
|
||||
|
||||
- **A-41 A meter read a few times a year (D-05, D-13, D-14, D-07, A-02, A-03, A-06, A-24).** An instance with imported
|
||||
history only, a heating-oil tank dipped with a stick once a year and a burner whose hours are noted every few months,
|
||||
showed that three separate rules break down when a meter's *measurement* is coarser than the question asked of it.
|
||||
The pages it reported are the energy type page, the Overview, the Analysis page and Tanks & consumables; all four read
|
||||
the two readers, so all four are fixed by the three changes below.
|
||||
- **A bucket waiting for a measurement is unresolved, not empty.** A dipstick interval books nothing until the next
|
||||
dipstick closes it, so "last 12 months" opening eleven days after the last one is covered by no run at all. The
|
||||
coverage evaluator called that *missing* — "No data for this period" — beside a coverage panel listing four years of
|
||||
it, and beside a burner that read "Only coarser data" for the same period. It is the same situation A-04 already
|
||||
names for a bucket that ends at now: what has accrued since the last reading is not yet known, and that is no
|
||||
shortfall. So a bucket that no run covers, that no *gap* run overlaps (a known hole keeps its own reason) and that
|
||||
holds no opening balance is `Unresolved` with `CoarseResolution` when the meter's last coverage before it cannot
|
||||
place a bucket of that size anyway, and lies within one interval of its own class (`LimitOf`, unbounded for data
|
||||
coarser than a month). It carries that resolution, so the card, the coverage panel, the chart's "only coarser data"
|
||||
and the table agree; it carries no number, because there is none. A meter that books its own buckets as it goes —
|
||||
an hourly source gone silent, a monthly sheet asked about a later month — is unchanged: a bucket it does not cover
|
||||
really has no data. `CoverageEvaluator.AwaitsMeasurement`, pinned by `CoverageEvaluatorTests`.
|
||||
- **Auto coarsens for the data only as far as the data gains by it.** `ResolutionClass.Coarse` means no more than
|
||||
"longer than a local month", and `MinimumSizeFor` maps it to years. Auto therefore answered a 12-month request with
|
||||
one bar per year — which resolved exactly nothing, because a dipstick taken every autumn straddles a New Year as
|
||||
surely as it straddles every month start, so the yearly buckets were unresolved too. The floor now comes from
|
||||
`ResolutionClassifier.PlanningResolution(runs, zone)`: one decision for the whole chart (a chart has one bucket
|
||||
size, D-05), the coarsest class among the plotted runs, with divided runs counted as monthly (A-03) and with the
|
||||
class capped at `Month` as soon as any coarse run in the range crosses a local year edge. A meter read on the
|
||||
quarter, whose intervals all lie inside one year, still charts in years — years do hold each of those whole
|
||||
(`CostReviewFixTests`). The 400-point limit and explicit sizes are untouched, and `MinimumSizeFor` keeps its
|
||||
meaning for drill-downs (D-51) and for the coarser interval the empty chart offers (A-28).
|
||||
- **A comparison line names the figure it is about.** Matched coverage (D-07) belongs to one series: on one page the
|
||||
tank's use shared no covered day with the year before while the burner beside it compared over October to May. The
|
||||
page stated the *use* measure's verdict — "Not comparable: the periods share no covered days" — directly above a
|
||||
table that showed the burner's matched comparison, and beside a runtime card showing its change. `ComparisonSummary`
|
||||
takes a `Subject`, and the energy type's Overview and History, Solar and the Overview's cost line pass the figure
|
||||
the line speaks of whenever they show more than one. Both statements are then true. The energy type's coverage
|
||||
panel also lists a measure's own availability whenever it differs from the scope's, which is what made "no data"
|
||||
read as a contradiction in the first place.
|
||||
- **A-42 Every dated list reads newest first.**
|
||||
- A table is read from the top, so its first row is the period the reader is in. `AnalysisTableModel.Build` turns the
|
||||
plan round once, after the rows are built and paired with the comparison by index, and puts the **total row above**
|
||||
them — it sums what follows it. Every page that renders `AnalysisTable` inherits this: the meter's Analysis tab, the
|
||||
energy type's History tab, `/trends`, Solar, Tanks & consumables and the Overview's history table. The plan itself
|
||||
is never reordered, so nothing that indexes it (drill-downs, comparison pairs, chart series) has to know.
|
||||
- The same rule applies to the other dated lists that were still ascending: a virtual meter's calculation preview
|
||||
(and its capped warnings), the tariffs of a meter's Tariffs tab and of `/admin/tariffs` (newest validity first
|
||||
inside each component, so the price in force is at the top), and the tariff changes among the meter page's
|
||||
contextual markers. The markers are now **one** list: events and price changes interleave by local day
|
||||
(`MeterMarkerList`), because two lists one after the other made the dates run down, jump back up and run down again.
|
||||
- Already newest first, and left alone: the record tabs (readings, normalized rows, events — D-50 keysets), the
|
||||
deliveries of a tank, and the import batches.
|
||||
- **The chart stays chronological** — it is read left to right — and so does the **CSV export** (D-55): a file is
|
||||
sorted, charted and differenced by whatever opens it, and every one of those expects time to run forwards.
|
||||
- Lists that are not dated keep their own order: meters by energy type and name, the flow table by node name, a
|
||||
virtual meter's source contributions by the formula, connectors and sources by name and priority.
|
||||
- **A-43 A meter with no comparable change is listed, not dropped.**
|
||||
- "Largest changes by meter" ranked the meters whose change could be measured over the days both periods cover (D-07)
|
||||
and showed nothing at all about the others. On the reported Heizöl page that left only the burner: the tank, read by
|
||||
dipstick a few times a year, has no matched coverage against last year and so vanished although it has data.
|
||||
- `MeterChanges.Of` now returns three things: the ranked changes (unchanged rule), **every other meter that has data
|
||||
in either period**, and how many the caps left over. A row of the second list carries the meter's two totals as the
|
||||
reader read them, so the page words each of them with the same status text every other figure gets (`FigureText`,
|
||||
`DisplayNames`): "No data", "Only coarser data", "Being prepared". Only one case needs more than that — both totals
|
||||
known yet no shared day — and it is said with the page's existing sentence, `Comparison_NotComparable`.
|
||||
- A meter with nothing on either side stays out: repeating "no data" twice is not information.
|
||||
- The two lists are capped separately, and the unranked one is ordered with the meters that have no change at all in
|
||||
front, so a long ranking can never crowd out the very rows this fix is about. What neither list holds is counted and
|
||||
said out loud ("Showing 4 of 10 meters"), never silently dropped.
|
||||
- The caption "A change is measured over the dates both periods cover" now stands above the ranked rows only.
|
||||
- **Checked and left as they are:** the Overview's "What changed" (`DashboardService.LineRows`/`CategoryRows`) already
|
||||
ranks rows without a comparable change last instead of dropping them; it drops only rows with no *money* on either
|
||||
side, and those are exactly the ones the composition panel lists with their price coverage and the attention list
|
||||
offers a tariff for (D-42, D-53). The attention list caps with a "show all", the meter list and the flow table drop
|
||||
nothing.
|
||||
|
||||
Reference in New Issue
Block a user