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
|
|
@ -2,7 +2,7 @@ from pathlib import Path
|
|||
|
||||
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.hosteurope import parse_hosteurope_csv
|
||||
|
||||
|
|
@ -21,6 +21,43 @@ def test_parse_anthropic_billing_csv():
|
|||
assert len(rows) == 2
|
||||
assert rows[0].provider == "anthropic"
|
||||
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():
|
||||
|
|
|
|||
|
|
@ -20,6 +20,20 @@ from fin_hub.services.ledger import (
|
|||
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):
|
||||
ledger = tmp_path / "ledger.db"
|
||||
set_opening_balance(ledger, 12000.0)
|
||||
|
|
|
|||
|
|
@ -40,6 +40,14 @@ def test_model_classes_importable():
|
|||
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():
|
||||
columns = ServiceCost.__table__.columns
|
||||
assert columns["client_id"].nullable is True
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue