#!/usr/bin/env python3 from __future__ import annotations import argparse import json import yaml from pathlib import Path ROOT = Path(__file__).resolve().parents[1] def main() -> int: ap = argparse.ArgumentParser() ap.add_argument("--manifest", default="runtime/active_artifact_manifest.yaml") ap.add_argument("--packet", default="Temp/final_decision_packet_active.json") ap.add_argument("--out", default="Temp/final_context_for_llm_v5.yaml") args = ap.parse_args() manifest_path = ROOT / args.manifest packet_path = ROOT / args.packet out_path = ROOT / args.out if not manifest_path.exists(): print(f"Manifest not found: {manifest_path}") return 1 if not packet_path.exists(): print(f"Packet not found: {packet_path}") return 1 manifest = yaml.safe_load(manifest_path.read_text(encoding="utf-8")) packet = json.loads(packet_path.read_text(encoding="utf-8")) # Also load GatherTradingData.json to get harness context fields! trading_data_path = ROOT / "GatherTradingData.json" trading_data = {} if trading_data_path.exists(): try: trading_data = json.loads(trading_data_path.read_text(encoding="utf-8")) except Exception: pass # Extract _harness_context from GatherTradingData.json harness_context = trading_data.get("data", {}).get("_harness_context", {}) # Extract info from manifest manifest_info = { "generated_at": manifest.get("generated_at"), "active_count_per_formula": manifest.get("active_count_per_formula"), "report_active_artifact_match_pct": manifest.get("report_active_artifact_match_pct"), } # Extract canonical metrics metrics = packet.get("canonical_metrics", {}) total_asset = metrics.get("total_asset_krw", {}).get("value") cash_shortfall = metrics.get("cash_shortfall_min_krw", {}).get("value") cash_recovered = metrics.get("cash_recovered_krw", {}).get("value") or metrics.get("cash_recovered", {}).get("value") gate = metrics.get("final_execution_gate", {}).get("value") or packet.get("routing_serving", {}).get("global_execution_gate", {}).get("value") hts_order_count = metrics.get("hts_order_count", {}).get("value") # Build blockers blockers = [] if gate != "PASS": blockers.append({ "gate": gate, "reason": "final_execution_gate is not PASS (AUDIT_ONLY / BLOCK_EXECUTION)" }) action_table = [] # Add per ticker decisions for item in packet.get("per_ticker_final_judgment", []): action_table.append({ "ticker": item.get("ticker"), "verdict": item.get("verdict"), "effective_confidence": item.get("effective_confidence"), "horizon": item.get("horizon") }) order_blueprints = [] if "order_blueprint" in packet and isinstance(packet["order_blueprint"], dict): order_blueprints = packet["order_blueprint"].get("rows", []) shadow_ledger_info = { "cash_raise_plan": packet.get("cash_raise_plan", {}), "total_asset_krw": total_asset, "cash_shortfall_min_krw": cash_shortfall, "cash_recovered_krw": cash_recovered, "hts_order_count": hts_order_count, "order_blueprints": order_blueprints } data_missing_info = [] data_quality = packet.get("data_quality", {}) if data_quality.get("missing_critical_field_count", {}).get("value", 0) > 0: data_missing_info.append(f"Missing {data_quality.get('missing_critical_field_count', {}).get('value')} critical fields") dq_gate_raw = harness_context.get("data_quality_gate_v2_json") if dq_gate_raw: if isinstance(dq_gate_raw, str): try: dq_gate_raw = json.loads(dq_gate_raw) except Exception: pass if isinstance(dq_gate_raw, dict): for warn in dq_gate_raw.get("special_warnings", []): data_missing_info.append(warn) education_notes = [ "LLM must copy and render already calculated values only.", "Do NOT perform any mathematical calculations.", "Strictly forbidden to modify target execution actions or sizing.", ] # Map the 11 contract-required sections: # 01_metadata_and_manifest_alias metadata_kst = manifest.get("generated_at", "") if metadata_kst.endswith("Z"): metadata_kst = metadata_kst.replace("Z", "+00:00") try: dt = datetime.fromisoformat(metadata_kst) kst = timezone(timedelta(hours=9)) dt_kst = dt.astimezone(kst) generated_at_kst = dt_kst.isoformat() except Exception: generated_at_kst = metadata_kst active_aliases = manifest.get("active_aliases", {}) active_alias = list(active_aliases.keys())[0] if active_aliases else "final_decision_packet_active" sec01 = { "document_id": "FINAL_CONTEXT_FOR_LLM_V5", "generated_at_kst": generated_at_kst, "active_artifact_alias": active_alias } # 02_portfolio_health cash_ratio = harness_context.get("cash_current_pct_d2") or harness_context.get("settlement_cash_pct") or 0.0 if not cash_ratio and total_asset: buy_power = harness_context.get("buy_power_krw") or harness_context.get("settlement_cash_d2_krw") or 0.0 cash_ratio = round(buy_power / total_asset * 100, 2) achievement_pct = harness_context.get("goal_achievement_pct") or 0.0 if not achievement_pct and total_asset: achievement_pct = round(total_asset / 500_000_000 * 100, 2) sec02 = { "total_asset_krw": total_asset or 0, "cash_ratio_pct": float(cash_ratio), "goal_achievement_pct": float(achievement_pct) } # 03_hard_blockers blocked_tickers = [] blocker_reasons = [] for blocker in blockers: blocker_reasons.append(blocker.get("reason")) for item in packet.get("per_ticker_final_judgment", []): if item.get("verdict") in ("BLOCK", "BLOCK_BUY"): blocked_tickers.append(item.get("ticker")) blocker_reasons.append(f"{item.get('ticker')} verdict is {item.get('verdict')}") sec03 = { "blocked_tickers": blocked_tickers, "blocker_reasons": blocker_reasons } # 04_sell_priority_table sell_priority_rows = [] raw_sell_priority = trading_data.get("data", {}).get("sell_priority", []) or [] for row in raw_sell_priority: sell_priority_rows.append({ "rank": row.get("Rank"), "ticker": row.get("Ticker"), "sell_action": row.get("Sell_Action"), "sell_reason_code": row.get("Action_Reason") or "STOP_OR_TIME_EXIT" }) sec04 = sell_priority_rows # 05_buy_hold_sell_action_table buy_hold_sell_rows = [] decisions_raw = harness_context.get("decisions_json") or [] if isinstance(decisions_raw, str): try: decisions_raw = json.loads(decisions_raw) except Exception: decisions_raw = [] prices_raw = harness_context.get("prices_json") or [] if isinstance(prices_raw, str): try: prices_raw = json.loads(prices_raw) except Exception: prices_raw = [] prices_by_ticker = {p.get("ticker"): p for p in prices_raw if p.get("ticker")} sell_qtys_raw = harness_context.get("sell_quantities_json") or [] if isinstance(sell_qtys_raw, str): try: sell_qtys_raw = json.loads(sell_qtys_raw) except Exception: sell_qtys_raw = [] sell_qtys_by_ticker = {q.get("ticker"): q.get("sell_qty") for q in sell_qtys_raw if q.get("ticker")} account_snap = trading_data.get("data", {}).get("account_snapshot", []) or [] acct_by_ticker = {a.get("ticker"): a for a in account_snap if a.get("ticker")} for d in decisions_raw: ticker = d.get("ticker") action = d.get("final_action") p_info = prices_by_ticker.get(ticker, {}) a_info = acct_by_ticker.get(ticker, {}) entry_price = p_info.get("avg_cost") or a_info.get("average_cost") or 0 stop_price = p_info.get("stop_price") or a_info.get("stop_price") or 0 qty = sell_qtys_by_ticker.get(ticker) or a_info.get("holding_quantity") or 0 buy_hold_sell_rows.append({ "ticker": ticker, "final_action": action, "entry_price": entry_price, "stop_price": stop_price, "quantity": qty }) sec05 = buy_hold_sell_rows # 06_cash_and_risk_budget available_cash = harness_context.get("buy_power_krw") or harness_context.get("settlement_cash_d2_krw") or 0 d2_cash = harness_context.get("settlement_cash_d2_krw") or available_cash sec06 = { "available_cash_krw": available_cash, "d2_cash_krw": d2_cash, "max_allowed_mdd_pct": 20.0 } # 07_shadow_ledger_visible_items sec07 = [] shadow_ledger_path = ROOT / "Temp" / "shadow_ledger_v2.json" if shadow_ledger_path.exists(): try: sl_data = json.loads(shadow_ledger_path.read_text(encoding="utf-8")) for f in sl_data.get("shadow_formulas", []): sec07.append({ "formula_id": f.get("formula_id"), "lifecycle_state": f.get("lifecycle_state"), "sample_n": f.get("sample_n"), "promotion_allowed": f.get("promotion_allowed") }) except Exception: pass # 08_data_missing_items sec08 = [] if data_missing_info: for idx, warn in enumerate(data_missing_info): sec08.append({ "missing_field": f"warning_{idx}", "reason": warn }) else: sec08.append({ "missing_field": "None", "reason": "No missing critical fields detected." }) # 09_market_regime_summary_precomputed regime_label = harness_context.get("market_regime_state") or "NEUTRAL" regime_score = harness_context.get("mrs_score") or 0 pos_scale = harness_context.get("regime_size_scale") or 1.0 sec09 = { "regime_label": regime_label, "regime_score": regime_score, "position_scale_factor": pos_scale } # 10_education_notes_preapproved sec10 = education_notes # 11_forbidden_phrases_and_no_math_rules sec11 = { "forbidden_phrases": [ "최종 수량 결정", "손절가 구하기", "익절가 구하기", "LLM에 의한 임의 수치 생성" ], "no_math_rule": "LLM must copy-only pre-calculated values. Arithmetic calculations are strictly prohibited." } context = { "formula_id": "FINAL_CONTEXT_FOR_LLM_V5", "executive": { "display_value": "FINAL_CONTEXT_FOR_LLM_V5", "source_key": "meta.builder_version", "manifest": manifest_info }, "blockers": blockers, "action_table": action_table, "shadow_ledger": shadow_ledger_info, "data_missing": data_missing_info, "education_notes": education_notes, "llm_forbidden_numeric_fields": [ "price", "quantity", "stop_price", "take_profit_price", "score", "gate" ], "instruction_source": "This file is the single read path source of truth for the LLM. Do not read other directories.", # 11 contract-required sections "01_metadata_and_manifest_alias": sec01, "02_portfolio_health": sec02, "03_hard_blockers": sec03, "04_sell_priority_table": sec04, "05_buy_hold_sell_action_table": sec05, "06_cash_and_risk_budget": sec06, "07_shadow_ledger_visible_items": sec07, "08_data_missing_items": sec08, "09_market_regime_summary_precomputed": sec09, "10_education_notes_preapproved": sec10, "11_forbidden_phrases_and_no_math_rules": sec11 } out_path.parent.mkdir(parents=True, exist_ok=True) out_path.write_text(yaml.safe_dump(context, sort_keys=False, allow_unicode=True), encoding="utf-8") print(json.dumps({ "formula_id": context["formula_id"], "section_count": len(context), "gate": "PASS" }, ensure_ascii=True)) return 0 if __name__ == "__main__": import sys sys.exit(main())