feat: bound AI-plan work to resource-control and stop zeroing missing tokens
Record the FIN-WP-0007 split after checking resource-control: they keep intelligence resource identity and provision-level class I metering; session tokens stay with State Hub; work-effectiveness is a reporting join. Missing token counts are now unknown, not zero.
This commit is contained in:
parent
004c041dc0
commit
09ef7fa967
10 changed files with 326 additions and 38 deletions
|
|
@ -11,6 +11,14 @@ forecasts, commitment candidates, and optimization scenarios from
|
||||||
forecast or allocation never becomes booked spend merely because fin-hub
|
forecast or allocation never becomes booked spend merely because fin-hub
|
||||||
stores or reports it.
|
stores or reports it.
|
||||||
|
|
||||||
|
Intelligence follows the same split. Resource-control owns portfolio
|
||||||
|
identity and provision-level class `I` metering when Railiance procures a
|
||||||
|
model API, reserved weights, or inference host. State Hub owns agent-session
|
||||||
|
token evidence. Fin-hub books the plan invoice, records entitlement, joins
|
||||||
|
those inputs, and reports effectiveness. It does not invent a `resource_id`,
|
||||||
|
parse sessions, or emit technical usage or allocation as if it were
|
||||||
|
resource-control.
|
||||||
|
|
||||||
Fin-hub does not generate legal invoices, perform bookkeeping, execute or
|
Fin-hub does not generate legal invoices, perform bookkeeping, execute or
|
||||||
track payments, approve contractual commitments, own client/resource identity,
|
track payments, approve contractual commitments, own client/resource identity,
|
||||||
or provision infrastructure. It records, reconciles, reports, projects, and
|
or provision infrastructure. It records, reconciles, reports, projects, and
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# Fin Hub
|
# Fin Hub
|
||||||
|
|
||||||
Resource viability hub for the FOS federation — budgets, commitments, burn rate,
|
Resource viability hub for the FOS federation — budgets, commitments, burn rate,
|
||||||
runway projection, and token spend tracking.
|
runway projection, and booked token / AI-plan spend.
|
||||||
|
|
||||||
Fin-hub extends `hub-core` with financial models and read surfaces. Generic hub
|
Fin-hub extends `hub-core` with financial models and read surfaces. Generic hub
|
||||||
primitives (domains, repos, messages, progress events) come from hub-core;
|
primitives (domains, repos, messages, progress events) come from hub-core;
|
||||||
|
|
|
||||||
22
SCOPE.md
22
SCOPE.md
|
|
@ -2,11 +2,18 @@
|
||||||
|
|
||||||
## In Scope
|
## In Scope
|
||||||
|
|
||||||
- Budget, commitment, burn rate, runway projection, and token spend models
|
- Budget, commitment, burn rate, runway projection, and booked token /
|
||||||
- Manual CSV import for cloud and API costs (v0.1)
|
AI-plan spend
|
||||||
|
- Manual CSV import for cloud, API, and subscription costs (v0.1)
|
||||||
- Runway calculator with alert thresholds
|
- Runway calculator with alert thresholds
|
||||||
- Client-level cost attribution, engagement revenue and margin reporting, and
|
- Client-level cost attribution, engagement revenue and margin reporting, and
|
||||||
billing-basis exports for external bookkeeping/invoicing systems
|
billing-basis exports for external bookkeeping/invoicing systems
|
||||||
|
- AI-plan entitlements, joins to State Hub session-token evidence, reporting
|
||||||
|
allocation of booked plan cost onto work, and effectiveness overlays
|
||||||
|
(work per token and work per euro) that never become booked spend
|
||||||
|
- Consumption of resource-control planning evidence (forecasts, technical
|
||||||
|
usage, allocations, optimization, settlement statements) without taking
|
||||||
|
authority for those records
|
||||||
- FOS §9 cross-hub signals: fin→dev (budget pressure), fin→ops (cost attribution), fin→canon (viability alerts)
|
- FOS §9 cross-hub signals: fin→dev (budget pressure), fin→ops (cost attribution), fin→canon (viability alerts)
|
||||||
- hub-core generic primitives (domains, repos, messages, progress)
|
- hub-core generic primitives (domains, repos, messages, progress)
|
||||||
|
|
||||||
|
|
@ -15,4 +22,13 @@
|
||||||
- Legal invoice generation or numbering, bookkeeping, payment execution or
|
- Legal invoice generation or numbering, bookkeeping, payment execution or
|
||||||
tracking, and banking integration
|
tracking, and banking integration
|
||||||
- Tax filing or legal entity management
|
- Tax filing or legal entity management
|
||||||
- Authority for client identity or infrastructure resource identity
|
- Authority for client identity or infrastructure / intelligence resource
|
||||||
|
identity (`resource:…` remains resource-control)
|
||||||
|
- Provision-level class `I` token metering against a portfolio resource
|
||||||
|
- Technical usage, utilization, and allocation-driver evidence for
|
||||||
|
inventoried resources (resource-control, sourced from platform telemetry)
|
||||||
|
- Parsing coding-agent session files or storing transcripts (State Hub /
|
||||||
|
llm-connect)
|
||||||
|
- Inventing a `resource_id` for an AI plan that resource-control has not
|
||||||
|
published
|
||||||
|
- A canonical token↔hour or token↔euro exchange rate
|
||||||
|
|
|
||||||
142
docs/ai-plan-token-effectiveness.md
Normal file
142
docs/ai-plan-token-effectiveness.md
Normal file
|
|
@ -0,0 +1,142 @@
|
||||||
|
# AI plan cost, token estimates, and effectiveness
|
||||||
|
|
||||||
|
Status: fin-hub design note for `FIN-WP-0007-T00`
|
||||||
|
Checked against: resource-control `SCOPE.md`, `INTENT.md`,
|
||||||
|
`docs/TermsOfResourceProcurement.md` §4.1 and §6,
|
||||||
|
`docs/fin-hub-resource-control-contract-v0.1.md`,
|
||||||
|
`docs/evidence-basis.md`,
|
||||||
|
`data/capability/platform-audit-storage.json` (2026-08-15),
|
||||||
|
and the ratified fin-hub contract
|
||||||
|
`docs/fin-resource-authority-contract-v0.1.md`.
|
||||||
|
|
||||||
|
This note records the authority split. It does not amend contract v0.1.
|
||||||
|
A proposed addendum is at the end for resource-control to accept or reject.
|
||||||
|
|
||||||
|
## Why a second check was required
|
||||||
|
|
||||||
|
`FIN-WP-0007` first treated resource-control as the wrong producer for
|
||||||
|
any AI-token number. That is too coarse.
|
||||||
|
|
||||||
|
Resource-control already names intelligence as a Railiance-procured
|
||||||
|
resource class — “commercial model APIs, reserved weights, inference
|
||||||
|
hosts” — and records class `I` consumption on a provision as *their*
|
||||||
|
gap (`owner: resource-control`), with unknown never stored as zero.
|
||||||
|
They do not yet inventory any AI plan, and they do not parse coding
|
||||||
|
sessions. Those two facts have to stay separate.
|
||||||
|
|
||||||
|
## Four quantities, four writers
|
||||||
|
|
||||||
|
| Quantity | Native unit | Authoritative writer | Fin-hub may |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| Booked plan / API invoice | currency | fin-hub | Book once as `booked_cost` |
|
||||||
|
| Recurring plan as commitment | currency / period | fin-hub | Record; not usage |
|
||||||
|
| Plan entitlement (included tokens or plan label) | token or declared capacity | fin-hub | Commercial fact, same discipline as engagement prices |
|
||||||
|
| Intelligence *resource* identity | `resource:…` | resource-control | Join when present; never invent |
|
||||||
|
| Provision-level class `I` usage | token | resource-control, when they meter a named provision | Consume via `FIN-WP-0004` `usage_observation` |
|
||||||
|
| Session token evidence | token | State Hub (`STATE-WP-0045`) | Join as session-token evidence |
|
||||||
|
| Technical allocation of a portfolio resource | share of a referenced fact | resource-control | Consume `AllocationEvidence`; do not emit it |
|
||||||
|
| Work done | task / outcome | State Hub / domain repos | Join key, not a cost driver |
|
||||||
|
| Reporting allocation of a plan invoice across work | share of a referenced fact | fin-hub | Local reporting derivation (`FIN-WP-0002` family) |
|
||||||
|
| Implied €/token and work-per-token / work-per-euro | derived | fin-hub | Overlay only; inherit the weakest input basis |
|
||||||
|
|
||||||
|
Session telemetry is not platform telemetry. Contract v0.1 says
|
||||||
|
technical usage is sourced from platform telemetry and written by
|
||||||
|
resource-control. Agent-session counts from State Hub therefore must
|
||||||
|
not be stored as resource-control `usage_observation`.
|
||||||
|
|
||||||
|
Reporting allocation of an invoice across workplans is not technical
|
||||||
|
allocation of a portfolio resource. Contract v0.1 keeps allocation
|
||||||
|
method and driver with resource-control. Fin-hub already has a
|
||||||
|
reporting-only consumer for that evidence (`FIN-WP-0002-T03`). Work
|
||||||
|
effectiveness uses a *local* reporting allocation and must not be
|
||||||
|
posted as `AllocationEvidence`.
|
||||||
|
|
||||||
|
## When a `resource_id` exists
|
||||||
|
|
||||||
|
If resource-control later inventories a Railiance-procured
|
||||||
|
intelligence resource (API account, reserved weights, inference
|
||||||
|
host, or license):
|
||||||
|
|
||||||
|
- fin-hub books the invoice and may attach the resource-control
|
||||||
|
`resource_id` they published;
|
||||||
|
- resource-control owns usage and technical allocation on that
|
||||||
|
resource, including class `I` tokens against the provision;
|
||||||
|
- fin-hub still owns entitlement, burn, runway, and the
|
||||||
|
work-effectiveness report.
|
||||||
|
|
||||||
|
Until that inventory row exists, `resource_id` stays `null`. A
|
||||||
|
null join is a visible gap, not a license to mint
|
||||||
|
`resource:anthropic:claude-max`.
|
||||||
|
|
||||||
|
## Financial entity
|
||||||
|
|
||||||
|
Resource-control requires every expense, estimate, and usage
|
||||||
|
observation to carry exactly one `financial_entity_id`, or an
|
||||||
|
explicit null with a named gap. Founder-private cards and uncleared
|
||||||
|
Auslagen stay outside their terms until booked to an entity *and* a
|
||||||
|
resource.
|
||||||
|
|
||||||
|
Fin-hub may still book those invoices for runway. It must not
|
||||||
|
silent-default the entity to Railiance or Binky, and it must not
|
||||||
|
pretend a private plan is a Railiance-procured resource.
|
||||||
|
|
||||||
|
## Measurement layers and estimates
|
||||||
|
|
||||||
|
Keep these records separate, joined by period, provider/plan, and
|
||||||
|
optional workplan/repo/`resource_id`:
|
||||||
|
|
||||||
|
1. booked plan cost
|
||||||
|
2. entitlement (or explicit unknown capacity)
|
||||||
|
3. consumption of class `I`, each row marked with how it was obtained
|
||||||
|
4. work done
|
||||||
|
|
||||||
|
Reuse resource-control’s evidence-basis vocabulary rather than
|
||||||
|
inventing a dialect: `invoiced`, `measured`, `quoted`, `derived`,
|
||||||
|
`projected`, `estimated`, `assumed`, `unknown`. Unknown carries no
|
||||||
|
quantity. Zero means measured and none.
|
||||||
|
|
||||||
|
State Hub `measurement_kind` maps as:
|
||||||
|
|
||||||
|
| State Hub | Evidence basis used here |
|
||||||
|
| --- | --- |
|
||||||
|
| `measured` | `measured` |
|
||||||
|
| `allocated` | `derived` (from a measured parent; method required) |
|
||||||
|
| `estimated` | `estimated` |
|
||||||
|
| `superseded` | excluded from totals |
|
||||||
|
| no events | `unknown` residual, not a zero observation |
|
||||||
|
|
||||||
|
A derived overlay (implied €/token, work per token) is only as strong
|
||||||
|
as its weakest input. A month whose coverage is incomplete is
|
||||||
|
`insufficient` for trend comparison, not interpolated.
|
||||||
|
|
||||||
|
## What fin-hub will not do
|
||||||
|
|
||||||
|
- Invent resource identity or provider-resource identity.
|
||||||
|
- Meter tokens against a portfolio provision.
|
||||||
|
- Parse Codex, Claude, Cursor, or Grok session files.
|
||||||
|
- Emit `usage_observation` or `AllocationEvidence` as if it were
|
||||||
|
resource-control.
|
||||||
|
- Collapse tokens into euros at capture time.
|
||||||
|
- Store missing tokens as `0`.
|
||||||
|
- Treat a plan entitlement as an invoice, or an implied €/token as a
|
||||||
|
price.
|
||||||
|
|
||||||
|
## Proposed contract addendum (not ratified)
|
||||||
|
|
||||||
|
Offer to resource-control, without changing v0.1 until they accept:
|
||||||
|
|
||||||
|
1. Intelligence resources remain in their portfolio when Railiance
|
||||||
|
procures them. Class `I` on a provision stays their meter.
|
||||||
|
2. Agent-session token events stay with State Hub. Fin-hub may join
|
||||||
|
them for financial reporting; that join is not technical usage.
|
||||||
|
3. Work-effectiveness allocation of a booked AI-plan fact is a
|
||||||
|
fin-hub reporting derivation and is not technical allocation.
|
||||||
|
4. A booked AI-plan fact may carry `resource_id=null` until they
|
||||||
|
publish one. Fin-hub will not mint a surrogate.
|
||||||
|
|
||||||
|
`FIN-WP-0004-T05` (Scaleway `platform:audit-storage` booked-cost
|
||||||
|
round trip) is unchanged and is class `S`, not class `I`.
|
||||||
|
|
||||||
|
Offered to resource-control on 2026-08-15 as State Hub message
|
||||||
|
`4e75cd00-87f1-46e1-8d17-9169289640a2`. Not assumed until they
|
||||||
|
accept, reject, or name a conflict.
|
||||||
|
|
@ -13,14 +13,28 @@ from fin_hub.ingest._csv import parse_amount, pick, read_csv_rows
|
||||||
class TokenSpendRow:
|
class TokenSpendRow:
|
||||||
provider: str
|
provider: str
|
||||||
model: str
|
model: str
|
||||||
tokens_in: int
|
tokens_in: int | None
|
||||||
tokens_out: int
|
tokens_out: int | None
|
||||||
cost: float
|
cost: float
|
||||||
currency: str
|
currency: str
|
||||||
session_id: str | None
|
session_id: str | None
|
||||||
recorded_at: datetime
|
recorded_at: datetime
|
||||||
|
|
||||||
|
|
||||||
|
def parse_optional_token_count(value: str) -> int | None:
|
||||||
|
"""Parse a token quantity. Empty is unknown; 0 is measured none."""
|
||||||
|
|
||||||
|
if not value:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
count = int(value)
|
||||||
|
except ValueError as error:
|
||||||
|
raise ValueError(f"token count must be an integer, got {value!r}") from error
|
||||||
|
if count < 0:
|
||||||
|
raise ValueError(f"token count cannot be negative: {count}")
|
||||||
|
return count
|
||||||
|
|
||||||
|
|
||||||
def parse_anthropic_billing_csv(path: Path, *, default_currency: str = "USD") -> list[TokenSpendRow]:
|
def parse_anthropic_billing_csv(path: Path, *, default_currency: str = "USD") -> list[TokenSpendRow]:
|
||||||
rows: list[TokenSpendRow] = []
|
rows: list[TokenSpendRow] = []
|
||||||
for row in read_csv_rows(path):
|
for row in read_csv_rows(path):
|
||||||
|
|
@ -28,8 +42,12 @@ def parse_anthropic_billing_csv(path: Path, *, default_currency: str = "USD") ->
|
||||||
cost_raw = pick(row, "cost", "amount", "total_cost", "usage_cost_usd")
|
cost_raw = pick(row, "cost", "amount", "total_cost", "usage_cost_usd")
|
||||||
if not model or not cost_raw:
|
if not model or not cost_raw:
|
||||||
continue
|
continue
|
||||||
tokens_in = int(pick(row, "input_tokens", "tokens_in", "prompt_tokens") or "0")
|
tokens_in = parse_optional_token_count(
|
||||||
tokens_out = int(pick(row, "output_tokens", "tokens_out", "completion_tokens") or "0")
|
pick(row, "input_tokens", "tokens_in", "prompt_tokens")
|
||||||
|
)
|
||||||
|
tokens_out = parse_optional_token_count(
|
||||||
|
pick(row, "output_tokens", "tokens_out", "completion_tokens")
|
||||||
|
)
|
||||||
recorded_raw = pick(row, "date", "usage_date", "recorded_at", "timestamp")
|
recorded_raw = pick(row, "date", "usage_date", "recorded_at", "timestamp")
|
||||||
recorded_at = (
|
recorded_at = (
|
||||||
datetime.fromisoformat(recorded_raw.replace("Z", "+00:00"))
|
datetime.fromisoformat(recorded_raw.replace("Z", "+00:00"))
|
||||||
|
|
|
||||||
|
|
@ -69,8 +69,8 @@ class TokenSpend(Base, TimestampMixin):
|
||||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||||
provider: Mapped[str] = mapped_column(String(32), nullable=False, index=True)
|
provider: Mapped[str] = mapped_column(String(32), nullable=False, index=True)
|
||||||
model: Mapped[str] = mapped_column(String(64), nullable=False)
|
model: Mapped[str] = mapped_column(String(64), nullable=False)
|
||||||
tokens_in: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
tokens_in: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
||||||
tokens_out: Mapped[int] = mapped_column(Integer, nullable=False, default=0)
|
tokens_out: Mapped[int | None] = mapped_column(Integer, nullable=True)
|
||||||
cost: Mapped[float] = mapped_column(Float, nullable=False, default=0.0)
|
cost: Mapped[float] = mapped_column(Float, nullable=False, default=0.0)
|
||||||
session_id: Mapped[str | None] = mapped_column(String(128), nullable=True)
|
session_id: Mapped[str | None] = mapped_column(String(128), nullable=True)
|
||||||
recorded_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
recorded_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from fin_hub.ingest.anthropic import parse_anthropic_billing_csv
|
from fin_hub.ingest.anthropic import parse_anthropic_billing_csv, parse_optional_token_count
|
||||||
from fin_hub.ingest.cloud import parse_cloud_cost_csv
|
from fin_hub.ingest.cloud import parse_cloud_cost_csv
|
||||||
from fin_hub.ingest.hosteurope import parse_hosteurope_csv
|
from fin_hub.ingest.hosteurope import parse_hosteurope_csv
|
||||||
|
|
||||||
|
|
@ -21,6 +21,43 @@ def test_parse_anthropic_billing_csv():
|
||||||
assert len(rows) == 2
|
assert len(rows) == 2
|
||||||
assert rows[0].provider == "anthropic"
|
assert rows[0].provider == "anthropic"
|
||||||
assert rows[0].tokens_in == 12000
|
assert rows[0].tokens_in == 12000
|
||||||
|
assert rows[0].tokens_out == 3000
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_optional_token_count_treats_empty_as_unknown():
|
||||||
|
assert parse_optional_token_count("") is None
|
||||||
|
assert parse_optional_token_count("0") == 0
|
||||||
|
assert parse_optional_token_count("12") == 12
|
||||||
|
with pytest.raises(ValueError, match="integer"):
|
||||||
|
parse_optional_token_count("n/a")
|
||||||
|
with pytest.raises(ValueError, match="negative"):
|
||||||
|
parse_optional_token_count("-1")
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_anthropic_cost_only_row_does_not_invent_zero_tokens(tmp_path: Path):
|
||||||
|
source = tmp_path / "anthropic-cost-only.csv"
|
||||||
|
source.write_text(
|
||||||
|
"model,cost,currency,usage_date\n"
|
||||||
|
"claude-opus-4-20250514,20.00,USD,2026-08-01\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
row = parse_anthropic_billing_csv(source)[0]
|
||||||
|
assert row.cost == 20.0
|
||||||
|
assert row.tokens_in is None
|
||||||
|
assert row.tokens_out is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_anthropic_explicit_zero_tokens_remain_zero(tmp_path: Path):
|
||||||
|
source = tmp_path / "anthropic-zero-tokens.csv"
|
||||||
|
source.write_text(
|
||||||
|
"model,input_tokens,output_tokens,cost,currency,usage_date\n"
|
||||||
|
"claude-haiku-3-20240307,0,0,0.00,USD,2026-08-02\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
row = parse_anthropic_billing_csv(source)[0]
|
||||||
|
assert row.tokens_in == 0
|
||||||
|
assert row.tokens_out == 0
|
||||||
|
assert row.cost == 0.0
|
||||||
|
|
||||||
|
|
||||||
def test_parse_hosteurope_csv():
|
def test_parse_hosteurope_csv():
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,20 @@ from fin_hub.services.ledger import (
|
||||||
FIXTURES = Path(__file__).parent / "fixtures"
|
FIXTURES = Path(__file__).parent / "fixtures"
|
||||||
|
|
||||||
|
|
||||||
|
def test_ledger_import_anthropic_cost_only_does_not_require_tokens(tmp_path: Path):
|
||||||
|
ledger = tmp_path / "ledger.db"
|
||||||
|
source = tmp_path / "anthropic-cost-only.csv"
|
||||||
|
source.write_text(
|
||||||
|
"model,cost,currency,usage_date\nclaude-opus-4-20250514,20.00,USD,2026-08-01\n",
|
||||||
|
encoding="utf-8",
|
||||||
|
)
|
||||||
|
result = import_csv(source, "anthropic", ledger_path=ledger)
|
||||||
|
assert result.rows_imported == 1
|
||||||
|
rollups = monthly_summary(ledger_path=ledger)
|
||||||
|
assert rollups[0].currency == "USD"
|
||||||
|
assert rollups[0].total == pytest.approx(20.0)
|
||||||
|
|
||||||
|
|
||||||
def test_ledger_import_and_monthly_summary(tmp_path: Path):
|
def test_ledger_import_and_monthly_summary(tmp_path: Path):
|
||||||
ledger = tmp_path / "ledger.db"
|
ledger = tmp_path / "ledger.db"
|
||||||
set_opening_balance(ledger, 12000.0)
|
set_opening_balance(ledger, 12000.0)
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,14 @@ def test_model_classes_importable():
|
||||||
assert EngagementPrice.__tablename__ == "fin_engagement_prices"
|
assert EngagementPrice.__tablename__ == "fin_engagement_prices"
|
||||||
|
|
||||||
|
|
||||||
|
def test_token_spend_token_counts_are_nullable():
|
||||||
|
columns = TokenSpend.__table__.columns
|
||||||
|
assert columns["tokens_in"].nullable is True
|
||||||
|
assert columns["tokens_out"].nullable is True
|
||||||
|
assert columns["tokens_in"].default is None
|
||||||
|
assert columns["tokens_out"].default is None
|
||||||
|
|
||||||
|
|
||||||
def test_service_cost_has_external_attribution_seam():
|
def test_service_cost_has_external_attribution_seam():
|
||||||
columns = ServiceCost.__table__.columns
|
columns = ServiceCost.__table__.columns
|
||||||
assert columns["client_id"].nullable is True
|
assert columns["client_id"].nullable is True
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ type: workplan
|
||||||
title: "AI plan cost, token estimates, and effectiveness"
|
title: "AI plan cost, token estimates, and effectiveness"
|
||||||
domain: financials
|
domain: financials
|
||||||
repo: fin-hub
|
repo: fin-hub
|
||||||
status: proposed
|
status: active
|
||||||
owner: grok
|
owner: grok
|
||||||
topic_slug: financials
|
topic_slug: financials
|
||||||
created: "2026-08-15"
|
created: "2026-08-15"
|
||||||
|
|
@ -12,7 +12,9 @@ updated: "2026-08-15"
|
||||||
related:
|
related:
|
||||||
- FIN-WP-0002
|
- FIN-WP-0002
|
||||||
- FIN-WP-0004
|
- FIN-WP-0004
|
||||||
|
- FIN-WP-0006
|
||||||
- STATE-WP-0045
|
- STATE-WP-0045
|
||||||
|
- RESOURCE-WP-0003
|
||||||
- ITC-CAP-0.2.0
|
- ITC-CAP-0.2.0
|
||||||
origin: assessment
|
origin: assessment
|
||||||
origin_ref: "info-tech-canon 0.3.0 review — AI token data-gap"
|
origin_ref: "info-tech-canon 0.3.0 review — AI token data-gap"
|
||||||
|
|
@ -44,22 +46,34 @@ wait for a new commerce or intelligence domain model.
|
||||||
|
|
||||||
## Boundaries
|
## Boundaries
|
||||||
|
|
||||||
|
Checked 2026-08-15 against resource-control SCOPE, INTENT, procurement
|
||||||
|
terms §4.1/§6, their v0.1 ratification, evidence-basis vocabulary, and
|
||||||
|
the backup capability restatement. Design note:
|
||||||
|
`docs/ai-plan-token-effectiveness.md`. Contract v0.1 is not silently
|
||||||
|
amended; the addendum in that note is offered, not assumed.
|
||||||
|
|
||||||
| Concept | Authoritative writer | Fin-hub role |
|
| Concept | Authoritative writer | Fin-hub role |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| Provider invoice / plan subscription charge | fin-hub | Book once as `booked_cost` |
|
| Provider invoice / plan subscription charge | fin-hub | Book once as `booked_cost` |
|
||||||
| Recurring plan as a financial commitment | fin-hub | Record cadence and amount; not usage |
|
| Recurring plan as a financial commitment | fin-hub | Record cadence and amount; not usage |
|
||||||
| Plan entitlement (included tokens or plan capacity) | fin-hub | Commercial fact, same discipline as engagement prices |
|
| Plan entitlement (included tokens or plan capacity) | fin-hub | Commercial fact, same discipline as engagement prices |
|
||||||
| Session token evidence (`measured` / `allocated` / `estimated` / `superseded`) | State Hub (`STATE-WP-0045`) | Consume aggregates; do not re-parse sessions |
|
| Intelligence / infrastructure `resource_id` | resource-control | Join when they publish one; never invent |
|
||||||
| Technical infrastructure usage and resource identity | resource-control | Out of scope here (class `S`/`C`/`P`, `FIN-WP-0004`) |
|
| Provision-level class `I` usage | resource-control (gap they already own) | Consume via `FIN-WP-0004` when emitted |
|
||||||
|
| Session token evidence (`measured` / `allocated` / `estimated` / `superseded`) | State Hub (`STATE-WP-0045`) | Join as session-token evidence; do not store as `usage_observation` |
|
||||||
|
| Technical allocation of a portfolio resource | resource-control | Consume `AllocationEvidence`; do not emit it |
|
||||||
| Work done (tasks, progress, outcomes) | State Hub / domain repos | Join key for effectiveness; not a cost driver |
|
| Work done (tasks, progress, outcomes) | State Hub / domain repos | Join key for effectiveness; not a cost driver |
|
||||||
|
| Reporting allocation of a plan invoice across work | fin-hub | Local `FIN-WP-0002`-family derivation |
|
||||||
| Implied €/token and work-per-token / work-per-euro | fin-hub | Reporting overlay; never a booked price |
|
| Implied €/token and work-per-token / work-per-euro | fin-hub | Reporting overlay; never a booked price |
|
||||||
| Vendor parsers, transcripts, model names, list prices | State Hub / llm-connect / operators | Not invented here |
|
| Vendor parsers, transcripts, model names, list prices | State Hub / llm-connect / operators | Not invented here |
|
||||||
|
|
||||||
Reuse `docs/fin-resource-authority-contract-v0.1.md` shapes
|
Reuse the *discipline* of contract v0.1 (unknown is not zero, residual
|
||||||
(`booked_cost`, `usage_observation`, `AllocationEvidence`). Resource-control
|
visible, estimates are not booked). Do not impersonate resource-control
|
||||||
is the wrong *producer* for AI-session usage; the *envelope* is the right
|
record types for agent-session telemetry or work-effectiveness shares.
|
||||||
one. A new AI-plan producer (State Hub aggregates, plus fin-hub's own
|
|
||||||
entitlement and booked facts) fills that role.
|
Founder-private AI plans may be booked for runway. They stay
|
||||||
|
`resource_id=null` and must not silent-default `financial_entity_id`
|
||||||
|
until the spend is booked to an entity and, if Railiance-procured, a
|
||||||
|
resource.
|
||||||
|
|
||||||
## Non-goals
|
## Non-goals
|
||||||
|
|
||||||
|
|
@ -67,6 +81,9 @@ entitlement and booked facts) fills that role.
|
||||||
- Completing the Scaleway `platform:audit-storage` booked-cost round trip
|
- Completing the Scaleway `platform:audit-storage` booked-cost round trip
|
||||||
(`FIN-WP-0004-T05`). That is class `S`, not class `I`.
|
(`FIN-WP-0004-T05`). That is class `S`, not class `I`.
|
||||||
- Owning session parsers or storing transcripts.
|
- Owning session parsers or storing transcripts.
|
||||||
|
- Inventing a `resource_id` or metering tokens against a provision.
|
||||||
|
- Emitting `usage_observation` or `AllocationEvidence` as if this repo
|
||||||
|
were resource-control.
|
||||||
- A canonical token↔hour or token↔euro exchange rate (ITC-CAP forbids
|
- A canonical token↔hour or token↔euro exchange rate (ITC-CAP forbids
|
||||||
asserting one; substitution is observed from the time series).
|
asserting one; substitution is observed from the time series).
|
||||||
- A new definition of “work” beyond joining to existing task / progress
|
- A new definition of “work” beyond joining to existing task / progress
|
||||||
|
|
@ -99,7 +116,7 @@ estimator method starts a new series.
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: FIN-WP-0007-T00
|
id: FIN-WP-0007-T00
|
||||||
status: todo
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "5e9bccfd-569a-43a2-b016-afed760add49"
|
state_hub_task_id: "5e9bccfd-569a-43a2-b016-afed760add49"
|
||||||
```
|
```
|
||||||
|
|
@ -117,11 +134,21 @@ Done when the note is committed, `SCOPE.md` matches the boundary, and
|
||||||
no task below has an ambiguous owner for booked cost, entitlement,
|
no task below has an ambiguous owner for booked cost, entitlement,
|
||||||
session tokens, or work.
|
session tokens, or work.
|
||||||
|
|
||||||
|
Completed 2026-08-15: boundary-checked against resource-control SCOPE,
|
||||||
|
INTENT, Terms §4.1/§6, their contract ratification, evidence-basis, and
|
||||||
|
the backup restatement. They own intelligence *resource* identity and
|
||||||
|
provision-level class `I` metering (not yet implemented). Session
|
||||||
|
telemetry and work-effectiveness stay outside that. Note:
|
||||||
|
`docs/ai-plan-token-effectiveness.md`. `SCOPE.md` and `INTENT.md`
|
||||||
|
updated. T04/T05 rewritten so fin-hub does not impersonate
|
||||||
|
`usage_observation` or `AllocationEvidence`. Proposed v0.1 addendum
|
||||||
|
offered to resource-control; not assumed.
|
||||||
|
|
||||||
## Stop treating missing tokens as zero
|
## Stop treating missing tokens as zero
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: FIN-WP-0007-T01
|
id: FIN-WP-0007-T01
|
||||||
status: todo
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "aefa287e-0e4d-40b1-adf6-a8e6df7caeb2"
|
state_hub_task_id: "aefa287e-0e4d-40b1-adf6-a8e6df7caeb2"
|
||||||
```
|
```
|
||||||
|
|
@ -139,6 +166,11 @@ Done when the existing fixture still parses, a missing-token fixture
|
||||||
does not emit zeros, and CAP-R8 (`unknown` never stored as zero) holds
|
does not emit zeros, and CAP-R8 (`unknown` never stored as zero) holds
|
||||||
on the token path.
|
on the token path.
|
||||||
|
|
||||||
|
Completed 2026-08-15: `parse_optional_token_count` treats empty as
|
||||||
|
unknown and `0` as measured none. `TokenSpend.tokens_in/out` are
|
||||||
|
nullable with no zero default. Ledger import of a cost-only Anthropic
|
||||||
|
row still books the amount and does not invent token quantities.
|
||||||
|
|
||||||
## Book AI-plan invoices as facts and commitments
|
## Book AI-plan invoices as facts and commitments
|
||||||
|
|
||||||
```task
|
```task
|
||||||
|
|
@ -186,7 +218,7 @@ invent a token ceiling.
|
||||||
Done when a plan month can be reported as booked euros + entitled
|
Done when a plan month can be reported as booked euros + entitled
|
||||||
capacity (or explicit unknown) without reading session logs.
|
capacity (or explicit unknown) without reading session logs.
|
||||||
|
|
||||||
## Consume State Hub token aggregates as usage observations
|
## Join State Hub session-token aggregates
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: FIN-WP-0007-T04
|
id: FIN-WP-0007-T04
|
||||||
|
|
@ -195,21 +227,28 @@ priority: high
|
||||||
state_hub_task_id: "29f1804f-a5ad-46b6-b9d7-8f074e31b04e"
|
state_hub_task_id: "29f1804f-a5ad-46b6-b9d7-8f074e31b04e"
|
||||||
```
|
```
|
||||||
|
|
||||||
After T00, ingest State Hub token summaries as `usage_observation`
|
After T00, ingest State Hub token summaries as **fin-hub session-token
|
||||||
records (or a compatible typed sibling). Preserve `measurement_kind`
|
evidence**, not as resource-control `usage_observation`. Preserve
|
||||||
(`measured`, `allocated`, `estimated`, `superseded`), period, model,
|
`measurement_kind` (`measured`, `allocated`, `estimated`,
|
||||||
repo/workplan association, confidence, and coverage. Do not re-parse
|
`superseded`), period, model, repo/workplan association, confidence,
|
||||||
Codex/Claude/Cursor/Grok session files in this repo.
|
and coverage. Map kinds onto the resource-control evidence-basis
|
||||||
|
vocabulary (`measured` / `derived` / `estimated` / excluded /
|
||||||
|
`unknown`). Do not re-parse Codex, Claude, Cursor, or Grok session
|
||||||
|
files. Do not invent a `resource_id`.
|
||||||
|
|
||||||
|
If resource-control later inventories a Railiance-procured
|
||||||
|
intelligence resource and emits `usage_observation` for it, consume
|
||||||
|
that through the existing `FIN-WP-0004` planning-evidence path.
|
||||||
|
|
||||||
Superseded rows must not enter totals. Estimated and measured must
|
Superseded rows must not enter totals. Estimated and measured must
|
||||||
remain separable. Months with no State Hub events produce an explicit
|
remain separable. Months with no State Hub events produce an explicit
|
||||||
unknown residual, not a zero-consumption observation.
|
unknown residual, not a zero-consumption observation.
|
||||||
|
|
||||||
Done when a recorded fixture of mixed measured/estimated/superseded
|
Done when a recorded fixture of mixed measured/estimated/superseded
|
||||||
aggregates stores as usage evidence, reconciles by kind, and cannot be
|
aggregates stores as session-token evidence, reconciles by kind, and
|
||||||
posted as booked spend.
|
cannot be posted as booked spend or as resource-control usage.
|
||||||
|
|
||||||
## Allocate booked plan cost across work
|
## Reporting-allocate booked plan cost across work
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: FIN-WP-0007-T05
|
id: FIN-WP-0007-T05
|
||||||
|
|
@ -219,17 +258,23 @@ state_hub_task_id: "db098143-90b7-475c-8846-e4c274e50b4a"
|
||||||
```
|
```
|
||||||
|
|
||||||
After T02 and T04, allocate each AI-plan `financial_fact_id` across
|
After T02 and T04, allocate each AI-plan `financial_fact_id` across
|
||||||
repos/workplans using `AllocationEvidence`: named method, shares, and
|
repos/workplans as a **fin-hub reporting allocation** (same family as
|
||||||
explicit residual. Allowed first methods: measured-token share,
|
`FIN-WP-0002` billing basis). This is not resource-control
|
||||||
measured+estimated-token share, session-count share, even split.
|
`AllocationEvidence` and must not be posted as technical allocation.
|
||||||
Method and estimator version are part of the record identity.
|
|
||||||
|
|
||||||
Shares plus residual must equal 1. Unattributed and unmeasured shares
|
Allowed first methods: measured-token share, measured+estimated-token
|
||||||
stay visible. Allocation never writes a new booked fact.
|
share, session-count share, even split. Method and estimator version
|
||||||
|
are part of the record identity. Shares plus residual must equal 1.
|
||||||
|
Unattributed and unmeasured shares stay visible. Allocation never
|
||||||
|
writes a new booked fact.
|
||||||
|
|
||||||
|
Technical allocation of a portfolio resource remains
|
||||||
|
resource-control's job and only applies when they have published a
|
||||||
|
`resource_id`.
|
||||||
|
|
||||||
Done when a one-plan, two-workplan fixture reconciles to the booked
|
Done when a one-plan, two-workplan fixture reconciles to the booked
|
||||||
effective amount, names its method, and keeps residual when coverage
|
effective amount, names its method, keeps residual when coverage is
|
||||||
is incomplete.
|
incomplete, and is not stored as `AllocationEvidence`.
|
||||||
|
|
||||||
## Publish an effectiveness report
|
## Publish an effectiveness report
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue