resource-control/docs/forecast-actual-control.md
tegwick 17de8b831e feat(portfolio): fold in RAILIANCE-WP-0016 apps-pg evidence
First delegated evidence from RESOURCE-WP-0003-T04 to land. railiance-platform
delivered apps-pg capacity, utilization, consumers, and the apps-pg-dbbytes-v1
allocation driver, and correctly delivered no EUR.

- data/resources/apps-pg.json: real capacity; allocation unattributed -> shared
  under apps-pg-dbbytes-v1; second consumer vergabe-teilnahme registered
- data/control-cycle/apps-pg-2026-09-base.json: first operational control-cycle
  record in the repository
- examples/control-cycle/apps-pg-*.json retired; the invented fixture collided
  with the real record's identifier
- data/portfolio-coverage-2026-08-14.json: gap marked delivered with three
  residual unknowns still open

The real evidence exposed a design gap in the T05 schema: v0.1 required a number
for every cost field, so recording genuine usage without a booked cost meant
inventing one. Schema 0.2 permits null costs, null unattributed_eur, a technical
unattributed_share, and null measurements. Null is unknown, never zero; an
unknown component makes the total null rather than the sum of the known parts;
and the comparator classifies unknown amounts as data_quality instead of
computing a variance. Existing 0.1 records are not rewritten.

apps-pg is now measured (idle at 5.8% of volume) and attributed, and remains
unpriced: delivered technical evidence does not create a booked cost.

86 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-14 09:36:57 +02:00

