fin-hub/tests/test_reporting_allocation.py
tegwick efd1b31001 feat: reporting-allocate AI-plan cost across work
Split a booked AI-plan fact across workplans or repos using measured
tokens, measured+estimated tokens, session counts, or an even split.
Unmeasured remainder stays a residual. The record is not resource-control
AllocationEvidence and does not create booked spend.
2026-08-15 19:35:43 +02:00

98 lines
4 KiB
Python

import sqlite3
from decimal import Decimal
from pathlib import Path
import pytest
from fin_hub.services.allocation import shared_cost_allocations
from fin_hub.services.exchange import ingest_planning_evidence
from fin_hub.services.ledger import import_csv, monthly_summary
from fin_hub.services.reporting_allocation import (
allocate_ai_plan_report,
list_ai_plan_allocations,
)
from fin_hub.services.session_tokens import ingest_session_token_file
FIXTURES = Path(__file__).parent / "fixtures"
def _claude_fact(tmp_path: Path) -> tuple[Path, str]:
ledger = tmp_path / "ledger.db"
import_csv(FIXTURES / "ai-plans.csv", "ai-plan", ledger_path=ledger)
with sqlite3.connect(ledger) as conn:
fact_id = conn.execute(
"SELECT financial_fact_id FROM ledger_entries "
"WHERE source_type = 'ai-plan' AND label = 'claude-max' AND is_current = 1"
).fetchone()[0]
return ledger, fact_id
def test_measured_token_share_two_workplans_keeps_unmeasured_residual(tmp_path: Path):
ledger, fact_id = _claude_fact(tmp_path)
ingest_session_token_file(
FIXTURES / "session-tokens-two-workplans.json", ledger_path=ledger
)
report = allocate_ai_plan_report(
fact_id, method="measured_token_share", ledger_path=ledger
)
by_key = {target.target_key: target for target in report.targets}
assert report.booked_amount == Decimal("200.00")
assert report.method == "measured_token_share"
assert report.residual_reason == "unmeasured"
assert report.residual_share == Decimal("0.25")
assert report.residual_amount == Decimal("50.00")
assert by_key["workplan:workplan-a"].amount == Decimal("100.00")
assert by_key["workplan:workplan-b"].amount == Decimal("50.00")
assert sum((target.amount for target in report.targets), report.residual_amount) == Decimal(
"200.00"
)
assert monthly_summary(ledger_path=ledger)[0].total == pytest.approx(240.0)
assert shared_cost_allocations(ledger_path=ledger) == []
assert list_ai_plan_allocations(ledger_path=ledger)[0].record_id == report.record_id
def test_even_split_does_not_use_token_weights(tmp_path: Path):
ledger, fact_id = _claude_fact(tmp_path)
ingest_session_token_file(
FIXTURES / "session-tokens-two-workplans.json", ledger_path=ledger
)
report = allocate_ai_plan_report(fact_id, method="even_split", ledger_path=ledger)
amounts = sorted(target.amount for target in report.targets)
assert amounts == [Decimal("100.00"), Decimal("100.00")]
assert report.residual_share == Decimal("0.00")
assert report.residual_reason == "none"
def test_reporting_allocation_is_not_planning_allocation(tmp_path: Path):
ledger, fact_id = _claude_fact(tmp_path)
report = allocate_ai_plan_report(fact_id, method="even_split", ledger_path=ledger)
with pytest.raises(ValueError, match="not resource-control"):
ingest_planning_evidence(
{
"record_type": "reporting_allocation",
"record_id": report.record_id,
"financial_fact_id": fact_id,
"method": "even_split",
},
ledger_path=ledger,
)
with sqlite3.connect(ledger) as conn:
planning = conn.execute(
"SELECT count(*) FROM sqlite_master WHERE name = 'planning_evidence'"
).fetchone()[0]
if planning:
stored = conn.execute(
"SELECT record_type FROM planning_evidence"
).fetchall()
assert stored == []
def test_non_ai_plan_fact_is_rejected(tmp_path: Path):
ledger = tmp_path / "ledger.db"
import_csv(FIXTURES / "cloud-costs.csv", "cloud", ledger_path=ledger)
with sqlite3.connect(ledger) as conn:
fact_id = conn.execute(
"SELECT financial_fact_id FROM ledger_entries WHERE is_current = 1"
).fetchone()[0]
with pytest.raises(ValueError, match="ai-plan"):
allocate_ai_plan_report(fact_id, method="even_split", ledger_path=ledger)