from __future__ import annotations from dataclasses import dataclass from datetime import datetime from uuid import UUID from trex_core.application.alarm_emitter import AlarmEmitter @dataclass(frozen=True) class LedgerSkipRecord: skip_key: str instance_key: str slot_id: UUID portfolio_id: str | None symbol: str | None order_id: str | None execution_id: str | None side: str qty: float price: float executed_at: datetime order_venue: str execution_venue: str skip_reason: str status: str resolution_reason: str | None resolved_at: datetime | None created_at: datetime updated_at: datetime def make_ledger_skip_key( *, instance_key: str, slot_id: UUID, side: str, order_id: str | None, execution_id: str | None, skip_reason: str, ) -> str: return ":".join( [ "ledger-skip", instance_key, str(slot_id), side, order_id or "-", execution_id or "-", skip_reason, ] ) def ledger_skip_alarm_fields(record: LedgerSkipRecord) -> dict[str, object]: return { "skip_key": record.skip_key, "instance_key": record.instance_key, "slot_id": str(record.slot_id), "portfolio_id": record.portfolio_id, "symbol": record.symbol, "order_id": record.order_id, "execution_id": record.execution_id, "side": record.side, "qty": record.qty, "price": record.price, "executed_at": record.executed_at.isoformat(), "order_venue": record.order_venue, "execution_venue": record.execution_venue, "skip_reason": record.skip_reason, "status": record.status, "resolution_reason": record.resolution_reason, "resolved_at": record.resolved_at.isoformat() if record.resolved_at else None, "created_at": record.created_at.isoformat(), "updated_at": record.updated_at.isoformat(), } def publish_ledger_skip_alarm( alarm_emitter: AlarmEmitter | None, record: LedgerSkipRecord, *, resolved: bool, ) -> None: if alarm_emitter is None: return code = "ledger_skip_resolved" if resolved else "ledger_skip_unresolved" title = ( f"원장 skip 해소: slot={record.slot_id} reason={record.resolution_reason or 'resolved'}" if resolved else f"원장 skip 발생: slot={record.slot_id} reason={record.skip_reason}" ) alarm_emitter.publish_alarm( code, title, fields=ledger_skip_alarm_fields(record), severity="info" if resolved else "critical", )