"""Hazelcast-compatible projection helpers for DITAv2.""" from __future__ import annotations from dataclasses import dataclass from datetime import datetime import os from typing import Any, Callable, Dict, Iterable, List, Optional from .account import AccountProjection from .contracts import KernelTransition, TradeSlot, TradeStage, VenueEvent from .control import KernelControlSnapshot from .journal import _transition_row from .utils import json_safe Writer = Callable[[str, Dict[str, Any]], None] @dataclass class HazelcastProjection: """Projection helper for BLUE/PINK-compatible durable writes.""" active_slots_map: str = "hz:dita_active_slots" trade_events_topic: str = "hz:dita_trade_events" control_map: str = "hz:dita_control" writer: Optional[Writer] = None control_snapshot: Optional[KernelControlSnapshot] = None def write_slot(self, slot: TradeSlot) -> Dict[str, Any]: row = build_position_state_row(slot, self.control_snapshot) if self.writer is not None: self.writer(self.active_slots_map, row) return row def write_transition( self, *, transition: KernelTransition, slot: TradeSlot, event: Optional[VenueEvent] = None, control: Optional[KernelControlSnapshot] = None, ) -> Dict[str, Any]: row = _transition_row(transition=transition, slot=slot, event=event, control=control) if self.writer is not None: self.writer(self.trade_events_topic, row) return row def write_control(self, control: KernelControlSnapshot) -> Dict[str, Any]: self.control_snapshot = control row = control.as_dict() if self.writer is not None: self.writer(self.control_map, row) return row def build_projection( *, writer: Optional[Writer] = None, client: Optional[Any] = None, prefer_real_hazelcast: Optional[bool] = None, control_snapshot: Optional[KernelControlSnapshot] = None, ) -> HazelcastProjection: """Build the active projection helper with an operator-visible switch. The default remains the callback-based projection helper. If a Hazelcast client is supplied and the caller opts in via ``prefer_real_hazelcast`` or ``DITA_V2_HAZELCAST=REAL``, the helper routes directly through the client-backed map/topic writer path. """ env_choice = os.environ.get("DITA_V2_HAZELCAST", "").strip().upper() real_requested = prefer_real_hazelcast if prefer_real_hazelcast is not None else env_choice in {"REAL", "REAL_HZ", "HAZELCAST"} if real_requested and client is not None: try: from .hazelcast_projection import HazelcastRowWriter writer = HazelcastRowWriter(client) except Exception: pass return HazelcastProjection(writer=writer, control_snapshot=control_snapshot) def build_position_state_row(slot: TradeSlot, control: Optional[KernelControlSnapshot] = None) -> Dict[str, Any]: """Build a state row shaped for durable compatibility.""" row = slot.to_dict() row.update( { "runtime_namespace": control.runtime_namespace if control else "dita_v2", "strategy_namespace": control.strategy_namespace if control else "dita_v2", "event_namespace": control.event_namespace if control else "dita_v2", "actor_name": control.actor_name if control else "ExecutionKernel", "exec_venue": control.exec_venue if control else "bingx", "data_venue": control.data_venue if control else "binance", "ledger_authority": control.ledger_authority if control else "exchange", } ) return row