Skip to content
Updated Sep 22, 2026 by Pablo Coufal · Owner: analysisactivefeature Edit on GitHub

Consumption Calculation ​

Business Context ​

Business-Level Definition ​

An energy manager does not work with meter states; they work with consumption. A meter state is the number on the display, and on its own it answers nothing. Consumption is the difference between two states, corrected by the meter's coefficients, spread across the period between them, and expressed in a unit people compare in.

This feature is the step that turns readings into consumption. It is the only place where that conversion happens, so that a figure shown in a chart, checked against an invoice, compared with last year or used to calculate emissions is the same figure everywhere, derived the same way.

Requirements Definition ​

Everything the product promises an energy manager rests on this step: without it there is no chart, no report, no invoice check, no benchmark, no saving and no emission figure — only raw numbers nobody can act on.

Three things make it more than a subtraction. A metering point is often measured more than one way — read remotely, read by hand, and billed — and the three disagree; each way has to be kept whole and comparable rather than merged into one lossy number. Gas is measured in cubic metres but managed in kilowatt-hours, and the conversion depends on coefficients that change over time, so the same volume converts differently in January and in June. And corrections are normal: a mistyped reading or a corrected coefficient must change history, quietly and completely, or two people reading the same screen a week apart will see different numbers and trust neither.

Technical Context ​

User Stories / Use Cases ​

  • As an energy manager, I want the consumption of a metering point for a period I choose, so I can see what it used.
  • As an energy manager, I want a gauge that is read both remotely and by hand to give me either series, so I can see whether they agree.
  • As an energy manager, I want gas shown in kilowatt-hours, converted with the coefficients that were valid at the time, so a cold January is not distorted by today's calorific value.
  • As an energy manager, I want a corrected reading or coefficient to correct the past as well, so historic comparisons stay true.
  • As an energy manager, I want a period with no measurement to read as unmeasured rather than as zero, so I do not mistake a gap for a saving.
  • As a facility manager, I want consumption of a whole building, with own generation counted the way the meters are configured, so I can see what the building really used.
  • As a facility manager, I want a building total to use the formula that was in force in the period I am looking at, not the one in force today.

UI/UX Design ​

N/A — the feature has no interface of its own. Its results are displayed by Chart Engine, Invoice Management and Building Management, each of which owns its own screens.

Functional Requirements ​

Series identity

  • Compute and store consumption separately per gauge, measured quantity, flow direction, tariff band and source.
  • Never form a difference across two sources: a manual reading is differenced against the previous manual reading, a remote value against the previous remote value.
  • Keep every source's series; a later source never overwrites an earlier one.
  • Reserve the calculated source for the output of virtual gauges.

From reading to consumption

  • For a cumulative series, compute the difference between two consecutive states and multiply it by the volume coefficient valid for the period.
  • Select the volume coefficient by the source of the series being computed: a gauge keeps one validity history per reading source, so the manual series is corrected by the manual history and the remote series by the remote one. The two are never substituted for each other, even where only one of them has a value.
  • Apply no correction where the gauge has no coefficient for that source and period: a volume coefficient corrects a meter that needs correcting, and its absence means none is needed. This is not the same as a missing calorific value, which leaves energy uncomputed.
  • Compute the invoiced quantity series without a volume coefficient: an invoiced quantity is already a quantity, in the unit the supplier billed.
  • Treat the cumulative register of one source and tariff as a piecewise-linear function of time between consecutive readings, and take each 15-minute value as the difference of the register at the interval's boundaries; readings need not be aligned to the quarter hour, and a reading finer than a quarter hour contributes only through the boundaries. Two manual readings therefore spread evenly by time; an aligned remote value is stored as it is; an hourly remote reading yields four equal quarters. The quarter hours holding the first and the last reading of a series carry their partial share, so a series always sums to the register difference.
  • Distribute a delta or average reading over the intervals between the previous reading of the same source and this one, by time overlap.
  • Spread a value across intervals by cumulative rounding — each interval's value is the difference of the rounded cumulative totals at its boundaries (6 decimals for quantities, 2 for money) — so that the series sums exactly to the value spread and any contiguous run of intervals sums to the rounded share.
  • Convert a volume quantity to energy as volume × volume coefficient × calorific value, using the values valid in the period being computed.
  • Resolve the calorific value in the order gauge, client, system; use no implicit fallback beyond the system value.
  • Convert a power reading to energy as power × interval length, keeping the source of the input reading.
  • Prefer directly measured energy over energy derived from power where the same source supplies both for the same interval.
  • Store gas consumption in kilowatt-hours and derive the volume figure for display from the coefficients valid in the period.
  • Write no row for an interval with no input.
  • Stop the calculation at a meter replacement: the closing reading ends the old period, the opening reading starts the new one, and no difference is formed across the boundary.

