import sqlite3 from decimal import Decimal from pathlib import Path from fin_hub.services.billing import build_billing_basis from fin_hub.services.exchange import ingest_planning_evidence from fin_hub.services.ledger import import_csv, record_engagement_price CLIENT_KEY = "client:acme|app:portal|instance:prod-01" def _seed_billing_ledger(tmp_path: Path): ledger = tmp_path / "ledger.db" direct = tmp_path / "direct.csv" direct.write_text( "product,amount,currency,invoice_date,environment,client_id,application_id,app_instance_id\n" "Dedicated service,20.00,EUR,2026-07-01,production,acme,portal,prod-01\n", encoding="utf-8", ) shared = tmp_path / "shared.csv" shared.write_text( "product,amount,currency,invoice_date,environment\n" "Shared cluster,100.00,EUR,2026-07-01,production\n", encoding="utf-8", ) import_csv(direct, "hosteurope", ledger_path=ledger) import_csv(shared, "hosteurope", ledger_path=ledger) with sqlite3.connect(ledger) as conn: shared_fact_id = conn.execute( "SELECT financial_fact_id FROM ledger_entries " "WHERE cost_attribution_key IS NULL" ).fetchone()[0] price = record_engagement_price( client_id="acme", application_id="portal", app_instance_id="prod-01", period_month="2026-07", amount="200.00", currency="EUR", source="agreement-v1", ledger_path=ledger, ) allocation = { "schema_version": "0.1", "record_type": "allocation", "record_id": "allocation:shared:2026-07:v1", "revision_of": None, "resource_id": "resource:shared_cluster", "service_id": "shared-cluster", "workload_id": None, "tenant_id": None, "environment": "production", "cost_attribution_key": "platform:shared-cluster", "period_start": "2026-07-01", "period_end": "2026-07-31", "currency": "EUR", "source_evidence": ["resource-control:namespace-cpu-v1"], "created_at": "2026-08-11T10:00:00Z", "financial_fact_ids": [shared_fact_id], "method": "namespace-cpu-v1", "allocated_amount": "100.00", "shares": [{"target_key": CLIENT_KEY, "share": "0.80"}], "residual_share": "0.20", } ingest_planning_evidence(allocation, ledger_path=ledger) return ledger, direct, price, allocation def test_billing_basis_combines_direct_and_allocated_cost_idempotently(tmp_path: Path): ledger, _direct, price, allocation = _seed_billing_ledger(tmp_path) first = build_billing_basis(ledger_path=ledger) second = build_billing_basis(ledger_path=ledger) assert first == second assert first.artifact_type == "billing_basis_report" assert "not an invoice" in first.disclaimer record = first.records[0] assert record.price_id == price.id assert record.revenue == Decimal("200.00") assert record.direct_cost == Decimal("20.00") assert record.allocated_cost == Decimal("80.00") assert record.total_cost == Decimal("100.00") assert record.margin == Decimal("100.00") assert record.allocation_ids == (allocation["record_id"],) assert record.financial_fact_ids assert "agreement-v1" in record.provenance assert first.exceptions[0].reason == "unattributed_allocation_residual" assert first.exceptions[0].amount == Decimal("20.00") def test_billing_basis_id_changes_and_exposes_correction_and_price_revision(tmp_path: Path): ledger, direct, price, _allocation = _seed_billing_ledger(tmp_path) before = build_billing_basis(ledger_path=ledger).records[0] direct.write_text( "product,amount,currency,invoice_date,environment,client_id,application_id,app_instance_id\n" "Dedicated service,25.00,EUR,2026-07-01,production,acme,portal,prod-01\n", encoding="utf-8", ) import_csv(direct, "hosteurope", ledger_path=ledger, force=True) revised_price = record_engagement_price( client_id="acme", application_id="portal", app_instance_id="prod-01", period_month="2026-07", amount="210.00", currency="EUR", source="agreement-v2", revision_of=price.id, ledger_path=ledger, ) after = build_billing_basis(ledger_path=ledger).records[0] assert after.billing_basis_id != before.billing_basis_id assert after.price_id == revised_price.id assert after.price_revision_of == price.id assert after.financial_fact_corrections assert after.direct_cost == Decimal("25.00") assert after.margin == Decimal("105.00") def test_client_cost_without_price_is_an_explicit_exception(tmp_path: Path): ledger = tmp_path / "ledger.db" source = tmp_path / "direct.csv" source.write_text( "product,amount,currency,invoice_date,client_id,application_id,app_instance_id\n" "Dedicated service,20.00,EUR,2026-07-01,acme,portal,prod-01\n", encoding="utf-8", ) import_csv(source, "hosteurope", ledger_path=ledger) export = build_billing_basis(ledger_path=ledger) assert export.records == () assert export.exceptions[0].reason == "missing_price_basis" assert export.exceptions[0].cost_attribution_key == CLIENT_KEY assert export.exceptions[0].amount == Decimal("20.00")