From 93f046c76c81720580a1534c065cf36dcaab3e93 Mon Sep 17 00:00:00 2001 From: kjh2064 Date: Mon, 22 Jun 2026 10:25:23 +0900 Subject: [PATCH] WBS-7.9, WBS-7.10: KIS data collection fallback resiliency & deploy_gas.py pre-deploy thin-adapter lint integration --- RetirementAssetPortfolio.yaml | 2 + docs/ROADMAP_WBS.md | 2 + src/quant_engine/kis_data_collection_v1.py | 13 +++++++ tools/deploy_gas.py | 44 ++++++++++++++++++++++ 4 files changed, 61 insertions(+) diff --git a/RetirementAssetPortfolio.yaml b/RetirementAssetPortfolio.yaml index 8f72275..568eb67 100644 --- a/RetirementAssetPortfolio.yaml +++ b/RetirementAssetPortfolio.yaml @@ -95,6 +95,8 @@ source_of_truth_order: 28: "prompts/*.md — reusable prompt entrypoints for analysis/review" 29: "examples/*.yaml and examples/examples.jsonl — examples are illustrative and never override rules" 30: "tests/*.yaml — consistency checks for future edits" + 31: "spec/03_risk_policy.yaml — legacy redirect stub for backward compatibility" + 32: "spec/04_strategy_rules.yaml — legacy redirect stub for backward compatibility" load_sequence: STEP_1_always: diff --git a/docs/ROADMAP_WBS.md b/docs/ROADMAP_WBS.md index 0b06c2b..4b96f54 100644 --- a/docs/ROADMAP_WBS.md +++ b/docs/ROADMAP_WBS.md @@ -1198,6 +1198,8 @@ python tools/update_sector_universe_from_naver.py --limit 10 --apply # 원본 [x] WBS-7.5: OVERHANG_PRESSURE_V1 폴백 비례화 (2026-06-21 완료, avg_volume_5d 비례식 + EXPERT_PRIOR 등록) [x] WBS-7.6: 슬리피지 실측 캡처 스캐폴딩 구축 완료 (2026-06-21, 비교 자체는 체결 5건 누적 대기) [x] WBS-7.8: ETF NAV 수집경로 재검토 + 공매도 잔고율 운영절차 문서화 (2026-06-21 완료) +[x] WBS-7.9: KIS 수집 예외 처리 & Fallback 고도화 (2026-06-22 완료, KIS 실패 시 Naver/Seed JSON 폴백 복원력 적용) +[x] WBS-7.10: GAS 배포 전 Thin Adapter 오염 사전 검출 연동 (2026-06-22 완료, deploy_gas.py에 audit/validate pre-deploy hook 탑재) ``` --- diff --git a/src/quant_engine/kis_data_collection_v1.py b/src/quant_engine/kis_data_collection_v1.py index 5a18720..68f9aac 100644 --- a/src/quant_engine/kis_data_collection_v1.py +++ b/src/quant_engine/kis_data_collection_v1.py @@ -225,8 +225,21 @@ def _collect_one(row: dict[str, Any], *, kis_account: str, include_naver: bool, normalized.setdefault("relative_return_20d", naver.get("relative_return_20d")) normalized.setdefault("volume_ratio_5d", naver.get("volume_ratio_5d")) normalized.setdefault("naver_price_status", naver.get("status")) + # KIS API 누락 또는 실패 시 Naver 가격 정보를 가격 필드들의 Fallback으로 지정 + normalized.setdefault("current_price", naver.get("close")) + normalized.setdefault("open", naver.get("open")) + normalized.setdefault("high", naver.get("high")) + normalized.setdefault("low", naver.get("low")) + normalized.setdefault("volume", naver.get("volume")) provenance["source_priority"].append("naver_finance") + # KIS 및 Naver 가격 정보가 모두 없을 시, GatherTradingData.json 원본 시드 가격을 최후의 수단으로 복원 + normalized.setdefault("current_price", _coerce_float(row.get("current_price") or row.get("Current_Price") or row.get("close"))) + normalized.setdefault("open", _coerce_float(row.get("open") or row.get("Open"))) + normalized.setdefault("high", _coerce_float(row.get("high") or row.get("High"))) + normalized.setdefault("low", _coerce_float(row.get("low") or row.get("Low"))) + normalized.setdefault("volume", _coerce_float(row.get("volume") or row.get("Volume"))) + normalized.setdefault("collection_as_of", _kst_now_iso()) return normalized, provenance diff --git a/tools/deploy_gas.py b/tools/deploy_gas.py index 2af8510..73f69c2 100644 --- a/tools/deploy_gas.py +++ b/tools/deploy_gas.py @@ -248,14 +248,58 @@ def sync_sector_insights_via_clasp_run() -> bool: return True +def run_pre_deploy_linter() -> bool: + print("[deploy_gas] Running pre-deploy gas thin-adapter audit...") + # Run auditor v1 + audit_res = subprocess.run( + ["python", "tools/audit_gas_thin_adapter_v1.py"], + cwd=str(ROOT), + shell=True, + capture_output=True, + text=True, + encoding="utf-8", + errors="replace", + ) + if audit_res.returncode != 0: + print("[deploy_gas] Error: tools/audit_gas_thin_adapter_v1.py failed") + print(audit_res.stdout) + print(audit_res.stderr) + return False + + # Run validator v2 + validate_res = subprocess.run( + ["python", "tools/validate_gas_thin_adapter_v2.py"], + cwd=str(ROOT), + shell=True, + capture_output=True, + text=True, + encoding="utf-8", + errors="replace", + ) + print(validate_res.stdout) + if validate_res.returncode != 0: + print("[deploy_gas] ABORT: GAS Thin Adapter validation failed!") + if validate_res.stderr: + print("STDERR: " + validate_res.stderr) + return False + + print("[deploy_gas] Pre-deploy thin-adapter audit PASS") + return True + + def main() -> None: parser = argparse.ArgumentParser(description="GAS auto-deploy") parser.add_argument("--dry-run", action="store_true", help="List files without writing") parser.add_argument("--skip-push", action="store_true", help="Bundle only, skip clasp push") + parser.add_argument("--skip-lint", action="store_true", help="Skip pre-deploy thin-adapter validation") parser.add_argument("--sync-sector-insights", action="store_true", help="POST sector insight JSON to a deployed GAS web app") parser.add_argument("--webapp-url", default=os.environ.get("GAS_WEBAPP_URL", DEFAULT_WEBAPP_URL), help="Apps Script web app URL for sync POST") args = parser.parse_args() + if not args.skip_lint: + if not run_pre_deploy_linter(): + raise SystemExit(1) + ok = build_deploy(dry_run=args.dry_run) if not ok: print("[deploy_gas] Some source files missing -- check warnings above")