코드 리뷰 F5 — Phase 5 consumer(agent-office /signal) prereq: PollState.signal_cycle_id (process auto-increment) + get_active_signals(now) + purge_expired_signals(now) helper. expires_at 없는 legacy signal은 expired 취급. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
60 lines
2.1 KiB
Python
60 lines
2.1 KiB
Python
"""PollState — process-wide singleton."""
|
|
from collections import deque
|
|
from dataclasses import dataclass, field
|
|
from datetime import datetime
|
|
|
|
|
|
@dataclass
|
|
class PollState:
|
|
portfolio: dict | None = None
|
|
news_sentiment: dict | None = None
|
|
screener_preview: dict | None = None
|
|
minute_bars: dict[str, deque] = field(default_factory=dict)
|
|
asking_price: dict[str, dict] = field(default_factory=dict)
|
|
# Phase 3b additions
|
|
daily_ohlcv: dict[str, list[dict]] = field(default_factory=dict)
|
|
chronos_predictions: dict[str, dict] = field(default_factory=dict)
|
|
minute_momentum: dict[str, str] = field(default_factory=dict)
|
|
signals: dict[str, dict] = field(default_factory=dict)
|
|
# F5 lifecycle
|
|
signal_cycle_id: int = 0
|
|
last_updated: dict[str, str] = field(default_factory=dict)
|
|
fetch_errors: dict[str, int] = field(default_factory=dict)
|
|
|
|
def get_active_signals(self, now: datetime) -> list[dict]:
|
|
"""expires_at > now 인 신호만 반환. expires_at 없거나 파싱 실패는 expired 취급."""
|
|
active: list[dict] = []
|
|
for sig in self.signals.values():
|
|
expires_at = sig.get("expires_at")
|
|
if not expires_at:
|
|
continue
|
|
try:
|
|
exp_dt = datetime.fromisoformat(expires_at)
|
|
except ValueError:
|
|
continue
|
|
if exp_dt > now:
|
|
active.append(sig)
|
|
return active
|
|
|
|
def purge_expired_signals(self, now: datetime) -> int:
|
|
"""만료된 signal 제거. expires_at 없거나 파싱 실패도 제거. 제거 개수 반환."""
|
|
to_drop = []
|
|
for ticker, sig in self.signals.items():
|
|
expires_at = sig.get("expires_at")
|
|
if not expires_at:
|
|
to_drop.append(ticker)
|
|
continue
|
|
try:
|
|
exp_dt = datetime.fromisoformat(expires_at)
|
|
except ValueError:
|
|
to_drop.append(ticker)
|
|
continue
|
|
if exp_dt <= now:
|
|
to_drop.append(ticker)
|
|
for t in to_drop:
|
|
del self.signals[t]
|
|
return len(to_drop)
|
|
|
|
|
|
state = PollState()
|