67 production .py modules that the running PINK service imports but which were never committed: prod/bingx/ (HTTP client, market/user streams, journal, config), prod/clean_arch/ adapters/persistence/runtime/dita/dita_v2 production modules and their co-located tests. Rule going forward: every module imported by launch_dolphin_pink.py / pink_direct.py must appear in git ls-files. Excludes _backup dirs, __pycache__, and non-code files. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
103 lines
3.1 KiB
Python
103 lines
3.1 KiB
Python
"""Debug journaling surfaces for DITAv2."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from datetime import datetime, timezone
|
|
from typing import Any, Callable, Dict, List, Optional, Protocol
|
|
|
|
from .contracts import KernelTransition, TradeSlot, TradeStage, VenueEvent
|
|
from .control import KernelControlSnapshot
|
|
from .utils import json_safe, json_text
|
|
|
|
JournalSink = Callable[[str, Dict[str, Any]], None]
|
|
|
|
|
|
class KernelJournal(Protocol):
|
|
"""Append-only debug journal interface."""
|
|
|
|
def record(self, row: Dict[str, Any]) -> None:
|
|
...
|
|
|
|
def record_transition(
|
|
self,
|
|
*,
|
|
transition: KernelTransition,
|
|
slot: TradeSlot,
|
|
event: Optional[VenueEvent] = None,
|
|
control: Optional[KernelControlSnapshot] = None,
|
|
) -> None:
|
|
...
|
|
|
|
|
|
@dataclass
|
|
class MemoryKernelJournal:
|
|
"""In-memory journal used in tests."""
|
|
|
|
rows: List[Dict[str, Any]] = field(default_factory=list)
|
|
capture_limit: int = 10_000
|
|
|
|
def record(self, row: Dict[str, Any]) -> None:
|
|
if len(self.rows) < self.capture_limit:
|
|
self.rows.append(dict(row))
|
|
|
|
def record_transition(
|
|
self,
|
|
*,
|
|
transition: KernelTransition,
|
|
slot: TradeSlot,
|
|
event: Optional[VenueEvent] = None,
|
|
control: Optional[KernelControlSnapshot] = None,
|
|
) -> None:
|
|
row = _transition_row(transition=transition, slot=slot, event=event, control=control)
|
|
self.record(row)
|
|
|
|
|
|
class ClickHouseKernelJournal:
|
|
"""Fire-and-forget ClickHouse journal.
|
|
|
|
The sink is a small callable of the form ``sink(table_name, row_dict)``.
|
|
"""
|
|
|
|
def __init__(self, sink: Optional[JournalSink] = None):
|
|
self.sink = sink
|
|
|
|
def record(self, row: Dict[str, Any]) -> None:
|
|
if self.sink is not None:
|
|
self.sink("dita_kernel_debug", row)
|
|
|
|
def record_transition(
|
|
self,
|
|
*,
|
|
transition: KernelTransition,
|
|
slot: TradeSlot,
|
|
event: Optional[VenueEvent] = None,
|
|
control: Optional[KernelControlSnapshot] = None,
|
|
) -> None:
|
|
self.record(_transition_row(transition=transition, slot=slot, event=event, control=control))
|
|
|
|
|
|
def _transition_row(
|
|
*,
|
|
transition: KernelTransition,
|
|
slot: TradeSlot,
|
|
event: Optional[VenueEvent],
|
|
control: Optional[KernelControlSnapshot],
|
|
) -> Dict[str, Any]:
|
|
return {
|
|
"ts": transition.timestamp.isoformat() if hasattr(transition.timestamp, "isoformat") else str(transition.timestamp),
|
|
"trade_id": transition.trade_id,
|
|
"slot_id": transition.slot_id,
|
|
"prev_state": transition.prev_state.value,
|
|
"next_state": transition.next_state.value,
|
|
"trigger": transition.trigger,
|
|
"intent_id": transition.intent_id,
|
|
"event_id": transition.event_id,
|
|
"control_mode": transition.control_mode,
|
|
"control_verbosity": transition.control_verbosity,
|
|
"slot_state": slot.to_dict(),
|
|
"event_payload": json_safe(event) if event is not None else {},
|
|
"control_snapshot": control.as_dict() if control is not None else {},
|
|
"slot_state_json": json_text(slot.to_dict()),
|
|
}
|