Harden resource cost evidence contract

This commit is contained in:
tegwick 2026-08-11 10:25:03 +02:00
parent 080f756fff
commit 00343307fd
22 changed files with 1623 additions and 130 deletions

View file

@ -21,8 +21,11 @@ def test_build_service_cost_report_groups_by_service():
report = build_service_cost_report(lines)
assert report["signal"] == "service_cost_attribution"
assert len(report["services"]) == 2
assert report["totals_by_month"]["2026-06"] == pytest.approx(99.8)
assert report["totals_by_period_currency"] == [
{"period_month": "2026-06", "currency": "EUR", "total": pytest.approx(99.8)}
]
assert report["attributions"][0]["cost_attribution_key"] is None
assert report["unattributed"][0]["total"] == pytest.approx(99.8)
def test_build_service_cost_report_groups_by_client_attribution():
@ -49,6 +52,7 @@ def test_build_service_cost_report_groups_by_client_attribution():
"client_id": "acme",
"application_id": "portal",
"app_instance_id": "prod-01",
"environment": "production",
"currency": "EUR",
"months": {"2026-07": 42.0},
"total": 42.0,
@ -77,3 +81,31 @@ def test_client_attribution_totals_do_not_mix_currencies():
("EUR", 42.0),
("USD", 50.0),
}
def test_service_totals_do_not_mix_currency_or_environment():
common = {
"service_id": "cluster",
"period_month": "2026-07",
"source": "fixture",
}
report = build_service_cost_report(
[
ServiceCostLine(amount=10.0, currency="EUR", environment="production", **common),
ServiceCostLine(amount=20.0, currency="USD", environment="production", **common),
ServiceCostLine(amount=30.0, currency="EUR", environment="development", **common),
]
)
assert {
(row["environment"], row["currency"], row["total"])
for row in report["services"]
} == {
("production", "EUR", 10.0),
("production", "USD", 20.0),
("development", "EUR", 30.0),
}
assert report["totals_by_period_currency"] == [
{"period_month": "2026-07", "currency": "EUR", "total": 40.0},
{"period_month": "2026-07", "currency": "USD", "total": 20.0},
]

207
tests/test_exchange.py Normal file
View file

