fix(reconcile): symbol을 슬롯 생성 시 함께 저장 + NULL 슬롯 backfill

reconcile_universe 시그니처를 set[str] → dict[str, str] (portfolio_id→symbol)로 변경.
기존 symbol=NULL 슬롯은 다음 reconcile 실행 시 자동 backfill.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-11 21:08:46 +09:00
co-authored by Claude Sonnet 4.6
parent 221a16342e
commit 24a6017457
29 changed files with 92 additions and 68 deletions
+20 -6
View File
@@ -118,7 +118,7 @@ class PostgreSQLSlotRepository:
return [self._row_to_slot(r) for r in cur.fetchall()]
def reconcile_universe(
self, instance_key: str, universe_portfolio_ids: set[str],
self, instance_key: str, universe: dict[str, str],
*, allow_retire: bool = True,
) -> dict[str, int]:
now = datetime.now(tz=timezone.utc)
@@ -131,24 +131,38 @@ class PostgreSQLSlotRepository:
(instance_key,),
)
existing = {r[0]: (r[1], r[2]) for r in cur.fetchall()}
# backfill symbol on slots that were created before this field was populated
cur.execute(
"SELECT portfolio_id FROM daily_slot "
"WHERE instance_key = %s AND portfolio_id IS NOT NULL AND symbol IS NULL",
(instance_key,),
)
null_symbol_pids = {r[0] for r in cur.fetchall()}
for pid in null_symbol_pids:
if pid in universe:
cur.execute(
"UPDATE daily_slot SET symbol = %s, updated_at = %s "
"WHERE instance_key = %s AND portfolio_id = %s AND symbol IS NULL",
(universe[pid], now, instance_key, pid),
)
# create new asset slots (idempotent via partial unique)
for pid in universe_portfolio_ids:
for pid, symbol in universe.items():
if pid in existing:
continue
cur.execute(
"INSERT INTO daily_slot "
"(slot_id, status, instance_key, portfolio_id, "
"created_at, updated_at) VALUES (%s, 'inactive', %s, %s, %s, %s) "
"(slot_id, status, symbol, instance_key, portfolio_id, "
"created_at, updated_at) VALUES (%s, 'inactive', %s, %s, %s, %s, %s) "
"ON CONFLICT (instance_key, portfolio_id) "
"WHERE portfolio_id IS NOT NULL DO NOTHING",
(uuid4(), instance_key, pid, now, now),
(uuid4(), symbol, instance_key, pid, now, now),
)
created += cur.rowcount
# retire / tombstone assets that left the universe (skipped under
# the mass-disappearance guard, dcy-020/OQ-2 D3)
if allow_retire:
for pid, (status, retired_at) in existing.items():
if pid in universe_portfolio_ids:
if pid in universe:
continue
if status in ("holding", "pending_buy", "pending_sell"):
tombstoned += 1