Appearance
Cost Calculation
Business Context
Business-Level Definition
An invoice is a single amount for a period. A cost is that amount placed in time, so it can be set against the consumption that caused it, against the same month last year, and against the other buildings in the portfolio.
This feature does that placement. It spreads each invoiced amount across the period it covers, keeps the amount with and without VAT, and defines what the cost of a whole building is — which is a question about who pays, not about who consumes.
Requirements Definition
Energy managers are judged on money, not on kilowatt-hours. Until an invoice becomes a series in time, the product can show what a building used but not what it cost, cannot compare a mild month against an expensive one, and cannot answer the question every budget meeting starts with: where did the money go, and is it more than last year.
Two rules make this harder than dividing by days. The amount is exact — a cost series that does not sum back to the invoice, to the last hundredth, is an accounting discrepancy, and one nobody will forgive. And a building's cost is not the sum of everything metered in it: a tenant on their own contract consumes inside the building but is billed by someone else, a sub-meter measures energy already counted by the meter above it, and energy sold to the grid is income, not cost.
Technical Context
User Stories / Use Cases
- As an energy manager, I want an invoice to appear as cost in the months it covers, so I can compare cost with consumption.
- As an energy manager, I want to enter one amount and have the other completed, so I do not retype what the system can derive.
- As an energy manager, I want the rate used to stay with the invoice, so last year's figure does not change when the law does.
- As a manager, I want the cost of a building to count only what the operator actually pays, so the number matches the budget.
- As an accountant, I want the monthly costs of an invoice to add back to the invoice exactly, so the report reconciles.
UI/UX Design
N/A — the feature has no interface of its own. The invoice form, the invoice list and the billing screen belong to Invoice Management; cost series are displayed by Chart Engine.
Functional Requirements
Completing the invoice amounts
- Accept one amount from the user, with an explicit statement of whether it includes VAT, and derive the other.
- Determine the rate from the catalogue by the invoice's medium and the first day of the invoiced period.
- Record the rate used on the invoice, so the derivation stays reproducible after the catalogue changes.
- Require both amounts from the user where no rate is in force for that medium and date.
- Store both amounts to exactly two decimal places.
- Warn, but allow saving, where amounts entered or overridden by hand imply a rate more than 0.5 percentage points away from the rate in force.
Spreading cost over time
- Spread each amount across the 15-minute intervals of the invoiced period, producing one series with VAT and one without.
- Shape the spread by the consumption profile of the same gauge and period where one is known — the
remoteseries, else themanualseries, never the effective series, which changes with the source priority; spread evenly where none is. - Weight intervals not covered by the profile with the average of the covered part, then normalise the whole profile so the series sums exactly to the invoiced amount; write the series by cumulative rounding to two decimal places, so that it sums to the amount exactly and every contiguous run of intervals sums to its rounded share.
- Produce one cost series per invoice with the tariff
total; produce cost series per tariff band only where the invoice itemises the amounts per band, and reject the write where those items do not sum to the invoiced amount. Never split an amount across bands in proportion to the quantities. - Keep the link from every cost value back to the invoice it came from.
- Carry a credit note as negative values over the period it corrects.
- Recompute only the cost series of the invoice that changed; leave consumption untouched.
- Remove the cost series of a deleted invoice.
Cost of a building
- Sum the cost series of the gauges whose output mode is
includeand whose direction isimport; the same attribute decides financial inclusion as decides physical inclusion, with no separate setting. - Leave out the gauges whose output mode is
subtract,reportOnlyorexclude: a tenant's invoice on a subtracted gauge is money the operator does not pay, and the arithmetic sign that the building-total formula gives a quantity has no meaning for money. - Treat energy delivered to the grid as income: an invoice or credit note on an
includegauge with directionexportis reported separately and never enters the building's cost. - Sum at read time, like a portfolio figure; store no cost series for a building's virtual gauge.
- Leave the operator's net cost after re-billing a tenant to a later feature on outgoing invoices.
Internationalization & Localization
Amounts are stored in the currency of the invoice and converted for display by the conversion service that Chart Engine owns. The rate catalogue carries a free-text legal reference per row, which is written in the language of the legislation and not translated.
Non-Functional Requirements
- Exactness. Amounts and their series are exact decimals end to end; no step of the spread is allowed to introduce a rounding drift.
- Reproducibility. Given the invoice and the rate recorded on it, both amounts and the whole series can be recomputed at any later date.
Performance
Spreading one invoice touches the intervals of one billing period for one gauge — a bounded piece of work, run when the invoice is saved. Invoice-driven recalculation is never triggered by a reading; a corrected reading reshapes a cost series only when that series is next recomputed, which keeps a bulk reading import from rewriting every invoice in the same window.
Transactional Operations
An invoice and its derived series are one unit of work: saving an invoice stores the amounts, the rate used, and the cost series together, or stores none of them. The series is replaced wholesale on recalculation rather than adjusted in place, so a partially rewritten period cannot exist. The exactness rule is enforced at the end of that unit: the sum of the written series is compared against the invoiced amount, and a mismatch fails the write rather than storing a series that does not reconcile.
Processes & Related Systems / Components
| Trigger | Owner of the trigger | What this feature does |
|---|---|---|
| Invoice created or corrected | Invoice Management | Complete the second amount, spread both over the period, store the rate used |
| Invoice deleted | Invoice Management | Remove the cost series of that invoice |
| Consumption profile of the period recomputed | Consumption Calculation | Reshape the cost series of invoices covering that period, keeping their totals |
| VAT rate added to the catalogue | platform operator | Apply to invoices whose period starts on or after the new validity date; earlier invoices keep the rate recorded on them |
| Cost series stored | this feature | Hand the affected window to Consumption Aggregation & Effective Consumption |
Diagrams & Models
Saving an invoice:
sequenceDiagram
actor U as Energy manager
participant I as Invoice Management
participant K as Cost Calculation
participant V as VAT rate catalogue
participant P as Consumption series
participant S as Cost series
U->>I: Enter one amount and the period
I->>K: Invoice saved
K->>V: Rate for medium and period start
V-->>K: Rate in force
K->>K: Derive the second amount, record the rate
K->>P: Profile for the gauge and period
P-->>K: Interval weights, or none
K->>K: Weight, normalise, check the sum
K->>S: Write both series
K-->>I: Amounts and series stored
How one amount becomes a series:
flowchart TD
A[Invoiced amount and period] --> B{Profile known<br/>for the period?}
B -->|no| C[Even weight on every interval]
B -->|partly| D[Covered intervals keep their weight]
D --> E[Uncovered intervals take the average of the covered part]
B -->|fully| F[Use the profile as the weights]
C --> G[Normalise so the sum equals the amount]
E --> G
F --> G
G --> H[Write the series with VAT and without]
H --> I{Sum equals the invoice?}
I -->|yes| J[Commit]
I -->|no| K[Fail the write]
API Analysis
N/A — the feature has no HTTP surface of its own. The invoice endpoints belong to Invoice Management and the read endpoints to Chart Engine.
Domain Model (ER diagram) & Data Attribute Table
erDiagram
INVOICE ||--o{ INVOICE_VALUE : "is itemised by"
INVOICE ||--o{ CONSUMPTION : "is spread into"
VAT_RATE ||--o{ INVOICE : "completes"
GAUGE ||--o{ INVOICE : "is billed for"
GAUGE ||--o{ CONSUMPTION : "costs"
- invoice — the amounts, the period, and the rate used.
- invoiceValue — the billed quantities and amounts per tariff band.
- vatRate — created by this feature; the statutory rate per medium and validity date.
- consumption — carries the cost series as its money kinds, alongside the quantity series.
- gauge — the metering point, its medium, and whether it counts towards the operator's cost.
Data
The rate catalogue is the feature's reference data and is seeded with the rates in force per medium:
sql
INSERT INTO vat_rate (medium, rate_percent, valid_from, legal_reference) VALUES
('electricity', 21.00, '2024-01-01', 'Zákon č. 349/2023 Sb.'),
('gas', 21.00, '2024-01-01', 'Zákon č. 349/2023 Sb.'),
('heat', 12.00, '2024-01-01', 'Zákon č. 349/2023 Sb.'),
('water', 12.00, '2024-01-01', 'Zákon č. 349/2023 Sb.');A worked example, so the exactness rule is unambiguous: an invoice of 10 000.00 without VAT at 21 % is 12 100.00 with VAT. Spread evenly across a 31-day January, the first ten days carry 3 903.23 and the remaining twenty-one 8 196.77 — which is 12 100.00, not 12 099.99. Shaped by a profile in which half the consumption fell in the first ten days, the same invoice gives 6 050.00 and 6 050.00. The monthly total is identical either way; only the shape inside the month differs, and that shape is what every finer-grained chart and every year-on-year comparison shows.
Test Data
Use the project's Test Data location named in the overlay. Search it for invoices attached to the seeded demo gauges — specifically one invoice on a gauge that also has readings for the same period (the profile-shaped case), one on a gauge with no readings (the even-spread case), and one credit note.
Logging
The project has no shared logging-conventions document yet, so this feature's events are stated in full here.
.info— invoice spread into a cost series: invoice, gauge, period, whether a profile was used,requestId..info— second amount derived: invoice, medium, rate applied, rate validity date..warn— amounts imply a rate outside the tolerance: invoice, implied rate, rate in force..warn— no rate in force for the medium and date; both amounts required from the user..error— the written series did not sum to the invoiced amount and the write was rejected: invoice, difference.
Monitoring
One feature-level need: the count of writes rejected because the series did not reconcile with its invoice. It should be zero, and a non-zero value is a correctness defect rather than a load symptom.
Caching
None. Cost series are read through the aggregation layer together with consumption.
Backward Compatibility and Migration
Legacy invoices are migrated as invoices, and their series are recomputed here rather than carried over, for the same reason as consumption. Two differences from the legacy behaviour are expected. A legacy period whose profile covered less than 95 % of days was spread evenly; it is now shaped by the part of the profile that is known, so the shape inside such a period changes while its total does not. And legacy fuel invoices were taxed at a rate fixed in code; they are recomputed against the catalogue, so a fuel invoice from a period with a different statutory rate now carries that rate.
Legal Context
VAT rates are set by legislation, and the figure the product shows has to match what the law said on the day the period started. This is why the rate is a catalogue row with a validity date and a legal reference rather than a constant, and why the rate used is recorded on the invoice: an invoice reproduced two years later must show the same amounts it showed when it was entered. The feature makes no tax determination of its own — it applies the rate recorded for the medium and date.
Cybersecurity Considerations
Cost series are tenant business data and are protected exactly as consumption is, by the tenant predicate the application-level guard applies to the hypertable. The rate catalogue is platform reference data, readable by every tenant and writable only by the platform operator.
Data Privacy. No personal data. An invoice identifies a metering point and a supplier, not a person.
Risk Assessment
Business Risks
The costly failure is a series that does not reconcile with its invoice: a report that disagrees with the accounting system by a few hundredths destroys trust in every other number on the page. The write is therefore rejected rather than stored when the sums disagree.
The second is the building cost being counted from the wrong set of gauges — including a tenant's own contract overstates the operator's cost, excluding a sub-meter that the operator does pay understates it. The configuration that decides this is per gauge and must be reviewed where energy is re-billed to tenants.
Technical Risks
| Risk | Consequence | Mitigation |
|---|---|---|
| Rounding drift across thousands of intervals | Series no longer sums to the invoice | Exact decimals throughout; the sum is checked before commit |
| Profile changes after the cost was spread | Cost shape no longer matches consumption | Recalculation reshapes the series and preserves the total |
| Rate catalogue missing a medium for a period | Second amount cannot be derived | Both amounts are requested from the user instead of guessing a rate |
| Rate corrected after invoices were entered | Historic figures would move | The rate used is stored on the invoice; only later periods are affected |
| Cascade — this feature stops | Cost charts, invoice checks and budget reporting stop gaining data; consumption is unaffected | Cost and quantity series are computed independently of each other |
Auditing, Reporting & Measurement
The amounts, the period and the rate used are recorded on the invoice, which carries its own audit trail; the derived series names the calculation that wrote it and the invoice it came from, so any monthly cost figure can be traced to the invoice behind it and recomputed. Reporting on cost is performed by the reporting features over the same series, not by this feature.