@ -0,0 +1,207 @@
from datetime import date, datetime, timezone
from decimal import Decimal
from pathlib import Path
import pytest
from pydantic import ValidationError
from fin_hub.models.engagement_price import EngagementPrice, _validate_engagement_price
from fin_hub.schemas.exchange import (
AllocationEvidence,
BookedCostEvidence,
ForecastEvidence,
)
from fin_hub.services.exchange import (
booked_cost_projection,
ingest_planning_evidence,
ingest_resource_forecast,
)
from fin_hub.services.ledger import import_csv
def _common_planning() -> dict:
return {
"schema_version": "0.1",
"record_id": "forecast:platform-audit-storage:2026-08",
"revision_of": None,
"resource_id": "resource:platform_audit_storage",
"service_id": "object-storage",
"workload_id": "platform-pg",
"tenant_id": None,
"environment": "production",
"cost_attribution_key": "platform:audit-storage",
"period_start": "2026-09-01",
"period_end": "2027-08-31",
"currency": "eur",
"source_evidence": ["resource-control:data/forecasts/platform-audit-storage"],
"created_at": "2026-08-10T17:10:00Z",
}
def test_booked_cost_money_relationships_and_decimal_json():
record = BookedCostEvidence(
financial_fact_id="fact:1",
correction_of=None,
adjustment_kind="charge",
source_type="provider_invoice",
source_document_id="invoice:1",
source_line_id="invoice:1:line:1",
content_fingerprint="sha256:abc",
provider="provider",
accounting_period="2026-07",
currency="eur",
net_amount="10.005",
discount_amount="1.00",
tax_status="known",
tax_amount="1.90",
gross_amount="10.90",
adjustment_amount="0",
effective_amount="10.90",
source_evidence_ref="invoice:1",
recorded_at=datetime.now(timezone.utc),
)
assert record.net_amount == Decimal("10.00")
assert record.currency == "EUR"
assert '"gross_amount":"10.90"' in record.model_dump_json()
def test_booked_cost_rejects_invalid_relationships():
with pytest.raises(ValidationError, match="gross_amount"):
BookedCostEvidence(
financial_fact_id="fact:1",
adjustment_kind="charge",
source_type="invoice",
source_document_id="doc",
source_line_id="line",
content_fingerprint="hash",
provider="provider",
accounting_period="2026-07",
currency="EUR",
net_amount="10",
discount_amount="0",
tax_status="known",
tax_amount="1.90",
gross_amount="12",
effective_amount="12",
source_evidence_ref="doc",
recorded_at=datetime.now(timezone.utc),
)
def test_planning_forecast_is_typed_and_idempotent(tmp_path: Path):
payload = {
**_common_planning(),
"record_type": "forecast",
"scenario": "base",
"forecast_version": "2026-08-base",
"costs": {
"infrastructure": "2.89",
"internal_labor": "60",
"external_services": "0",
"setup": "0",
"other": "0",
},
"uncertainty": "provider price excludes unknown tax",
"assumptions": ["180 GB stored"],
}
ledger = tmp_path / "ledger.db"
first = ingest_planning_evidence(payload, ledger_path=ledger)
second = ingest_planning_evidence(payload, ledger_path=ledger)
assert isinstance(first, ForecastEvidence)
assert first.costs.total == Decimal("62.89")
assert second.record_id == first.record_id
def test_resource_control_backup_forecast_enters_planning_store(tmp_path: Path):
payload = {
"schema_version": "0.1",
"record_type": "forecast",
"workload": "platform-pg",
"cost_attribution_key": "platform:audit-storage",
"provider_id": "scaleway-standard-multi-az",
"created_at": "2026-08-10T17:10:00Z",
"scenario": "base",
"forecast_ref": None,
"rows": [
{
"period": "2026-09",
"database_gb": 5,
"stored_gb": 180,
"wal_gb": 30,
"restore_egress_gb": 5,
"write_requests": 2500,
"read_requests": 1000,
"infrastructure_eur": 2.89,
"internal_labor_hours": 1,
"internal_labor_eur": 60,
"total_eur": 62.89,
}
],
}
records = ingest_resource_forecast(
payload,
resource_id="resource:platform_audit_storage",
source_evidence_ref="resource-control:data/forecasts/platform-audit-storage",
ledger_path=tmp_path / "ledger.db",
)
assert len(records) == 1
assert isinstance(records[0], ForecastEvidence)
assert records[0].costs.total == Decimal("62.89")
def test_allocation_requires_shares_and_residual_to_reconcile():
payload = {
**_common_planning(),
"record_type": "allocation",
"record_id": "allocation:1",
"financial_fact_ids": ["fact:1"],
"method": "namespace-cpu-v1",
"allocated_amount": "10.00",
"shares": [{"target_key": "client:acme", "share": "0.75"}],
"residual_share": "0.20",
}
with pytest.raises(ValidationError, match="must equal 1"):
AllocationEvidence.model_validate(payload)
def test_booked_cost_projection_uses_current_corrected_fact(tmp_path: Path):
source = tmp_path / "cost.csv"
ledger = tmp_path / "ledger.db"
source.write_text(
"product,amount,currency,invoice_date,environment\n"
"Server,10.00,EUR,2026-07-01,production\n",
encoding="utf-8",
)
import_csv(source, "hosteurope", ledger_path=ledger)
source.write_text(
"product,amount,currency,invoice_date,environment\n"
"Server,12.00,EUR,2026-07-01,production\n",
encoding="utf-8",
)
import_csv(source, "hosteurope", ledger_path=ledger, force=True)
records = booked_cost_projection(ledger_path=ledger)
assert len(records) == 1
assert records[0].effective_amount == Decimal("12.00")
assert records[0].correction_of is not None
assert records[0].environment == "production"
def test_sqlalchemy_price_uses_shared_invariants():
price = EngagementPrice(
client_id="acme",
application_id="portal",
app_instance_id="prod-01",
cost_attribution_key="wrong",
effective_from=date(2026, 7, 1),
amount=Decimal("100.001"),
currency="eur",
source="agreement",
)
with pytest.raises(ValueError, match="does not match"):
_validate_engagement_price(None, None, price)

View file

