Implement public metrics service (WP-0006-T05)

src/target_revenue/metrics.py: compute_metrics(manifest, entries, as_of)
is pure/deterministic like fold.py, reusing fold.py/conversion.py
unchanged. Returns facts/calculations/forecasts as three explicitly
separated blocks (TrustServicePRD TS-FR-5), covering the mandatory Q9
set plus the recommended velocity/forecast tier - forecasts are always
null rather than populated once a Phase has converted or velocity is
non-positive, so nothing disguises a projection as a fact.

Adds GET /phases/{id}/metrics (unauthenticated per FR-9/FR-10).
tests/test_metrics.py (6 tests) needs no Docker/Postgres and runs
under plain system Python. One new Docker-gated test in
test_ledger_hosting.py proves the hosted /metrics response exactly
matches compute_metrics() run offline against the same export.
This commit is contained in:
tegwick 2026-07-29 21:42:52 +02:00
parent b7be3d3512
commit dfc1d90c28
6 changed files with 287 additions and 3 deletions

View file

@ -192,7 +192,7 @@ with plain system Python; no stray Docker containers left running.
```task
id: TREV-WP-0006-T05
status: todo
status: done
priority: medium
state_hub_task_id: "6bab5e64-0091-4a25-906d-758310cbe723"
```
@ -202,6 +202,29 @@ Q9 (Initial Target, cumulative credits, Outstanding Target, conversion
status, last checkpoint, Longstop timestamp as mandatory; velocity/forecast
as recommended, clearly labeled as forecasts per concept §14.6).
**Result:** `src/target_revenue/metrics.py`'s `compute_metrics(manifest,
entries, as_of)` — pure and deterministic like `fold.py` (the only input
that varies with wall-clock time, `as_of`, is an explicit parameter, never
read internally), reusing `fold.py`/`conversion.py` unchanged. Returns
three explicitly separated blocks per TrustServicePRD TS-FR-5: `facts`
(Q9's mandatory set — Initial Target, cumulative Development/Remission
Credit, Outstanding Target, conversion status, last ledger entry id,
Longstop timestamp), `calculations` (target satisfaction percentage,
Development/Remission Credit velocity per day, days since last material
progress), and `forecasts` (projected conversion date, `null` whenever
already converted or velocity is zero/negative — never populated as a
disguised fact). Added `GET /phases/{id}/metrics` to `service/app.py`,
unauthenticated per FR-9/FR-10. `tests/test_metrics.py` (6 tests, no
Docker/Postgres required, runs under plain system Python — no new hard
dependency) covers the mandatory-facts set, fact/calculation/forecast
separation, percentage correctness, the zero-material-entries case, and
`as_of` timezone-awareness. Added one Docker-gated test to
`tests/test_ledger_hosting.py` proving the hosted `/metrics` response
(fetched with no Authorization header) exactly equals
`compute_metrics()` run offline against the same exported Manifest +
Ledger. Full suite (offline 36 + metrics 6 + Docker-gated 22) verified
passing; no stray containers left running.
## Conversion Attestation publication
```task