from __future__ import annotations import json from dataclasses import dataclass, field from decimal import Decimal from typing import Any, Literal from .pricing_models import PricingModel PublicationArtifactKind = Literal["product", "meter", "price", "commitment", "configuration"] ArtifactMappingStatus = Literal["exact", "approximate", "unsupported"] PublicationOperationKind = Literal["create", "update", "noop", "retire", "rollback"] DriftSeverity = Literal["info", "warn", "error"] def _serialize_value(value: Any) -> Any: if isinstance(value, Decimal): return str(value) if hasattr(value, "__dataclass_fields__"): return { key: _serialize_value(getattr(value, key)) for key in value.__dataclass_fields__ } if isinstance(value, tuple): return [_serialize_value(item) for item in value] if isinstance(value, list): return [_serialize_value(item) for item in value] if isinstance(value, dict): return {key: _serialize_value(item) for key, item in value.items()} return value def _payload_signature(payload: Any) -> str: return json.dumps(_serialize_value(payload), sort_keys=True) @dataclass(frozen=True) class CatalogProduct: id: str name: str description: str currency: str lifecycle_phase: str active_pricing_model_id: str | None = None metadata: dict[str, Any] = field(default_factory=dict) @dataclass(frozen=True) class PublishableProduct: key: str product_id: str name: str description: str currency: str lifecycle_phase: str active: bool metadata: dict[str, Any] = field(default_factory=dict) @dataclass(frozen=True) class PublishableMeter: key: str meter_id: str name: str event_name: str unit: str aggregation: str = "sum" metadata: dict[str, Any] = field(default_factory=dict) @dataclass(frozen=True) class PublishablePrice: key: str price_id: str product_key: str component_id: str component_kind: str label: str currency: str billing_treatment: str cadence: str | None = None amount: Decimal | None = None unit_price: Decimal | None = None included_units: Decimal | None = None meter_key: str | None = None metadata: dict[str, Any] = field(default_factory=dict) @dataclass(frozen=True) class PublishableCommitment: key: str commitment_id: str kind: str value: str unit: str | None = None description: str = "" metadata: dict[str, Any] = field(default_factory=dict) @dataclass(frozen=True) class PublishableConfiguration: key: str configuration_id: str product_key: str model_id: str model_name: str segment: str | None price_keys: tuple[str, ...] commitment_keys: tuple[str, ...] metadata: dict[str, Any] = field(default_factory=dict) @dataclass(frozen=True) class PublicationBundle: bundle_id: str model_id: str model_name: str product: PublishableProduct meters: tuple[PublishableMeter, ...] prices: tuple[PublishablePrice, ...] commitments: tuple[PublishableCommitment, ...] configurations: tuple[PublishableConfiguration, ...] provider_hints: dict[str, Any] = field(default_factory=dict) notes: tuple[str, ...] = () @dataclass(frozen=True) class ProviderMappedArtifact: provider: str source_key: str source_kind: PublicationArtifactKind provider_id: str provider_object_type: str mapping_status: ArtifactMappingStatus payload: dict[str, Any] metadata: dict[str, Any] = field(default_factory=dict) notes: tuple[str, ...] = () @dataclass(frozen=True) class ProviderPublicationPackage: provider: str bundle_id: str model_id: str model_name: str artifacts: tuple[ProviderMappedArtifact, ...] notes: tuple[str, ...] = () @dataclass(frozen=True) class DriftFinding: provider_id: str provider_object_type: str severity: DriftSeverity summary: str expected: dict[str, Any] = field(default_factory=dict) actual: dict[str, Any] = field(default_factory=dict) suggested_action: str | None = None @dataclass(frozen=True) class PublicationOperation: kind: PublicationOperationKind provider_id: str provider_object_type: str source_key: str | None source_kind: PublicationArtifactKind | None mapping_status: ArtifactMappingStatus | None summary: str desired_payload: dict[str, Any] = field(default_factory=dict) current_payload: dict[str, Any] = field(default_factory=dict) desired_metadata: dict[str, Any] = field(default_factory=dict) current_metadata: dict[str, Any] = field(default_factory=dict) desired_notes: tuple[str, ...] = () current_notes: tuple[str, ...] = () @dataclass(frozen=True) class PublishedProviderArtifact: provider: str source_key: str source_kind: PublicationArtifactKind provider_id: str provider_object_type: str mapping_status: ArtifactMappingStatus payload: dict[str, Any] metadata: dict[str, Any] = field(default_factory=dict) notes: tuple[str, ...] = () @dataclass(frozen=True) class PublicationRevision: revision_id: str model_id: str model_name: str summary: str operations: tuple[PublicationOperation, ...] snapshot: tuple[PublishedProviderArtifact, ...] replaced_revision_id: str | None = None @dataclass(frozen=True) class ProviderPublicationState: provider: str active_revision_id: str | None = None active_model_id: str | None = None artifacts: tuple[PublishedProviderArtifact, ...] = () revisions: tuple[PublicationRevision, ...] = () @dataclass(frozen=True) class PublicationPlan: provider: str bundle_id: str model_id: str model_name: str operations: tuple[PublicationOperation, ...] drift: tuple[DriftFinding, ...] unsupported_artifacts: tuple[ProviderMappedArtifact, ...] summary: str @dataclass(frozen=True) class PublicationApplyResult: plan: PublicationPlan revision: PublicationRevision state: ProviderPublicationState summary: str def build_publication_bundle( product: CatalogProduct, model: PricingModel, *, configuration_id: str | None = None, segment: str | None = None, ) -> PublicationBundle: product_key = f"product:{product.id}" publishable_product = PublishableProduct( key=product_key, product_id=product.id, name=product.name, description=product.description, currency=product.currency, lifecycle_phase=product.lifecycle_phase, active=model.status != "retired", metadata={ **product.metadata, "adaptive_pricing_model_id": model.id, "adaptive_pricing_model_status": model.status, }, ) meters: list[PublishableMeter] = [] prices: list[PublishablePrice] = [] for component in model.charge_components: meter_key: str | None = None if component.kind == "usage" and component.meter: meter_key = f"meter:{model.id}:{component.id}" meters.append( PublishableMeter( key=meter_key, meter_id=component.meter, name=component.label or component.meter, event_name=component.meter, unit=component.unit or "usage_unit", metadata={ "adaptive_pricing_model_id": model.id, "source_component_id": component.id, }, ) ) prices.append( PublishablePrice( key=f"price:{model.id}:{component.id}", price_id=f"{model.id}:{component.id}", product_key=product_key, component_id=component.id, component_kind=component.kind, label=component.label or component.id, currency=model.currency, billing_treatment=component.billing_treatment or "recurring", cadence=component.cadence or ( model.access_fee_cadence if component.kind in {"access", "support", "discount", "risk_adjustment"} else None ), amount=component.amount, unit_price=component.unit_price, included_units=component.included_units, meter_key=meter_key, metadata={ **component.metadata, "adaptive_pricing_model_id": model.id, "source_component_id": component.id, "source_component_kind": component.kind, }, ) ) commitments = tuple( PublishableCommitment( key=f"commitment:{model.id}:{commitment.id}", commitment_id=commitment.id, kind=commitment.kind, value=commitment.value, unit=commitment.unit, description=commitment.description, metadata={"adaptive_pricing_model_id": model.id}, ) for commitment in model.commitments ) configuration = PublishableConfiguration( key=f"configuration:{configuration_id or model.id}", configuration_id=configuration_id or model.id, product_key=product_key, model_id=model.id, model_name=model.name, segment=segment, price_keys=tuple(price.key for price in prices), commitment_keys=tuple(commitment.key for commitment in commitments), metadata={ "adaptive_pricing_model_id": model.id, "lifecycle_phase": model.lifecycle_phase, }, ) notes = ( "Publication bundles preserve the internal pricing model as the source of truth.", "Provider mappings may mark artifacts exact, approximate, or unsupported without mutating the bundle.", ) return PublicationBundle( bundle_id=f"bundle:{model.id}", model_id=model.id, model_name=model.name, product=publishable_product, meters=tuple(meters), prices=tuple(prices), commitments=commitments, configurations=(configuration,), provider_hints=model.provider_hints, notes=notes, ) def _published_artifact(mapped: ProviderMappedArtifact) -> PublishedProviderArtifact: return PublishedProviderArtifact( provider=mapped.provider, source_key=mapped.source_key, source_kind=mapped.source_kind, provider_id=mapped.provider_id, provider_object_type=mapped.provider_object_type, mapping_status=mapped.mapping_status, payload=mapped.payload, metadata=mapped.metadata, notes=mapped.notes, ) def _artifact_changed( desired: ProviderMappedArtifact, current: PublishedProviderArtifact, ) -> bool: return any( [ desired.mapping_status != current.mapping_status, desired.provider_object_type != current.provider_object_type, _payload_signature(desired.payload) != _payload_signature(current.payload), _payload_signature(desired.metadata) != _payload_signature(current.metadata), desired.notes != current.notes, ] ) def _diff_summary( desired: ProviderMappedArtifact, current: PublishedProviderArtifact, ) -> DriftFinding: return DriftFinding( provider_id=desired.provider_id, provider_object_type=desired.provider_object_type, severity="warn", summary="Provider shadow state differs from desired pricing definition.", expected={ "payload": desired.payload, "metadata": desired.metadata, "mapping_status": desired.mapping_status, "notes": list(desired.notes), }, actual={ "payload": current.payload, "metadata": current.metadata, "mapping_status": current.mapping_status, "notes": list(current.notes), }, suggested_action="Publish the desired definition again or reconcile the provider-side drift.", ) def plan_publication( package: ProviderPublicationPackage, current_state: ProviderPublicationState | None = None, ) -> PublicationPlan: current_state = current_state or ProviderPublicationState(provider=package.provider) current_index = {artifact.provider_id: artifact for artifact in current_state.artifacts} desired_publishable = tuple( artifact for artifact in package.artifacts if artifact.mapping_status != "unsupported" ) unsupported = tuple( artifact for artifact in package.artifacts if artifact.mapping_status == "unsupported" ) operations: list[PublicationOperation] = [] drift: list[DriftFinding] = [] desired_ids = {artifact.provider_id for artifact in desired_publishable} for artifact in desired_publishable: current = current_index.get(artifact.provider_id) if current is None: operations.append( PublicationOperation( kind="create", provider_id=artifact.provider_id, provider_object_type=artifact.provider_object_type, source_key=artifact.source_key, source_kind=artifact.source_kind, mapping_status=artifact.mapping_status, summary="Create provider artifact from canonical pricing definition.", desired_payload=artifact.payload, desired_metadata=artifact.metadata, desired_notes=artifact.notes, ) ) continue if _artifact_changed(artifact, current): operations.append( PublicationOperation( kind="update", provider_id=artifact.provider_id, provider_object_type=artifact.provider_object_type, source_key=artifact.source_key, source_kind=artifact.source_kind, mapping_status=artifact.mapping_status, summary="Update provider artifact to match canonical pricing definition.", desired_payload=artifact.payload, current_payload=current.payload, desired_metadata=artifact.metadata, current_metadata=current.metadata, desired_notes=artifact.notes, current_notes=current.notes, ) ) drift.append(_diff_summary(artifact, current)) continue operations.append( PublicationOperation( kind="noop", provider_id=artifact.provider_id, provider_object_type=artifact.provider_object_type, source_key=artifact.source_key, source_kind=artifact.source_kind, mapping_status=artifact.mapping_status, summary="Provider artifact already matches canonical pricing definition.", desired_payload=artifact.payload, current_payload=current.payload, desired_metadata=artifact.metadata, current_metadata=current.metadata, desired_notes=artifact.notes, current_notes=current.notes, ) ) for artifact in current_state.artifacts: if artifact.provider_id in desired_ids: continue operations.append( PublicationOperation( kind="retire", provider_id=artifact.provider_id, provider_object_type=artifact.provider_object_type, source_key=artifact.source_key, source_kind=artifact.source_kind, mapping_status=artifact.mapping_status, summary="Retire managed provider artifact no longer present in the desired pricing definition.", current_payload=artifact.payload, current_metadata=artifact.metadata, current_notes=artifact.notes, ) ) drift.append( DriftFinding( provider_id=artifact.provider_id, provider_object_type=artifact.provider_object_type, severity="warn", summary="Managed provider artifact exists in shadow state but not in the desired pricing definition.", actual={ "payload": artifact.payload, "metadata": artifact.metadata, "mapping_status": artifact.mapping_status, "notes": list(artifact.notes), }, suggested_action="Retire the artifact or republish the desired model.", ) ) summary = ( f"{package.provider}: {sum(op.kind == 'create' for op in operations)} create, " f"{sum(op.kind == 'update' for op in operations)} update, " f"{sum(op.kind == 'retire' for op in operations)} retire, " f"{sum(op.kind == 'noop' for op in operations)} noop, " f"{len(unsupported)} unsupported." ) return PublicationPlan( provider=package.provider, bundle_id=package.bundle_id, model_id=package.model_id, model_name=package.model_name, operations=tuple(operations), drift=tuple(drift), unsupported_artifacts=unsupported, summary=summary, ) def _next_revision_id(state: ProviderPublicationState) -> str: return f"{state.provider}-rev-{len(state.revisions) + 1:04d}" def _next_active_model_id(artifacts: list[PublishedProviderArtifact]) -> str | None: product = next((artifact for artifact in artifacts if artifact.source_kind == "product"), None) if product is None: return None if product.metadata.get("model_id"): return str(product.metadata["model_id"]) payload_metadata = product.payload.get("metadata", {}) if payload_metadata.get("adaptive_pricing_model_id"): return str(payload_metadata["adaptive_pricing_model_id"]) return None def apply_publication( package: ProviderPublicationPackage, current_state: ProviderPublicationState | None = None, ) -> PublicationApplyResult: current_state = current_state or ProviderPublicationState(provider=package.provider) plan = plan_publication(package, current_state) desired_index = { artifact.provider_id: artifact for artifact in package.artifacts if artifact.mapping_status != "unsupported" } artifact_index = {artifact.provider_id: artifact for artifact in current_state.artifacts} for operation in plan.operations: if operation.kind in {"create", "update", "noop"}: artifact_index[operation.provider_id] = _published_artifact( desired_index[operation.provider_id] ) elif operation.kind == "retire": artifact_index.pop(operation.provider_id, None) snapshot = tuple(sorted(artifact_index.values(), key=lambda artifact: artifact.provider_id)) revision = PublicationRevision( revision_id=_next_revision_id(current_state), model_id=package.model_id, model_name=package.model_name, summary=plan.summary, operations=plan.operations, snapshot=snapshot, replaced_revision_id=current_state.active_revision_id, ) state = ProviderPublicationState( provider=package.provider, active_revision_id=revision.revision_id, active_model_id=_next_active_model_id(list(snapshot)), artifacts=snapshot, revisions=current_state.revisions + (revision,), ) return PublicationApplyResult( plan=plan, revision=revision, state=state, summary=plan.summary, ) def rollback_publication( state: ProviderPublicationState, revision_id: str, ) -> PublicationApplyResult: revision = next((item for item in state.revisions if item.revision_id == revision_id), None) if revision is None: raise ValueError(f"unknown revision_id '{revision_id}'") rollback_operation = PublicationOperation( kind="rollback", provider_id=revision.revision_id, provider_object_type="revision", source_key=None, source_kind=None, mapping_status=None, summary=f"Rollback provider shadow state to {revision.revision_id}.", ) new_revision = PublicationRevision( revision_id=_next_revision_id(state), model_id=revision.model_id, model_name=revision.model_name, summary=f"Rolled back provider shadow state to {revision.revision_id}.", operations=(rollback_operation,), snapshot=revision.snapshot, replaced_revision_id=state.active_revision_id, ) new_state = ProviderPublicationState( provider=state.provider, active_revision_id=new_revision.revision_id, active_model_id=revision.model_id, artifacts=revision.snapshot, revisions=state.revisions + (new_revision,), ) return PublicationApplyResult( plan=PublicationPlan( provider=state.provider, bundle_id=f"rollback:{revision.revision_id}", model_id=revision.model_id, model_name=revision.model_name, operations=(rollback_operation,), drift=(), unsupported_artifacts=(), summary=new_revision.summary, ), revision=new_revision, state=new_state, summary=new_revision.summary, ) def provider_state_to_dict(state: ProviderPublicationState) -> dict[str, Any]: return _serialize_value(state) def _operation_from_dict(raw: dict[str, Any]) -> PublicationOperation: return PublicationOperation( kind=raw["kind"], provider_id=raw["provider_id"], provider_object_type=raw["provider_object_type"], source_key=raw.get("source_key"), source_kind=raw.get("source_kind"), mapping_status=raw.get("mapping_status"), summary=raw["summary"], desired_payload=dict(raw.get("desired_payload", {})), current_payload=dict(raw.get("current_payload", {})), desired_metadata=dict(raw.get("desired_metadata", {})), current_metadata=dict(raw.get("current_metadata", {})), desired_notes=tuple(raw.get("desired_notes", [])), current_notes=tuple(raw.get("current_notes", [])), ) def _artifact_from_dict(raw: dict[str, Any]) -> PublishedProviderArtifact: return PublishedProviderArtifact( provider=raw["provider"], source_key=raw["source_key"], source_kind=raw["source_kind"], provider_id=raw["provider_id"], provider_object_type=raw["provider_object_type"], mapping_status=raw["mapping_status"], payload=dict(raw.get("payload", {})), metadata=dict(raw.get("metadata", {})), notes=tuple(raw.get("notes", [])), ) def _revision_from_dict(raw: dict[str, Any]) -> PublicationRevision: return PublicationRevision( revision_id=raw["revision_id"], model_id=raw["model_id"], model_name=raw["model_name"], summary=raw["summary"], operations=tuple(_operation_from_dict(item) for item in raw.get("operations", [])), snapshot=tuple(_artifact_from_dict(item) for item in raw.get("snapshot", [])), replaced_revision_id=raw.get("replaced_revision_id"), ) def provider_state_from_dict(raw: dict[str, Any]) -> ProviderPublicationState: return ProviderPublicationState( provider=raw["provider"], active_revision_id=raw.get("active_revision_id"), active_model_id=raw.get("active_model_id"), artifacts=tuple(_artifact_from_dict(item) for item in raw.get("artifacts", [])), revisions=tuple(_revision_from_dict(item) for item in raw.get("revisions", [])), )