feat: persist skipped ledger fills

Co-authored-by: genichin <4282227+genichin@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2026-08-24 13:23:06 +00:00
committed by GitHub
co-authored by genichin
parent df0258bc36
commit 76cde2edd0
19 changed files with 1246 additions and 151 deletions
+120 -4
View File
@@ -1,10 +1,13 @@
from __future__ import annotations
import json
from datetime import datetime
from types import SimpleNamespace
from uuid import UUID
from trex_core.application.ledger_outbox import OutboxOutcome
from trex_core.application.ledger_port import LedgerTrade
from trex_core.application.ledger_skip import LedgerSkipRecord
from trex_core.runtime import cli
@@ -63,6 +66,33 @@ def _evidence():
}
def _skip_record():
return LedgerSkipRecord(
skip_key="ledger-skip:inst-1:slot-1:sell:12345678:0001234567:kis-daily-ccld:20260824:12345678:0001234567:10:missing_execution_id",
instance_key="inst-1",
slot_id=UUID(_SLOT_ID),
portfolio_id="PF-1",
symbol="005930",
order_id="12345678:0001234567",
execution_id="kis-daily-ccld:20260824:12345678:0001234567:10",
side="sell",
qty=10.0,
price=70000.0,
executed_at=datetime.fromisoformat("2026-08-24T00:01:02+00:00"),
order_venue="SOR",
execution_venue="unknown",
skip_reason="missing_execution_id",
status="resolved",
resolution_reason="manual_ledger_backfill",
resolved_at=datetime.fromisoformat("2026-08-24T00:03:02+00:00"),
created_at=datetime.fromisoformat("2026-08-24T00:01:02+00:00"),
updated_at=datetime.fromisoformat("2026-08-24T00:03:02+00:00"),
)
_SLOT_ID = "00000000-0000-0000-0000-000000000001"
def test_ledger_backfill_parser_accepts_required_flags():
parser = cli._build_parser()
args = parser.parse_args([
@@ -80,7 +110,7 @@ def test_ledger_backfill_dry_run_emits_trade_and_writes_audit(monkeypatch, tmp_p
monkeypatch.setattr(cli, "_lookup_ledger_backfill_evidence",
lambda cfg, **kwargs: (_evidence(), SimpleNamespace(mode="env", identity_label="kis-config")))
monkeypatch.setattr(cli, "_resolve_ledger_backfill_slot",
lambda dsn, **kwargs: {"slot_id": "slot-1", "portfolio_id": "PF-1", "status": "inactive", "symbol": "005930"})
lambda dsn, **kwargs: {"slot_id": _SLOT_ID, "portfolio_id": "PF-1", "status": "inactive", "symbol": "005930"})
monkeypatch.setattr(cli, "_lookup_outbox_by_execution_id", lambda dsn, **kwargs: [])
rc = cli._cmd_ledger_backfill(_args(tmp_path, dry_run=True), _cfg(), lambda _: True)
@@ -99,7 +129,7 @@ def test_ledger_backfill_live_requires_live_flag(monkeypatch, tmp_path, capsys):
monkeypatch.setattr(cli, "_lookup_ledger_backfill_evidence",
lambda cfg, **kwargs: (_evidence(), SimpleNamespace(mode="env", identity_label="kis-config")))
monkeypatch.setattr(cli, "_resolve_ledger_backfill_slot",
lambda dsn, **kwargs: {"slot_id": "slot-1", "portfolio_id": "PF-1", "status": "inactive", "symbol": "005930"})
lambda dsn, **kwargs: {"slot_id": _SLOT_ID, "portfolio_id": "PF-1", "status": "inactive", "symbol": "005930"})
monkeypatch.setattr(cli, "_lookup_outbox_by_execution_id", lambda dsn, **kwargs: [])
rc = cli._cmd_ledger_backfill(_args(tmp_path, live=False), _cfg(live=True), lambda _: True)
@@ -115,7 +145,7 @@ def test_ledger_backfill_existing_completed_row_is_idempotent(monkeypatch, tmp_p
monkeypatch.setattr(cli, "_lookup_ledger_backfill_evidence",
lambda cfg, **kwargs: (_evidence(), SimpleNamespace(mode="env", identity_label="kis-config")))
monkeypatch.setattr(cli, "_resolve_ledger_backfill_slot",
lambda dsn, **kwargs: {"slot_id": "slot-1", "portfolio_id": "PF-1", "status": "inactive", "symbol": "005930"})
lambda dsn, **kwargs: {"slot_id": _SLOT_ID, "portfolio_id": "PF-1", "status": "inactive", "symbol": "005930"})
existing = [{
"correlation_id": "PF-1:12345678:0001234567:kis-daily-ccld:20260824:12345678:0001234567:10",
"asset_id": "PF-1",
@@ -147,12 +177,23 @@ def test_ledger_backfill_existing_completed_row_is_idempotent(monkeypatch, tmp_p
def process_correlation_id(self, correlation_id):
raise AssertionError("completed execution must not be re-processed")
class _Store:
def __init__(self, *_args, **_kwargs):
pass
def resolve_for_fill(self, **_kwargs):
return []
import trex_core.adapters.ledger.portfolio_hub as hub_mod
import trex_core.adapters.persistence.postgresql as pg_mod
monkeypatch.setattr(pg_mod, "PostgreSQLLedgerOutbox", lambda dsn: _Outbox())
monkeypatch.setattr(hub_mod, "PortfolioHubLedger", lambda *args, **kwargs: object())
monkeypatch.setattr(cli, "LedgerWriter", _Writer)
monkeypatch.setattr(
"trex_core.adapters.persistence.ledger_skip_store.PostgreSQLLedgerSkipStore",
_Store,
)
rc = cli._cmd_ledger_backfill(_args(tmp_path), _cfg(), lambda _: True)
@@ -165,7 +206,7 @@ def test_ledger_backfill_live_enqueue_and_processes_exact_trade(monkeypatch, tmp
monkeypatch.setattr(cli, "_lookup_ledger_backfill_evidence",
lambda cfg, **kwargs: (_evidence(), SimpleNamespace(mode="env", identity_label="kis-config")))
monkeypatch.setattr(cli, "_resolve_ledger_backfill_slot",
lambda dsn, **kwargs: {"slot_id": "slot-1", "portfolio_id": "PF-1", "status": "inactive", "symbol": "005930"})
lambda dsn, **kwargs: {"slot_id": _SLOT_ID, "portfolio_id": "PF-1", "status": "inactive", "symbol": "005930"})
monkeypatch.setattr(cli, "_lookup_outbox_by_execution_id", lambda dsn, **kwargs: [])
class _Outbox:
@@ -195,12 +236,23 @@ def test_ledger_backfill_live_enqueue_and_processes_exact_trade(monkeypatch, tmp
assert correlation_id == self.seen_outbox.trade.correlation_id
return OutboxOutcome.COMPLETED
class _Store:
def __init__(self, *_args, **_kwargs):
pass
def resolve_for_fill(self, **_kwargs):
return []
import trex_core.adapters.ledger.portfolio_hub as hub_mod
import trex_core.adapters.persistence.postgresql as pg_mod
monkeypatch.setattr(pg_mod, "PostgreSQLLedgerOutbox", lambda dsn: outbox)
monkeypatch.setattr(hub_mod, "PortfolioHubLedger", lambda *args, **kwargs: object())
monkeypatch.setattr(cli, "LedgerWriter", _Writer)
monkeypatch.setattr(
"trex_core.adapters.persistence.ledger_skip_store.PostgreSQLLedgerSkipStore",
_Store,
)
rc = cli._cmd_ledger_backfill(_args(tmp_path), _cfg(), lambda _: True)
@@ -210,3 +262,67 @@ def test_ledger_backfill_live_enqueue_and_processes_exact_trade(monkeypatch, tmp
assert outbox.trade is not None
assert outbox.trade.order_venue == "SOR"
assert outbox.trade.venue is None
def test_ledger_backfill_resolves_matching_skip(monkeypatch, tmp_path, capsys):
monkeypatch.setattr(cli, "_lookup_ledger_backfill_evidence",
lambda cfg, **kwargs: (_evidence(), SimpleNamespace(mode="env", identity_label="kis-config")))
monkeypatch.setattr(cli, "_resolve_ledger_backfill_slot",
lambda dsn, **kwargs: {"slot_id": _SLOT_ID, "portfolio_id": "PF-1", "status": "inactive", "symbol": "005930"})
monkeypatch.setattr(cli, "_lookup_outbox_by_execution_id", lambda dsn, **kwargs: [])
class _Outbox:
def enqueue(self, trade: LedgerTrade):
self.trade = trade
def get(self, correlation_id):
return SimpleNamespace(
status=SimpleNamespace(value="completed"),
attempts=1,
last_outcome=SimpleNamespace(value="completed"),
last_error=None,
)
class _Writer:
def __init__(self, *_args, **_kwargs):
pass
def process_correlation_id(self, _correlation_id):
return OutboxOutcome.COMPLETED
class _Store:
def __init__(self, *_args, **_kwargs):
pass
def resolve_for_fill(self, **_kwargs):
return [_skip_record()]
published = []
class _Emitter:
def publish_alarm(self, code, title, *, fields=None, severity="critical"):
published.append((code, severity, fields))
class _Transport:
def close(self):
return None
import trex_core.adapters.ledger.portfolio_hub as hub_mod
import trex_core.adapters.persistence.postgresql as pg_mod
monkeypatch.setattr(pg_mod, "PostgreSQLLedgerOutbox", lambda dsn: _Outbox())
monkeypatch.setattr(hub_mod, "PortfolioHubLedger", lambda *args, **kwargs: object())
monkeypatch.setattr(cli, "LedgerWriter", _Writer)
monkeypatch.setattr(
"trex_core.adapters.persistence.ledger_skip_store.PostgreSQLLedgerSkipStore",
_Store,
)
monkeypatch.setattr(cli, "_default_alarm_emitter", lambda cfg: (_Emitter(), _Transport()))
rc = cli._cmd_ledger_backfill(_args(tmp_path), _cfg(), lambda _: True)
assert rc == 0
out = json.loads(capsys.readouterr().out)
assert len(out["resolved_skips"]) == 1
assert out["resolved_skips"][0]["resolution_reason"] == "manual_ledger_backfill"
assert published[0][0] == "ledger_skip_resolved"
+132
View File
@@ -0,0 +1,132 @@
from __future__ import annotations
import json
from datetime import UTC, datetime
from types import SimpleNamespace
from uuid import UUID
from trex_core.application.ledger_skip import LedgerSkipRecord
from trex_core.runtime import cli
def _cfg():
return SimpleNamespace(dsn="postgresql://x/y", rabbitmq=None)
def _record(*, status="unresolved", resolution_reason=None):
return LedgerSkipRecord(
skip_key="ledger-skip:inst-1:slot-1:sell:O-1:E-1:missing_execution_id",
instance_key="inst-1",
slot_id=UUID("00000000-0000-0000-0000-000000000001"),
portfolio_id="PF-1",
symbol="005930",
order_id="O-1",
execution_id="E-1",
side="sell",
qty=10.0,
price=70000.0,
executed_at=datetime(2026, 8, 24, 0, 1, 2, tzinfo=UTC),
order_venue="SOR",
execution_venue="unknown",
skip_reason="missing_execution_id",
status=status,
resolution_reason=resolution_reason,
resolved_at=(
datetime(2026, 8, 24, 0, 5, 0, tzinfo=UTC)
if status == "resolved"
else None
),
created_at=datetime(2026, 8, 24, 0, 1, 2, tzinfo=UTC),
updated_at=datetime(2026, 8, 24, 0, 5, 0, tzinfo=UTC),
)
def test_ledger_skips_parser_accepts_list_and_resolve():
parser = cli._build_parser()
args = parser.parse_args(["ledger", "skips", "list", "--status", "all"])
assert args.ledger_cmd == "skips"
assert args.ledger_skips_cmd == "list"
args = parser.parse_args(["ledger", "skips", "resolve", "--skip-key", "abc"])
assert args.ledger_skips_cmd == "resolve"
def test_ledger_skips_list_outputs_records(monkeypatch, capsys):
class _Store:
def __init__(self, *_args, **_kwargs):
pass
def list_records(self, **_kwargs):
return [_record()]
monkeypatch.setattr(
"trex_core.adapters.persistence.ledger_skip_store.PostgreSQLLedgerSkipStore",
_Store,
)
args = SimpleNamespace(
cmd="ledger",
ledger_cmd="skips",
ledger_skips_cmd="list",
date="2026-08-24",
portfolio_id="PF-1",
status="unresolved",
limit=10,
instance="inst-1",
config=None,
config_path=None,
env_file=None,
json=True,
timeout=10.0,
)
rc = cli._cmd_ledger_skips_list(args, _cfg())
assert rc == 0
out = json.loads(capsys.readouterr().out)
assert out["count"] == 1
assert out["records"][0]["skip_reason"] == "missing_execution_id"
def test_ledger_skips_resolve_marks_record_and_emits_alert(monkeypatch, capsys):
class _Store:
def __init__(self, *_args, **_kwargs):
pass
def resolve_by_key(self, skip_key, *, resolution_reason):
assert skip_key == "abc"
return _record(status="resolved", resolution_reason=resolution_reason)
published = []
class _Emitter:
def publish_alarm(self, code, title, *, fields=None, severity="critical"):
published.append((code, severity, fields))
class _Transport:
def close(self):
return None
monkeypatch.setattr(
"trex_core.adapters.persistence.ledger_skip_store.PostgreSQLLedgerSkipStore",
_Store,
)
monkeypatch.setattr(cli, "_default_alarm_emitter", lambda cfg: (_Emitter(), _Transport()))
args = SimpleNamespace(
cmd="ledger",
ledger_cmd="skips",
ledger_skips_cmd="resolve",
skip_key="abc",
reason="operator_reconciled",
instance="inst-1",
config=None,
config_path=None,
env_file=None,
json=True,
timeout=10.0,
)
rc = cli._cmd_ledger_skips_resolve(args, _cfg())
assert rc == 0
out = json.loads(capsys.readouterr().out)
assert out["result"] == "resolved"
assert published[0][0] == "ledger_skip_resolved"