@ -1,4 +1,6 @@
import sqlite3
import os
import shutil
from pathlib import Path
import pytest
@ -11,6 +13,7 @@ from fin_hub.services.ledger import (
monthly_burn_series,
monthly_summary,
record_engagement_price,
reverse_financial_fact,
set_opening_balance,
)
@ -44,6 +47,86 @@ def test_ledger_import_skips_unchanged_file(tmp_path: Path):
assert second.rows_imported == 0
def test_ledger_import_is_idempotent_when_file_is_renamed_or_touched(tmp_path: Path):
ledger = tmp_path / "ledger.db"
source = tmp_path / "costs.csv"
shutil.copy(FIXTURES / "cloud-costs.csv", source)
renamed = tmp_path / "renamed.csv"
first = import_csv(source, "cloud", ledger_path=ledger)
os.utime(source, (source.stat().st_atime, source.stat().st_mtime + 1))
touched = import_csv(source, "cloud", ledger_path=ledger)
shutil.copy(source, renamed)
delivered_again = import_csv(renamed, "cloud", ledger_path=ledger)
assert first.rows_imported == 3
assert touched.rows_imported == 0
assert delivered_again.rows_imported == 0
assert monthly_summary(ledger_path=ledger)[0].entry_count == 3
def test_changed_fact_requires_and_applies_one_correction(tmp_path: Path):
ledger = tmp_path / "ledger.db"
source = tmp_path / "costs.csv"
source.write_text(
"service,amount,currency,period_month\ncompute,10.00,EUR,2026-07\n",
encoding="utf-8",
)
import_csv(source, "cloud", ledger_path=ledger)
source.write_text(
"service,amount,currency,period_month\ncompute,12.00,EUR,2026-07\n",
encoding="utf-8",
)
with pytest.raises(ValueError, match="explicit correction"):
import_csv(source, "cloud", ledger_path=ledger)
corrected = import_csv(source, "cloud", ledger_path=ledger, force=True)
duplicate = import_csv(source, "cloud", ledger_path=ledger, force=True)
assert corrected.rows_imported == 1
assert duplicate.rows_imported == 0
rollup = monthly_summary(ledger_path=ledger)[0]
assert rollup.total == 12.0
assert rollup.entry_count == 1
with sqlite3.connect(ledger) as conn:
facts = conn.execute(
"SELECT correction_of, is_current FROM ledger_entries ORDER BY id"
).fetchall()
assert facts[0][1] == 0
assert facts[1][0] is not None
assert facts[1][1] == 1
def test_financial_fact_reversal_is_append_only_and_zeroes_effective_total(tmp_path: Path):
ledger = tmp_path / "ledger.db"
source = tmp_path / "costs.csv"
source.write_text(
"service,amount,currency,period_month\ncompute,10.00,EUR,2026-07\n",
encoding="utf-8",
)
import_csv(source, "cloud", ledger_path=ledger)
with sqlite3.connect(ledger) as conn:
fact_id = conn.execute(
"SELECT financial_fact_id FROM ledger_entries"
).fetchone()[0]
reversal_id = reverse_financial_fact(
fact_id, source="provider-credit-note", ledger_path=ledger
)
rollup = monthly_summary(ledger_path=ledger)[0]
assert rollup.total == 0.0
with sqlite3.connect(ledger) as conn:
rows = conn.execute(
"SELECT financial_fact_id, correction_of, adjustment_kind, is_current "
"FROM ledger_entries ORDER BY id"
).fetchall()
assert rows == [
(fact_id, None, "charge", 0),
(reversal_id, fact_id, "reversal", 1),
]
def test_ledger_import_preserves_client_attribution(tmp_path: Path):
source = tmp_path / "attributed.csv"
source.write_text(

View file

@ -1,5 +1,10 @@
"""Smoke tests for fin-hub model registration."""
from datetime import date
from decimal import Decimal
import pytest
from fin_hub.models import (
Budget,
BurnRate,
@ -9,6 +14,8 @@ from fin_hub.models import (
ServiceCost,
TokenSpend,
)
from fin_hub.models.engagement_price import _validate_engagement_price
from fin_hub.models.service_cost import _normalize_service_cost_attribution
from hub_core.models.base import Base
@ -39,3 +46,41 @@ def test_service_cost_has_external_attribution_seam():
assert columns["application_id"].nullable is True
assert columns["app_instance_id"].nullable is True
assert columns["cost_attribution_key"].nullable is True
def test_service_cost_orm_uses_shared_money_and_period_invariants():
cost = ServiceCost(
service_id="cluster",
environment="production",
period_month="2026-07",
amount=Decimal("10.005"),
currency="eur",
source=" invoice ",
)
_normalize_service_cost_attribution(None, None, cost)
assert cost.amount == Decimal("10.00")
assert cost.currency == "EUR"
assert cost.source == "invoice"
cost.period_month = "2026-13"
with pytest.raises(ValueError, match="YYYY-MM"):
_normalize_service_cost_attribution(None, None, cost)
def test_engagement_price_orm_normalizes_same_basis_as_sqlite():
price = EngagementPrice(
client_id="acme",
application_id="portal",
app_instance_id="prod-01",
cost_attribution_key=None,
period_month="placeholder",
effective_from=date(2026, 7, 15),
amount=Decimal("100.005"),
currency="eur",
source=" agreement ",
)
_validate_engagement_price(None, None, price)
assert price.period_month == "2026-07"
assert price.amount == Decimal("100.00")
assert price.currency == "EUR"
assert price.source == "agreement"