Advance resource cost evidence contract
This commit is contained in:
parent
d1528944c3
commit
49519a715a
7 changed files with 631 additions and 21 deletions
|
|
@ -13,6 +13,8 @@ from fin_hub.schemas.exchange import (
|
|||
)
|
||||
from fin_hub.services.exchange import (
|
||||
booked_cost_projection,
|
||||
exchange_health,
|
||||
financial_constraint_projection,
|
||||
ingest_planning_evidence,
|
||||
ingest_resource_forecast,
|
||||
)
|
||||
|
|
@ -192,6 +194,33 @@ def test_booked_cost_projection_uses_current_corrected_fact(tmp_path: Path):
|
|||
assert records[0].environment == "production"
|
||||
|
||||
|
||||
def test_hosteurope_fact_joins_external_resource_by_stable_fact_id(tmp_path: Path):
|
||||
source = tmp_path / "hosteurope.csv"
|
||||
ledger = tmp_path / "ledger.db"
|
||||
source.write_text(
|
||||
"product,amount,currency,invoice_date,environment\n"
|
||||
"Server,49.00,EUR,2026-07-01,production\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
import_csv(source, "hosteurope", ledger_path=ledger)
|
||||
fact = booked_cost_projection(ledger_path=ledger)[0]
|
||||
|
||||
joined = booked_cost_projection(
|
||||
ledger_path=ledger,
|
||||
fact_resource_ids={
|
||||
fact.financial_fact_id: "resource:hosteurope:railiance01"
|
||||
},
|
||||
)
|
||||
|
||||
assert joined[0].resource_id == "resource:hosteurope:railiance01"
|
||||
assert joined[0].provider == "hosteurope"
|
||||
with pytest.raises(ValueError, match="unknown current financial facts"):
|
||||
booked_cost_projection(
|
||||
ledger_path=ledger,
|
||||
fact_resource_ids={"fact:unknown": "resource:hosteurope:railiance01"},
|
||||
)
|
||||
|
||||
|
||||
def test_sqlalchemy_price_uses_shared_invariants():
|
||||
price = EngagementPrice(
|
||||
client_id="acme",
|
||||
|
|
@ -205,3 +234,110 @@ def test_sqlalchemy_price_uses_shared_invariants():
|
|||
)
|
||||
with pytest.raises(ValueError, match="does not match"):
|
||||
_validate_engagement_price(None, None, price)
|
||||
|
||||
|
||||
def test_financial_constraints_are_bounded_typed_and_idempotent():
|
||||
kwargs = {
|
||||
"domain_slug": "financials",
|
||||
"period_start": date(2026, 8, 1),
|
||||
"period_end": date(2026, 8, 31),
|
||||
"currency": "eur",
|
||||
"budget_ceiling": "100.00",
|
||||
"active_commitment": "20.00",
|
||||
"spent": "85.00",
|
||||
"monthly_burn": "30.00",
|
||||
"runway_months": "2.5",
|
||||
"runway_threshold_months": "3",
|
||||
"resource_id": "resource:hosteurope:railiance01",
|
||||
"source_evidence": ("fin-hub:budget:2026-08", "fin-hub:runway:2026-08"),
|
||||
"generated_at": datetime(2026, 8, 11, 13, 0, tzinfo=timezone.utc),
|
||||
}
|
||||
|
||||
first = financial_constraint_projection(**kwargs)
|
||||
second = financial_constraint_projection(**kwargs)
|
||||
|
||||
assert first == second
|
||||
assert [signal.signal_kind for signal in first.signals] == [
|
||||
"budget_ceiling",
|
||||
"active_commitment",
|
||||
"burn_pressure",
|
||||
"runway_pressure",
|
||||
]
|
||||
assert first.signals[0].classification == "policy_constraint"
|
||||
assert first.signals[2].classification == "informational_warning"
|
||||
assert first.signals[3].metric_value == Decimal("2.5")
|
||||
assert all(signal.currency == "EUR" for signal in first.signals)
|
||||
assert "Not payment authority" in first.disclaimer
|
||||
|
||||
|
||||
def test_financial_constraints_do_not_invent_missing_authoritative_values():
|
||||
export = financial_constraint_projection(
|
||||
domain_slug="financials",
|
||||
period_start=date(2026, 8, 1),
|
||||
period_end=date(2026, 8, 31),
|
||||
currency="EUR",
|
||||
source_evidence=("fin-hub:budget:none",),
|
||||
generated_at=datetime(2026, 8, 11, 13, 0, tzinfo=timezone.utc),
|
||||
)
|
||||
|
||||
assert export.signals == ()
|
||||
|
||||
|
||||
def test_financial_constraint_rejects_invalid_period_or_unbounded_provenance():
|
||||
with pytest.raises(ValueError, match="period_end"):
|
||||
financial_constraint_projection(
|
||||
domain_slug="financials",
|
||||
period_start=date(2026, 9, 1),
|
||||
period_end=date(2026, 8, 31),
|
||||
currency="EUR",
|
||||
source_evidence=("fin-hub:budget",),
|
||||
)
|
||||
with pytest.raises(ValueError, match="source_evidence"):
|
||||
financial_constraint_projection(
|
||||
domain_slug="financials",
|
||||
period_start=date(2026, 8, 1),
|
||||
period_end=date(2026, 8, 31),
|
||||
currency="EUR",
|
||||
source_evidence=(),
|
||||
)
|
||||
|
||||
|
||||
def test_exchange_health_exposes_rejection_stale_forecast_and_unattributed_cost(
|
||||
tmp_path: Path,
|
||||
):
|
||||
ledger = tmp_path / "ledger.db"
|
||||
source = tmp_path / "hosteurope.csv"
|
||||
source.write_text(
|
||||
"product,amount,currency,invoice_date,environment\n"
|
||||
"Shared service,10.00,EUR,2026-07-01,production\n",
|
||||
encoding="utf-8",
|
||||
)
|
||||
import_csv(source, "hosteurope", ledger_path=ledger)
|
||||
stale = {
|
||||
**_common_planning(),
|
||||
"record_type": "forecast",
|
||||
"record_id": "forecast:stale",
|
||||
"period_start": "2026-01-01",
|
||||
"period_end": "2026-01-31",
|
||||
"scenario": "base",
|
||||
"forecast_version": "v1",
|
||||
"costs": {},
|
||||
"assumptions": [],
|
||||
}
|
||||
ingest_planning_evidence(stale, ledger_path=ledger)
|
||||
invalid = {**stale, "record_id": "forecast:rejected", "currency": "EURO"}
|
||||
with pytest.raises(ValidationError):
|
||||
ingest_planning_evidence(invalid, ledger_path=ledger)
|
||||
with pytest.raises(ValidationError):
|
||||
ingest_planning_evidence(invalid, ledger_path=ledger)
|
||||
|
||||
health = exchange_health(ledger_path=ledger, as_of=date(2026, 8, 11))
|
||||
|
||||
assert health.booked_fact_count == 1
|
||||
assert health.current_planning_record_count == 1
|
||||
assert health.rejected_delivery_count == 2
|
||||
assert {issue.code for issue in health.issues} == {
|
||||
"rejected_planning_delivery",
|
||||
"stale_forecast",
|
||||
"unattributed_booked_cost",
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue