From 999872a537050d780ac6826df2e6c6542894e4e3 Mon Sep 17 00:00:00 2001 From: rp-maintainers Date: Fri, 2 Oct 2026 17:42:38 +0000 Subject: [PATCH] feat(finops): mirror daily typed cost evidence --- src/routeplane/__init__.py | 40 +++ src/routeplane/resources/__init__.py | 40 +++ src/routeplane/resources/finops.py | 381 +++++++++++++++++++++- tests/test_resources.py | 459 +++++++++++++++++++++++++++ 4 files changed, 917 insertions(+), 3 deletions(-) diff --git a/src/routeplane/__init__.py b/src/routeplane/__init__.py index e96c35e..ccd9963 100644 --- a/src/routeplane/__init__.py +++ b/src/routeplane/__init__.py @@ -17,9 +17,29 @@ from .meta import RouteplaneMeta, RouteplaneRateLimits from .resources import ( CacheSavings, + CostEvidenceComponent, + CostEvidenceCoverage, + CostEvidenceCoverageCount, + CostEvidenceFreshness, + CostEvidenceMoney, + CostEvidenceRateSnapshot, + CostEvidenceScope, + CostEvidenceSource, + CostEvidenceValue, + CostEvidenceWindow, + DailyCostBasis, + DailyCostEvidence, DailyPricingEvidence, DailyUsageReport, Decision, + RateActivationTransition, + RateComponent, + RateQuantityMissingReason, + RateQuantityState, + RateResolution, + RateResolutionMissingReason, + RateScopeClass, + RateUnit, RunStep, SaverMetrics, TimeseriesData, @@ -36,8 +56,28 @@ "RouteplaneRateLimits", "Decision", "CacheSavings", + "CostEvidenceComponent", + "CostEvidenceCoverage", + "CostEvidenceCoverageCount", + "CostEvidenceFreshness", + "CostEvidenceMoney", + "CostEvidenceRateSnapshot", + "CostEvidenceScope", + "CostEvidenceSource", + "CostEvidenceValue", + "CostEvidenceWindow", + "DailyCostBasis", + "DailyCostEvidence", "DailyPricingEvidence", "DailyUsageReport", + "RateActivationTransition", + "RateComponent", + "RateQuantityMissingReason", + "RateQuantityState", + "RateResolution", + "RateResolutionMissingReason", + "RateScopeClass", + "RateUnit", "RunStep", "SaverMetrics", "TimeseriesData", diff --git a/src/routeplane/resources/__init__.py b/src/routeplane/resources/__init__.py index 1bf80a1..f277cc9 100644 --- a/src/routeplane/resources/__init__.py +++ b/src/routeplane/resources/__init__.py @@ -11,9 +11,29 @@ from .feedback import FeedbackResource from .finops import ( CacheSavings, + CostEvidenceComponent, + CostEvidenceCoverage, + CostEvidenceCoverageCount, + CostEvidenceFreshness, + CostEvidenceMoney, + CostEvidenceRateSnapshot, + CostEvidenceScope, + CostEvidenceSource, + CostEvidenceValue, + CostEvidenceWindow, + DailyCostBasis, + DailyCostEvidence, DailyPricingEvidence, DailyUsageReport, FinopsResource, + RateActivationTransition, + RateComponent, + RateQuantityMissingReason, + RateQuantityState, + RateResolution, + RateResolutionMissingReason, + RateScopeClass, + RateUnit, SaverMetrics, TimeseriesData, UsageData, @@ -31,9 +51,29 @@ "AnalyticsResource", "CacheResource", "CacheSavings", + "CostEvidenceComponent", + "CostEvidenceCoverage", + "CostEvidenceCoverageCount", + "CostEvidenceFreshness", + "CostEvidenceMoney", + "CostEvidenceRateSnapshot", + "CostEvidenceScope", + "CostEvidenceSource", + "CostEvidenceValue", + "CostEvidenceWindow", + "DailyCostBasis", + "DailyCostEvidence", "DailyPricingEvidence", "Decision", "DailyUsageReport", + "RateActivationTransition", + "RateComponent", + "RateQuantityMissingReason", + "RateQuantityState", + "RateResolution", + "RateResolutionMissingReason", + "RateScopeClass", + "RateUnit", "FeedbackResource", "FinopsResource", "LogsResource", diff --git a/src/routeplane/resources/finops.py b/src/routeplane/resources/finops.py index 541609d..b2d6dbe 100644 --- a/src/routeplane/resources/finops.py +++ b/src/routeplane/resources/finops.py @@ -2,15 +2,35 @@ from __future__ import annotations -from typing import Literal, Optional, TypedDict +from typing import Literal, Optional, TypedDict, Union from ._base import BaseResource, prune_none __all__ = [ "CacheSavings", + "CostEvidenceComponent", + "CostEvidenceCoverage", + "CostEvidenceCoverageCount", + "CostEvidenceFreshness", + "CostEvidenceMoney", + "CostEvidenceRateSnapshot", + "CostEvidenceScope", + "CostEvidenceSource", + "CostEvidenceValue", + "CostEvidenceWindow", + "DailyCostBasis", + "DailyCostEvidence", "DailyPricingEvidence", "DailyUsageReport", "FinopsResource", + "RateActivationTransition", + "RateComponent", + "RateQuantityMissingReason", + "RateQuantityState", + "RateResolution", + "RateResolutionMissingReason", + "RateScopeClass", + "RateUnit", "SaverMetrics", "TimeseriesData", "UsageData", @@ -52,7 +72,360 @@ class UsageData(_UsageDataRequired, total=False): by_use_case: dict[str, FinOpsUsageTotals] -class DailyModelUsage(TypedDict): +# This is the public daily estimate projection, not billed/reconciled authority. +# TypedDicts describe the wire only: the SDK neither validates nor synthesizes it. +RateComponent = Literal[ + "input_tokens", + "output_tokens", + "reasoning_tokens", + "cache_read_tokens", + "cache_write_tokens", + "embedding_tokens", + "cache_storage", + "search_units", + "rerank_units", + "images", + "audio_input", + "audio_output", + "characters", + "video", + "model_tool_calls", + "executed_mcp_calls", + "provider_built_in_calls", +] +RateUnit = Literal[ + "token", + "byte_second", + "search_unit", + "rerank_unit", + "image", + "audio_second", + "character", + "video_second", + "model_tool_call", + "executed_mcp_call", + "provider_built_in_call", +] +RateScopeClass = Literal[ + "legacy_unspecified", + "provider_public_list", + "tenant_byo_contract", + "routeplane_provider_account", + "smart_credits_tenant_contract", + "smart_credits_platform_card", +] +RateQuantityMissingReason = Literal[ + "provider_usage_absent", + "provider_metric_unsupported", + "usage_payload_truncated", + "usage_attribution_unavailable", +] +RateResolutionMissingReason = Literal[ + "provider_usage_absent", + "model_unpriced", + "actual_service_tier_unpriced", + "component_or_unit_unpriced", + "effective_rate_gap", + "scope_rate_gap", + "actual_service_tier_unavailable", + "ambiguous_rate", + "arithmetic_overflow", + "mixed_currencies", + "mixed_rate_cards", + "pricing_coverage_unavailable", +] + + +class _ObservedQuantity(TypedDict): + state: Literal["observed"] + + +class _PartialQuantity(TypedDict): + state: Literal["partial"] + reason: RateQuantityMissingReason + + +class _UnavailableQuantity(TypedDict): + state: Literal["unavailable"] + reason: RateQuantityMissingReason + + +RateQuantityState = Union[_ObservedQuantity, _PartialQuantity, _UnavailableQuantity] + + +class _EstimatedResolution(TypedDict): + status: Literal["estimated"] + rate_reference: Optional[str] + rate_scope: RateScopeClass + currency: str + estimated_amount_micro: Optional[int] + + +class _UnpricedResolution(TypedDict): + status: Literal["unpriced"] + reason: RateResolutionMissingReason + + +class _UnavailableResolution(TypedDict): + status: Literal["unavailable"] + reason: RateResolutionMissingReason + + +RateResolution = Union[_EstimatedResolution, _UnpricedResolution, _UnavailableResolution] + + +class CostEvidenceComponent(TypedDict): + component: RateComponent + unit: RateUnit + qualifier: Optional[str] + quantity_state: RateQuantityState + resolution: RateResolution + observation_count: Optional[int] + observed_quantity: Optional[int] + amount_unavailable_reason: Optional[ + Literal[ + "outside_json_safe_integer_range", + "aggregate_authority_unavailable", + "incompatible_rate_scope", + "mixed_or_invalid_currency", + ] + ] + + +class _GenesisTransition(TypedDict): + state: Literal["genesis"] + + +class _ContiguousTransition(TypedDict): + state: Literal["contiguous"] + + +class _SkippedTransition(TypedDict): + state: Literal["skipped"] + from_generation: int + to_generation: int + + +RateActivationTransition = Union[_GenesisTransition, _ContiguousTransition, _SkippedTransition] + + +class CostEvidenceRateSnapshot(TypedDict): + snapshot_id: str + snapshot_version: str + generation: int + receipt_posture: Literal["receipt_anchored_current_control_plane_head", "unavailable"] + restart_protection_limited: bool + transition: RateActivationTransition + + +class CostEvidenceMoney(TypedDict): + # Exact signed micro-currency amount, not a float or legacy USD fallback. + amount_micro: int + currency: str + scale: Literal[6] + + +class CostEvidenceScope(TypedDict): + tenant_id: str + environment: None + cell: None + dimensions: dict[str, str] + + +class CostEvidenceWindow(TypedDict): + requested_start: str + requested_end: str + effective_start: str + effective_end: str + complete_through: None + + +class CostEvidenceFreshness(TypedDict): + generated_at: str + newest_source_at: Optional[str] + retention_start: None + + +CostEvidenceSource = TypedDict( + "CostEvidenceSource", + { + "class": Literal["durable_rollup", "unavailable"], + "durability": Literal["durable", "unavailable"], + "systems": list[Literal["telemetry_rung_1"]], + "schema_version": Literal["routeplane.finops.rate-evidence.v1"], + }, +) + + +class _AvailableCoverageCount(TypedDict): + availability: Literal["available", "partial"] + value: int + + +class _UnavailableCoverageCount(TypedDict): + availability: Literal["unavailable"] + value: None + + +CostEvidenceCoverageCount = Union[_AvailableCoverageCount, _UnavailableCoverageCount] + + +class CostEvidenceCoverage(TypedDict): + eligible_count: CostEvidenceCoverageCount + observed_count: CostEvidenceCoverageCount + priced_count: CostEvidenceCoverageCount + unpriced_count: CostEvidenceCoverageCount + billed_count: CostEvidenceCoverageCount + reconciled_count: CostEvidenceCoverageCount + allocated_count: CostEvidenceCoverageCount + missing_reasons: list[ + Literal[ + "provider_usage_absent", + "model_unpriced", + "pricing_component_unpriced", + "pricing_coverage_unavailable", + "billing_coverage_unavailable", + "reconciliation_coverage_unavailable", + "allocation_coverage_unavailable", + "rate_version_unavailable", + "complete_through_unavailable", + "retention_boundary_unavailable", + "telemetry_loss_not_quantified", + "source_unavailable", + "unsupported", + ] + ] + + +class _CostBasisFields(TypedDict): + settlement_state: None + reconciliation_state: None + rate_versions: list[str] + fx_source: None + fx_version: None + contract_rate_id: None + invoice_id: None + reconciliation_id: None + + +class _EstimatedCostBasis(_CostBasisFields): + cost_status: Literal["estimated"] + usage_source: str + rate_source: str + rate_version: str + rate_effective_at: str + + +class _UnpricedCostBasis(_CostBasisFields): + cost_status: Literal["unpriced"] + usage_source: str + rate_source: None + rate_version: None + rate_effective_at: None + + +class _UnavailableCostBasis(_CostBasisFields): + cost_status: Literal["unavailable"] + usage_source: None + rate_source: None + rate_version: None + rate_effective_at: None + + +DailyCostBasis = Union[_EstimatedCostBasis, _UnpricedCostBasis, _UnavailableCostBasis] + + +class _CostEvidenceValueFields(TypedDict): + unit: Literal["micro_currency"] + scope: CostEvidenceScope + window: CostEvidenceWindow + freshness: CostEvidenceFreshness + source: CostEvidenceSource + coverage: CostEvidenceCoverage + derivation: None + evidence_id: str + + +class _CompleteCostEvidenceValue(_CostEvidenceValueFields): + data_state: Literal["live"] + availability: Literal["available"] + value: CostEvidenceMoney + cost_basis: _EstimatedCostBasis + + +class _UnpricedCostEvidenceValue(_CostEvidenceValueFields): + data_state: Literal["live"] + availability: Literal["unavailable"] + value: None + cost_basis: _UnpricedCostBasis + + +class _UnavailableCostEvidenceValue(_CostEvidenceValueFields): + data_state: Literal["unavailable"] + availability: Literal["unavailable"] + value: None + cost_basis: _UnavailableCostBasis + + +class _PartialUnavailableCostEvidenceValue(_CostEvidenceValueFields): + data_state: Literal["partial"] + availability: Literal["unavailable"] + value: None + cost_basis: _UnavailableCostBasis + + +class _PartialCostEvidenceValue(_CostEvidenceValueFields): + data_state: Literal["partial"] + availability: Literal["partial"] + value: CostEvidenceMoney + cost_basis: _EstimatedCostBasis + + +CostEvidenceValue = Union[ + _CompleteCostEvidenceValue, + _UnpricedCostEvidenceValue, + _UnavailableCostEvidenceValue, + _PartialUnavailableCostEvidenceValue, + _PartialCostEvidenceValue, +] + + +class _CostSurfaceCoverage(TypedDict): + represented: list[Literal["chat_completions"]] + not_covered: list[Literal["non_chat_requests"]] + + +class _DailyCostEvidenceFields(TypedDict): + surface_coverage: _CostSurfaceCoverage + components: list[CostEvidenceComponent] + components_truncated: bool + rate_snapshots: list[CostEvidenceRateSnapshot] + rate_snapshots_truncated: bool + + +class _DailyCompleteCostEvidence(_DailyCostEvidenceFields): + total: Union[ + _CompleteCostEvidenceValue, + _UnpricedCostEvidenceValue, + _UnavailableCostEvidenceValue, + ] + estimated_priced_subtotal: None + + +class _DailyPartialCostEvidence(_DailyCostEvidenceFields): + total: _PartialUnavailableCostEvidenceValue + estimated_priced_subtotal: Optional[_PartialCostEvidenceValue] + + +DailyCostEvidence = Union[_DailyCompleteCostEvidence, _DailyPartialCostEvidence] + + +class _OptionalDailyCostEvidence(TypedDict, total=False): + # Older servers omit this entire envelope; nested nullable fields are required. + cost_evidence: DailyCostEvidence + + +class DailyModelUsage(_OptionalDailyCostEvidence): provider: str model: str requests: int @@ -123,7 +496,7 @@ class DailyLatency(TypedDict): p99: Optional[float] -class DailyUsageTotals(TypedDict): +class DailyUsageTotals(_OptionalDailyCostEvidence): requests: int errors: int prompt_tokens: int @@ -231,6 +604,8 @@ def usage_daily( monetary fields are null unless the adjacent pricing evidence proves complete, versioned coverage. A fully priced zero remains numeric zero; costs are gateway price-book estimates, not billed or reconciled amounts. + Newer servers include ``cost_evidence`` at totals/day/model/key levels; + its typed estimate and partial subtotal are independent of legacy costs. """ params = prune_none({"from": from_date, "to": to_date}) data: DailyUsageReport = self._get("finops/usage/daily", params=params).json() diff --git a/tests/test_resources.py b/tests/test_resources.py index 5e2c17e..a94224c 100644 --- a/tests/test_resources.py +++ b/tests/test_resources.py @@ -5,11 +5,14 @@ """ import json +from copy import deepcopy +from typing import get_args import httpx import pytest import respx +import routeplane from routeplane.resources import ( AnalyticsResource, CacheResource, @@ -21,6 +24,7 @@ PromptsResource, ProvidersResource, ResidencyResource, + finops, ) BASE = "https://api.routeplane.ai/v1" @@ -208,6 +212,7 @@ def test_finops_usage_daily_range(): ) out = FinopsResource(**_kwargs()).usage_daily(from_date="2026-07-01", to_date="2026-07-20") assert out == report + assert "cost_evidence" not in out["totals"] params = _sent(route).url.params assert params["from"] == "2026-07-01" assert params["to"] == "2026-07-20" @@ -279,6 +284,460 @@ def test_finops_usage_daily_preserves_unavailable_cost_and_usage(): assert out["totals"]["total_tokens"] == 6 +def _daily_cost_evidence(): + # Authored mirror of finops__usage_daily__typed_cost_evidence.json: legacy + # 123 is independent of the five exact component estimates totaling 15. + return { + "total": { + "data_state": "live", + "availability": "available", + "value": {"amount_micro": 15, "currency": "USD", "scale": 6}, + "unit": "micro_currency", + "scope": { + "tenant_id": "tenant_00000001", + "environment": None, + "cell": None, + "dimensions": {}, + }, + "window": { + "requested_start": "2026-09-25T00:00:00Z", + "requested_end": "2026-09-25T23:59:59Z", + "effective_start": "2026-09-25T00:00:00Z", + "effective_end": "2026-09-25T23:59:59Z", + "complete_through": None, + }, + "freshness": { + "generated_at": "2026-09-26T00:00:00Z", + "newest_source_at": "2026-09-25T12:00:00Z", + "retention_start": None, + }, + "source": { + "class": "durable_rollup", + "durability": "durable", + "systems": ["telemetry_rung_1"], + "schema_version": "routeplane.finops.rate-evidence.v1", + }, + "coverage": { + name: {"availability": "available", "value": value} + for name, value in [ + ("eligible_count", 1), + ("observed_count", 1), + ("priced_count", 1), + ("unpriced_count", 0), + ("billed_count", 0), + ("reconciled_count", 0), + ("allocated_count", 0), + ] + } + | {"missing_reasons": []}, + "derivation": None, + "cost_basis": { + "cost_status": "estimated", + "settlement_state": None, + "reconciliation_state": None, + "usage_source": "gateway_normalized_chat", + "rate_source": "signed_rate_snapshot", + "rate_version": "generation-1", + "rate_versions": ["generation-1"], + "rate_effective_at": "2026-09-25T12:00:00Z", + "fx_source": None, + "fx_version": None, + "contract_rate_id": None, + "invoice_id": None, + "reconciliation_id": None, + }, + "evidence_id": "fev1_AAECAwQFBgcICQoLDA0ODw", + }, + "estimated_priced_subtotal": None, + "surface_coverage": { + "represented": ["chat_completions"], + "not_covered": ["non_chat_requests"], + }, + "components": [ + { + "component": component, + "unit": "token", + "qualifier": None, + "quantity_state": {"state": "observed"}, + "resolution": { + "status": "estimated", + "rate_reference": "rate_public", + "rate_scope": "provider_public_list", + "currency": "USD", + "estimated_amount_micro": amount, + }, + "observation_count": 1, + "observed_quantity": 1, + "amount_unavailable_reason": None, + } + for component, amount in zip( + [ + "input_tokens", + "output_tokens", + "reasoning_tokens", + "cache_read_tokens", + "cache_write_tokens", + ], + [1, 2, 3, 4, 5], + ) + ], + "components_truncated": False, + "rate_snapshots": [ + { + "snapshot_id": "rates_0000000000000000000001", + "snapshot_version": "generation-1", + "generation": 1, + "receipt_posture": "receipt_anchored_current_control_plane_head", + "restart_protection_limited": False, + "transition": {"state": "genesis"}, + } + ], + "rate_snapshots_truncated": False, + } + + +def _daily_report_with_evidence(evidence): + # Each placement includes its existing required fields; no SDK conversion + # from these legacy monetary values is permitted. + pricing = { + "data_state": "live", + "availability": "available", + "value": 123, + "unit": "micro_currency", + "currency": "USD", + "scale": 6, + "cost_status": "estimated", + "source": { + "class": "durable_rollup", + "systems": ["telemetry_rung_1"], + "schema_version": "routeplane.finops.cost.v1", + }, + "coverage": { + "eligible_count": 1, + "observed_count": 1, + "priced_count": 1, + "unpriced_count": 0, + "invalid_pricing_count": 0, + "versioned_priced_count": 1, + "pricing_coverage_state": "known", + "pricing_book_versions": ["pb_1"], + "pricing_book_versions_truncated": False, + "numeric_overflowed": False, + "missing_reasons": [], + }, + "component_coverage": { + "input_output_split_available": False, + "cost_split_coverage_state": "legacy_unknown", + "input_attributed_count": 0, + "output_attributed_count": 0, + "invalid_input_count": 0, + "invalid_output_count": 0, + "missing_reasons": ["legacy_cost_split_coverage_unknown"], + "inr_view_available": False, + }, + } + model = { + "provider": "openai", + "model": "gpt-fixture", + "requests": 1, + "errors": 0, + "total_tokens": 5, + "cost_micro_usd": 123, + "pricing": deepcopy(pricing), + "cost_evidence": deepcopy(evidence), + } + key = { + **deepcopy(model), + "key": "key_fixture", + "prompt_tokens": 3, + "completion_tokens": 2, + "cost_inr_paise": None, + } + totals = { + "requests": 1, + "errors": 0, + "prompt_tokens": 3, + "completion_tokens": 2, + "total_tokens": 5, + "cost_micro_usd": 123, + "input_cost_micro_usd": None, + "output_cost_micro_usd": None, + "cost_inr_paise": None, + "pricing": deepcopy(pricing), + "cost_evidence": deepcopy(evidence), + } + day = { + **deepcopy(totals), + "date": "2026-09-25", + "streaming": 0, + "sovereign_routed": 0, + "latency_ms": {"p50": None, "p95": None, "p99": None}, + "by_model": [model], + "by_key": [key], + } + return { + "tenant_id": "tenant_00000001", + "from": "2026-09-25", + "to": "2026-09-25", + "days": [day], + "totals": totals, + "note": "durable estimated usage", + } + + +@pytest.mark.parametrize( + "state", ["complete", "zero", "unpriced", "unavailable", "partial", "partial_zero"] +) +@respx.mock +def test_finops_daily_cost_evidence_preserved_at_all_four_placements(state): + evidence = _daily_cost_evidence() + total = evidence["total"] + if state == "zero": + total["value"]["amount_micro"] = 0 + for component in evidence["components"]: + component["resolution"]["estimated_amount_micro"] = 0 + elif state in {"unpriced", "unavailable", "partial", "partial_zero"}: + estimated_basis = deepcopy(total["cost_basis"]) + total["value"] = None + total["availability"] = "unavailable" + total["data_state"] = ( + "live" + if state == "unpriced" + else "unavailable" + if state == "unavailable" + else "partial" + ) + total["cost_basis"] = { + key: ("unpriced" if state == "unpriced" else "unavailable") + if key == "cost_status" + else [] + if key == "rate_versions" + else "gateway_normalized_chat" + if key == "usage_source" and state == "unpriced" + else None + for key in total["cost_basis"] + } + for name in ("priced_count", "unpriced_count", "allocated_count"): + total["coverage"][name] = {"availability": "unavailable", "value": None} + total["coverage"]["missing_reasons"] = [ + "pricing_component_unpriced" + if state == "unpriced" + else "source_unavailable" + if state == "unavailable" + else "pricing_coverage_unavailable" + ] + if state == "unavailable": + total["source"].update( + {"class": "unavailable", "durability": "unavailable", "systems": []} + ) + total["freshness"]["newest_source_at"] = None + evidence["components"] = [] + evidence["rate_snapshots"] = [] + total["coverage"]["eligible_count"]["value"] = 0 + total["coverage"]["observed_count"] = {"availability": "unavailable", "value": None} + else: + for component in evidence["components"]: + if state == "unpriced" or component["component"] == "cache_write_tokens": + component["resolution"] = { + "status": "unpriced", + "reason": "component_or_unit_unpriced", + } + elif state == "partial_zero": + component["resolution"]["estimated_amount_micro"] = 0 + if state.startswith("partial"): + subtotal = deepcopy(total) + subtotal["availability"] = "partial" + subtotal["value"] = { + "amount_micro": 0 if state == "partial_zero" else 10, + "currency": "USD", + "scale": 6, + } + subtotal["cost_basis"] = estimated_basis + evidence["estimated_priced_subtotal"] = subtotal + report = _daily_report_with_evidence(evidence) + if state == "unavailable": + # No rollup rows means no model/key/day rows to fabricate; only the + # requested window's totals carry source-unavailable evidence. + report["days"] = [] + for field in ("requests", "errors", "prompt_tokens", "completion_tokens", "total_tokens"): + report["totals"][field] = 0 + report["totals"]["cost_micro_usd"] = None + pricing = report["totals"]["pricing"] + pricing.update( + { + "data_state": "unavailable", + "availability": "unavailable", + "value": None, + "cost_status": "unavailable", + } + ) + pricing["coverage"].update( + { + "eligible_count": 0, + "observed_count": 0, + "priced_count": 0, + "versioned_priced_count": 0, + "pricing_book_versions": [], + "missing_reasons": ["no_rollup_rows"], + } + ) + route = respx.get(f"{BASE}/finops/usage/daily").mock( + return_value=httpx.Response(200, json=report) + ) + out = FinopsResource(**_kwargs()).usage_daily(from_date="2026-09-25", to_date="2026-09-25") + assert out == report + assert dict(_sent(route).url.params) == {"from": "2026-09-25", "to": "2026-09-25"} + assert _sent(route).headers["x-routeplane-api-key"] == "rp_test" + if state == "unavailable": + assert out["days"] == [] + assert out["totals"]["requests"] == 0 + assert out["totals"]["cost_micro_usd"] is None + assert out["totals"]["cost_evidence"]["total"]["source"]["class"] == "unavailable" + assert out["totals"]["cost_evidence"] == evidence + return + placements = [ + out["totals"], + out["days"][0], + out["days"][0]["by_model"][0], + out["days"][0]["by_key"][0], + ] + for placement in placements: + assert placement["cost_micro_usd"] == 123 + assert placement["cost_evidence"] == evidence + if state == "complete": + assert placement["cost_evidence"]["total"]["value"]["amount_micro"] == 15 + if state == "zero": + assert placement["cost_evidence"]["total"]["value"]["amount_micro"] == 0 + if state.startswith("partial"): + assert placement["cost_evidence"]["total"]["value"] is None + subtotal = placement["cost_evidence"]["estimated_priced_subtotal"] + assert subtotal["value"]["amount_micro"] == (0 if state == "partial_zero" else 10) + for context_key in ("scope", "window", "freshness", "source", "coverage"): + assert subtotal[context_key] == placement["cost_evidence"]["total"][context_key] + + +@respx.mock +def test_finops_daily_cost_diagnostics_transitions_and_truncation_are_not_normalized(): + evidence = _daily_cost_evidence() + total = evidence["total"] + total.update({"data_state": "partial", "availability": "unavailable", "value": None}) + total["cost_basis"] = { + key: "unavailable" if key == "cost_status" else [] if key == "rate_versions" else None + for key in total["cost_basis"] + } + for name in ("priced_count", "unpriced_count", "allocated_count"): + total["coverage"][name] = {"availability": "unavailable", "value": None} + total["coverage"]["missing_reasons"] = ["rate_version_unavailable"] + for component in evidence["components"]: + component["quantity_state"] = {"state": "unavailable", "reason": "usage_payload_truncated"} + component["resolution"] = {"status": "unavailable", "reason": "ambiguous_rate"} + component["observation_count"] = None + component["observed_quantity"] = None + component["qualifier"] = "diagnostic" + component["amount_unavailable_reason"] = "outside_json_safe_integer_range" + evidence["components_truncated"] = True + evidence["rate_snapshots_truncated"] = True + snapshot = evidence["rate_snapshots"][0] + # Truncation makes aggregate authority structurally unsound, so the server + # marks every retained snapshot's receipt posture unavailable. + snapshot["receipt_posture"] = "unavailable" + evidence["rate_snapshots"] += [ + { + **deepcopy(snapshot), + "generation": 2, + "snapshot_version": "generation-2", + "transition": {"state": "contiguous"}, + }, + { + **deepcopy(snapshot), + "generation": 5, + "snapshot_version": "generation-5", + "receipt_posture": "unavailable", + "restart_protection_limited": True, + "transition": {"state": "skipped", "from_generation": 2, "to_generation": 5}, + }, + ] + evidence["components"][0]["resolution"] = { + "status": "estimated", + "rate_reference": None, + "rate_scope": "tenant_byo_contract", + "currency": "EUR", + "estimated_amount_micro": None, + } + evidence["components"][0]["amount_unavailable_reason"] = "aggregate_authority_unavailable" + report = _daily_report_with_evidence(evidence) + respx.get(f"{BASE}/finops/usage/daily").mock(return_value=httpx.Response(200, json=report)) + assert FinopsResource(**_kwargs()).usage_daily() == report + + +def test_finops_daily_cost_types_are_exported_with_only_outer_field_optional(): + for model in ( + finops.DailyModelUsage, + finops.DailyKeyUsage, + finops.DailyUsageTotals, + finops.DailyUsage, + ): + assert model.__optional_keys__ == {"cost_evidence"} + assert {"cost_micro_usd", "pricing"} <= model.__required_keys__ + for model in ( + finops.CostEvidenceMoney, + finops.CostEvidenceScope, + finops.CostEvidenceWindow, + finops.CostEvidenceFreshness, + finops.CostEvidenceSource, + finops.CostEvidenceCoverage, + finops.CostEvidenceComponent, + finops.CostEvidenceRateSnapshot, + ): + assert model.__optional_keys__ == set() + assert getattr(routeplane, model.__name__) is model + for name in ( + "DailyCostEvidence", + "CostEvidenceValue", + "DailyCostBasis", + "CostEvidenceCoverageCount", + ): + union = getattr(finops, name) + assert getattr(routeplane, name) is union + assert all(not variant.__optional_keys__ for variant in get_args(union)) + assert set(get_args(finops.RateComponent)) == { + "input_tokens", + "output_tokens", + "reasoning_tokens", + "cache_read_tokens", + "cache_write_tokens", + "embedding_tokens", + "cache_storage", + "search_units", + "rerank_units", + "images", + "audio_input", + "audio_output", + "characters", + "video", + "model_tool_calls", + "executed_mcp_calls", + "provider_built_in_calls", + } + assert set(get_args(finops.RateUnit)) == { + "token", + "byte_second", + "search_unit", + "rerank_unit", + "image", + "audio_second", + "character", + "video_second", + "model_tool_call", + "executed_mcp_call", + "provider_built_in_call", + } + assert len(get_args(finops.RateScopeClass)) == 6 + for union in (finops.RateQuantityState, finops.RateResolution, finops.RateActivationTransition): + assert len(get_args(union)) == 3 + assert all(not variant.__optional_keys__ for variant in get_args(union)) + + @respx.mock def test_finops_timeseries(): route = respx.get(f"{BASE}/finops/timeseries").mock(