Files
siloqy/prod/clean_arch/dita_v2/journal.py
Codex 84e4a50e3f repo hygiene: track the PINK launcher import closure
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>
2026-06-12 15:09:32 +02:00

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()),
}