Virtual gauges and building totals

  • Evaluate a virtual gauge's formula for each 15-minute interval, taking each member's effective consumption as the input, and store the result as a series of its own with source calculated.
  • Apply each member's sign as its formula term states: included values add, subtracted values subtract, excluded and report-only members do not enter the result.
  • Evaluate with the formula version valid for the interval being computed, not the version in force today.
  • Store one total per building and medium, evaluated the way any other virtual gauge is.
  • Compute a building total from its members' series, never from the readings behind them: a total is one level above its members, not a second derivation of the same inputs.
  • Write no value for an interval in which any member has none, so a total is never completed from part of its members.
  • Evaluate a virtual gauge only once every member it depends on is current, and treat a formula that would close a dependency cycle as invalid.
  • Keep calculated outside the source priority: it is the output of a formula, not a competing account of the same measurement, and a difference of two remote readings stays remote.

Corrections

  • Recompute the affected series when a reading is added, changed or removed, limited to the gauge and the intervals the change touches.
  • Recompute the affected series from the validity date forward when a volume coefficient or a calorific value changes.
  • Publish a recalculation-in-progress state per gauge and period, so a surface displaying an affected figure can mark it provisional; it is returned with the period totals that cover it.
  • Make a recalculation idempotent: running it twice over the same inputs produces the same rows.

Internationalization & Localization ​

N/A — the feature produces numbers and no user-facing text. Unit names, number formats and the choice of display unit belong to the surfaces that present the result.

Non-Functional Requirements ​

  • Reproducibility. The whole series for a gauge can be rebuilt from its inputs at any time, and rebuilding it changes nothing else.
  • Reliability. A failed recalculation leaves the previous values in place; it never leaves a series half-written.
  • Scalability. Write volume is one row per gauge, quantity, source and quarter hour, and the design assumes both the number of gauges and the length of history grow.

Performance ​

PathExpectationHow it is met
Manual reading savedConsumption visible immediatelyComputed on the write path for the intervals the reading closes
Remote batch receivedConsumption present within 30 seconds of receiptComputed per batch, asynchronously; the lag is measured
Coefficient changeRecalculation proportional to the affected period, not the whole historyBounded by gauge and by validity date

Transactional Operations ​

The calculation writes one series for one gauge and one period atomically: either every interval in the recomputed window is present with its new value, or none of it changed. A recalculation request raised by a reading or coefficient change is enqueued in the same transaction as the change itself, so a committed change can never lose its recalculation, and a rolled-back one never schedules work for a value that was not stored. Concurrency on the same gauge and window is made safe by the unique index that carries the series identity: a second writer upserts onto the same rows rather than duplicating them.

