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:
parent
22b21be552
commit
d343888a41
10 changed files with 578 additions and 1 deletions
60
tests/fixtures/session-tokens-2026-08.json
vendored
Normal file
60
tests/fixtures/session-tokens-2026-08.json
vendored
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
121
tests/test_session_tokens.py
Normal file
121
tests/test_session_tokens.py
Normal 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,
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue