"""Bridge: EnergiBridge-inspired IR mapper for BeVault MVP. This module introduces a minimal, canonical representation mapping BeVault primitives into a single, serializable IR (Objects/Morphisms/PlanDelta). It's intentionally lightweight and is designed to support plug-and-play adapters without vendor lock-in. The goal is to enable reproducible cross-adapter interactions and deterministic backtests later in the MVP. """ from __future__ import annotations from dataclasses import asdict, dataclass from typing import Optional from .core import HedgeDelta, LocalArbProblem, SharedSignals @dataclass class CanonicalIR: """A tiny, serializable canonical IR representation. This is not a full-fledged IR. It captures the core primitives in a structured, versioned form suitable for signing, auditing, and replay. """ version: str objects: dict morphisms: dict plan_delta: Optional[dict] = None def to_dict(self) -> dict: return { "version": self.version, "objects": self.objects, "morphisms": self.morphisms, "plan_delta": self.plan_delta, } def to_canonical_ir(local: LocalArbProblem, signals: SharedSignals, delta: Optional[HedgeDelta] = None) -> CanonicalIR: """Map BeVault MVP primitives to a CanonicalIR structure. This function provides a tiny, deterministic bridge from the MVP domain objects to a stable representation that can be serialized, signed, and replayed by adapters. """ objects = { "LocalArbProblem": asdict_safe(local), } morphisms = { "SharedSignals": asdict_safe(signals), } plan_delta = asdict_safe(delta) if delta is not None else None return CanonicalIR(version="0.1", objects=objects, morphisms=morphisms, plan_delta=plan_delta) def asdict_safe(obj) -> dict: """Safely convert a dataclass instance to a dict, handling None gracefully.""" if obj is None: return {} # Use dataclasses.asdict for clean conversion; if input isn't a dataclass, # fall back to its string representation to avoid raising. try: return asdict(obj) except Exception: return {"value": obj} __all__ = ["CanonicalIR", "to_canonical_ir"]