feat: ingest State Hub session tokens without booking them

Store mixed measured/estimated/superseded aggregates as session-token
evidence. Map kinds onto the evidence-basis vocabulary, keep unknown
months unknown, and refuse to post the snapshot as booked spend or
resource-control usage.
This commit is contained in:
tegwick 2026-08-15 19:21:41 +02:00
parent 22b21be552
commit d343888a41
10 changed files with 578 additions and 1 deletions

View file

@ -0,0 +1,60 @@
{
"schema_version": "0.1",
"record_type": "session_token_evidence",
"record_id": "session-tokens:state-hub:2026-08",
"revision_of": null,
"source_system": "state-hub",
"period_start": "2026-08-01",
"period_end": "2026-08-31",
"captured_at": "2026-08-15T12:00:00Z",
"source_evidence": [
"state-hub:/token-events/aggregate/?month=2026-08"
],
"totals": {
"measured": {
"tokens_in": 100,
"tokens_out": 50,
"event_count": 2,
"confidence": "1.0"
},
"allocated": {
"tokens_in": 20,
"tokens_out": 10,
"event_count": 1,
"confidence": "0.70",
"method": "state-hub-task-share"
},
"estimated": {
"tokens_in": 1000,
"tokens_out": 500,
"event_count": 3,
"confidence": "0.35"
},
"superseded": {
"tokens_in": 800,
"tokens_out": 200,
"event_count": 1,
"confidence": "0.0"
}
},
"associations": [
{
"scope": "repo",
"scope_id": "cce28433-34ce-4ab1-9ab4-4dac9222c802",
"label": "fin-hub",
"measurement_kind": "measured",
"tokens_in": 100,
"tokens_out": 50,
"event_count": 2
},
{
"scope": "workplan",
"scope_id": "88de5e03-a53f-4b62-a0a7-1365756da2d7",
"label": "FIN-WP-0007",
"measurement_kind": "estimated",
"tokens_in": 1000,
"tokens_out": 500,
"event_count": 3
}
]
}

View file

@ -0,0 +1,121 @@
import json
from pathlib import Path
import pytest
from fin_hub.services.exchange import ingest_planning_evidence
from fin_hub.services.ledger import import_csv, monthly_summary
from fin_hub.services.session_tokens import (
ingest_session_token_evidence,
ingest_session_token_file,
list_current_session_token_evidence,
summarize_session_token_evidence,
)
FIXTURES = Path(__file__).parent / "fixtures"
def test_ingest_mixed_aggregates_and_summarize(tmp_path: Path):
ledger = tmp_path / "ledger.db"
record = ingest_session_token_file(
FIXTURES / "session-tokens-2026-08.json", ledger_path=ledger
)
again = ingest_session_token_file(
FIXTURES / "session-tokens-2026-08.json", ledger_path=ledger
)
assert again.record_id == record.record_id
summary = summarize_session_token_evidence(record)
assert summary.unknown_residual is False
assert summary.tokens_measured == 150
assert summary.tokens_derived == 30
assert summary.tokens_estimated == 1500
assert "excluded" not in summary.by_basis
assert set(summary.by_basis) == {"measured", "derived", "estimated"}
assert summary.by_basis["derived"].method == "state-hub-task-share"
stored = list_current_session_token_evidence(ledger_path=ledger)
assert [item.record_id for item in stored] == [record.record_id]
assert monthly_summary(ledger_path=ledger) == []
def test_empty_month_is_unknown_residual_not_zero(tmp_path: Path):
ledger = tmp_path / "ledger.db"
record = ingest_session_token_evidence(
{
"record_id": "session-tokens:state-hub:2026-07",
"period_start": "2026-07-01",
"period_end": "2026-07-31",
"captured_at": "2026-08-15T12:00:00Z",
"source_evidence": ["state-hub:/token-events/aggregate/?month=2026-07"],
"totals": {
"superseded": {
"tokens_in": 9,
"tokens_out": 1,
"event_count": 1,
"confidence": "0",
}
},
},
ledger_path=ledger,
)
summary = summarize_session_token_evidence(record)
assert summary.unknown_residual is True
assert summary.tokens_measured is None
assert summary.tokens_derived is None
assert summary.tokens_estimated is None
assert summary.coverage is None
def test_allocated_events_require_a_method():
with pytest.raises(ValueError, match="method"):
ingest_session_token_evidence(
{
"record_id": "session-tokens:bad",
"period_start": "2026-08-01",
"period_end": "2026-08-31",
"captured_at": "2026-08-15T12:00:00Z",
"source_evidence": ["state-hub"],
"totals": {
"allocated": {
"tokens_in": 1,
"tokens_out": 1,
"event_count": 1,
}
},
},
ledger_path=Path("/tmp/unused"),
)
def test_session_tokens_cannot_be_posted_as_planning_usage(tmp_path: Path):
ledger = tmp_path / "ledger.db"
payload = json.loads((FIXTURES / "session-tokens-2026-08.json").read_text())
with pytest.raises(ValueError, match="not resource-control"):
ingest_planning_evidence(payload, ledger_path=ledger)
with pytest.raises(ValueError, match="usage_observation"):
ingest_session_token_evidence(
{
"record_type": "usage_observation",
"record_id": "usage:1",
"period_start": "2026-08-01",
"period_end": "2026-08-31",
"resource_id": "resource:invented",
"source_evidence": ["nope"],
"created_at": "2026-08-15T12:00:00Z",
"measures": [{"name": "tokens", "value": "1", "unit": "token"}],
},
ledger_path=ledger,
)
def test_session_tokens_cannot_be_imported_as_booked_spend(tmp_path: Path):
ledger = tmp_path / "ledger.db"
with pytest.raises(ValueError, match="Unknown source type"):
import_csv(FIXTURES / "session-tokens-2026-08.json", "session-tokens", ledger_path=ledger)
with pytest.raises(ValueError, match="booked spend"):
ingest_session_token_evidence(
{
"record_type": "booked_cost",
"financial_fact_id": "fact:1",
},
ledger_path=ledger,
)