TriggerOwner of the triggerWhat this feature does
Manual reading created, edited or deletedReading ManagementRecompute the manual series for the affected intervals
Remote batch ingestedRemote Connection ManagementCompute the remote series for the intervals in the batch
Invoice saved, corrected or deletedCost CalculationRecompute the invoiced quantity series for the billing period
Volume coefficient added or voidedGauge ManagementRecompute the series of that reading source only, from the validity date forward
Calorific value changedClient ManagementRecompute the affected energy series from the validity date forward
Formula version added or correctedGauge ManagementRecompute that virtual gauge's series from the version's validity date forward
Gauge output mode, direction or purpose changedGauge ManagementRecompute once the resulting formula version is accepted
Gauge moved to another buildingBuilding ManagementRecompute the totals of both buildings
Source priority changedGauge Management, Client ManagementChange no stored series; recompute the virtual series that consumed that gauge
Meter replacedMeter Replacement FlowClose the old period and start the new one; form no difference across the boundary
Series stored or recomputedthis featureHand the affected window to Consumption Aggregation & Effective Consumption

Recalculation and propagation ​

Every stored series is derived from something else, so a change anywhere has to reach everything built on it. The unit of invalidation is one gauge and one window of intervals — never a single value, never a whole gauge — and an invalidation is written in the same transaction as the change that caused it, so a committed change cannot lose its recalculation. Each level emits its own invalidation as it finishes, which is how a corrected reading reaches a building total without either step knowing about the other.

ChangeWhat it invalidatesHow far it travels
Reading added, changed or removedthat gauge's series of that source, for the touched intervalsvirtual gauges that have it as a member, their totals, the summaries over both
Remote batch receivedthe same, bounded by the batch windowas above
Invoice saved, corrected or deletedthe invoiced series for the billing periodas above
Volume coefficient added or voidedthat gauge's series of that reading source, from the validity dateas above
Calorific value added, corrected or withdrawnthe energy series of the gauges that resolve to it, from the validity dateas above
Source priority changednothing stored — only the effective view, and only in intervals where more than one source has a valuethe virtual series that consumed that gauge
Formula version added or correctedthat virtual gauge's series, from the version's validity dateits dependents and their summaries
Gauge output mode, direction or purpose changedthe building total's formula, through a new versionas for a formula version
Gauge moved to another buildingthe totals of both buildingsas for a formula version
Reading retention thinningnothing—

Four properties keep the cascade finite. Recomputation is an idempotent upsert over the window, so running it twice changes nothing. Invalidations for the same gauge and window are coalesced, so a bulk import produces one recomputation per gauge rather than one per reading. Evaluation is topological — a virtual gauge is evaluated once every member it depends on is current — which is why a cyclic formula is invalid. And atomicity is per level, not across the chain: a total may briefly be older than its members, which is what the recalculation-in-progress state exists to show, and it is published for dependents as well as for the gauge that changed.

A failed recomputation keeps the previous values and leaves its gauge and window marked as pending, so a stale figure is never presented as current and the work is retried rather than lost.

Nothing above the building is refreshed, because nothing above the building is stored: a figure for a set of buildings is summed when it is read — see Consumption Aggregation & Effective Consumption.

Diagrams & Models ​

Computing one series from one new manual reading:

sequenceDiagram
    actor U as Energy manager
    participant R as Reading Management
    participant C as Consumption Calculation
    participant G as Gauge coefficients
    participant S as Consumption series
    U->>R: Save reading (state, time)
    R->>C: Reading stored
    C->>C: Find previous reading of the same series
    C->>G: Coefficients for this source, valid in the period
    G-->>C: Volume coefficient of that source, calorific value
    C->>C: Difference, convert, spread over intervals
    C->>S: Upsert the interval values
    C-->>R: Series updated

Choosing what to compute for one input value:

flowchart TD
    A[Input value for a gauge] --> B{Reading type}
    B -->|power| C[Energy equals power times interval length]
    B -->|energy| D[Use the value as energy]
    B -->|volume| E[Volume times the coefficient of this source<br/>times the calorific value]
    C --> F{Directly measured energy<br/>from the same source?}
    F -->|yes| G[Discard the power-derived value]
    F -->|no| H[Keep the derived energy]
    D --> H
    E --> H
    H --> I{Cumulative series?}
    I -->|yes| J[Difference against the previous value of the same source]
    I -->|no| K[Use the value for its own interval]
    J --> L[Spread evenly over the intervals between the two readings]
    K --> M[Store one value per interval and source]
    L --> M

