feat(intents): dcy-011 buy intent portfolio_id 필수 계약 + ledger skip 가시화 (BAT-015)
dcy-011 전 주기 delivery. issue #6(buy 식별자 정정) + issue #11(ledger silent skip) 흡수. 거버넌스: 기존 Implemented REQ는 change-req, 신규 기능만 신규 REQ(OQ-3). - REQ-007 (change-req): buy admission portfolio_id 필수화 + SnapshotStore symbol resolve + unknown_portfolio 거부 (hard cutover, OQ-1/OQ-2) - REQ-021 (change-req): buy 메시지 payload symbol 제거 - REQ-018 (change-req): _record_ledger skip 가시화 (WARN + ledger_skipped 카운터) - REQ-055 (신규): closed↔ledger_outbox anti-join 운영 점검 find_ledger_gap_slots 코드: intents.py(BuyIntent portfolio_id 필수 + resolve_symbol 주입 + _admit_buy reject 분기), messaging_handlers.py, runtime/daemon.py(_resolve_symbol 배선), execution.py(skip WARN 로그), adapters/persistence/ops_checks.py(신규 anti-join). 검증: 단위 282 passed/32 skipped, 인프라 게이트(PG+RabbitMQ) 314 passed, ruff clean. BAT-015(standard) release-candidate. srs/index Next ID → REQ-056. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
"""Operational integrity checks — read-only (REQ-055 / dcy-011 FR-5, issue #11).
|
||||
|
||||
`find_ledger_gap_slots` detects `closed` slots that produced NO ledger_outbox
|
||||
row (the silent-skip symptom): either `portfolio_id IS NULL` (skip at fill) or
|
||||
no outbox row carries the slot's portfolio_id as `asset_id`. Read-only — no
|
||||
schema change; `daily_slot` is durable so this is a permanent detector even
|
||||
without an outbox marker row (dcy-010 OQ-4).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import date
|
||||
from typing import Any, Optional
|
||||
|
||||
from trex_core.adapters.persistence.postgresql import _require_psycopg
|
||||
|
||||
_LEDGER_GAP_SQL = """
|
||||
SELECT s.slot_id, s.trading_date, s.symbol, s.portfolio_id, s.broker_order_id
|
||||
FROM daily_slot s
|
||||
WHERE s.status = 'closed'
|
||||
AND (%(td)s::date IS NULL OR s.trading_date = %(td)s)
|
||||
AND (
|
||||
s.portfolio_id IS NULL
|
||||
OR NOT EXISTS (
|
||||
SELECT 1 FROM ledger_outbox o WHERE o.asset_id = s.portfolio_id
|
||||
)
|
||||
)
|
||||
ORDER BY s.trading_date, s.slot_id
|
||||
"""
|
||||
|
||||
_COLS = ("slot_id", "trading_date", "symbol", "portfolio_id", "broker_order_id")
|
||||
|
||||
|
||||
def find_ledger_gap_slots(
|
||||
dsn: str,
|
||||
*,
|
||||
trading_date: Optional[date] = None,
|
||||
psycopg_module: Any = None,
|
||||
) -> list[dict]:
|
||||
"""Return closed slots with no corresponding ledger_outbox row (FR-5).
|
||||
|
||||
Pass `trading_date` to scope to one day; omit for all days.
|
||||
"""
|
||||
psycopg = _require_psycopg(psycopg_module)
|
||||
with psycopg.connect(dsn) as conn:
|
||||
with conn.cursor() as cur:
|
||||
cur.execute(_LEDGER_GAP_SQL, {"td": trading_date})
|
||||
return [dict(zip(_COLS, row)) for row in cur.fetchall()]
|
||||
Reference in New Issue
Block a user