From b20c21cbd2eb1cc8fab7434896fce7dc1742f810 Mon Sep 17 00:00:00 2001 From: hyowons Date: Thu, 11 Jun 2026 15:56:45 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20VI=20=EB=B0=B0=EC=A7=80=20=EC=9E=90?= =?UTF-8?q?=EB=8F=99=20=EB=A7=8C=EB=A3=8C=20=E2=80=94=20=ED=95=B4=EC=A0=9C?= =?UTF-8?q?=201h=20=EB=A9=94=EC=8B=9C=EC=A7=80=20=EB=AF=B8=EC=88=98?= =?UTF-8?q?=EC=8B=A0=20=EC=8B=9C=20=EC=98=81=EA=B5=AC=20=EC=9E=94=EB=A5=98?= =?UTF-8?q?=20=EB=B0=A9=EC=A7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 관찰(2026-06-11): VI 발동 1h 이후 해제 메시지가 안 오고 1224(해제시각)도 발동시각과 동일 → active=true 박제, 배지가 안 사라짐 - get_vi/active_vis 읽기 시점에 발동 후 180초(2분+임의연장30s+여유) 경과 시 active=False 처리 (_vi_expired). 저장 원본은 유지 - 1h 원본 필드 stderr 로그 추가 — 9068/1224 매핑 실증용 (VI 드물어 부담 없음) Co-Authored-By: Claude Opus 4.8 (1M context) --- .../stock/workspace/scripts/realtime_hub.py | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/agents/stock/workspace/scripts/realtime_hub.py b/agents/stock/workspace/scripts/realtime_hub.py index 6a061c6..9e78e97 100644 --- a/agents/stock/workspace/scripts/realtime_hub.py +++ b/agents/stock/workspace/scripts/realtime_hub.py @@ -45,6 +45,8 @@ _BACKOFF_MAX = 30.0 _MAX_CONN_AGE = 25 * 60 # recv 타임아웃 — 이 시간 내 아무것도 안 오면 루프 돌며 stop/age 체크 _RECV_TIMEOUT = 5.0 +# VI 배지 자동 만료 (초) — 해제 1h 메시지 미수신 대비. VI 단일가 2분 + 임의연장 ≤30초 + 여유 30초. +_VI_ACTIVE_TTL = 180.0 # 장 마감 시간대 대기 — 개장 임박(평일 07:00~08:00)엔 1분, 그 외엔 다음 영업일 07:00까지 자되 6시간 상한. _PREOPEN_POLL_SEC = 60.0 # 개장 임박: 1분마다 → 08:00 개장을 1분 안에 포착 _CLOSED_WAIT_CAP = 6 * 3600.0 # 밤·주말·휴장: 다음 개장까지 자되 이 상한(자가교정용 — 날짜 계산 오류 시에도 6h마다 재확인) @@ -97,11 +99,31 @@ class RealtimeHub: def get_vi(self, code: str) -> dict | None: with self._lock: v = self._vi.get(kc._clean_code(code)) - return dict(v) if v else None + return self._vi_expired(v) if v else None def active_vis(self) -> dict[str, dict]: with self._lock: - return {c: dict(v) for c, v in self._vi.items() if v.get('active')} + out = {} + for c, v in self._vi.items(): + ev = self._vi_expired(v) + if ev.get('active'): + out[c] = ev + return out + + @staticmethod + def _vi_expired(v: dict) -> dict: + """VI 자동 만료 — 해제(1h) 메시지가 안 오는 경우 대비. + VI 단일가는 2분 + 임의연장 ≤30초라, 발동 후 _VI_ACTIVE_TTL 초과 시 읽기 시점에 active=False. + 저장 상태는 건드리지 않는다(실증·디버깅용 원본 유지).""" + ev = dict(v) + if ev.get('active') and ev.get('ts'): + try: + age = (datetime.now(KST) - datetime.fromisoformat(ev['ts'])).total_seconds() + if age > _VI_ACTIVE_TTL: + ev['active'] = False + except Exception: + pass + return ev def is_connected(self) -> bool: return self._connected.is_set() @@ -328,6 +350,9 @@ class RealtimeHub: } elif typ == '1h': # VI 발동/해제 # active 판정: 9068 VI발동구분. 해제시각(1224) 유무와 함께 실증 확인 예정(A단계). + # 실증 로그 — VI는 드물어 부담 없음. 2026-06-11 관찰: 발동 후 해제 1h가 안 오고 + # 1224(해제시각)가 발동시각과 동일 → 읽기 시점 TTL 만료(_vi_expired)로 방어 중. + print(f'[1h raw] {code} {vals}', file=sys.stderr, flush=True) kind_raw = (vals.get('9068') or '').strip() with self._lock: self._vi[code] = {