Initial commit: OpenClaw 워크스페이스 버전관리 시작
설정·스크립트·스킬·문서·큐레이션 메모리 추적. 시크릿(credentials/identity)·런타임 상태(state/logs/sessions/sqlite)· 백업(clobbered/bak)·dream 캐시는 .gitignore로 제외. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+112
@@ -0,0 +1,112 @@
|
||||
#!/usr/bin/env python3
|
||||
"""stock.briefing 폴백·강제 재실행.
|
||||
|
||||
retry/final: 오늘 portfolio_daily_snapshot.json에 오늘 키가 이미 있으면 skip (idempotent).
|
||||
없으면 stock_portfolio_report.py send 재실행. final 모드에선 그래도 실패 시 레이 텔레그램 알림.
|
||||
|
||||
force: 스냅샷 존재 여부 무관하게 21:00 fresh fetch로 데이터 덮어쓰기. 20:10 데이터가
|
||||
부정확할 때 21:00 안정된 데이터로 갱신용. 스냅샷 있으면 run 모드(스냅샷만, 메일·텔레그램 X),
|
||||
없으면 send 모드로 폴백 + 실패 시 알림.
|
||||
|
||||
usage:
|
||||
briefing_fallback.py retry # 20:30 — 스냅샷 없으면 재실행, 실패해도 알림 없음
|
||||
briefing_fallback.py final # 스냅샷 없으면 재실행 + 그래도 없으면 알림 (전통적 폴백)
|
||||
briefing_fallback.py force # 21:00 — 무조건 fresh fetch (스냅샷 갱신만, 노이즈 없음)
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import subprocess
|
||||
import sys
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from zoneinfo import ZoneInfo
|
||||
|
||||
KST = ZoneInfo('Asia/Seoul')
|
||||
WORKSPACE = Path('/Users/snowoyh/.openclaw/agents/stock/workspace')
|
||||
SNAPSHOT = WORKSPACE / 'state' / 'portfolio_daily_snapshot.json'
|
||||
REPORT = WORKSPACE / 'scripts' / 'stock_portfolio_report.py'
|
||||
|
||||
sys.path.insert(0, str(WORKSPACE / 'scripts'))
|
||||
|
||||
|
||||
def today_snapshot_exists() -> bool:
|
||||
if not SNAPSHOT.exists():
|
||||
return False
|
||||
try:
|
||||
data = json.loads(SNAPSHOT.read_text())
|
||||
except Exception:
|
||||
return False
|
||||
today = datetime.now(KST).strftime('%Y-%m-%d')
|
||||
return today in data
|
||||
|
||||
|
||||
def run_briefing() -> int:
|
||||
return subprocess.call(['/usr/bin/python3', str(REPORT), 'send'])
|
||||
|
||||
|
||||
def alert(text: str) -> None:
|
||||
from send_balance_to_budget import send_telegram
|
||||
send_telegram(text)
|
||||
|
||||
|
||||
def main() -> int:
|
||||
if len(sys.argv) < 2 or sys.argv[1] not in ('retry', 'final', 'force'):
|
||||
print('usage: briefing_fallback.py {retry|final|force}', file=sys.stderr)
|
||||
return 2
|
||||
mode = sys.argv[1]
|
||||
now = datetime.now(KST).strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
# 휴장일/주말은 메인 stock.briefing이 self-skip하므로 폴백도 의미 없음.
|
||||
# 특히 final 모드의 거짓 "스냅샷 없음" 텔레그램 경보를 차단한다.
|
||||
try:
|
||||
from holiday_sync import is_market_day_today
|
||||
if not is_market_day_today():
|
||||
print(f'[{mode}] {now} KRX 휴장일/주말 — 폴백 스킵')
|
||||
return 0
|
||||
except Exception as e:
|
||||
print(f'[{mode}] holiday-check-fail: {e} — 평소대로 진행', file=sys.stderr)
|
||||
|
||||
if mode == 'force':
|
||||
# 21:00 무조건 재실행 — 20:10 데이터가 부정확할 때 fresh fetch로 덮어씀.
|
||||
# 스냅샷 있으면 run(스냅샷만), 없으면 send(메일·텔레그램·스냅샷 + 실패 시 알림).
|
||||
if today_snapshot_exists():
|
||||
print(f'[force] {now} 스냅샷 갱신 (run 모드, 메일·텔레그램 없음).')
|
||||
code = subprocess.call(['/usr/bin/python3', str(REPORT), 'run'])
|
||||
print(f'[force] {now} run exit={code}')
|
||||
return 0 if code == 0 else 1
|
||||
print(f'[force] {now} 스냅샷 없음 → send 재실행 (메일·텔레그램 포함).')
|
||||
code = run_briefing()
|
||||
print(f'[force] {now} send exit={code}')
|
||||
if today_snapshot_exists():
|
||||
return 0
|
||||
alert(
|
||||
f'⚠️ stock.briefing {now} 폴백 모두 실패\n'
|
||||
f'오늘({datetime.now(KST):%Y-%m-%d}) 스냅샷이 생성되지 않았습니다.\n'
|
||||
f'수동 실행: python3 {REPORT} send'
|
||||
)
|
||||
return 1
|
||||
|
||||
if today_snapshot_exists():
|
||||
print(f'[{mode}] {now} 오늘 스냅샷 있음, skip.')
|
||||
return 0
|
||||
|
||||
print(f'[{mode}] {now} 오늘 스냅샷 없음 → stock.briefing 재실행.')
|
||||
code = run_briefing()
|
||||
print(f'[{mode}] {now} stock.briefing exit={code}')
|
||||
|
||||
if today_snapshot_exists():
|
||||
print(f'[{mode}] {now} 재실행으로 스냅샷 생성됨.')
|
||||
return 0
|
||||
|
||||
if mode == 'final':
|
||||
alert(
|
||||
f'⚠️ stock.briefing {now} 폴백 모두 실패\n'
|
||||
f'오늘({datetime.now(KST):%Y-%m-%d}) 스냅샷이 생성되지 않았습니다.\n'
|
||||
f'수동 실행: python3 {REPORT} send'
|
||||
)
|
||||
return 1 if mode == 'final' else 0
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user