154 lines
7 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Forecast-to-actual resource control
## Why the €2.89, €5.14, €62.89, and €65.14 figures differ
They describe two volumes and two cost scopes:
| Figure | Stored volume | Scope | Calculation |
| --- | ---: | --- | --- |
| €2.89 | 180 GB | infrastructure only | 180 × €0.01606 |
| €62.89 | 180 GB | infrastructure + internal labor | €2.89 + 1 h × €60 |
| €5.14 | 320 GB | infrastructure only | 320 × €0.01606 |
| €65.14 | 320 GB | infrastructure + internal labor | €5.14 + 1 h × €60 |
The older €62.89 estimate was not “storage without elastic cost”; it already
included the €60 monthly operator allowance. The normalized table introduced a
different stored volume and showed infrastructure separately. Future reports
must always label both `stored_gb` and cost scope.
## Control records
A forecast is a timestamped, immutable record of what was believed at decision
time. The initial base forecast is
`data/forecasts/platform-audit-storage-scaleway-base-2026-08.json`. Do not edit
it after actual evidence exists. If assumptions change, create a new forecast
with a new creation date and retain the prior one so forecast accuracy remains
auditable.
After every billing month, record a technical `usage_observation` under
`data/actuals/`. Authoritative booked financial facts come from fin-hub and
are joined by reference; resource-control does not originate a second booked
actual.
The required cost and usage proxies are:
- database size and provider stored bytes;
- WAL volume;
- write and read requests;
- restore-test egress;
- infrastructure invoice cost;
- internal operations hours and valued labor cost;
- total attributable cost;
- backup success, maximum observed RPO, and restore RTO when measured.
Provider stored bytes are intentionally distinct from logical database size.
They expose base-backup retention, WAL, compression, versioning, incomplete
multipart uploads, and lifecycle behavior. Invoice cost is distinct from the
provider's usage estimator and must reconcile to non-secret billing evidence.
## Error calculation and review
`make variance ACTUAL=data/actuals/YYYY-MM.json` reports signed error and
absolute percentage error for each numeric proxy. Initially review rather than
automatically rewrite assumptions:
- investigate infrastructure cost error above 10%;
- investigate stored-byte or WAL error above 20%;
- investigate any unplanned egress or request-class charge;
- investigate labor error above 1 hour or 25%;
- investigate every backup failure, RPO breach, or restore-RTO regression.
After three comparable months, calculate mean absolute percentage error by
proxy and recalibrate the next forecast. Avoid MAPE where the forecast is zero;
use absolute error and explain the new activity instead. Separate forecast
error from price variance: a bill can differ because usage was wrong, the rate
changed, tax/discount treatment differed, or an unmodeled SKU appeared.
Quarterly provider comparison should use the latest actual trailing three
months, the next 12-month forecast, one monthly restore, an exit event, and
observed—not aspirational—operator labor.
## General portfolio control record
The storage-specific v0.1 observation remains valid for the backup procurement
case. New portfolio controls use
`schemas/resource-control-cycle.schema.json`, whose resource-specific
`usage_proxies` allow the same comparison mechanism to cover storage, VMs or
cluster compute, shared platform services, managed services, and future
resource types without pretending they share the same utilization unit.
Each record separates:
- provisioned and used capacity, with a fixed, elastic, or hybrid model;
- infrastructure, internal labor, and external labor cost;
- booked financial facts, referenced rather than copied from fin-hub;
- allocation method, driver, version, and unattributed residual;
- service constraints and their units;
- low/base/high forecast scenario or observed actual;
- evidence and uncertainty.
Forecast records are append-only. A changed forecast receives a new
`record_id`, creation time, and `revision_of` reference. Actuals must name the
exact original `forecast_ref`; a later revision must never replace the
decision-time baseline when measuring forecast error.
Run a generic comparison with:
```sh
make control-cycle FORECAST=examples/control-cycle/storage-forecast.json \
ACTUAL=examples/control-cycle/storage-actual.json
```
The examples cover storage and `reef-railiance` cluster compute. Their numbers
are illustrative contract fixtures, not booked facts. Operational records
replace them once the owner workplans publish observations — the illustrative
`apps-pg` pair was retired on 2026-08-14 when `RAILIANCE-WP-0016` delivered the
real one to `data/control-cycle/apps-pg-2026-09-base.json`.
Illustrative fixtures live in `examples/control-cycle/`; operational records
live in `data/control-cycle/` and must cite the authoritative repository
evidence they came from and state their uncertainty. The validator enforces
both.
### Schema 0.2 — unknown is not zero
Real evidence arrives incomplete. `RAILIANCE-WP-0016` supplied apps-pg
capacity, utilization, consumers, and an allocation driver, and correctly
supplied no EUR: the host bill belongs to `resource:hosteurope:railiance01`,
whose price is still unknown. Schema 0.1 could not express that — every cost
field required a number, so the only way to record real usage was to invent a
cost.
Schema 0.2 permits `null` for each cost component, for `unattributed_eur`, and
for any measurement value, and adds `unattributed_share` so a shared-cost
residual can be recorded from technical evidence before any EUR exists. The
rules:
- `null` means unknown or unbounded, never zero;
- if any cost component is unknown, `total` is `null` — never the sum of the
parts that happen to be known;
- the comparator returns `status: "unknown"` for that component and classifies
it `data_quality`, overriding any explicit attribution, because a missing
amount is an evidence gap rather than a price or labour movement;
- known components still compute their variance alongside unknown ones.
Existing 0.1 records stay valid and are never rewritten. Immutability applies
to the record, not to the schema, so `schema_version` accepts both.
## Variance attribution
Every material variance is assigned to one of six controlled categories:
- `demand`: the amount of consumed service differed;
- `provider_price`: rate, discount, tax, currency, or billed SKU differed;
- `allocation`: a shared-cost driver or attribution changed;
- `labor`: internal or external effort differed;
- `model`: a formula, assumption, capacity behavior, or omitted component was
wrong;
- `data_quality`: evidence is missing, late, inconsistent, or uses a different
unit.
The comparator defaults usage differences to demand, infrastructure cost to
provider price, and labor fields to labor, while preserving explicit
attribution supplied with the actual. Missing proxies and unit mismatches fail
closed as data-quality errors. Attribution explains an error; it does not
rewrite the forecast.