What a change reaches:

flowchart LR
    A[Reading or invoice] --> B[Series of that source]
    B --> C[Effective consumption<br/>read-time]
    C --> D[Virtual gauge series<br/>source calculated]
    D --> E[Building total<br/>a virtual gauge]
    E --> F[Hourly, daily, monthly,<br/>yearly summaries]
    F --> G[Read-time sums over<br/>a set of gauges]
    H[Coefficient or<br/>calorific value] --> B
    I[Source priority] --> C
    J[Formula version] --> D

API Analysis ​

N/A — the feature has no HTTP surface of its own. It is driven by the events in Processes & Related Systems / Components above. Its series are served as charts by Chart Engine, as invoice figures by Invoice Management, and as period totals by Consumption Aggregation & Effective Consumption, where the recalculation state below is also exposed.

Domain Model (ER diagram) & Data Attribute Table ​

erDiagram
    GAUGE ||--o{ READING : "is read as"
    GAUGE ||--o{ VOLUME_COEFFICIENT : "is corrected by"
    GAUGE ||--o{ CONSUMPTION : "consumes"
    GAUGE ||--o{ CALORIFIC_VALUE : "may state"
    CLIENT ||--o{ CALORIFIC_VALUE : "states"
    READING ||--o{ CONSUMPTION : "is differenced into"
    CALORIFIC_VALUE ||--o{ CONSUMPTION : "converts"
    INVOICE ||--o{ CONSUMPTION : "bills"
  • consumption — created by this feature; one value per gauge, interval and source, including the series of virtual gauges.

A virtual gauge's series is the virtual gauge's consumption. It is not the building's calculated consumption held in buildingCalculatedConsumption, which is a separately recorded reference figure from an audit or a user snapshot. The two names are close and the things are unrelated.

  • reading — the input states and interval values.
  • gauge — the metering point, its medium, its unit and its source priority.
  • volumeCoefficient — the meter correction valid in a period, per reading source.
  • calorificValue — the gauge and client scopes of the calorific value the conversion uses; the platform scope is defaultCalorificValue.
  • invoice — the billed quantity that forms the invoiced series.

Data ​

No configuration data of its own. The calculation needs a volume coefficient only where the meter needs correcting, and then one per reading source the gauge is read by; and a calorific value only where a volume quantity is converted to energy. Both are maintained as gauge and client data, not as configuration of this feature.

A representative stored result for one gauge, one quarter hour, read both ways:

sql
INSERT INTO consumption
    (gauge_id, slot_at, kind, type, direction, tariff, source, value, unit)
VALUES
    ('018f…', '2026-01-15T09:15:00Z', 'consumption', 'energy', 'import', 'high', 'remote', 12.480000, 'kWh'),
    ('018f…', '2026-01-15T09:15:00Z', 'consumption', 'energy', 'import', 'high', 'manual', 12.500000, 'kWh');

Test Data ​

Use the project's Test Data location named in the overlay — the demo gauge seed is the fixture that produces gauges with readings. Search it for the gauge medium and reading source combinations this feature branches on: a gas gauge with a volume coefficient, an electricity gauge with tariff bands, and a gauge carrying both manual and remote readings over the same period with a different coefficient on each of its two histories — that last one is what proves the histories are not substituted for each other.

Worked cases with inputs, the calculation step by step, the resulting consumption rows and the check sums, one per Confluence example and per reading mode: calculation-cases.md. The rules that document settled (R-1 to R-11) are listed at its end with the artefacts they change.

Logging ​

The project has no shared logging-conventions document yet, so this feature's events are stated in full here.

  • .info — series computed for a gauge and window: gauge, quantity, source, interval count, trigger, requestId.
  • .info — recalculation started and finished: gauge, window, reason, duration.
  • .warn — an input could not be converted: gauge, interval, which coefficient or calorific value was missing.
  • .warn — a difference was discarded because it crossed a meter replacement boundary.
  • .error — a recalculation failed and the previous values were kept: gauge, window, cause.

Monitoring ​

Two feature-level needs: the lag between a remote batch arriving and its consumption being present, measured against the 30-second expectation above; and the count of recalculations that ended in error, which is the signal that a series is silently stale.

Caching ​

None. The series is read through the aggregation layer, which is where the cost of repeated reads is answered; caching a second copy here would add a way for two readers to disagree.

Backward Compatibility and Migration ​

Consumption is not migrated. It is recomputed from migrated readings and invoices, so the legacy daily figures are reproduced by the same rules that govern new data rather than carried across as numbers nobody can re-derive. Two consequences are expected and accepted: a legacy period whose readings are coarser than a quarter hour produces an evenly spread series, and a legacy figure that relied on a coefficient nobody recorded is recomputed without it and will differ.

Migrated readings that carry a date but no time are stored at midnight for clients whose legacy setting said so, which keeps a day's consumption on the day it was billed.

N/A — no contract, licence or regulation applies to this step. The figures it produces are not a billing document: the legal weight sits on the invoice, which Cost Calculation covers. Metrological certification of the meters themselves is an obligation of whoever operates them, discharged outside this product, and the feature neither asserts nor checks it.

Cybersecurity Considerations ​

Consumption is tenant business data and carries the tenant on every row. Because the table is a hypertable, database row-level security is not available, and isolation is enforced by the application-level guard that requires the tenant predicate on every statement — the same mechanism, and the same failure mode to watch, as the readings it derives from.

Data Privacy. No personal data. Consumption describes a metering point, not a person; the actor columns name the calculation that wrote the row, not a user.

Risk Assessment ​

Business Risks ​

A wrong figure here is wrong everywhere downstream and is not visibly wrong: a coefficient taken from the wrong reading source, or a conversion missing its calorific value, shifts consumption by a plausible-looking margin rather than producing an obvious error. The mitigation is that the series is reproducible — a suspected figure can be recomputed from its inputs and compared — that every series records which source it came from, and that a conversion without its calorific value is logged rather than silently defaulted.

The second risk is delay. If recalculation falls behind, screens show stale figures that look current; this is why the recalculation-in-progress state is published and the lag is monitored.

Technical Risks ​

RiskConsequenceMitigation
Recalculation storm after a bulk correctionBacklog delays all series, not just the corrected onesRecalculation is bounded by gauge and period, and enqueued rather than run inline
A correction arrives while the same window is being recomputedTwo writers on the same rowsSeries identity is a unique index; writes are upserts
Missing calorific value for a volume-to-energy conversionEnergy cannot be computed for the periodNo implicit default; the interval carries no row and the gap is logged and visible
A gauge's two coefficient histories are conflatedOne series is corrected by the other's factor, shifting it silentlySelection is keyed by the series' reading source; a source with no coefficient is left uncorrected rather than borrowing the other's
Input retention removes the readings behind old consumptionOld series can no longer be rebuilt from sourceRetention applies to inputs only; the computed series and its aggregates are kept indefinitely
A formula closes a dependency cycleEvaluation cannot terminateA cyclic formula is invalid and is rejected where formulas are saved
A member has no value for an intervalA total silently counts part of its membersNo total value is written for that interval at all
Cascade — this feature stopsCharts, reports, invoice checks, benchmarking, savings and emissions all stop gaining new dataFailures leave previous values intact, so existing history stays readable

Auditing, Reporting & Measurement ​

Consumption rows are not themselves audited: they are derived, and the audit trail that matters is the one on their inputs — the reading action log for readings, the invoice trail for invoices, and the append-only coefficient history for coefficients. Each row names the calculation that wrote it and when it was last recomputed, which is enough to trace a figure back to the change that produced it.

The feature's own measurement is the pair named under Monitoring: recalculation lag, and recalculation failures.