chore: remove 40 more orphaned one-off scripts from tools/

Second pass over the 47 lower-confidence orphan candidates held back
from the earlier 34-file cleanup. This time checked two signals, not
one: (1) is the script's own filename referenced anywhere, and (2)
does the script's own Temp/*.json output get read by any other file
(a script can be "orphaned" by name but still load-bearing if
something else consumes what it produces).

7 of the 47 failed check (2) - their outputs (outcome_ledger_v1.json,
pre_distribution_early_warning_v3.json, shadow_ledger_v1.json,
calibration_registry_v1.json, final_execution_decision_v4.json,
live_outcome_ledger_v1.json, final_decision_packet_v2.json) are read
by other tools even though the producing script's name never appears
elsewhere - kept those 7 in place.

The remaining 40 (WBS-ticket-tagged one-offs: wbs81/92/93/95/96_*,
build_p0-p6_*; and misc build_*/validate_* diagnostics with no
external reference by name or by output) were deleted. Re-ran the
full-repo reference search (zero hits) and quick-mode release DAG +
WBS validator afterward - failure set is unchanged (same 10
tasks already known blocked on missing live trading data / a running
web app + Playwright, unrelated to this deletion).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-30 14:29:08 +09:00
parent 2439d5e24d
commit 73572b6211
40 changed files with 0 additions and 6501 deletions
@@ -1,126 +0,0 @@
from __future__ import annotations
import ast
import json
import re
from dataclasses import dataclass
from pathlib import Path
from src.quant_engine.refactor_master_helpers import ROOT, collect_gas_files
FORBIDDEN_TOKENS = ("decision", "sizing", "stop_loss", "take_profit", "risk_score")
ALLOWED_TOKENS = ("collect", "normalize", "export", "display")
_JS_BLOCK_COMMENT_RE = re.compile(r"/\*.*?\*/", re.S)
_JS_LINE_COMMENT_RE = re.compile(r"//.*?$", re.M)
_JS_STRING_RE = re.compile(
r"""(?x)
(?:
"(?:\\.|[^"\\])*"
| '(?:\\.|[^'\\])*'
| `(?:\\.|[^`\\])*`
)
"""
)
@dataclass
class FunctionRow:
file: str
name: str
line: int
allowed_responsibility: str
matched_tokens: list[str]
def classify(name: str, body: str, file_name: str) -> tuple[str, list[str]]:
cleaned = _JS_BLOCK_COMMENT_RE.sub(" ", body)
cleaned = _JS_LINE_COMMENT_RE.sub(" ", cleaned)
cleaned = _JS_STRING_RE.sub(" ", cleaned)
low = f"{name}\n{cleaned}".lower()
matched_forbidden = [
tok
for tok in FORBIDDEN_TOKENS
if re.search(rf"(?<![A-Za-z0-9_]){re.escape(tok)}(?![A-Za-z0-9_])", low)
]
matched_allowed = [
tok
for tok in ALLOWED_TOKENS
if re.search(rf"(?<![A-Za-z0-9_]){re.escape(tok)}(?![A-Za-z0-9_])", low)
]
if matched_forbidden:
return "forbidden", matched_forbidden
if matched_allowed or file_name == "gas_harness_rows.gs":
return "allowed", matched_allowed or ["harness_rows"]
return "helper", []
def function_bodies(path: Path) -> list[tuple[str, int, str]]:
text = path.read_text(encoding="utf-8", errors="ignore")
lines = text.splitlines()
header_indexes: list[tuple[str, int]] = []
header_re = re.compile(r"^(?:function\s+([A-Za-z0-9_]+)|(?:var|let|const)\s+([A-Za-z0-9_]+)\s*=\s*function\b)")
for idx, line in enumerate(lines):
stripped = line.strip()
m = header_re.match(stripped)
if m:
name = (m.group(1) or m.group(2) or "").strip()
header_indexes.append((name, idx))
results: list[tuple[str, int, str]] = []
for name, start_idx in header_indexes:
brace_depth = 0
end_idx = len(lines)
started = False
for idx in range(start_idx, len(lines)):
scan = lines[idx]
scan = _JS_BLOCK_COMMENT_RE.sub(" ", scan)
scan = _JS_LINE_COMMENT_RE.sub(" ", scan)
scan = _JS_STRING_RE.sub(" ", scan)
brace_depth += scan.count("{")
if scan.count("{"):
started = True
brace_depth -= scan.count("}")
if started and brace_depth <= 0:
end_idx = idx + 1
break
body = "\n".join(lines[start_idx:end_idx])
results.append((name, start_idx + 1, body))
return results
def build_audit_payload() -> dict:
rows: list[FunctionRow] = []
for path in collect_gas_files():
for name, line, body in function_bodies(path):
allowed_responsibility, matched_tokens = classify(name, body, path.name)
rows.append(
FunctionRow(
file=str(path.relative_to(ROOT)),
name=name,
line=line,
allowed_responsibility=allowed_responsibility,
matched_tokens=matched_tokens,
)
)
inventory_coverage_pct = 100.0 if rows else 0.0
forbidden_rows = [row for row in rows if row.allowed_responsibility == "forbidden"]
return {
"formula_id": "GAS_BUSINESS_LOGIC_AUDIT_V1",
"function_inventory_coverage_pct": inventory_coverage_pct,
"function_count": len(rows),
"forbidden_function_count": len(forbidden_rows),
"approved_exceptions": [
"runtime_report_rendering",
"data_collection_helpers",
],
"rows": [row.__dict__ for row in rows],
"gate": "PASS" if inventory_coverage_pct >= 100.0 else "FAIL",
}
def write_audit(out_path: Path) -> dict:
result = build_audit_payload()
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
return result
-117
View File
@@ -1,117 +0,0 @@
from __future__ import annotations
from pathlib import Path
import yaml
ROOT = Path(__file__).resolve().parents[1]
def infer_type_and_unit(name: str) -> tuple[str, str]:
lower_name = name.lower()
if "price" in lower_name:
return "number", "KRW_per_share"
elif any(q in lower_name for q in ["qty", "quantity", "count"]):
return "integer", "shares"
elif any(p in lower_name for p in ["pct", "ratio", "rate", "percent"]):
return "number", "percent"
elif any(k in lower_name for k in ["krw", "amount", "value", "cash"]):
return "number", "KRW"
elif "date" in lower_name or "updated" in lower_name:
return "date_ISO8601", "none"
elif "status" in lower_name or "mode" in lower_name or "action" in lower_name or "state" in lower_name or "gate" in lower_name:
return "string", "none"
else:
return "number", "none" # default to number for scores/metrics
def main() -> int:
field_dict_path = ROOT / "spec" / "12_field_dictionary.yaml"
mapping_path = ROOT / "spec" / "14_raw_workbook_mapping.yaml"
snapshot_path = ROOT / "spec" / "15_account_snapshot_contract.yaml"
if not field_dict_path.exists():
print("Field dictionary not found.")
return 1
# Load existing fields
field_data = yaml.safe_load(field_dict_path.read_text(encoding="utf-8")) or {}
fields = field_data.get("field_dictionary", {}).get("fields", {})
canonical_names = set(fields.keys())
def is_field_mapped(col_name: str) -> bool:
if col_name in canonical_names:
return True
for fid, info in fields.items():
if not info:
continue
aliases = info.get("aliases", [])
if col_name in aliases:
return True
return False
# Extract all unmapped column/field names
unmapped_names = set()
# 1. raw mapping columns
if mapping_path.exists():
mapping_data = yaml.safe_load(mapping_path.read_text(encoding="utf-8")) or {}
sheets = mapping_data.get("raw_workbook", {}).get("required_sheets", {})
for _, sheet_info in sheets.items():
req = sheet_info.get("required_columns", [])
rec = sheet_info.get("recommended_columns", [])
for col in (req + rec):
if not is_field_mapped(col):
unmapped_names.add(col)
# 2. snapshot fields
if snapshot_path.exists():
snap_data = yaml.safe_load(snapshot_path.read_text(encoding="utf-8")) or {}
contract = snap_data.get("account_snapshot_contract", {})
# required capture fields
groups = contract.get("required_capture_groups", {})
for _, group_info in groups.items():
fields_in_group = group_info.get("required_fields", [])
for f in fields_in_group:
if not is_field_mapped(f):
unmapped_names.add(f)
# canonical fields
canonicals = contract.get("canonical_fields", {})
for f in canonicals.keys():
if not is_field_mapped(f):
unmapped_names.add(f)
if not unmapped_names:
print("No unmapped fields found.")
return 0
print(f"Found {len(unmapped_names)} unmapped fields. Adding to dictionary...")
# Populate unmapped fields into dictionary
for name in sorted(unmapped_names):
# Determine canonical key (lower snake case)
canonical_key = name.lower()
if canonical_key in fields:
# key collision on lowercase version, append unique suffix or skip if mapped
if name not in fields[canonical_key].get("aliases", []):
fields[canonical_key].setdefault("aliases", []).append(name)
else:
ftype, funit = infer_type_and_unit(name)
fields[canonical_key] = {
"canonical_name": canonical_key,
"type": ftype,
"unit": funit,
"aliases": [name]
}
# Save dictionary back to spec/12_field_dictionary.yaml
field_data["field_dictionary"]["fields"] = fields
field_dict_path.write_text(yaml.safe_dump(field_data, sort_keys=False, allow_unicode=True), encoding="utf-8")
print("Auto-populated 12_field_dictionary.yaml successfully.")
return 0
if __name__ == "__main__":
raise SystemExit(main())
-75
View File
@@ -1,75 +0,0 @@
# tools/bootstrap_env.py — Local Virtual Environment Bootstrapper
import os
import sys
import subprocess
import json
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
VENV_DIR = ROOT / ".venv"
EVIDENCE_DIR = ROOT / "Temp" / "evidence" / "WBS-PH1-A"
def log(msg: str):
print(f"[BOOTSTRAP] {msg}")
def run_cmd(args: list[str]) -> bool:
try:
subprocess.run(args, check=True)
return True
except subprocess.CalledProcessError as e:
log(f"Command failed: {args} - Error: {e}")
return False
def main() -> int:
log("Initializing local Python environment verification...")
# 1. Create venv if not exists
venv_created = False
if not VENV_DIR.exists():
log(f"Creating virtual environment in {VENV_DIR}...")
# Use localized "python" as required by AGENTS.md Windows environment rules
venv_created = run_cmd([sys.executable, "-m", "venv", str(VENV_DIR)])
else:
log("Virtual environment folder '.venv' already exists.")
venv_created = True
# Get venv pip and python path
if os.name == "nt":
venv_python = VENV_DIR / "Scripts" / "python.exe"
venv_pip = VENV_DIR / "Scripts" / "pip.exe"
else:
venv_python = VENV_DIR / "bin" / "python"
venv_pip = VENV_DIR / "bin" / "pip"
# 2. Install core packages
dependencies_installed = False
if venv_pip.exists():
log("Installing core dependencies (PyYAML, openpyxl, yfinance, psycopg[binary], psycopg2-binary, pytest)...")
# Ensure latest pip and then install dependencies
run_cmd([str(venv_pip), "install", "--upgrade", "pip"])
dependencies_installed = run_cmd([str(venv_pip), "install", "PyYAML", "openpyxl", "yfinance", "psycopg[binary]", "psycopg2-binary", "pytest"])
else:
log("Error: venv pip.exe not found.")
# 3. Verify python alignment
python_version_verified = venv_python.exists()
if python_version_verified:
log(f"Python verified successfully: {venv_python}")
# 4. Write evidence JSON
EVIDENCE_DIR.mkdir(parents=True, exist_ok=True)
evidence_file = EVIDENCE_DIR / "verdict.json"
verdict = {
"venv_created": venv_created,
"dependencies_installed": dependencies_installed,
"python_version_verified": python_version_verified
}
evidence_file.write_text(json.dumps(verdict, indent=2), encoding="utf-8")
log(f"Evidence file saved to: {evidence_file}")
# Success if everything is true
success = venv_created and dependencies_installed and python_version_verified
return 0 if success else 1
if __name__ == "__main__":
sys.exit(main())
-20
View File
@@ -1,20 +0,0 @@
import json
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
def main():
report = {
"formula_id": "DAILY_FEEDBACK_REPORT_V1",
"prediction_match_rate_pct": 54.76,
"sample_count": 312,
"gate": "MONITOR"
}
out_file = ROOT / "Temp" / "daily_feedback_report.json"
out_file.parent.mkdir(parents=True, exist_ok=True)
with open(out_file, "w", encoding="utf-8") as f:
json.dump(report, f, indent=2)
print(json.dumps(report, indent=2))
if __name__ == "__main__":
main()
-74
View File
@@ -1,74 +0,0 @@
from __future__ import annotations
import argparse
import json
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
DEFAULT_SCORE = ROOT / "Temp" / "data_integrity_score_v1.json"
DEFAULT_OUT = ROOT / "Temp" / "data_integrity_100_lock_v1.json"
def _load(path: Path) -> dict[str, Any]:
if not path.exists():
return {}
try:
x = json.loads(path.read_text(encoding="utf-8"))
except Exception:
return {}
return x if isinstance(x, dict) else {}
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--score", default=str(DEFAULT_SCORE))
ap.add_argument("--out", default=str(DEFAULT_OUT))
args = ap.parse_args()
sp = Path(args.score)
op = Path(args.out)
if not sp.is_absolute():
sp = ROOT / sp
if not op.is_absolute():
op = ROOT / op
score_json = _load(sp)
score = float(score_json.get("score") or 0.0)
m = score_json.get("metrics") if isinstance(score_json.get("metrics"), dict) else {}
placeholder_safety = float(m.get("placeholder_safety_pct") or 0.0)
required_comp = float(m.get("required_field_completeness_pct") or 0.0)
gate = "PASS_100" if score >= 100.0 and placeholder_safety >= 100.0 and required_comp >= 100.0 else "FAIL_NOT_100"
reasons = []
if score < 100.0:
reasons.append("DATA_INTEGRITY_SCORE_NOT_100")
if placeholder_safety < 100.0:
reasons.append("PLACEHOLDER_SAFETY_NOT_100")
if required_comp < 100.0:
reasons.append("REQUIRED_COMPLETENESS_NOT_100")
out = {
"formula_id": "DATA_INTEGRITY_100_LOCK_V1",
"gate": gate,
"reasons": reasons,
"metrics": {
"data_integrity_score": score,
"placeholder_safety_pct": placeholder_safety,
"required_field_completeness_pct": required_comp,
},
"target": {
"data_integrity_score": 100.0,
"placeholder_safety_pct": 100.0,
"required_field_completeness_pct": 100.0,
},
}
op.parent.mkdir(parents=True, exist_ok=True)
op.write_text(json.dumps(out, ensure_ascii=False, indent=2), encoding="utf-8")
print(json.dumps(out, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())
-195
View File
@@ -1,195 +0,0 @@
from __future__ import annotations
import argparse
import json
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
import yaml
ROOT = Path(__file__).resolve().parents[1]
DEFAULT_JSON = ROOT / "GatherTradingData.json"
DEFAULT_OUT = ROOT / "Temp" / "data_integrity_score_v1.json"
DEFAULT_POLICY = ROOT / "spec" / "strategy_execution_lock_policy.yaml"
def _load(path: Path) -> dict[str, Any]:
data = json.loads(path.read_text(encoding="utf-8"))
return data if isinstance(data, dict) else {}
def _rows(v: Any) -> list[dict[str, Any]]:
if isinstance(v, list):
return [x for x in v if isinstance(x, dict)]
return []
def _load_policy(path: Path) -> dict[str, Any]:
if not path.exists():
return {}
try:
payload = yaml.safe_load(path.read_text(encoding="utf-8"))
except Exception:
return {}
root = payload.get("strategy_execution_lock_policy") if isinstance(payload, dict) else {}
obj = root.get("data_integrity_score_v1") if isinstance(root, dict) else {}
return obj if isinstance(obj, dict) else {}
def _is_placeholder(v: Any, placeholder_tokens: set[Any]) -> bool:
if v is None:
return None in placeholder_tokens
if isinstance(v, str):
return v.strip() in placeholder_tokens
return False
def _is_allowed_tp_stale(row: dict[str, Any], field: str, val: Any) -> bool:
if field == "tp1_price" and val is None:
return str(row.get("tp1_state") or "").upper() in {
"TP1_ALREADY_TRIGGERED",
"DEFERRED_SECULAR_LEADER",
"DEFERRED_SECULAR_LEADER_OVERHEAT_PENDING",
"TRAILING_STOP_PRIORITY_SECULAR_LEADER",
}
if field == "tp2_price" and val is None:
return str(row.get("tp2_state") or "").upper() in {"TP2_ALREADY_TRIGGERED"}
return False
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--json", default=str(DEFAULT_JSON))
ap.add_argument("--out", default=str(DEFAULT_OUT))
ap.add_argument("--policy", default=str(DEFAULT_POLICY))
args = ap.parse_args()
json_path = Path(args.json)
out_path = Path(args.out)
policy_path = Path(args.policy)
if not json_path.is_absolute():
json_path = ROOT / json_path
if not out_path.is_absolute():
out_path = ROOT / out_path
if not policy_path.is_absolute():
policy_path = ROOT / policy_path
payload = _load(json_path)
policy = _load_policy(policy_path)
data = payload.get("data") if isinstance(payload.get("data"), dict) else {}
h = data.get("_harness_context") if isinstance(data.get("_harness_context"), dict) else {}
required_sheets = policy.get("required_sheets") if isinstance(policy.get("required_sheets"), list) else ["data_feed", "sector_flow", "macro", "event_risk", "core_satellite", "sell_priority"]
present = sum(1 for s in required_sheets if isinstance(data.get(s), list) and len(data.get(s)) > 0)
sheet_completeness = present / len(required_sheets) * 100.0
bp = _rows(h.get("order_blueprint_json"))
prices = _rows(h.get("prices_json"))
price_keys = {str(r.get("ticker") or "") for r in prices}
bp_keys = {str(r.get("ticker") or "") for r in bp}
cross_mismatch = len([t for t in bp_keys if t and t not in price_keys])
mismatch_rate = (cross_mismatch / max(1, len(bp_keys))) * 100.0
json_status = str(h.get("json_validation_status") or "")
type_ok = 100.0 if json_status else 80.0
captured_at = str(h.get("captured_at") or "")
timeliness = 100.0 if captured_at else 70.0
data_feed_rows = _rows(data.get("data_feed"))
required_fields = policy.get("data_feed_required_fields") if isinstance(policy.get("data_feed_required_fields"), list) else ["Ticker", "Close", "MA20", "ATR20", "Volume"]
total_required_cells = max(1, len(data_feed_rows) * max(1, len(required_fields)))
missing_required_cells = 0
for row in data_feed_rows:
for f in required_fields:
v = row.get(f)
if v is None or (isinstance(v, str) and not v.strip()):
missing_required_cells += 1
required_field_completeness = max(0.0, 100.0 - (missing_required_cells / total_required_cells) * 100.0)
placeholder_raw = policy.get("placeholder_tokens") if isinstance(policy.get("placeholder_tokens"), list) else ["DATA_MISSING", "", "-", None]
placeholder_tokens = set(placeholder_raw)
prices = _rows(h.get("prices_json"))
placeholder_checks = 0
placeholder_hits = 0
placeholder_ledger: list[dict[str, Any]] = []
for row in prices:
ticker = str(row.get("ticker") or "")
for f in ("stop_price", "tp1_price", "tp2_price"):
placeholder_checks += 1
val = row.get(f)
if _is_allowed_tp_stale(row, f, val):
continue
if _is_placeholder(val, placeholder_tokens):
placeholder_hits += 1
placeholder_ledger.append({"ticker": ticker, "field": f, "value": val})
placeholder_safety = 100.0 if placeholder_checks == 0 else max(0.0, 100.0 - (placeholder_hits / placeholder_checks) * 100.0)
sla_hours = float(policy.get("captured_at_sla_hours") or 24.0)
sla_penalty = float(policy.get("timeliness_penalty_if_sla_breached_pct") or 30.0)
sla_breached = False
capture_age_hours = None
if captured_at:
try:
dt = datetime.fromisoformat(captured_at.replace("Z", "+00:00"))
if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc)
now = datetime.now(timezone.utc)
capture_age_hours = max(0.0, (now - dt.astimezone(timezone.utc)).total_seconds() / 3600.0)
if capture_age_hours > sla_hours:
sla_breached = True
except Exception:
capture_age_hours = None
if sla_breached:
timeliness = max(0.0, timeliness - sla_penalty)
w = policy.get("weights") if isinstance(policy.get("weights"), dict) else {}
ws = float(w.get("sheet_completeness_pct") or 0.25)
wc = float(w.get("cross_mismatch_safety_pct") or 0.20)
wt = float(w.get("timeliness_pct") or 0.15)
wtp = float(w.get("type_presence_pct") or 0.10)
wr = float(w.get("required_field_completeness_pct") or 0.20)
wp = float(w.get("placeholder_safety_pct") or 0.10)
score = round(max(0.0, min(100.0, ws * sheet_completeness + wc * (100.0 - mismatch_rate) + wt * timeliness + wtp * type_ok + wr * required_field_completeness + wp * placeholder_safety)), 2)
grade = "A" if score >= 95 else "B" if score >= 90 else "C" if score >= 80 else "D"
pass_th = float(policy.get("pass_threshold") or 90.0)
watch_th = float(policy.get("watch_threshold") or 80.0)
gate = "PASS" if score >= pass_th else "WATCH_ONLY" if score >= watch_th else "EXPORT_BLOCKED_CRITICAL"
result = {
"formula_id": "DATA_INTEGRITY_SCORE_V1",
"score": score,
"grade": grade,
"gate": gate,
"metrics": {
"sheet_completeness_pct": round(sheet_completeness, 2),
"cross_mismatch_rate_pct": round(mismatch_rate, 2),
"timeliness_pct": timeliness,
"type_presence_pct": type_ok,
"required_field_completeness_pct": round(required_field_completeness, 2),
"placeholder_safety_pct": round(placeholder_safety, 2),
"placeholder_hits_count": placeholder_hits,
"placeholder_checks_count": placeholder_checks,
"placeholder_ledger": placeholder_ledger[:100],
"capture_age_hours": round(capture_age_hours, 2) if isinstance(capture_age_hours, (int, float)) else None,
"sla_breached": sla_breached,
"json_validation_status": json_status or None,
},
"policy_used": {
"policy_path": str(policy_path),
"required_sheets": required_sheets,
"data_feed_required_fields": required_fields,
"captured_at_sla_hours": sla_hours,
"timeliness_penalty_if_sla_breached_pct": sla_penalty,
"pass_threshold": pass_th,
"watch_threshold": watch_th,
},
}
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
print(json.dumps(result, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())
-174
View File
@@ -1,174 +0,0 @@
from __future__ import annotations
"""DATA_QUALITY_GATE_V2_PY — GAS calcDataQualityGateV2_의 Python authoritative 재산출.
근거: GAS 원본(gas_data_feed.gs:8643)이 필드경로 버그로 실재 데이터를 0으로 깐다(false-negative).
정공법: 동일 8개 카테고리를 GatherTradingData.json에서 올바른 키로 결정론 재산출한다.
핵심 원칙 (거짓 금지 AND 과대 금지):
- 데이터-존재 카테고리(prediction/cash/cluster/stop_loss/sell_engine): 실데이터 fill rate로 채점.
- 표본-PENDING 카테고리(trade_quality/alpha_eval/pattern): 실제 평가 표본 누적 필요 → 0이 아니라 PENDING.
데이터 품질 분모에서 제외(과대 방지). 성과축에서 별도 PENDING 표기.
- overall_completeness_pct = 데이터-존재 카테고리 평균. 성과(eval)와 데이터품질을 분리.
"""
import argparse
import json
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
DEFAULT_JSON = ROOT / "GatherTradingData.json"
DEFAULT_PA = ROOT / "Temp" / "predictive_alpha_engine_v2.json"
DEFAULT_OUT = ROOT / "Temp" / "data_quality_gate_v2_py.json"
# 데이터-존재 카테고리 vs 표본-PENDING 카테고리
DATA_CATEGORIES = ["prediction", "cash", "cluster", "stop_loss", "sell_engine"]
PENDING_CATEGORIES = ["trade_quality", "alpha_eval", "pattern"]
def _load(path: Path) -> dict[str, Any]:
if not path.exists():
return {}
try:
return json.loads(path.read_text(encoding="utf-8"))
except Exception:
return {}
def _merged_hctx(payload: dict[str, Any]) -> dict[str, Any]:
data = payload.get("data") if isinstance(payload.get("data"), dict) else {}
hctx = data.get("_harness_context") if isinstance(data.get("_harness_context"), dict) else {}
merged = dict(hctx)
if isinstance(payload.get("hApex"), dict):
merged.update(payload["hApex"])
return merged
def _gj(hctx: dict[str, Any], key: str) -> Any:
"""harness_context의 *_json 필드를 dict/list로 파싱."""
v = hctx.get(key)
if isinstance(v, str):
try:
return json.loads(v)
except Exception:
return v
return v
def _is_valid(v: Any) -> bool:
return v is not None and v not in ("-", "PENDING", "", "null")
def _fill_rate(fields: list[Any]) -> int:
if not fields:
return 0
filled = sum(1 for f in fields if _is_valid(f))
return round(filled / len(fields) * 100)
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--json", default=str(DEFAULT_JSON))
ap.add_argument("--pa", default=str(DEFAULT_PA))
ap.add_argument("--out", default=str(DEFAULT_OUT))
args = ap.parse_args()
jp = Path(args.json) if Path(args.json).is_absolute() else ROOT / args.json
pap = Path(args.pa) if Path(args.pa).is_absolute() else ROOT / args.pa
op = Path(args.out) if Path(args.out).is_absolute() else ROOT / args.out
payload = _load(jp)
hctx = _merged_hctx(payload)
pa = _load(pap)
# ── 데이터-존재 카테고리 (올바른 키로 재산출) ──────────────────────────
pa_rows = pa.get("rows") if isinstance(pa.get("rows"), list) else []
pa0 = pa_rows[0] if pa_rows else {}
prediction_fields = [
pa0.get("thesis_score"), pa0.get("antithesis_score"),
pa0.get("synthesis_verdict"), pa0.get("direction_confidence"),
]
cash_shortfall = _gj(hctx, "cash_shortfall_json")
cash_shortfall_val = (
cash_shortfall.get("cash_shortfall_min_krw") if isinstance(cash_shortfall, dict)
else hctx.get("cash_shortfall_min_krw")
)
cash_fields = [
hctx.get("settlement_cash_d2_krw"), hctx.get("cash_floor_status"), cash_shortfall_val,
]
cluster = _gj(hctx, "semiconductor_cluster_json") or {}
cluster_fields = [cluster.get("cluster_state"), cluster.get("combined_pct")]
pp = _gj(hctx, "profit_preservation_json")
pp0 = pp[0] if isinstance(pp, list) and pp else {}
stop_loss_fields = [
pp0.get("protected_stop_price"), pp0.get("auto_trailing_stop"),
pp0.get("profit_preservation_state"),
]
scrs = _gj(hctx, "scrs_v2_json") or {}
combo = scrs.get("selected_combo") or []
combo0 = combo[0] if combo else {}
sell_engine_fields = [
scrs.get("emergency_level"), combo0.get("immediate_qty"), combo0.get("rebound_wait_qty"),
]
data_scores = {
"prediction": _fill_rate(prediction_fields),
"cash": _fill_rate(cash_fields),
"cluster": _fill_rate(cluster_fields),
"stop_loss": _fill_rate(stop_loss_fields),
"sell_engine": _fill_rate(sell_engine_fields),
}
# ── 표본-PENDING 카테고리 (실표본 누적 필요 → 데이터품질 분모 제외) ────
tq = _gj(hctx, "trade_quality_report_json") or {}
tq_records = tq.get("records") or []
alpha_hist = _gj(hctx, "alpha_history_summary_json") or {}
acc_rate = alpha_hist.get("prediction_accuracy_rate")
pattern = _gj(hctx, "pattern_blacklist_auto_json")
pending_status = {
"trade_quality": "PENDING" if not tq_records else "READY",
"alpha_eval": "PENDING" if not _is_valid(acc_rate) else "READY",
"pattern": "PENDING" if not isinstance(pattern, dict) or not pattern.get("status") else "READY",
}
# ── overall = 데이터-존재 카테고리 평균 (성과/eval 분리) ───────────────
data_vals = list(data_scores.values())
overall = round(sum(data_vals) / len(data_vals)) if data_vals else 0
grade = "COMPLETE" if overall >= 90 else "PARTIAL" if overall >= 60 else "INSUFFICIENT"
# category_scores: 데이터 카테고리는 점수, PENDING 카테고리는 'PENDING' 문자열
category_scores: dict[str, Any] = dict(data_scores)
for cat, st in pending_status.items():
category_scores[cat] = st
pending_list = [c for c, s in pending_status.items() if s == "PENDING"]
result = {
"formula_id": "DATA_QUALITY_GATE_V2_PY",
"authoritative_over": "GAS calcDataQualityGateV2_ (field-path bug fix)",
"overall_completeness_pct": overall,
"completeness_grade": grade,
"data_category_scores": data_scores,
"category_scores": category_scores,
"pending_categories": pending_list,
"pending_status": pending_status,
"denominator_note": "overall = 데이터-존재 카테고리 평균. trade_quality/alpha_eval/pattern은 "
"표본 누적 필요 → PENDING(분모 제외). 거짓 0% AND 과대 0%.",
"numeric_generation_allowed": 0,
}
op.parent.mkdir(parents=True, exist_ok=True)
op.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
print(
f"DATA_QUALITY_GATE_V2_PY overall={overall}% grade={grade} "
f"data_scores={data_scores} pending={pending_list}"
)
return 0
if __name__ == "__main__":
raise SystemExit(main())
-161
View File
@@ -1,161 +0,0 @@
from __future__ import annotations
import argparse
import json
import re
from pathlib import Path
from typing import Any
import yaml
ROOT = Path(__file__).resolve().parents[1]
DEFAULT_JSON = ROOT / "GatherTradingData.json"
DEFAULT_OUT = ROOT / "Temp" / "decision_evidence_score_v1.json"
DEFAULT_POLICY = ROOT / "spec" / "strategy_execution_lock_policy.yaml"
def _load(path: Path) -> dict[str, Any]:
data = json.loads(path.read_text(encoding="utf-8"))
return data if isinstance(data, dict) else {}
def _rows(v: Any) -> list[dict[str, Any]]:
if isinstance(v, list):
return [x for x in v if isinstance(x, dict)]
if isinstance(v, str):
try:
return _rows(json.loads(v))
except Exception:
return []
return []
def _load_policy(path: Path) -> dict[str, Any]:
if not path.exists():
return {}
try:
payload = yaml.safe_load(path.read_text(encoding="utf-8"))
except Exception:
return {}
root = payload.get("strategy_execution_lock_policy") if isinstance(payload, dict) else {}
obj = root.get("decision_evidence_score_v1") if isinstance(root, dict) else {}
return obj if isinstance(obj, dict) else {}
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--json", default=str(DEFAULT_JSON))
ap.add_argument("--out", default=str(DEFAULT_OUT))
ap.add_argument("--policy", default=str(DEFAULT_POLICY))
args = ap.parse_args()
json_path = Path(args.json)
out_path = Path(args.out)
policy_path = Path(args.policy)
if not json_path.is_absolute():
json_path = ROOT / json_path
if not out_path.is_absolute():
out_path = ROOT / out_path
if not policy_path.is_absolute():
policy_path = ROOT / policy_path
# GAS 규칙 코드 → 공식 ID 역산 맵
# GAS가 버전 없는 규칙 코드를 사용할 때 정규식이 매칭하지 못하는 경우를 보완
_RULE_CODE_TO_FORMULA: dict[str, str] = {
"SELL_RULE:": "SELL_WATERFALL_ENGINE_V1", # 매도 규칙 엔진
"DE1_": "LLM_SERVING_CONSTRAINT_V1", # Direction E1 #1 수동 검토
"WHIPSAW_V1": "ANTI_WHIPSAW_GATE_V1", # 반등 의심 게이트 (이미 regex 매칭)
}
payload = _load(json_path)
policy = _load_policy(policy_path)
data = payload.get("data") if isinstance(payload.get("data"), dict) else {}
h = data.get("_harness_context") if isinstance(data.get("_harness_context"), dict) else {}
bp = _rows(h.get("order_blueprint_json"))
required_keys = tuple(policy.get("required_keys")) if isinstance(policy.get("required_keys"), list) else ("ticker", "order_type", "validation_status", "rationale_code")
actionable = {str(x).upper() for x in (policy.get("actionable_order_types") if isinstance(policy.get("actionable_order_types"), list) else ["BUY", "SELL", "STOP_LOSS", "ADD_ON", "STAGED_BUY"])}
rationale_pat = str(policy.get("rationale_formula_regex") or r"([A-Z][A-Z0-9_]*_V[0-9]+|NO_EXECUTION:[A-Z_]+)")
rationale_re = re.compile(rationale_pat)
complete = 0
conflicts = 0
rationale_ok = 0
rationale_total = 0
decisions_out: list[dict[str, Any]] = []
for r in bp:
if all(str(r.get(k) or "").strip() for k in required_keys):
complete += 1
ot = str(r.get("order_type") or "").upper()
vs = str(r.get("validation_status") or "").upper()
if ot in ("BUY", "ADD_ON", "STAGED_BUY") and vs == "PASS" and str(r.get("blocked_by_gate") or "").strip():
conflicts += 1
if ot in actionable and vs in ("PASS", "BLOCKED", "REVIEW_ONLY"):
rationale_total += 1
rc = str(r.get("rationale_code") or "")
inferred_formula = ""
matched = bool(rationale_re.search(rc))
if not matched:
# 정규식 미매칭 시 규칙 코드 역산 맵으로 공식 ID 보완
for prefix, fid in _RULE_CODE_TO_FORMULA.items():
if prefix in rc:
inferred_formula = fid
matched = bool(rationale_re.search(rc + "|" + fid))
break
if matched:
rationale_ok += 1
decisions_out.append({
"ticker": str(r.get("ticker") or ""),
"order_type": ot,
"validation_status": vs,
"rationale_code": rc,
"inferred_formula": inferred_formula,
"rationale_ok": matched,
})
total = max(1, len(bp))
completeness_pct = complete / total * 100.0
conflict_rate = conflicts / total * 100.0
rationale_quality_pct = 100.0 if rationale_total == 0 else (rationale_ok / rationale_total) * 100.0
w = policy.get("weights") if isinstance(policy.get("weights"), dict) else {}
wc = float(w.get("completeness_pct") or 0.55)
wf = float(w.get("conflict_safety_pct") or 0.20)
wr = float(w.get("rationale_quality_pct") or 0.25)
score = round(max(0.0, min(100.0, wc * completeness_pct + wf * (100.0 - conflict_rate) + wr * rationale_quality_pct)), 2)
pass_th = float(policy.get("pass_threshold") or 92.0)
caution_th = float(policy.get("caution_threshold") or 80.0)
no_action_state = str(policy.get("no_actionable_orders_state") or "NO_ACTIONABLE_ORDERS")
if rationale_total == 0:
gate = no_action_state
else:
gate = "PASS" if score >= pass_th else ("NO_NEW_BUY" if score >= caution_th else "BLOCK")
result = {
"formula_id": "DECISION_EVIDENCE_SCORE_V1",
"score": score,
"gate": gate,
"metrics": {
"completeness_pct": round(completeness_pct, 2),
"conflict_rate_pct": round(conflict_rate, 2),
"rationale_quality_pct": round(rationale_quality_pct, 2),
"rationale_total": rationale_total,
"rationale_ok": rationale_ok,
"rows": len(bp),
},
"decisions": decisions_out,
"policy_used": {
"policy_path": str(policy_path),
"pass_threshold": pass_th,
"caution_threshold": caution_th,
"actionable_order_types": sorted(actionable),
"rationale_formula_regex": rationale_pat,
"no_actionable_orders_state": no_action_state,
},
}
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
print(json.dumps(result, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())
-159
View File
@@ -1,159 +0,0 @@
"""build_fundamental_raw_evidence_v3.py — FUNDAMENTAL_RAW_EVIDENCE_V3
P0-011: 펀더멘털 실측화.
ROE/OPM/OCF/FCF 누락을 DATA_MISSING으로 명시하고, 필드 커버리지를 기반으로
confidence_cap을 자동 하향한다. LONG 판단은 커버리지 < 임계치이면 CANDIDATE_ONLY로 강등한다.
"""
from __future__ import annotations
import argparse
import json
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
DEFAULT_RAW = ROOT / "Temp" / "fundamental_raw_v2.json"
DEFAULT_FINAL_JDG = ROOT / "Temp" / "final_judgment_gate_v1.json"
DEFAULT_OUT = ROOT / "Temp" / "fundamental_raw_evidence_v3.json"
# 필수 펀더멘털 필드 (P0-011 요구사항)
REQUIRED_FIELDS = ["roe_pct", "opm_pct", "ocf_krw", "fcf_krw"]
COVERAGE_THRESHOLD = 0.95 # 95% 이상이어야 LONG 판단 허용
LONG_HORIZONS = {"LONG", "POSITION", "MOMENTUM"} # horizon 값 중 장기 분류
def _load(path: Path) -> dict[str, Any]:
if not path.exists():
return {}
try:
obj = json.loads(path.read_text(encoding="utf-8"))
except Exception:
return {}
return obj if isinstance(obj, dict) else {}
def _field_presence(row: dict[str, Any], field: str) -> bool:
"""필드 값이 실제 데이터(None/빈값 아님)인지 확인."""
v = row.get(field)
return v is not None and str(v).strip() not in ("", "None", "DATA_MISSING", "N/A")
def _coverage(row: dict[str, Any], fields: list[str]) -> float:
present = sum(1 for f in fields if _field_presence(row, f))
return present / len(fields) if fields else 0.0
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--raw", default=str(DEFAULT_RAW))
ap.add_argument("--fj", default=str(DEFAULT_FINAL_JDG))
ap.add_argument("--out", default=str(DEFAULT_OUT))
args = ap.parse_args()
raw_path = Path(args.raw) if Path(args.raw).is_absolute() else ROOT / args.raw
raw = _load(raw_path)
fj = _load(Path(args.fj) if Path(args.fj).is_absolute() else ROOT / args.fj)
# data_feed의 OCF_B/FCF_B를 보완 소스로 활용
gtd = _load(ROOT / "GatherTradingData.json")
df_list = (gtd.get("data") or {}).get("data_feed") or []
if not isinstance(df_list, list):
df_list = []
df_by_ticker: dict[str, dict[str, Any]] = {str(r.get("Ticker") or ""): r for r in df_list}
raw_rows = raw.get("rows", [])
non_etf = [r for r in raw_rows if not r.get("is_etf")]
# verdict/horizon lookup from final judgment
horizon_by_ticker: dict[str, str] = {}
for row in fj.get("rows", []) if isinstance(fj.get("rows"), list) else []:
t = str(row.get("ticker") or "")
h = str(row.get("best_horizon") or row.get("horizon") or "")
if t:
horizon_by_ticker[t] = h
evidence_rows = []
total_field_slots = 0
filled_field_slots = 0
for row in non_etf:
ticker = str(row.get("ticker") or "")
df_row = df_by_ticker.get(ticker, {})
field_status: dict[str, str] = {}
# OCF/FCF는 raw_v2의 ocf_krw/fcf_krw 우선, 없으면 data_feed의 OCF_B/FCF_B 사용
if not _field_presence(row, "ocf_krw") and _field_presence(df_row, "OCF_B"):
row = dict(row); row["ocf_krw"] = df_row["OCF_B"]
if not _field_presence(row, "fcf_krw") and _field_presence(df_row, "FCF_B"):
row = dict(row); row["fcf_krw"] = df_row["FCF_B"]
for field in REQUIRED_FIELDS:
if _field_presence(row, field):
field_status[field] = str(row[field])
filled_field_slots += 1
else:
field_status[field] = "DATA_MISSING"
total_field_slots += 1
field_coverage = _coverage(row, REQUIRED_FIELDS)
horizon = horizon_by_ticker.get(ticker, "UNKNOWN")
is_long_horizon = any(lh in horizon.upper() for lh in LONG_HORIZONS)
long_buy_downgraded = is_long_horizon and field_coverage < COVERAGE_THRESHOLD
evidence_rows.append({
"ticker": ticker,
"name": row.get("name", ""),
"source": row.get("source", ""),
"as_of_date": row.get("as_of_date", ""),
"field_coverage_pct": round(field_coverage * 100, 2),
"horizon": horizon,
"is_long_horizon": is_long_horizon,
"long_buy_downgraded_to_candidate_only": long_buy_downgraded,
"downgrade_reason": f"fundamental_coverage={field_coverage*100:.0f}% < {COVERAGE_THRESHOLD*100:.0f}%" if long_buy_downgraded else None,
"fields": field_status,
"source_path": str(raw_path.relative_to(ROOT)),
"formula_id": "FUNDAMENTAL_RAW_EVIDENCE_V3",
})
overall_coverage = (filled_field_slots / total_field_slots * 100.0) if total_field_slots > 0 else 0.0
roe_opm_ocf_fcf_missing_count = sum(
1 for r in evidence_rows
for field in REQUIRED_FIELDS
if r["fields"].get(field) == "DATA_MISSING"
)
long_buy_with_missing = [r for r in evidence_rows if r["long_buy_downgraded_to_candidate_only"]]
# gate 판정
if overall_coverage >= 95.0 and len(long_buy_with_missing) == 0:
gate = "PASS"
elif overall_coverage >= 50.0:
gate = "CAUTION"
else:
gate = "FAIL"
result = {
"formula_id": "FUNDAMENTAL_RAW_EVIDENCE_V3",
"gate": gate,
"fundamental_source_field_coverage_pct": round(overall_coverage, 2),
"roe_opm_ocf_fcf_missing_count": roe_opm_ocf_fcf_missing_count,
"long_horizon_buy_with_missing_fundamental_count": len(long_buy_with_missing),
"long_buy_downgraded_tickers": [r["ticker"] for r in long_buy_with_missing],
"coverage_threshold_pct": COVERAGE_THRESHOLD * 100,
"non_etf_ticker_count": len(non_etf),
"rows": evidence_rows,
"generated_at": datetime.now(timezone.utc).isoformat(),
"source_path": "Temp/fundamental_raw_evidence_v3.json",
}
out_path = Path(args.out) if Path(args.out).is_absolute() else ROOT / args.out
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
summary = {k: v for k, v in result.items() if k != "rows"}
print(json.dumps(summary, indent=2, ensure_ascii=False))
return 0
if __name__ == "__main__":
raise SystemExit(main())
-298
View File
@@ -1,298 +0,0 @@
#!/usr/bin/env python3
"""
build_honest_performance_guard_v2.py
────────────────────────────────────────────────────────────────────────
정직 성과증빙 하네스 V2 (P0_01 단계)
P0_01: design vs validated 분리를 엄격하게
모든 *_score 필드에 score_kind ∈ {DESIGN, VALIDATED} 라벨을 강제하고,
VALIDATED는 live_sample_n >= 30일 때만 허용한다.
보고서에 노출되는 점수는 VALIDATED만 허용.
출력:
- Temp/honest_performance_guard_v2.json
- Temp/p0_01_strictness_report.json
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
from datetime import datetime
from typing import Any
ROOT = Path(__file__).resolve().parent.parent
# 입력 파일
OP_REPORT = ROOT / "Temp" / "operational_report.json"
REBOUND_EFF = ROOT / "Temp" / "rebound_sell_efficiency_v1.json"
LATE_CHASE = ROOT / "Temp" / "late_chase_attribution_v1.json"
PREDICTION_ACC = ROOT / "Temp" / "prediction_accuracy_harness_v2.json"
# 출력 파일
OUTPUT_V2 = ROOT / "Temp" / "honest_performance_guard_v2.json"
REPORT_P001 = ROOT / "Temp" / "p0_01_strictness_report.json"
SAMPLE_THRESHOLD = 30
ACCEPTED_SCORE_KINDS = {"DESIGN", "VALIDATED"}
if sys.stdout.encoding and sys.stdout.encoding.lower() not in ("utf-8", "utf8"):
sys.stdout = open(sys.stdout.fileno(), mode="w", encoding="utf-8", buffering=1)
def load_json(p: Path) -> dict | list:
if not p.exists():
return {}
try:
return json.loads(p.read_text(encoding="utf-8"))
except Exception as e:
print(f"[WARN] Failed to load {p.name}: {e}")
return {}
def check_all_scores_have_kind_and_sample_n(obj: Any, path: str = "") -> list[dict]:
"""모든 *_score 필드가 score_kind와 sample_n을 가지는지 검사."""
violations = []
if isinstance(obj, dict):
for key, value in obj.items():
current_path = f"{path}.{key}" if path else key
# *_score 필드 검사
if key.endswith("_score"):
if not isinstance(value, dict):
violations.append({
"path": current_path,
"issue": "SCORE_NOT_DICT",
"value": value,
"detail": f"점수가 dict가 아님. 값={value}"
})
else:
# score_kind 검사
score_kind = value.get("score_kind")
sample_n = value.get("sample_n")
score_value = value.get("value")
if score_kind is None:
violations.append({
"path": current_path,
"issue": "MISSING_SCORE_KIND",
"detail": "score_kind 필드 누락"
})
elif score_kind not in ACCEPTED_SCORE_KINDS:
violations.append({
"path": current_path,
"issue": "INVALID_SCORE_KIND",
"value": score_kind,
"detail": f"허용되지 않는 값: {score_kind}"
})
if sample_n is None:
violations.append({
"path": current_path,
"issue": "MISSING_SAMPLE_N",
"detail": "sample_n 필드 누락"
})
# VALIDATED인데 sample_n < 30 검사
if score_kind == "VALIDATED" and isinstance(sample_n, int):
if sample_n < SAMPLE_THRESHOLD:
violations.append({
"path": current_path,
"issue": "INVALID_VALIDATED_LABEL",
"sample_n": sample_n,
"detail": f"VALIDATED 라벨인데 sample_n={sample_n} < {SAMPLE_THRESHOLD}"
})
# 재귀 검사
elif isinstance(value, (dict, list)):
violations.extend(check_all_scores_have_kind_and_sample_n(value, current_path))
elif isinstance(obj, list):
for i, item in enumerate(obj):
current_path = f"{path}[{i}]"
violations.extend(check_all_scores_have_kind_and_sample_n(item, current_path))
return violations
def build_strictness_report(rebound: dict, chase: dict, pred_acc: dict) -> dict:
"""P0_01 엄격성 검사 보고서 작성."""
report = {
"phase": "P0_01_DESIGN_VS_VALIDATED_SEPARATION",
"generated_at": datetime.now().isoformat(),
"threshold_sample_min": SAMPLE_THRESHOLD,
"findings": {
"rebound_efficiency": {},
"late_chase_attribution": {},
"prediction_accuracy": {}
},
"violations": [],
"corrections_required": []
}
# 1. rebound_efficiency 검사
rb_metrics = rebound.get("metrics", {})
rb_combo = rb_metrics.get("combo_count", 0)
rb_score = rb_metrics.get("rebound_efficiency_score", 0)
report["findings"]["rebound_efficiency"] = {
"metric_name": "rebound_efficiency_score",
"current_value": rb_score,
"sample_n": rb_combo,
"meets_validated_threshold": rb_combo >= SAMPLE_THRESHOLD,
"required_score_kind": "VALIDATED" if rb_combo >= SAMPLE_THRESHOLD else "DESIGN",
"annotation_suffix": f" [설계점수, n={rb_combo}]" if rb_combo < SAMPLE_THRESHOLD else ""
}
if rb_combo < SAMPLE_THRESHOLD:
report["corrections_required"].append({
"metric": "rebound_efficiency_score",
"action": "ANNOTATE_DESIGN",
"new_structure": {
"score_kind": "DESIGN",
"value": rb_score,
"sample_n": rb_combo,
"annotation": f"n={rb_combo} < {SAMPLE_THRESHOLD}. 실측 미검증."
}
})
# 2. late_chase_attribution 검사
chase_metrics = chase.get("metrics", {})
chase_sample = chase_metrics.get("sample_n", 0)
chase_rate = chase_metrics.get("chase_entry_rate_pct", 0)
report["findings"]["late_chase_attribution"] = {
"metric_name": "late_chase_attribution",
"current_value": chase_rate,
"sample_n": chase_sample,
"meets_validated_threshold": chase_sample >= SAMPLE_THRESHOLD,
"required_score_kind": "VALIDATED" if chase_sample >= SAMPLE_THRESHOLD else "DESIGN"
}
if chase_sample < SAMPLE_THRESHOLD:
report["corrections_required"].append({
"metric": "late_chase_attribution",
"action": "ANNOTATE_DESIGN",
"new_structure": {
"score_kind": "DESIGN",
"value": chase_rate,
"sample_n": chase_sample,
"annotation": f"뒷박 차단 효과 미검증 (n={chase_sample})"
}
})
# 3. prediction_accuracy 검사
t5_sample = pred_acc.get("t5_sample", 0)
t5_rate = pred_acc.get("t5_op_rate", 0)
report["findings"]["prediction_accuracy"] = {
"metric_name": "t5_match_rate_pct",
"current_value": t5_rate,
"sample_n": t5_sample,
"meets_validated_threshold": t5_sample >= SAMPLE_THRESHOLD,
"required_score_kind": "VALIDATED" if t5_sample >= SAMPLE_THRESHOLD else "DESIGN"
}
if t5_sample < SAMPLE_THRESHOLD:
report["corrections_required"].append({
"metric": "t5_match_rate_pct",
"action": "ANNOTATE_DESIGN",
"new_structure": {
"score_kind": "DESIGN",
"value": t5_rate,
"sample_n": t5_sample,
"annotation": f"실측 미검증 (n={t5_sample})"
}
})
# 최종 verdict
report["verdict"] = {
"all_scores_properly_labeled": len(report["corrections_required"]) == 0,
"required_corrections_count": len(report["corrections_required"]),
"status": "PASS" if len(report["corrections_required"]) == 0 else "FAIL_CORRECTION_REQUIRED"
}
return report
def main() -> int:
print("=" * 80)
print(" P0_01: Design vs Validated 엄격한 분리")
print("=" * 80)
# 입력 로드
rebound = load_json(REBOUND_EFF)
chase = load_json(LATE_CHASE)
pred_acc = load_json(PREDICTION_ACC)
# P0_01 보고서 생성
p001_report = build_strictness_report(rebound, chase, pred_acc)
print(f"\n[1] 재정렬 효율 (rebound_efficiency_score)")
rb_find = p001_report["findings"]["rebound_efficiency"]
print(f" 현재값: {rb_find['current_value']}")
print(f" 표본 수: {rb_find['sample_n']} / {SAMPLE_THRESHOLD}")
print(f" 필수 라벨: {rb_find['required_score_kind']}")
print(f"\n[2] 뒷박 매수 (late_chase_attribution)")
chase_find = p001_report["findings"]["late_chase_attribution"]
print(f" 현재값: {chase_find['current_value']}")
print(f" 표본 수: {chase_find['sample_n']} / {SAMPLE_THRESHOLD}")
print(f" 필수 라벨: {chase_find['required_score_kind']}")
print(f"\n[3] 예측 정확도 (T+5 일치율)")
pred_find = p001_report["findings"]["prediction_accuracy"]
print(f" 현재값: {pred_find['current_value']}%")
print(f" 표본 수: {pred_find['sample_n']} / {SAMPLE_THRESHOLD}")
print(f" 필수 라벨: {pred_find['required_score_kind']}")
print(f"\n[결과]")
print(f" 필요한 수정: {p001_report['verdict']['required_corrections_count']}")
print(f" 상태: {p001_report['verdict']['status']}")
# 보고서 저장
REPORT_P001.write_text(
json.dumps(p001_report, ensure_ascii=False, indent=2),
encoding="utf-8"
)
print(f"\n✓ P0_01 보고서 저장: {REPORT_P001.name}")
# V2 가드 생성
guard_v2 = {
"schema_version": "honest_performance_guard_v2",
"generated_at": datetime.now().isoformat(),
"p0_01_strictness": p001_report["verdict"],
"required_corrections": p001_report["corrections_required"],
"action_plan": [
{
"step": 1,
"title": "모든 *_score 필드를 dict 구조로 변환",
"fields": ["score_kind", "value", "sample_n", "annotation"]
},
{
"step": 2,
"title": "각 필드에 score_kind ∈ {DESIGN, VALIDATED} 할당",
"rule": "sample_n >= 30 → VALIDATED, else → DESIGN"
},
{
"step": 3,
"title": "보고서 노출 규칙 적용",
"rule": "DESIGN 점수는 보고서 요약에 단독 노출 금지. (설계, n=N) 접미사 필수"
}
]
}
OUTPUT_V2.write_text(
json.dumps(guard_v2, ensure_ascii=False, indent=2),
encoding="utf-8"
)
print(f"✓ P0_01 가드 저장: {OUTPUT_V2.name}")
return 0 if p001_report['verdict']['status'] == "PASS" else 1
if __name__ == "__main__":
sys.exit(main())
-374
View File
@@ -1,374 +0,0 @@
from __future__ import annotations
import argparse
import json
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
DEFAULT_JSON = ROOT / "GatherTradingData.json"
DEFAULT_REPORT = ROOT / "Temp" / "operational_report.json"
DEFAULT_DQR = ROOT / "Temp" / "data_quality_reconciliation_v1.json"
DEFAULT_FJ = ROOT / "Temp" / "final_judgment_gate_v1.json"
DEFAULT_SCR = ROOT / "Temp" / "smart_cash_recovery_v5.json"
DEFAULT_HARDENING = ROOT / "Temp" / "strategy_hardening_harness_v2.json"
DEFAULT_OUTCOME = ROOT / "Temp" / "operational_outcome_lock_v1.json"
DEFAULT_ALPHA = ROOT / "Temp" / "operational_alpha_calibration_v2.json"
DEFAULT_OUT = ROOT / "Temp" / "operational_truth_score_v1.json"
def _load(path: Path) -> dict[str, Any]:
if not path.exists():
return {}
try:
obj = json.loads(path.read_text(encoding="utf-8"))
except Exception:
return {}
return obj if isinstance(obj, dict) else {}
def _as_float(value: Any, default: float = 0.0) -> float:
try:
return float(value)
except Exception:
return default
def _as_int(value: Any, default: int = 0) -> int:
try:
return int(float(value))
except Exception:
return default
def _as_dict(value: Any) -> dict[str, Any]:
if isinstance(value, dict):
return value
if isinstance(value, str) and value.strip():
try:
parsed = json.loads(value)
return parsed if isinstance(parsed, dict) else {}
except Exception:
return {}
return {}
def _extract_harness_root(payload: dict[str, Any]) -> dict[str, Any]:
h_apex = payload.get("hApex")
data_apex = ((payload.get("data") or {}).get("_harness_context")) if isinstance(payload.get("data"), dict) else None
if isinstance(h_apex, dict) and isinstance(data_apex, dict):
merged = dict(data_apex)
merged.update(h_apex)
return merged
if isinstance(h_apex, dict):
return h_apex
if isinstance(data_apex, dict):
return data_apex
return payload
def _score_from_span(primary: float, secondary: float) -> float:
return round(max(0.0, 100.0 - abs(primary - secondary)), 2)
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--json", default=str(DEFAULT_JSON))
ap.add_argument("--report", default=str(DEFAULT_REPORT))
ap.add_argument("--dq", default=str(DEFAULT_DQR))
ap.add_argument("--fj", default=str(DEFAULT_FJ))
ap.add_argument("--scr", default=str(DEFAULT_SCR))
ap.add_argument("--hardening", default=str(DEFAULT_HARDENING))
ap.add_argument("--outcome", default=str(DEFAULT_OUTCOME))
ap.add_argument("--alpha", default=str(DEFAULT_ALPHA))
ap.add_argument("--out", default=str(DEFAULT_OUT))
args = ap.parse_args()
def _rp(path_str: str) -> Path:
path = Path(path_str)
return path if path.is_absolute() else ROOT / path
payload = _load(_rp(args.json))
report = _load(_rp(args.report))
hctx = _extract_harness_root(payload)
dqr = _load(_rp(args.dq))
fj = _load(_rp(args.fj))
scr = _load(_rp(args.scr))
hardening = _load(_rp(args.hardening))
outcome = _load(_rp(args.outcome))
alpha = _load(_rp(args.alpha))
summary = report.get("summary") if isinstance(report.get("summary"), dict) else {}
sections = report.get("sections") if isinstance(report.get("sections"), list) else []
section_names = {str(s.get("name") or "") for s in sections if isinstance(s, dict)}
schema = _as_float(dqr.get("schema_presence_score"))
modern = _as_float(dqr.get("modern_investment_quality_score"))
legacy = _as_float(dqr.get("legacy_investment_quality_score"))
invest_score = _as_float(dqr.get("investment_quality_score"))
cap_basis = _as_float(dqr.get("confidence_cap_basis_score"), min(modern or invest_score, legacy or invest_score))
quality_gap = max(0.0, modern - cap_basis)
quality_conflict = bool(dqr.get("quality_conflict_flag"))
fj_gate = str(fj.get("gate") or "MISSING")
fj_coverage = _as_float(fj.get("coverage_pct"))
fj_silent = _as_int(fj.get("silent_pass_violations"))
fj_late = len(fj.get("late_chase_buy_violations") or [])
export_gate = _as_dict(hctx.get("export_gate_json"))
export_status = str(_first_non_null(export_gate.get("json_validation_status"), hctx.get("json_validation_status"), summary.get("json_validation_status")) or "UNKNOWN")
export_allowed = export_gate.get("hts_entry_allowed")
execution_allowed = bool(scr.get("execution_allowed"))
cash_status = str(scr.get("status") or "UNKNOWN")
cash_damage = _as_float(scr.get("value_damage_pct_avg"))
hardening_meta = hardening.get("meta_scores") if isinstance(hardening.get("meta_scores"), dict) else {}
hardening_overall = _as_float(hardening_meta.get("overall_hardening_score"))
readiness_gate = str(hardening_meta.get("readiness_gate") or "MISSING")
readiness_reasons = hardening_meta.get("readiness_reasons") if isinstance(hardening_meta.get("readiness_reasons"), list) else []
outcome_metrics = outcome.get("metrics") if isinstance(outcome.get("metrics"), dict) else {}
t20_count = _as_float(outcome_metrics.get("operational_t20_count"))
t20_pass = _as_float(outcome_metrics.get("operational_t20_pass_rate"))
expectancy = _as_float(outcome_metrics.get("execution_expectancy_pct"))
win_rate = _as_float(outcome_metrics.get("execution_win_rate_pct"))
alpha_gate = str(alpha.get("gate") or "MISSING")
alpha_confidence = _as_float(alpha.get("confidence_score"))
# 누적손익 교차 검사: executive_brief vs pnl_attribution (±10만원 허용)
import re as _re
def _extract_pnl_from_section(name: str) -> float | None:
for sec in sections:
if not isinstance(sec, dict) or sec.get("name") != name:
continue
md = str(sec.get("markdown") or "")
# "누적 평가손익" 텍스트 뒤에 오는 원화 금액만 추출 (총자산 등 오매칭 방지)
m = _re.search(r"누적\s*평가손익[^\n]*?([+\-]\s*[\d,]+)원", md)
if m:
try:
return float(m.group(1).replace(",", "").replace(" ", ""))
except Exception:
pass
return None
_pnl_brief = _extract_pnl_from_section("executive_brief")
_pnl_attr = _extract_pnl_from_section("pnl_attribution")
_pnl_consistent = (
_pnl_brief is None or _pnl_attr is None
or abs(_pnl_brief - _pnl_attr) <= 100_000 # 10만원 이내 = 정상
)
report_consistency_checks = [
bool(report),
"routing_serving_trace" in section_names,
"QEH_AUDIT_BLOCK" in section_names,
"concise_hts_input_sheet" in section_names,
"reference_price_ledger" in section_names,
bool(summary.get("canonical_order_ok")),
export_status in {"EXPORT_READY", "REVIEW_ONLY", "PENDING_EXPORT", "EXPORT_BLOCKED_CRITICAL"},
_pnl_consistent, # 누적손익 섹션 간 일치 (±10만원)
]
report_consistency_score = round(sum(1 for ok in report_consistency_checks if ok) / len(report_consistency_checks) * 100.0, 2)
data_truth_score = _score_from_span(modern if modern else invest_score, cap_basis if cap_basis else invest_score)
if schema >= 99.0 and data_truth_score > 0:
data_truth_score = round(min(100.0, (schema + data_truth_score) / 2.0), 2)
decision_truth_score = 100.0
if fj_gate != "PASS":
decision_truth_score = min(decision_truth_score, 55.0)
if fj_coverage < 100.0:
decision_truth_score = min(decision_truth_score, fj_coverage)
if fj_silent > 0:
decision_truth_score = 0.0
if fj_late > 0:
decision_truth_score = min(decision_truth_score, 40.0)
execution_truth_score = 100.0
if export_status == "EXPORT_BLOCKED_CRITICAL":
execution_truth_score = 0.0
elif export_status == "EXPORT_READY" and export_allowed is True:
execution_truth_score = 100.0
elif export_status == "REVIEW_ONLY":
# Partial credit: human review required but not hard-blocked
execution_truth_score = 40.0
else:
execution_truth_score = 0.0
if not execution_allowed:
execution_truth_score = min(execution_truth_score, 20.0)
if cash_status != "PASS":
execution_truth_score = min(execution_truth_score, 25.0)
if cash_damage > 10.0:
execution_truth_score = min(execution_truth_score, max(0.0, 100.0 - (cash_damage - 10.0) * 5.0))
# replay T+20 보정 — 운영 T+20이 없으면 replay(estimated)로 최소 상향
_pred_path = ROOT / "Temp" / "prediction_accuracy_harness_v2.json"
_pred_data: dict = {}
try:
import json as _json
_pred_data = _json.loads(_pred_path.read_text(encoding="utf-8")) if _pred_path.exists() else {}
except Exception:
pass
_replay_t20_n = _pred_data.get("t20_replay_sample") or 0
_replay_calibrated = str(_pred_data.get("replay_calibration_state") or "") == "REPLAY_CALIBRATED"
performance_readiness_score = hardening_overall if hardening_overall > 0 else 0.0
if readiness_gate != "PERFORMANCE_READY":
performance_readiness_score = min(performance_readiness_score, 60.0)
# T+20 미달 패널티 — replay 충분 시 30→50으로 완화 (estimated=true 명시)
# 순서: replay 우선 확인 → 미달 캡 결정
_t20_cap = 30.0
if _replay_calibrated and _replay_t20_n >= 30:
_t20_cap = 50.0 # replay 510건 확보 → 운영 미달 패널티 완화
if "OPERATIONAL_T20_SAMPLE_LT_30" in readiness_reasons or t20_count < 30:
performance_readiness_score = min(performance_readiness_score, _t20_cap)
# Guard: only penalise T+20 pass-rate when there is actual T+20 data.
# t20_pass=0 when t20_count=0 is vacuously zero, not a failure signal.
if t20_count >= 10 and t20_pass < 60.0:
performance_readiness_score = min(performance_readiness_score, t20_pass)
# Guard: expectancy/win_rate derived from T+20 evaluations — vacuous when count=0.
if t20_count >= 10 and expectancy <= 0.1:
performance_readiness_score = min(performance_readiness_score, 20.0)
if t20_count >= 10 and win_rate < 45.0:
performance_readiness_score = min(performance_readiness_score, win_rate)
if cash_damage > 10.0:
performance_readiness_score = min(performance_readiness_score, max(0.0, 100.0 - cash_damage * 4.0))
if alpha_gate != "PERFORMANCE_READY":
performance_readiness_score = min(performance_readiness_score, alpha_confidence)
weighted_score = round(
(data_truth_score * 0.25)
+ (decision_truth_score * 0.20)
+ (execution_truth_score * 0.20)
+ (performance_readiness_score * 0.20)
+ (report_consistency_score * 0.15),
2,
)
blocking_reasons: list[str] = []
if cap_basis < 50.0:
blocking_reasons.append("DATA_QUALITY_CAP_BASIS_LT_50")
# Gap threshold raised from 20→40 after blended cap_basis fix (V2).
# Gap of 20-40% is expected: modern harness elevates quality from sparse raw fields.
# Gap >40% still indicates genuine data-vs-processing conflict.
if quality_gap >= 40.0:
blocking_reasons.append("LEGACY_MODERN_QUALITY_GAP_WIDE")
if fj_gate != "PASS" or fj_silent > 0:
blocking_reasons.append("DECISION_GATE_NOT_STABLE")
if export_status == "EXPORT_BLOCKED_CRITICAL":
blocking_reasons.append("EXPORT_GATE_NOT_READY")
elif export_status != "EXPORT_READY" and export_status != "REVIEW_ONLY":
blocking_reasons.append("EXPORT_GATE_NOT_READY")
elif export_status == "REVIEW_ONLY":
blocking_reasons.append("EXPORT_GATE_REVIEW_ONLY") # soft — not a hard block
if not execution_allowed or cash_status != "PASS":
blocking_reasons.append("CASH_RECOVERY_EXECUTION_BLOCKED")
if readiness_gate != "PERFORMANCE_READY" or t20_count < 30:
blocking_reasons.append("PERFORMANCE_NOT_READY")
if cash_damage > 10.0:
blocking_reasons.append("VALUE_DAMAGE_GT_10")
if not bool(summary.get("canonical_order_ok")):
blocking_reasons.append("REPORT_CANONICAL_ORDER_INVALID")
hard_blocking = [r for r in blocking_reasons if r != "EXPORT_GATE_REVIEW_ONLY"]
if not hard_blocking and weighted_score >= 100.0:
gate = "PASS_100"
llm_allowed_actions = ["HTS_READY"]
elif "EXPORT_GATE_NOT_READY" in blocking_reasons or "CASH_RECOVERY_EXECUTION_BLOCKED" in blocking_reasons:
gate = "BLOCK_EXECUTION"
llm_allowed_actions = ["EXPLAIN_ONLY", "RENDER_LEDGER_ONLY"]
elif "DATA_QUALITY_CAP_BASIS_LT_50" in blocking_reasons or "LEGACY_MODERN_QUALITY_GAP_WIDE" in blocking_reasons:
gate = "DATA_CONFLICT"
llm_allowed_actions = ["EXPLAIN_ONLY", "RENDER_LEDGER_ONLY"]
elif "PERFORMANCE_NOT_READY" in blocking_reasons:
gate = "WATCH_PENDING_SAMPLE"
llm_allowed_actions = ["EXPLAIN_ONLY", "RENDER_LEDGER_ONLY"]
elif "EXPORT_GATE_REVIEW_ONLY" in blocking_reasons:
gate = "REVIEW_ONLY_PENDING"
llm_allowed_actions = ["EXPLAIN_ONLY", "RENDER_LEDGER_ONLY"]
else:
gate = "WATCH_PENDING_SAMPLE"
llm_allowed_actions = ["EXPLAIN_ONLY", "RENDER_LEDGER_ONLY"]
# [R2-2] 히스테리시스: score 변동 ±3 이내면 직전 gate 유지 (경계 밴딩).
# 동일 xlsx 미세 입력변동이 gate를 점프시키는 비결정론을 방지.
_HYSTERESIS_BAND = 3.0
try:
_prev_path = _rp(args.out)
if _prev_path.exists():
_prev = json.loads(_prev_path.read_text(encoding="utf-8"))
_prev_score = float(_prev.get("score_0_100") or 0.0)
_prev_gate = str(_prev.get("gate") or "")
_gate_rank = {"PASS_100": 4, "WATCH_PENDING_SAMPLE": 3, "REVIEW_ONLY_PENDING": 3,
"DATA_CONFLICT": 2, "BLOCK_EXECUTION": 1}
_cur_rank = _gate_rank.get(gate, 2)
_prev_rank = _gate_rank.get(_prev_gate, 2)
# 점수 차이가 밴드 이내이고 hard_blocking 상태가 바뀌지 않았으면 이전 gate 유지
if (abs(weighted_score - _prev_score) <= _HYSTERESIS_BAND
and _prev_gate in _gate_rank
and abs(_cur_rank - _prev_rank) <= 1):
gate = _prev_gate
llm_allowed_actions = _prev.get("llm_allowed_actions") or llm_allowed_actions
except Exception:
pass # 히스테리시스 실패 시 계산된 gate 그대로 사용
hard_block_count = len([reason for reason in blocking_reasons if reason in {
"DATA_QUALITY_CAP_BASIS_LT_50",
"EXPORT_GATE_NOT_READY",
"CASH_RECOVERY_EXECUTION_BLOCKED",
"REPORT_CANONICAL_ORDER_INVALID",
# EXPORT_GATE_REVIEW_ONLY is soft — excluded from hard block count
}])
result = {
"formula_id": "OPERATIONAL_TRUTH_SCORE_V1",
"score_0_100": weighted_score,
"gate": gate,
"hard_block_count": hard_block_count,
"blocking_reasons": blocking_reasons,
"llm_allowed_actions": llm_allowed_actions,
"data_truth_score": round(data_truth_score, 2),
"decision_truth_score": round(decision_truth_score, 2),
"execution_truth_score": round(execution_truth_score, 2),
"performance_readiness_score": round(performance_readiness_score, 2),
"report_consistency_score": round(report_consistency_score, 2),
"metric_basis": {
"schema_presence_score": schema,
"legacy_investment_quality_score": legacy,
"modern_investment_quality_score": modern,
"investment_quality_score": invest_score,
"confidence_cap_basis_score": cap_basis,
"quality_gap_pct": round(quality_gap, 2),
"quality_conflict_flag": quality_conflict,
"final_judgment_gate": fj_gate,
"final_judgment_coverage_pct": fj_coverage,
"smart_cash_recovery_status": cash_status,
"smart_cash_recovery_execution_allowed": execution_allowed,
"export_status": export_status,
"export_allowed": export_allowed,
"operational_t20_count": t20_count,
"operational_t20_pass_rate": t20_pass,
"execution_expectancy_pct": expectancy,
"execution_win_rate_pct": win_rate,
"alpha_calibration_gate": alpha_gate,
"alpha_calibration_confidence_score": alpha_confidence,
},
}
out_path = _rp(args.out)
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
print(json.dumps(result, ensure_ascii=False, indent=2))
return 0
def _first_non_null(*values: Any) -> Any:
for value in values:
if value is not None:
return value
return None
if __name__ == "__main__":
raise SystemExit(main())
-173
View File
@@ -1,173 +0,0 @@
#!/usr/bin/env python3
"""
build_p0_02_masking_removal.py
────────────────────────────────────────────────────────────────────────
P0_02: 값 손상 지표에서 adjusted 마스킹 제거
핵심 변경:
1. value_damage_raw_pct는 게이트 입력으로 사용 (항상 raw 값)
2. value_damage_adjusted_pct는 annotation only (참고용)
3. cap_pass=false를 summary에 그대로 전파
4. 같은 지표가 3개의 다른 값을 가지는 문제 제거
출력:
- Temp/p0_02_masking_removal_report.json
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
from datetime import datetime
ROOT = Path(__file__).resolve().parent.parent
# 입력 파일
CASH_RECOVERY = ROOT / "Temp" / "cash_recovery_optimizer_v4.json"
SMART_CASH_V7 = ROOT / "Temp" / "smart_cash_recovery_v7_authoritative.json"
# 출력 파일
REPORT_P002 = ROOT / "Temp" / "p0_02_masking_removal_report.json"
if sys.stdout.encoding and sys.stdout.encoding.lower() not in ("utf-8", "utf8"):
sys.stdout = open(sys.stdout.fileno(), mode="w", encoding="utf-8", buffering=1)
def load_json(p: Path) -> dict:
if not p.exists():
return {}
try:
return json.loads(p.read_text(encoding="utf-8"))
except Exception as e:
print(f"[WARN] Failed to load {p.name}: {e}")
return {}
def find_masking_violations(cash_rec: dict, smart_v7: dict) -> list[dict]:
"""adjusted가 0.0으로 강제되는 부분 찾기."""
violations = []
# 1. cash_recovery_optimizer_v4에서 raw vs adjusted 충돌 검사
raw_dmg = cash_rec.get("value_damage_raw_pct", 0)
adj_dmg = cash_rec.get("value_damage_adjusted_pct", 0)
if raw_dmg > 0 and adj_dmg == 0.0:
violations.append({
"location": "cash_recovery_optimizer_v4.json",
"issue": "ADJUSTED_MASKED_TO_ZERO",
"raw_value": raw_dmg,
"adjusted_value": adj_dmg,
"detail": f"raw={raw_dmg} > adjusted={adj_dmg}. adjusted가 마스킹되었을 가능성.",
"severity": "CRITICAL"
})
# 2. smart_cash_recovery_v7에서 마스킹 검사
raw_v7 = smart_v7.get("raw_value_damage_pct_avg")
opt_v7 = smart_v7.get("optimized_value_damage_pct_avg")
cap_pass = smart_v7.get("cap_pass")
if raw_v7 is not None and opt_v7 is not None:
if raw_v7 > opt_v7 and cap_pass == False:
violations.append({
"location": "smart_cash_recovery_v7_authoritative.json",
"issue": "CAP_FAIL_NOT_PROPAGATED",
"raw_value": raw_v7,
"optimized_value": opt_v7,
"cap_pass": cap_pass,
"detail": f"raw={raw_v7} > optimized={opt_v7} AND cap_pass=false. 하지만 summary에 cap_pass 정보가 전파되지 않을 가능성.",
"severity": "HIGH"
})
return violations
def build_p002_report(cash_rec: dict, smart_v7: dict) -> dict:
"""P0_02 마스킹 제거 보고서."""
violations = find_masking_violations(cash_rec, smart_v7)
report = {
"phase": "P0_02_NO_ADJUSTED_MASKING",
"generated_at": datetime.now().isoformat(),
"violations_found": len(violations),
"violations": violations,
"required_actions": [
{
"action": "USE_RAW_AS_GATE_INPUT",
"description": "value_damage_raw_pct를 게이트 입력으로 항상 사용",
"fields": ["value_damage_raw_pct"],
"rule": "gate_input = raw, adjusted는 annotation only"
},
{
"action": "REMOVE_MASKING_LOGIC",
"description": "adjusted=0.0 강제 로직 제거",
"files": [
"tools/build_cash_recovery_optimizer_v4.py (line 109-113)",
"tools/build_value_preservation_scorer_v1.py"
]
},
{
"action": "PROPAGATE_CAP_PASS",
"description": "cap_pass=false를 summary에 명시적으로 표시",
"example": "raw=15.7 > cap=10.0 → cap_pass=false (summary에 명시)"
}
],
"metric_structure": {
"before_p002": {
"value_damage_raw_pct": 15.7,
"value_damage_adjusted_pct": 0.0,
"cap_pass": "MISSING_FROM_SUMMARY"
},
"after_p002": {
"value_damage_raw_pct": 15.7,
"value_damage_adjusted_pct": {"value": 15.7, "annotation": "raw 값 (cap=10.0)"},
"cap_pass": True,
"gate_input": "value_damage_raw_pct (항상 raw)"
}
}
}
return report
def main() -> int:
print("=" * 80)
print(" P0_02: Adjusted 마스킹 제거 및 Raw 값 복원")
print("=" * 80)
# 입력 로드
cash_rec = load_json(CASH_RECOVERY)
smart_v7 = load_json(SMART_CASH_V7)
# P0_02 보고서 생성
p002_report = build_p002_report(cash_rec, smart_v7)
print(f"\n[검사 결과]")
print(f" 마스킹 위반 발견: {p002_report['violations_found']}")
for i, v in enumerate(p002_report["violations"], 1):
print(f"\n [{i}] {v['issue']}")
print(f" 위치: {v['location']}")
print(f" 심각도: {v['severity']}")
if "raw_value" in v:
print(f" raw: {v['raw_value']}")
if "adjusted_value" in v:
print(f" adjusted: {v['adjusted_value']}")
print(f"\n[필수 조치]")
for i, action in enumerate(p002_report["required_actions"], 1):
print(f" {i}. {action['action']}")
print(f"{action['description']}")
# 보고서 저장
REPORT_P002.write_text(
json.dumps(p002_report, ensure_ascii=False, indent=2),
encoding="utf-8"
)
print(f"\n✓ P0_02 보고서 저장: {REPORT_P002.name}")
return 0 if p002_report['violations_found'] == 0 else 1
if __name__ == "__main__":
sys.exit(main())
-223
View File
@@ -1,223 +0,0 @@
#!/usr/bin/env python3
"""
build_p0_03_unified_coverage.py
────────────────────────────────────────────────────────────────────────
P0_03: 커버리지 분모 통일 — 288 vs 204 분모 불일치 해소
핵심 변경:
1. spec/13_formula_registry.yaml에서 active=true 공식만 수집 (단일 분모)
2. deprecated/orphan을 active=false로 명시
3. 골든 커버리지를 단일 분모로만 계산
4. 장식용 100% 필드 전면 삭제
출력:
- Temp/p0_03_unified_coverage.json
- Temp/p0_03_denominator_audit.json
"""
from __future__ import annotations
import json
import sys
import re
from pathlib import Path
from datetime import datetime
from typing import Any
ROOT = Path(__file__).resolve().parent.parent
# 입력 파일
FORMULA_REGISTRY = ROOT / "spec" / "13_formula_registry.yaml"
YAML_CODE_COVERAGE = ROOT / "Temp" / "yaml_code_coverage_v1.json"
# 출력 파일
OUTPUT_UNIFIED = ROOT / "Temp" / "p0_03_unified_coverage.json"
AUDIT_REPORT = ROOT / "Temp" / "p0_03_denominator_audit.json"
if sys.stdout.encoding and sys.stdout.encoding.lower() not in ("utf-8", "utf8"):
sys.stdout = open(sys.stdout.fileno(), mode="w", encoding="utf-8", buffering=1)
def load_yaml_simple(p: Path) -> dict:
"""간단한 YAML 파싱 (설치된 라이브러리 없이)."""
if not p.exists():
return {}
text = p.read_text(encoding="utf-8")
result = {}
# execution_order 섹션 찾기
in_exec_order = False
current_list = []
for line in text.split("\n"):
stripped = line.strip()
if stripped.startswith("execution_order:"):
in_exec_order = True
continue
if in_exec_order:
if stripped.startswith("- "):
formula_id = stripped[2:].strip()
if formula_id:
current_list.append(formula_id)
elif stripped and not stripped.startswith("-") and not stripped.startswith("#"):
# 다음 섹션 시작
in_exec_order = False
result["execution_order"] = current_list
return result
def load_json(p: Path) -> dict | list:
if not p.exists():
return {} if p.suffix == ".json" else {}
try:
return json.loads(p.read_text(encoding="utf-8"))
except Exception as e:
print(f"[WARN] Failed to load {p.name}: {e}")
return {}
def build_denominator_audit(formula_registry: dict, yaml_coverage: dict) -> dict:
"""분모 감사 보고서."""
audit = {
"generated_at": datetime.now().isoformat(),
"findings": {}
}
# 1. execution_order에서 active=true 공식 수집
exec_order = formula_registry.get("execution_order", [])
active_count = len(exec_order)
audit["findings"]["execution_order"] = {
"total_in_registry": active_count,
"formula_ids": exec_order[:10] + (["..."] if len(exec_order) > 10 else [])
}
# 2. yaml_code_coverage와의 비교
yaml_cov = yaml_coverage.get("coverage_summary", {})
yaml_formula_count = yaml_cov.get("formula_total", 0)
orphan_count = yaml_cov.get("orphan_code_formula_count", 0)
audit["findings"]["yaml_code_coverage"] = {
"formula_total": yaml_formula_count,
"orphan_code_formula_count": orphan_count,
"effective_denominator": yaml_formula_count - orphan_count
}
# 3. 분모 불일치 진단
expected_denominator = active_count
actual_288 = 288 # 기존 분모
actual_204 = 204 # 다른 분모
audit["findings"]["denominator_collision"] = {
"expected_unified": expected_denominator,
"legacy_288": actual_288,
"legacy_204": actual_204,
"collision_exists": (actual_288 != actual_204),
"recommendation": f"Use execution_order count={expected_denominator} as SINGLE source of truth"
}
return audit
def build_unified_coverage(formula_registry: dict) -> dict:
"""통일된 커버리지 계산."""
exec_order = formula_registry.get("execution_order", [])
active_formula_count = len(exec_order)
# 현재는 exec_order 개수를 분모로 사용하는 것만 계산
unified = {
"schema_version": "unified_coverage_v1",
"generated_at": datetime.now().isoformat(),
"denominator_single_source": "spec/13_formula_registry.yaml:execution_order",
"active_formula_count": active_formula_count,
"coverage_calculation_rule": {
"numerator": "GAS implementation + Python harness implementation",
"denominator": "execution_order count (deprecated/orphan excluded)",
"formula": f"coverage_pct = (gs_impl_count + py_impl_count) / {active_formula_count} * 100"
},
"required_corrections": [
{
"issue": "DECORATIVE_100_FIELD_REMOVAL",
"description": "'adjusted_coverage_pct (참고용, PASS 미사용)' 같은 필드 제거",
"action": "Delete all '**_adjusted', '**_参考용' fields"
},
{
"issue": "SINGLE_DENOMINATOR_LOCK",
"description": "모든 커버리지 계산을 execution_order 분모로 통일",
"action": f"Use denominator={active_formula_count} for all coverage metrics"
},
{
"issue": "GOLDEN_COVERAGE_UNIFICATION",
"description": "골든 커버리지를 64.93 / 90.2 / 67.93 / 100 중 1개로 선택 불가",
"action": "Calculate single golden_coverage_ratio with execution_order denominator"
}
],
"implementation_checklist": [
{"step": 1, "task": "measure_yaml_gs_ps_coverage.py 업데이트 (active=true만 수집)"},
{"step": 2, "task": "deprecated/orphan formula를 spec/13_formula_registry.yaml에서 active=false로 명시"},
{"step": 3, "task": "모든 *_adjusted, *_참고용 필드 제거"},
{"step": 4, "task": "validate_golden_coverage_100.py 업데이트 (단일 분모 검증)"}
]
}
return unified
def main() -> int:
print("=" * 80)
print(" P0_03: 커버리지 분모 통일 (288 vs 204 불일치 해소)")
print("=" * 80)
# 입력 로드
formula_reg = load_yaml_simple(FORMULA_REGISTRY)
yaml_cov = load_json(YAML_CODE_COVERAGE)
# 분모 감사
denominator_audit = build_denominator_audit(formula_reg, yaml_cov)
print(f"\n[1] Execution Order 공식 수")
print(f" 총 개수: {denominator_audit['findings']['execution_order']['total_in_registry']}")
print(f"\n[2] YAML 코드 커버리지")
yaml_find = denominator_audit["findings"]["yaml_code_coverage"]
print(f" 공식 총 수: {yaml_find['formula_total']}")
print(f" 고아 공식: {yaml_find['orphan_code_formula_count']}")
print(f" 유효 분모: {yaml_find['effective_denominator']}")
print(f"\n[3] 분모 불일치 진단")
denom_find = denominator_audit["findings"]["denominator_collision"]
print(f" 기대값(execution_order): {denom_find['expected_unified']}")
print(f" 기존 분모 1: {denom_find['legacy_288']}")
print(f" 기존 분모 2: {denom_find['legacy_204']}")
print(f" 충돌: {'YES' if denom_find['collision_exists'] else 'NO'}")
# 통일된 커버리지 계산
unified_cov = build_unified_coverage(formula_reg)
print(f"\n[4] 필수 수정사항")
for i, corr in enumerate(unified_cov['required_corrections'], 1):
print(f" {i}. {corr['issue']}")
print(f"{corr['description']}")
print(f"{corr['action']}")
# 보고서 저장
AUDIT_REPORT.write_text(
json.dumps(denominator_audit, ensure_ascii=False, indent=2),
encoding="utf-8"
)
print(f"\n✓ P0_03 분모 감사 저장: {AUDIT_REPORT.name}")
OUTPUT_UNIFIED.write_text(
json.dumps(unified_cov, ensure_ascii=False, indent=2),
encoding="utf-8"
)
print(f"✓ P0_03 통일 커버리지 저장: {OUTPUT_UNIFIED.name}")
return 0
if __name__ == "__main__":
sys.exit(main())
-234
View File
@@ -1,234 +0,0 @@
#!/usr/bin/env python3
"""
build_p3_01_stop_loss_taxonomy.py
────────────────────────────────────────────────────────────────────────
P3_01: 손절 체계 재정의 — ABSOLUTE_RISK_STOP vs RELATIVE_ALERT 분리
문제 진단:
기존: "시장대비 10% 빠지면 매도" → 목적 불명확
실제: (1) 절대 리스크 스탑 + (2) 상대성과 경보 혼합
해결:
1. ABSOLUTE_RISK_STOP_V1: ATR 기반 절대 하방 캡
2. RELATIVE_UNDERPERFORMANCE_ALERT_V1: 강도 약화 신호 (로테이션용)
출력:
- Temp/p3_01_stop_taxonomy_spec.json
- Temp/p3_01_implementation_plan.json
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
from datetime import datetime
ROOT = Path(__file__).resolve().parent.parent
OUTPUT_SPEC = ROOT / "Temp" / "p3_01_stop_taxonomy_spec.json"
OUTPUT_PLAN = ROOT / "Temp" / "p3_01_implementation_plan.json"
if sys.stdout.encoding and sys.stdout.encoding.lower() not in ("utf-8", "utf8"):
sys.stdout = open(sys.stdout.fileno(), mode="w", encoding="utf-8", buffering=1)
def build_stop_taxonomy() -> dict:
"""손절 체계 정의."""
spec = {
"schema_version": "stop_loss_taxonomy_v1",
"generated_at": datetime.now().isoformat(),
"root_cause": "절대 리스크(자본보호)와 상대강도(기회비용)를 혼합 → 손절 논리 불명확",
"diagnosis": {
"issue_1": "절대 스탑 없이 상대 로테이션만 사용 → 시장 폭락 시 -30% 출혈 가능",
"issue_2": "지정가와 수량 규칙 없음 → HTS 입력 불가능 (HS007)",
"issue_3": "비호가 가격 사용 → TICK_NORMALIZER 통과 실패 (HS008)",
"issue_4": "상대성과만으로 전량 청산 → 기회비용 최악 (회복 불가)"
},
"taxonomy": {
"ABSOLUTE_RISK_STOP_V1": {
"purpose": "자본 하방 보호 (항상 1순위)",
"target": "손실을 ATR/퍼센트 캡으로 제한",
"formula_core": "max(entry*0.92, entry - ATR20*1.5)",
"formula_core_note": "ATR20_Pct >= 8%면 *2.0으로 확대",
"formula_satellite": "entry - ATR20*2.0",
"formula_satellite_fallback": "entry*0.88",
"order_method": "지정가 (갭하락 시 09:00~09:15 시장가 금지)",
"quantity_rule": "3단계: 50% 즉시 + 50% 나머지",
"sample_prices": {
"entry": 100000,
"atr20": 2000,
"core_stop": 98000,
"core_stop_pct": "-2.0%",
"satellite_stop": 96000,
"satellite_stop_pct": "-4.0%"
},
"implementation": "spec/exit/stop_loss.yaml:ABSOLUTE_RISK_STOP_V1"
},
"RELATIVE_UNDERPERFORMANCE_ALERT_V1": {
"purpose": "기회비용 관리 (로테이션) — 손절매 아님",
"target": "강도 약화 신호 포착 → 신규 매수 차단 or 일부 trim",
"formula": "excess_ret_20d <= min(-10, rel_threshold_pct)",
"excess_ret_20d": "ret_stock_20d - beta_adj * ret_market_20d",
"rel_threshold_pct": "-clip(1.5 * sigma20_pct, 6, 18)",
"confirmation": "2영업일 연속 종가 확인 (단발 노이즈 차단)",
"action_ladder": {
"WATCH": "alert만 충족 → 신규매수 금지, 보유 유지",
"TRIM_30": "alert + [수급이탈|섹터순위하락|MA20이탈] 중 1개 → 30% 지정가",
"TRIM_50": "alert + 확인조건 2개↑ OR 절손 <=-20% → 50% 분할매도",
"EXIT_100": "하드스탑|회계위험|거래정지 → 전량 하네스 지정방식"
},
"sample_trigger": {
"stock_ret_20d": "0%",
"market_ret_20d": "5%",
"excess_ret": "-5% (시장 수익 미달)",
"beta_adj_threshold": "-10% (기준)",
"trigger": "YES"
},
"implementation": "spec/exit/stop_loss.yaml:RELATIVE_UNDERPERFORMANCE_ALERT_V1"
},
"FUNDAMENTAL_THESIS_BREAK_V1": {
"purpose": "재무 위험 (ROE 붕괴, FCF 악화 등)",
"independent": "절대/상대 스탑과 독립 평가",
"implementation": "spec/exit/stop_loss.yaml:FUNDAMENTAL_THESIS_BREAK_V1"
}
},
"mandatory_fields": {
"stop_trigger": "[price, qty, order_method, reason] 4필드 강제",
"price_normalization": "TICK_NORMALIZER_V1 통과 필수 (HS008)",
"multi_condition": "다중조건 접속사 금지: '또는', '실패 시' (HS007)",
"single_relative_exit_100": "상대성과만으로 EXIT_100 금지"
}
}
return spec
def build_implementation_plan() -> dict:
"""구현 계획."""
plan = {
"phase": "P3_01_STOP_LOSS_TAXONOMY_FIX",
"priority": "P0",
"files_to_update": [
"spec/exit/stop_loss.yaml",
"spec/13_formula_registry.yaml",
"gas_data_feed.gs (함수 3개 신규)",
"tools/validate_stop_loss_policy_v1.py (신규)"
],
"tasks": [
{
"task_id": "P3_01_A",
"title": "stop_loss.yaml 리팩토링",
"steps": [
"ABSOLUTE_RISK_STOP_V1 섹션 신규 생성",
"RELATIVE_UNDERPERFORMANCE_ALERT_V1 섹션 신규 생성",
"기존 '시장대비 N%' 문구 → ALERT로 강등",
"모든 트리거에 [price, qty, method, reason] 4필드 명시"
],
"acceptance": {
"stop_policy_ambiguous_phrase_count": 0,
"stop_action_has_price_qty_method_reason": True,
"relative_only_full_liquidation_count": 0
}
},
{
"task_id": "P3_01_B",
"title": "formula_registry에 3개 공식 등록",
"steps": [
"ABSOLUTE_RISK_STOP_V1 formula_id 등록",
"RELATIVE_UNDERPERFORMANCE_ALERT_V1 formula_id 등록",
"FUNDAMENTAL_THESIS_BREAK_V1 formula_id 등록",
"execution_order에 포함 (active=true)"
],
"acceptance": {
"formula_count": 3,
"execution_order_included": True
}
},
{
"task_id": "P3_01_C",
"title": "GAS 함수 구현",
"functions": [
"calcAbsoluteRiskStopV1_(): entry, atr20, pct → stop_price",
"calcRelativeUnderperfAlertV1_(): ret_stock, ret_market → alert_flag",
"calcStopActionLadderV1_(): alert + conditions → action (WATCH/TRIM/EXIT)"
],
"file": "gas_data_feed.gs",
"acceptance": {
"function_count": 3,
"gated_to_datasheet": True
}
},
{
"task_id": "P3_01_D",
"title": "검증 도구 구현",
"file": "tools/validate_stop_loss_policy_v1.py",
"checks": [
"gap_down 프로토콜: 09:00~09:15 시장가 투매 금지",
"TICK_NORMALIZER_V1: 모든 지정가 통과",
"multi-condition: 접속사 금지",
"single-relative EXIT_100: 금지"
],
"acceptance": {
"validation_pass": True,
"violations": 0
}
}
],
"risk_mitigation": [
"기존 '시장대비 N%' 로직은 ALERT로만 사용 (전량 청산 금지)",
"ABSOLUTE_RISK_STOP은 매우 항상 1순위 (코어 손절)",
"상대성과는 신규 매수만 차단 (보유 포지션은 허용)"
],
"rollout": {
"phase_1": "spec/exit/stop_loss.yaml 리팩토링 + formula_registry 등록",
"phase_2": "GAS 함수 구현 + 데이터 전송",
"phase_3": "analysis_prompt.md 업데이트 (LLM 지침)",
"phase_4": "실전 신호 검증 (3건 이상)"
}
}
return plan
def main() -> int:
print("=" * 80)
print(" P3_01: 손절 체계 재정의 — ABSOLUTE vs RELATIVE 분리")
print("=" * 80)
# 스펙 생성
spec = build_stop_taxonomy()
OUTPUT_SPEC.write_text(
json.dumps(spec, ensure_ascii=False, indent=2),
encoding="utf-8"
)
print(f"\n✓ 손절 분류 스펙 저장: {OUTPUT_SPEC.name}")
print(f" 분류: {len(spec['taxonomy'])}개 (ABSOLUTE, RELATIVE, FUNDAMENTAL)")
# 구현 계획
plan = build_implementation_plan()
OUTPUT_PLAN.write_text(
json.dumps(plan, ensure_ascii=False, indent=2),
encoding="utf-8"
)
print(f"✓ 구현 계획 저장: {OUTPUT_PLAN.name}")
print(f" 파일 업데이트: {len(plan['files_to_update'])}")
print(f" 태스크: {len(plan['tasks'])}")
# 진단 요약
print(f"\n[문제 진단]")
for i, issue in enumerate(spec['diagnosis'].values(), 1):
print(f" {i}. {issue}")
print(f"\n[3가지 손절 메커니즘]")
for name, detail in spec['taxonomy'].items():
print(f"{name}")
print(f"{detail['purpose']}")
print(f"\n[다음 액션]")
for task in plan['tasks'][:2]:
print(f" {task['task_id']}: {task['title']}")
return 0
if __name__ == "__main__":
sys.exit(main())
-60
View File
@@ -1,60 +0,0 @@
#!/usr/bin/env python3
"""P4_01: 라우팅·서빙·판단 단일화 — SCALP/SWING/MOMENTUM/POSITION 결정론화"""
from __future__ import annotations
import json, sys
from pathlib import Path
from datetime import datetime
ROOT = Path(__file__).resolve().parent.parent
OUTPUT = ROOT / "Temp" / "p4_01_routing_packet_spec.json"
if sys.stdout.encoding and sys.stdout.encoding.lower() not in ("utf-8", "utf8"):
sys.stdout = open(sys.stdout.fileno(), mode="w", encoding="utf-8", buffering=1)
def build_routing_spec() -> dict:
return {
"schema_version": "unified_route_packet_v1",
"generated_at": datetime.now().isoformat(),
"purpose": "SCALP/SWING/MOMENTUM/POSITION 판단을 결정론적 JSON으로 잠금",
"route_dimensions": ["SCALP", "SWING", "MOMENTUM", "POSITION"],
"style_weights": {
"SCALP": {"technical": 0.50, "smart_money": 0.25, "liquidity": 0.15, "fundamental": 0.10},
"SWING": {"smart_money": 0.35, "technical": 0.30, "liquidity": 0.20, "fundamental": 0.15},
"MOMENTUM": {"fundamental": 0.40, "smart_money": 0.30, "technical": 0.20, "liquidity": 0.10},
"POSITION": {"fundamental": 0.55, "smart_money": 0.20, "liquidity": 0.15, "technical": 0.10}
},
"conviction_to_pct": {
"<35": "진입 금지",
"35-49": "1.5% (PILOT)",
"50-64": "3%",
"65-79": "5%",
"80+": "7%"
},
"route_formula": "score = weighted_score × data_quality × regime_scale × anti_chase × liquidity × cash",
"mandatory_output": [
"ticker별 4스타일 점수(0-100)",
"best_style",
"recommended_pct",
"blocked_reason_codes (if blocked)"
],
"implementation_files": [
"spec/xx_routing_contract.yaml",
"gas_data_feed.gs: buildRoutePacket_()",
"tools/validate_capital_style_allocation_v1.py"
]
}
def main() -> int:
print("=" * 70)
print(" P4_01: 라우팅·서빙·판단 단일화")
print("=" * 70)
spec = build_routing_spec()
OUTPUT.write_text(json.dumps(spec, ensure_ascii=False, indent=2), encoding="utf-8")
print(f"\n✓ 라우팅 스펙 저장: {OUTPUT.name}")
print(f" 4가지 스타일 권중 정의 완료")
print(f" LLM 자유도 제거: 결정론적 JSON으로 잠금")
return 0
if __name__ == "__main__":
sys.exit(main())
-59
View File
@@ -1,59 +0,0 @@
#!/usr/bin/env python3
"""P5_01: 뒷북 매수·설거지 차단 — alpha_lead + pre_distribution 게이트"""
from __future__ import annotations
import json, sys
from pathlib import Path
from datetime import datetime
ROOT = Path(__file__).resolve().parent.parent
OUTPUT = ROOT / "Temp" / "p5_01_anti_chase_spec.json"
if sys.stdout.encoding and sys.stdout.encoding.lower() not in ("utf-8", "utf8"):
sys.stdout = open(sys.stdout.fileno(), mode="w", encoding="utf-8", buffering=1)
def build_spec() -> dict:
return {
"schema_version": "anti_late_entry_v1",
"generated_at": datetime.now().isoformat(),
"problem": "late_chase_status=DEGRADE_BUY_PERMISSION 발동중. 뒷북+설거지 차단 필요",
"solution_1_alpha_lead_entry": {
"name": "ALPHA_LEAD_ENTRY_GATE_V1",
"rules": {
"pilot_allowed": "alpha_lead_score >= 75 AND lead_entry_state == PILOT_ALLOWED",
"add_on_allowed": "pilot_pnl >= 0 AND flow_confirmed=true AND breakout_volume_confirmed=true",
"pullback_allowed": "confirmed_add_on=true AND pullback_to_ma20_or_atr_band=true"
},
"tranche_order": ["T1(30%)", "T2(30%)", "T3(40%)"],
"forbidden": ["CONFIRMED_ADD_ON 없이 T3 진입", "분위기로 PILOT 승격"]
},
"solution_2_pre_distribution_gate": {
"name": "PRE_DISTRIBUTION_EARLY_WARNING_V1",
"block_buy_if": [
"distribution_risk_score >= 70",
"price_up_volume_down == true",
"foreign_inst_net_sell_5d == true",
"candle_upper_tail_cluster == true"
]
},
"implementation": [
"spec/exit/pre_distribution_gate.yaml",
"gas_data_feed.gs: calcAlphaLeadV1_(), calcDistributionRiskV1_()",
"tools/validate_alpha_execution_harness.py"
]
}
def main() -> int:
print("=" * 70)
print(" P5_01: 뒷북 매수·설거지 차단")
print("=" * 70)
spec = build_spec()
OUTPUT.write_text(json.dumps(spec, ensure_ascii=False, indent=2), encoding="utf-8")
print(f"\n✓ 뒷북 차단 스펙 저장: {OUTPUT.name}")
print(f" 1. Alpha Lead Entry: alpha_lead_score >= 75")
print(f" 2. Pre-Distribution: distribution_risk >= 70 → BUY 블록")
print(f" 3. Tranche 순서: T1(30%) → T2(30%) → T3(40%)")
return 0
if __name__ == "__main__":
sys.exit(main())
-64
View File
@@ -1,64 +0,0 @@
#!/usr/bin/env python3
"""P6_01: 가치보존형 현금확보 — 5,913만원 부족액 최소 훼손으로 조성"""
from __future__ import annotations
import json, sys
from pathlib import Path
from datetime import datetime
ROOT = Path(__file__).resolve().parent.parent
OUTPUT = ROOT / "Temp" / "p6_01_cash_optimizer_spec.json"
if sys.stdout.encoding and sys.stdout.encoding.lower() not in ("utf-8", "utf8"):
sys.stdout = open(sys.stdout.fileno(), mode="w", encoding="utf-8", buffering=1)
def build_spec() -> dict:
return {
"schema_version": "cash_recovery_optimizer_v1",
"generated_at": datetime.now().isoformat(),
"problem": {
"current_cash_pct": 3.86,
"target_cash_pct": 15.0,
"shortfall_krw": 41342219,
"cash_floor_status": "BELOW_FLOOR",
"market_regime": "BREAKDOWN"
},
"objective": "현금 부족액 충족 AND 주식가치 훼손 최소 (raw <= 10%)",
"approach": "K2 50/50 분할: immediate_qty + rebound_wait_qty",
"key_rules": {
"rule_1": "K2 즉시 50% / 반등 대기 50% (rebound_trigger_price 전 실행 금지)",
"rule_2": "매도 순서: K3 regime_adjusted_sell_priority 사용 (코어 주도주 마지막)",
"rule_3": "value_damage_raw_pct <= 10% 상한 (cap_pass=false 허용 안함)",
"rule_4": "emergency_full_sell=true 조건: half_expected*2 < shortfall_min 일 때만"
},
"formulas": {
"rebound_trigger_price": "prevClose + 0.5*ATR20 (tick 정규화)",
"value_damage_raw": "sum(target_sell_krw) / current_portfolio_value * 100"
},
"implementation": [
"spec/exit/cash_recovery.yaml",
"gas_data_feed.gs: calcCashRecoveryOptimizerV1_()",
"tools/validate_value_preservation_v1.py (raw <= 10% 검증)"
],
"sample_case": {
"current_asset": 394191813,
"shortfall": 41342219,
"target_damage": "10% max",
"expected_recovery": 37108765
}
}
def main() -> int:
print("=" * 70)
print(" P6_01: 가치보존형 현금확보")
print("=" * 70)
spec = build_spec()
OUTPUT.write_text(json.dumps(spec, ensure_ascii=False, indent=2), encoding="utf-8")
print(f"\n✓ 현금 최적화 스펙 저장: {OUTPUT.name}")
print(f" 문제: 현금 3.86% → 목표 15% (부족액: 4,134만원)")
print(f" 해결: K2 50/50 분할매도 + value_damage <= 10% 유지")
print(f" 순서: K3 우선순위 적용 (코어 주도주 마지막)")
return 0
if __name__ == "__main__":
sys.exit(main())
-179
View File
@@ -1,179 +0,0 @@
"""build_pass_100_honest_gate_v1.py — PASS_100_HONEST_GATE_V1
P5-T1: 거짓 없는 최종 합격선.
P5의 pass_100_honest_criteria 12개를 체크하고 전부 충족 시에만 HTS_READY 승격 허용.
구조 점수가 아닌 실제 데이터 기반으로만 PASS 가능.
"""
from __future__ import annotations
import json
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
from v7_hardening_common import ROOT, TEMP, load_json, save_json
DEFAULT_OUT = TEMP / "pass_100_honest_v1.json"
def _f(v: Any, default: float = 0.0) -> float:
try:
return float(v)
except Exception:
return default
def _criterion(cid: str, actual: Any, target: str, passed: bool, source: str, note: str = "") -> dict[str, Any]:
return {
"criterion_id": cid,
"actual": actual,
"target": target,
"passed": bool(passed),
"source": source,
"note": note,
}
def main() -> int:
trg = load_json(TEMP / "truth_reconciliation_gate_v1.json")
scr = load_json(TEMP / "smart_cash_recovery_v6.json") or load_json(TEMP / "smart_cash_recovery_v5.json")
cov = load_json(TEMP / "yaml_gs_ps_coverage.json")
parity = load_json(TEMP / "formula_gas_parity_v1.json")
golden = load_json(TEMP / "formula_behavioral_coverage_v3.json")
ycc = load_json(TEMP / "yaml_code_coverage_v1.json")
fund = load_json(TEMP / "fundamental_raw_v2.json")
pred = load_json(TEMP / "prediction_accuracy_harness_v2.json")
olock = load_json(TEMP / "operational_outcome_lock_v1.json")
late = load_json(TEMP / "late_chase_attribution_v1.json")
proof = load_json(TEMP / "algorithm_guidance_proof_v1.json")
stl = load_json(TEMP / "single_truth_ledger_v2.json")
criteria = [
# C1: 교차파일 정합성 (P0-T3)
_criterion("TRUTH_RECONCILIATION_PASS",
trg.get("gate"), "PASS",
trg.get("gate") == "PASS",
"truth_reconciliation_gate_v1.json",
"동일 지표 파일간 불일치 0건"),
# C2: 은폐 지표 0 (P0-T1)
_criterion("MASKED_METRIC_COUNT_ZERO",
abs(_f(scr.get("value_damage_pct_avg")) - _f(scr.get("value_damage_pct_avg_raw"))),
"== 0",
abs(_f(scr.get("value_damage_pct_avg")) - _f(scr.get("value_damage_pct_avg_raw"))) < 0.01,
"smart_cash_recovery_v6.json",
"value_damage 표시값=원시값"),
# C3: adjusted PASS 신호 0 (P0-T4)
_criterion("DENOMINATOR_ADJUSTED_PASS_ZERO",
cov.get("status"), "!= OK (strict 기준)",
cov.get("status") != "OK",
"yaml_gs_ps_coverage.json",
"strict < 100이면 status FAIL이어야 정직"),
# C4: GAS strict 커버리지 decision_critical 100% (P1-T1)
# 현재 GAS V2 함수들이 의사결정 핵심공식 커버 → 12/12 체크
_criterion("GS_STRICT_DECISION_CRITICAL_100",
"V2_VARIANTS_COVER_DECISION_CRITICAL",
"decision_critical 12공식 GAS 구현 존재",
True, # calcAntiLateEntryGateV2_, calcDistributionRiskRow_, etc. 존재 확인됨
"gas_data_feed.gs",
"V2 함수들이 의사결정 핵심공식 커버 (strict 100%는 P1 완료 후)"),
# C5: GAS↔Python 패리티 (P1-T2)
_criterion("GAS_PYTHON_PARITY_ZERO_MISMATCH",
parity.get("mismatch_count", parity.get("fail_count", 0)),
"== 0",
int(parity.get("mismatch_count", parity.get("fail_count", 0))) == 0,
"formula_gas_parity_v1.json"),
# C6: Golden test coverage >= 0.98 (P2-T1)
_criterion("GOLDEN_COVERAGE_GE_98",
_f(golden.get("behavioral_coverage_pct")),
">= 98.0",
_f(golden.get("behavioral_coverage_pct")) >= 98.0,
"formula_behavioral_coverage_v3.json"),
# C7: 펀더멘털 커버리지 >= 80% (P2 FUNDAMENTAL)
_criterion("FUNDAMENTAL_FIELD_COMPLETENESS_GE_80",
_f(fund.get("coverage_pct")),
">= 80.0",
_f(fund.get("coverage_pct")) >= 80.0,
"fundamental_raw_v2.json",
"현재 OCF/FCF 미수집 (GAS fetchFundamentalsWithCache_ 보완 필요)"),
# C8: prediction_match_rate >= 60 (P4-T1, DATA-GATED)
_criterion("PREDICTION_MATCH_RATE_GE_60",
_f(pred.get("t5_ap_combined") or pred.get("prediction_match_rate_pct")),
">= 60.0",
_f(pred.get("t5_ap_combined") or pred.get("prediction_match_rate_pct")) >= 60.0,
"prediction_accuracy_harness_v2.json",
"DATA-GATED: 표본 누적 필요"),
# C9: t20_operational >= 30 (P4-T1, DATA-GATED)
_criterion("T20_OPERATIONAL_SAMPLES_GE_30",
int(olock.get("metrics", {}).get("operational_t20_count") or 0),
">= 30",
int(olock.get("metrics", {}).get("operational_t20_count") or 0) >= 30,
"operational_outcome_lock_v1.json",
"DATA-GATED: 실측 T+20 30건 누적 필요"),
# C10: late_chase_live_samples >= 30 (P4-T2, DATA-GATED)
_criterion("LATE_CHASE_LIVE_SAMPLES_GE_30",
int(late.get("samples") or 0),
">= 30",
int(late.get("samples") or 0) >= 30,
"late_chase_attribution_v1.json",
"DATA-GATED: 뒷박 라이브 귀인 표본 30건 필요"),
# C11: value_damage honest display (P0-T1 완료)
_criterion("VALUE_DAMAGE_DISPLAY_EQUALS_RAW",
{"display": _f(scr.get("value_damage_pct_avg")), "raw": _f(scr.get("value_damage_pct_avg_raw"))},
"display == raw",
abs(_f(scr.get("value_damage_pct_avg")) - _f(scr.get("value_damage_pct_avg_raw"))) < 0.01,
"smart_cash_recovery_v6.json"),
# C12: honest_proof_score >= 90 (P0-T5, DATA-GATED until T+20)
_criterion("HONEST_PROOF_SCORE_GE_90",
_f(proof.get("honest_proof_score")),
">= 90.0",
_f(proof.get("honest_proof_score")) >= 90.0,
"algorithm_guidance_proof_v1.json",
"DATA-GATED: live_validation(T+20 30건) + prediction(60%) 충족 후 달성 가능"),
]
failed = [c for c in criteria if not c["passed"]]
passed_count = len(criteria) - len(failed)
gate = "PASS" if not failed else "BLOCK_DEPLOYMENT"
data_gated = [c["criterion_id"] for c in failed if "DATA-GATED" in c.get("note", "")]
code_fixable = [c["criterion_id"] for c in failed if "DATA-GATED" not in c.get("note", "")]
result = {
"formula_id": "PASS_100_HONEST_GATE_V1",
"gate": gate,
"pass_100_honest_allowed": gate == "PASS",
"passed_count": passed_count,
"total_count": len(criteria),
"failed_count": len(failed),
"failed_criteria": [c["criterion_id"] for c in failed],
"data_gated_criteria": data_gated,
"code_fixable_criteria": code_fixable,
"criteria": criteria,
"honest_note": "이 게이트는 구조 점수가 아닌 실제 데이터 기반으로만 PASS 가능. DATA-GATED 항목은 6월 말~7월 자연 해소.",
"generated_at": datetime.now(timezone.utc).isoformat(),
}
save_json(str(DEFAULT_OUT), result)
summary = {k: v for k, v in result.items() if k != "criteria"}
print(json.dumps(summary, indent=2, ensure_ascii=True))
if gate == "PASS":
print("PASS_100_HONEST_GATE_V1_PASS")
else:
print(f"PASS_100_HONEST_GATE_V1_BLOCK ({len(failed)} criteria failed)")
for c in failed:
note = f" [{c['note']}]" if c.get("note") else ""
print(f" {c['criterion_id']}: {c['actual']} vs target={c['target']}{note}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
-88
View File
@@ -1,88 +0,0 @@
#!/usr/bin/env python3
import argparse
import hashlib
import json
import os
from pathlib import Path
import yaml
ROOT = Path(__file__).resolve().parent.parent
def file_sha256(path: Path) -> str:
if not path.exists():
return ""
h = hashlib.sha256()
try:
with path.open("rb") as f:
for chunk in iter(lambda: f.read(65536), b""):
h.update(chunk)
return h.hexdigest()
except Exception:
return ""
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("--out", required=True)
args = parser.parse_args()
out_path = Path(args.out)
if not out_path.is_absolute():
out_path = ROOT / out_path
# Load active artifact manifest
manifest_path = ROOT / "runtime" / "active_artifact_manifest.yaml"
manifest_data = {}
if manifest_path.exists():
try:
manifest_data = yaml.safe_load(manifest_path.read_text(encoding="utf-8"))
except Exception as e:
print(f"Error reading manifest: {e}")
# Load canonical metrics from final_decision_packet_active.json
packet_path = ROOT / "Temp" / "final_decision_packet_active.json"
packet_hash = file_sha256(packet_path)
# Count files
files = [p for p in ROOT.rglob("*") if p.is_file() and not p.parts[len(ROOT.parts):].count("Temp") and not p.parts[len(ROOT.parts):].count(".git")]
baseline = {
"formula_id": "REFACTOR_BASELINE_MANIFEST_V2",
"total_files": len(files),
"active_manifest_rows": manifest_data.get("manifest_rows", []),
"canonical_metrics_hash": packet_hash,
"lock_temp_edits": True,
"source_zip_sha256": "c8d214d3c880392b176c26947367d832a55fd9f4a107bad69c7f272cd4c6b01e"
}
# Write baseline manifest
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(yaml.safe_dump(baseline, allow_unicode=True, default_flow_style=False), encoding="utf-8")
print(f"Written baseline manifest to {out_path}")
# Write rollback manifest v2
rollback_path = ROOT / "runtime" / "rollback_manifest_v2.yaml"
rollback = {
"formula_id": "REFACTOR_ROLLBACK_MANIFEST_V2",
"previous_active_packet": "Temp/final_decision_packet_active.json",
"previous_manifest": "runtime/active_artifact_manifest.yaml",
"rollback_files": [
{"path": "runtime/active_artifact_manifest.yaml", "sha256": file_sha256(manifest_path)},
{"path": "Temp/final_decision_packet_active.json", "sha256": packet_hash}
]
}
rollback_path.write_text(yaml.safe_dump(rollback, allow_unicode=True, default_flow_style=False), encoding="utf-8")
print(f"Written rollback manifest to {rollback_path}")
# Ensure lineage events log exists
lineage_log = ROOT / "runtime" / "lineage_events.jsonl"
if not lineage_log.exists():
lineage_log.touch()
return 0
if __name__ == "__main__":
raise SystemExit(main())
-149
View File
@@ -1,149 +0,0 @@
"""build_truth_reconciliation_gate_v1.py — TRUTH_RECONCILIATION_GATE_V1
P0-T3: 동일 지표가 파일마다 다른 값을 가지면 자동 FAIL.
감시 지표: prediction_match_rate_pct, t20_pass_rate, value_damage_pct_avg,
gs_coverage_pct, portfolio_alpha_confidence, performance_readiness_score
허용 오차: 비율 지표 ±0.5%p, 금액 지표 ±1원
"""
from __future__ import annotations
import json
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
TEMP = ROOT / "Temp"
DEFAULT_OUT = TEMP / "truth_reconciliation_gate_v1.json"
TOLERANCE_RATE = 0.5 # %p
TOLERANCE_KRW = 1.0 # 원
# 감시 지표: (정규화된 metric_id, json_pointer_list, 단위, 제외 파일 패턴)
# 위양성 방지: 같은 key명이 다른 개념에 쓰이는 파일은 명시 제외
MONITORED_METRICS: list[tuple[str, list[str], str, set[str]]] = [
("prediction_match_rate_pct",
["prediction_match_rate_pct", "t5_ap_combined"],
"rate",
# v5 = legacy v5.todo.batch 파일 (builder 없음), v7 = 다른 블렌드 점수
{"prediction_accuracy_harness_v5", "smart_cash_recovery_v7"}),
("t20_pass_rate",
["t20_pass_rate"], # pass_rate_pct는 제외 (completion_gap과 혼동)
"rate",
{"completion_gap", "phase_checks"}), # 완료기준 통과율 파일 제외
("value_damage_pct_avg",
["value_damage_pct_avg"],
"rate",
# 다른 목적함수 + 구버전 아카이브 파일 제외 (현재 파이프라인 외 레거시)
{"dynamic_value_preservation", "cash_raise_value_optimizer",
"cash_raise_value_preservation", "value_preserving_cash_raise_v1",
"hts_sell_blueprint",
"smart_cash_recovery_v7.json"}), # v7 non-authoritative (2026-05-31 legacy)
("gs_strict_coverage_pct", # gs_coverage_pct 대신 strict 전용 포인터
["gs_coverage_pct"],
"rate",
{"gs_native_coverage_lock"}), # native coverage는 다른 개념
("portfolio_alpha_confidence",
["portfolio_alpha_confidence", "alpha_confidence"],
"rate",
set()),
("performance_readiness_score",
["performance_readiness_score", "blended_performance_readiness_score"],
"rate",
set()),
]
def _load(p: Path) -> dict[str, Any]:
if not p.exists():
return {}
try:
obj = json.loads(p.read_text(encoding="utf-8"))
return obj if isinstance(obj, dict) else {}
except Exception:
return {}
def _extract(d: dict[str, Any], pointers: list[str]) -> float | None:
for ptr in pointers:
v = d.get(ptr)
if v is not None:
try:
f = float(v)
if f != 0.0 or ptr in d:
return f
except (TypeError, ValueError):
pass
return None
def main() -> int:
# 모든 Temp JSON 로드
json_files = list(TEMP.glob("*.json"))
# 제외: 보고서/golden/binary
exclude_patterns = {"formula_golden", "formula_behavioral", "formula_gas_parity", "engine_audit_2026"}
candidates = [f for f in json_files if not any(ex in f.name for ex in exclude_patterns)]
observations: dict[str, list[dict[str, Any]]] = {m[0]: [] for m in MONITORED_METRICS}
for f in candidates:
d = _load(f)
if not d:
continue
rel = str(f.relative_to(ROOT))
for metric_id, pointers, unit, exclude_patterns in MONITORED_METRICS:
# 제외 패턴 파일 스킵
if any(ep in f.name for ep in exclude_patterns):
continue
val = _extract(d, pointers)
if val is not None:
observations[metric_id].append({"file": rel, "value": val})
conflicts: list[dict[str, Any]] = []
for metric_id, pointers, unit, _ in MONITORED_METRICS:
obs = observations[metric_id]
if len(obs) < 2:
continue
values = [o["value"] for o in obs]
min_v, max_v = min(values), max(values)
tol = TOLERANCE_RATE if unit == "rate" else TOLERANCE_KRW
if (max_v - min_v) > tol:
conflicts.append({
"metric_id": metric_id,
"min": min_v,
"max": max_v,
"spread": round(max_v - min_v, 4),
"tolerance": tol,
"unit": unit,
"observations": sorted(obs, key=lambda x: x["value"]),
})
gate = "PASS" if not conflicts else "FAIL"
result = {
"formula_id": "TRUTH_RECONCILIATION_GATE_V1",
"gate": gate,
"conflict_count": len(conflicts),
"conflicts": conflicts,
"monitored_metrics": [m[0] for m in MONITORED_METRICS],
"excluded_per_metric": {m[0]: list(m[3]) for m in MONITORED_METRICS if m[3]},
"files_scanned": len(candidates),
"generated_at": datetime.now(timezone.utc).isoformat(),
}
DEFAULT_OUT.parent.mkdir(parents=True, exist_ok=True)
DEFAULT_OUT.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
summary = {k: v for k, v in result.items() if k != "conflicts"}
print(json.dumps(summary, indent=2, ensure_ascii=False))
if gate == "PASS":
print("TRUTH_RECONCILIATION_GATE_V1_PASS")
else:
print(f"TRUTH_RECONCILIATION_GATE_V1_FAIL ({len(conflicts)} conflicts)")
for c in conflicts:
print(f" {c['metric_id']}: spread={c['spread']} (tol={c['tolerance']})")
for o in c["observations"]:
print(f" {o['file']}: {o['value']}")
return 0 if gate == "PASS" else 1
if __name__ == "__main__":
raise SystemExit(main())
-157
View File
@@ -1,157 +0,0 @@
from __future__ import annotations
import argparse
import json
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
TEMP = ROOT / "Temp"
DEFAULT_CAPITAL = TEMP / "capital_style_allocation_v1.json"
DEFAULT_HORIZON = TEMP / "horizon_classification_v1.json"
DEFAULT_FUND = TEMP / "fundamental_multifactor_v3.json"
DEFAULT_OUT = TEMP / "unified_route_packet_v1.json"
FORMULA_ID = "UNIFIED_ROUTE_PACKET_V1"
VALID_STYLES = ("SCALP", "SWING", "MOMENTUM", "POSITION")
def _load(path: Path) -> dict[str, Any]:
if not path.exists():
return {}
try:
obj = json.loads(path.read_text(encoding="utf-8"))
except Exception:
return {}
return obj if isinstance(obj, dict) else {}
def _f(v: Any, default: float = 0.0) -> float:
try:
return float(v)
except Exception:
return default
def _best_style(row: dict[str, Any]) -> dict[str, Any]:
styles = row.get("styles") or []
best = max(
[s for s in styles if isinstance(s, dict)],
key=lambda s: _f(s.get("conviction_score")),
default={},
)
return best if isinstance(best, dict) else {}
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--capital", default=str(DEFAULT_CAPITAL))
ap.add_argument("--horizon", default=str(DEFAULT_HORIZON))
ap.add_argument("--fund", default=str(DEFAULT_FUND))
ap.add_argument("--out", default=str(DEFAULT_OUT))
args = ap.parse_args()
capital_path = Path(args.capital)
horizon_path = Path(args.horizon)
fund_path = Path(args.fund)
out_path = Path(args.out)
if not capital_path.is_absolute():
capital_path = ROOT / capital_path
if not horizon_path.is_absolute():
horizon_path = ROOT / horizon_path
if not fund_path.is_absolute():
fund_path = ROOT / fund_path
if not out_path.is_absolute():
out_path = ROOT / out_path
capital = _load(capital_path)
horizon = _load(horizon_path)
fund = _load(fund_path)
fund_rows = {str(r.get("ticker") or ""): r for r in (fund.get("rows") or []) if isinstance(r, dict)}
hz_rows = {str(r.get("ticker") or ""): r for r in (horizon.get("rows") or []) if isinstance(r, dict)}
rows_out: list[dict[str, Any]] = []
blocked_count = 0
style_score_range_violations = 0
every_ticker_has_one_best_style = True
blocked_reason_codes_non_empty_when_blocked = True
for row in capital.get("rows") or []:
if not isinstance(row, dict):
continue
ticker = str(row.get("ticker") or "")
name = str(row.get("name") or "")
sb = row.get("signal_breakdown") or {}
best = _best_style(row)
best_style = str(best.get("style") or "UNKNOWN")
conviction = _f(best.get("conviction_score"))
recommended_pct = _f(best.get("recommended_pct"))
actual_horizon = str(hz_rows.get(ticker, {}).get("horizon") or "UNKNOWN")
expected_horizon = {"SCALP": "SHORT", "SWING": "SHORT", "MOMENTUM": "MID", "POSITION": "LONG"}.get(best_style, "UNKNOWN")
buy_allowed = bool((fund_rows.get(ticker) or {}).get("buy_allowed"))
liquidity_label = str(sb.get("liquidity_label") or "UNKNOWN")
macro_gate = str(sb.get("macro_gate") or "UNKNOWN")
blocked_reason_codes: list[str] = []
if not best_style or best_style not in VALID_STYLES:
every_ticker_has_one_best_style = False
blocked_reason_codes.append("BEST_STYLE_MISSING")
if not (0.0 <= conviction <= 100.0):
style_score_range_violations += 1
blocked_reason_codes.append("CONVICTION_RANGE")
if liquidity_label == "FROZEN":
blocked_reason_codes.append("LIQUIDITY_FROZEN")
if macro_gate == "AVOID_NEW_BUY":
blocked_reason_codes.append("MACRO_AVOID_NEW_BUY")
if not buy_allowed:
blocked_reason_codes.append("FUNDAMENTAL_BUY_BLOCK")
if expected_horizon != "UNKNOWN" and actual_horizon not in ("UNKNOWN", "ETF") and expected_horizon != actual_horizon:
blocked_reason_codes.append("STYLE_HORIZON_MISMATCH")
if conviction < 35.0:
blocked_reason_codes.append("CONVICTION_LT_35")
blocked = len(blocked_reason_codes) > 0
if blocked:
blocked_count += 1
if not blocked_reason_codes:
blocked_reason_codes_non_empty_when_blocked = False
rows_out.append({
"ticker": ticker,
"name": name,
"best_style": best_style,
"best_style_conviction_score": round(conviction, 2),
"recommended_pct": recommended_pct,
"expected_horizon": expected_horizon,
"actual_horizon": actual_horizon,
"blocked": blocked,
"blocked_reason_codes": blocked_reason_codes,
"signal_breakdown": sb,
"formula_id": FORMULA_ID,
})
result = {
"formula_id": FORMULA_ID,
"gate": "PASS" if every_ticker_has_one_best_style and style_score_range_violations == 0 and blocked_reason_codes_non_empty_when_blocked else "FAIL",
"ticker_count": len(rows_out),
"blocked_count": blocked_count,
"every_ticker_has_one_best_style": every_ticker_has_one_best_style,
"every_style_score_range_0_100": style_score_range_violations == 0,
"blocked_reason_codes_non_empty_when_blocked": blocked_reason_codes_non_empty_when_blocked,
"rows": rows_out,
"source": {
"capital_style_allocation_v1_json": str(capital_path),
"horizon_classification_v1_json": str(horizon_path),
"fundamental_multifactor_v3_json": str(fund_path),
},
}
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
print(json.dumps({k: v for k, v in result.items() if k != "rows"}, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -1,59 +0,0 @@
#!/usr/bin/env python3
"""Check whether WBS-9.5 can be promoted from DATA_GATED to DONE."""
from __future__ import annotations
import argparse
import json
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
from tools.build_sector_flow_history_progress_v1 import DEFAULT_JSON, FORMULA_ID, main as build_progress_main # type: ignore
def _load(path: Path) -> dict:
if not path.exists():
return {}
try:
payload = json.loads(path.read_text(encoding="utf-8"))
return payload if isinstance(payload, dict) else {}
except Exception:
return {}
def main() -> int:
ap = argparse.ArgumentParser(description="Check WBS-9.5 readiness.")
ap.add_argument("--input", default=str(ROOT / "Temp" / "sector_flow_history_progress_v1.json"))
ap.add_argument("--json", action="store_true")
args = ap.parse_args()
input_path = Path(args.input)
input_path = input_path if input_path.is_absolute() else ROOT / input_path
if not input_path.exists():
build_progress_main()
payload = _load(input_path)
current = int(payload.get("current_dates") or 0)
target = int(payload.get("target_dates") or 30)
ready = current >= target and payload.get("status") == "DONE"
result = {
"formula_id": "WBS_9_5_RELIABILITY_READY_V1",
"source": str(input_path),
"current_dates": current,
"target_dates": target,
"ready": ready,
"status": "DONE" if ready else "DATA_GATED",
"message": "WBS-9.5 can be promoted" if ready else "WBS-9.5 remains DATA_GATED",
}
print(json.dumps(result, ensure_ascii=False, indent=2) if args.json else f"{result['status']}: {result['message']} ({current}/{target})")
return 0 if ready else 1
if __name__ == "__main__":
raise SystemExit(main())
-277
View File
@@ -1,277 +0,0 @@
"""
GAS_THIN_ADAPTER_POLICY_V1 — Phase 2: Extract
spec/39_gas_thin_adapter_policy.yaml의 extract 단계.
audit_gas_thin_adapter_v1.py 결과(forbidden 23개)를 읽어
각 GAS 함수의 Python canonical 대응을 매핑한다.
결과를 Temp/gas_python_migration_map_v1.json에 저장.
"""
from __future__ import annotations
import json
import sys
from datetime import datetime
from pathlib import Path
ROOT = Path(__file__).parent.parent
# ── GAS forbidden 함수 → Python canonical 매핑 ────────────────────────────
# status:
# MAPPED — Python에 동등 구현 존재, GAS 버전 제거 가능
# NEEDS_STUB — Python 등가 미존재, stub 신규 작성 필요
# PARTIAL — 부분 구현 존재, 확장 필요
MIGRATION_MAP: list[dict] = [
{
"gas_file": "gdc_01_fetch_fundamentals.gs",
"gas_function": "_mergePositionRecord_",
"responsibility": ["stop_loss"],
"status": "MAPPED",
"python_module": "src/quant_engine/convert_xlsx_to_json.py",
"python_function": "normalize_backdata_harness_payload",
"note": "포지션 레코드 병합은 convert_xlsx_to_json의 backdata 정규화 로직이 담당",
},
{
"gas_file": "gdc_02_account_satellite.gs",
"gas_function": "_addTickerRoute_",
"responsibility": ["unknown"],
"status": "MAPPED",
"python_module": "src/quant_engine/inject_computed_harness.py",
"python_function": "calc_semiconductor_cluster",
"note": "ticker 라우팅 집계는 inject_computed_harness의 cluster/route 집계 로직이 담당",
},
{
"gas_file": "gdf_01_price_metrics.gs",
"gas_function": "calcApexTradePlan_",
"responsibility": ["sizing", "normalize"],
"status": "MAPPED",
"python_module": "src/quant_engine/compute_formula_outputs.py",
"python_function": "compute_position_size",
"note": "포지션 사이징 계획은 compute_position_size (POSITION_SIZE_V1) 담당",
},
{
"gas_file": "gdf_02_harness_assembly.gs",
"gas_function": "assembleHarnessCoreLayers_",
"responsibility": ["sizing"],
"status": "MAPPED",
"python_module": "src/quant_engine/inject_computed_harness.py",
"python_function": "main",
"note": "하네스 코어 레이어 조립은 inject_computed_harness.main() 전체 파이프라인이 담당",
},
{
"gas_file": "gdf_02_harness_assembly.gs",
"gas_function": "applyApexCashPreservationSuite_",
"responsibility": ["unknown"],
"status": "MAPPED",
"python_module": "src/quant_engine/inject_computed_harness.py",
"python_function": "cash_recovery",
"note": "현금 보존 스위트는 cash_recovery + compute_cash_recovery_optimizer 담당",
},
{
"gas_file": "gdf_02_harness_assembly.gs",
"gas_function": "applyApexFeedbackSignalSuite_",
"responsibility": ["decision"],
"status": "MAPPED",
"python_module": "src/quant_engine/compute_formula_outputs.py",
"python_function": "compute_final_decision",
"note": "피드백 신호 스위트는 compute_final_decision + compute_timing_decision 담당",
},
{
"gas_file": "gdf_02_harness_assembly.gs",
"gas_function": "applyProposal54BuyBlockLocks_",
"responsibility": ["decision"],
"status": "MAPPED",
"python_module": "src/quant_engine/inject_computed_harness.py",
"python_function": "main",
"note": "Proposal-54 매수 차단 잠금은 inject_computed_harness의 buy_permission 로직이 담당",
},
{
"gas_file": "gdf_02_harness_assembly.gs",
"gas_function": "calcStopBreachAlert_",
"responsibility": ["stop_loss"],
"status": "MAPPED",
"python_module": "src/quant_engine/inject_computed_harness.py",
"python_function": "calc_stop_breach_alerts",
"note": "직접 대응: calc_stop_breach_alerts (STOP_BREACH_ALERT_V1)",
},
{
"gas_file": "gdf_02_harness_assembly.gs",
"gas_function": "calcAbsoluteRiskStopV1_",
"responsibility": ["stop_loss"],
"status": "MAPPED",
"python_module": "src/quant_engine/compute_formula_outputs.py",
"python_function": "compute_stop_price_core",
"note": "절대 리스크 스탑은 compute_stop_price_core (STOP_PRICE_CORE_V1) 담당",
},
{
"gas_file": "gdf_02_harness_assembly.gs",
"gas_function": "calcTpTriggerAlert_",
"responsibility": ["take_profit"],
"status": "MAPPED",
"python_module": "src/quant_engine/compute_formula_outputs.py",
"python_function": "compute_tp_validity",
"note": "TP 트리거 알람은 compute_tp_validity (TP_TRIGGER_ALERT_V1) 담당",
},
{
"gas_file": "gdf_03_portfolio_gates.gs",
"gas_function": "calcTpQuantityLadder_",
"responsibility": ["sizing", "take_profit"],
"status": "MAPPED",
"python_module": "src/quant_engine/compute_formula_outputs.py",
"python_function": "compute_position_size",
"note": "TP 수량 사다리는 compute_position_size + TP 비율 적용으로 담당",
},
{
"gas_file": "gdf_03_portfolio_gates.gs",
"gas_function": "scoreSellCandidate_",
"responsibility": ["unknown"],
"status": "MAPPED",
"python_module": "src/quant_engine/inject_computed_harness.py",
"python_function": "check_sanity",
"note": "매도 후보 채점은 check_sanity (SELL_PRICE_SANITY_V1) + cash_recovery 순위 담당",
},
{
"gas_file": "gdf_03_portfolio_gates.gs",
"gas_function": "calcPrices_",
"responsibility": ["stop_loss", "take_profit"],
"status": "MAPPED",
"python_module": "src/quant_engine/compute_formula_outputs.py",
"python_function": "compute_stop_price_core",
"note": "지정가/스탑/TP 가격 계산은 compute_stop_price_core + compute_tp_validity 담당",
},
{
"gas_file": "gdf_03_portfolio_gates.gs",
"gas_function": "runRouteFlow_",
"responsibility": ["stop_loss"],
"status": "NEEDS_STUB",
"python_module": "tools/gas_thin_adapter_stubs_v1.py",
"python_function": "stub_run_route_flow",
"note": "라우트 플로우 실행 — Python harness에 직접 대응 없음. stub 생성 필요.",
},
{
"gas_file": "gdf_03_portfolio_gates.gs",
"gas_function": "buildOrderBlueprint_",
"responsibility": ["stop_loss", "take_profit"],
"status": "MAPPED",
"python_module": "src/quant_engine/compute_formula_outputs.py",
"python_function": "main (order_blueprint_json 조립)",
"note": "주문 청사진 조립은 compute_formula_outputs 의 blueprint_rows 생성 로직이 담당",
},
{
"gas_file": "gdf_03_portfolio_gates.gs",
"gas_function": "calcDistributionRiskRow_",
"responsibility": ["risk_score"],
"status": "MAPPED",
"python_module": "src/quant_engine/inject_computed_harness.py",
"python_function": "calc_distribution_detector_per_ticker",
"note": "설거지 위험 점수는 calc_distribution_detector_per_ticker (DISTRIBUTION_SELL_DETECTOR_V1) 담당",
},
{
"gas_file": "gdf_04_execution_quality.gs",
"gas_function": "calcProfitPreservationRow_",
"responsibility": ["stop_loss"],
"status": "MAPPED",
"python_module": "src/quant_engine/inject_computed_harness.py",
"python_function": "trailing_stop_v2",
"note": "이익 보존 래칫은 trailing_stop_v2 (PROFIT_RATCHET_TIERED_V2) 담당",
},
{
"gas_file": "gdf_04_execution_quality.gs",
"gas_function": "calcSmartCashRaiseV2_",
"responsibility": ["stop_loss"],
"status": "MAPPED",
"python_module": "src/quant_engine/inject_computed_harness.py",
"python_function": "cash_recovery",
"note": "스마트 현금 조달은 cash_recovery + K2 rebound logic 담당",
},
{
"gas_file": "gdf_04_execution_quality.gs",
"gas_function": "calcApexExecutionHarness_",
"responsibility": ["sizing", "normalize", "decision"],
"status": "MAPPED",
"python_module": "src/quant_engine/inject_computed_harness.py",
"python_function": "main",
"note": "Apex 실행 하네스 전체는 inject_computed_harness.main() 파이프라인이 담당",
},
{
"gas_file": "gdf_04_execution_quality.gs",
"gas_function": "calcCashPreservationSellEngineV2_",
"responsibility": ["sizing"],
"status": "MAPPED",
"python_module": "src/quant_engine/inject_computed_harness.py",
"python_function": "cash_recovery",
"note": "현금 보존 매도 엔진은 cash_recovery (CASH_RECOVERY_OPTIMIZER_V1) 담당",
},
{
"gas_file": "gdf_04_execution_quality.gs",
"gas_function": "calcExportGate_",
"responsibility": ["unknown"],
"status": "NEEDS_STUB",
"python_module": "tools/gas_thin_adapter_stubs_v1.py",
"python_function": "stub_calc_export_gate",
"note": "export gate 계산 — Python harness에 직접 대응 없음. stub 생성 필요.",
},
{
"gas_file": "gdf_04_execution_quality.gs",
"gas_function": "buildWatchLedger_",
"responsibility": ["stop_loss", "take_profit"],
"status": "NEEDS_STUB",
"python_module": "tools/gas_thin_adapter_stubs_v1.py",
"python_function": "stub_build_watch_ledger",
"note": "관찰 원장 조립 — Python harness에 직접 대응 없음. stub 생성 필요.",
},
{
"gas_file": "gdf_05_alpha_engines.gs",
"gas_function": "buildShadowLedger_",
"responsibility": ["stop_loss", "sizing", "take_profit"],
"status": "MAPPED",
"python_module": "src/quant_engine/compute_formula_outputs.py",
"python_function": "check_sell_price_sanity (shadow_ledger 필드 포함)",
"note": "그림자 원장(BLOCKED blueprint 분리)은 check_sell_price_sanity의 shadow_ledger 필드 담당",
},
]
def main() -> int:
mapped = [m for m in MIGRATION_MAP if m["status"] == "MAPPED"]
stubs = [m for m in MIGRATION_MAP if m["status"] == "NEEDS_STUB"]
partial = [m for m in MIGRATION_MAP if m["status"] == "PARTIAL"]
output = {
"policy_id": "GAS_THIN_ADAPTER_POLICY_V1",
"phase": "extract",
"generated_at": datetime.now().isoformat(),
"summary": {
"total_forbidden": len(MIGRATION_MAP),
"mapped_count": len(mapped),
"needs_stub_count": len(stubs),
"partial_count": len(partial),
"extraction_readiness_pct": round(len(mapped) / len(MIGRATION_MAP) * 100, 1),
},
"mapping": MIGRATION_MAP,
"needs_stub": [
{"gas_function": m["gas_function"], "python_function": m["python_function"],
"responsibility": m["responsibility"], "note": m["note"]}
for m in stubs
],
}
out = ROOT / "Temp" / "gas_python_migration_map_v1.json"
out.parent.mkdir(exist_ok=True)
out.write_text(json.dumps(output, ensure_ascii=False, indent=2), encoding="utf-8")
print("=== GAS_THIN_ADAPTER_POLICY_V1 Phase-2: Extract ===")
print(f"총 forbidden 함수 : {len(MIGRATION_MAP)}")
print(f" MAPPED : {len(mapped)}개 (Python 대응 확인)")
print(f" NEEDS_STUB : {len(stubs)}개 (stub 신규 작성 필요)")
print(f" PARTIAL : {len(partial)}")
print(f"extraction_readiness: {output['summary']['extraction_readiness_pct']}%")
print()
print("── NEEDS_STUB 목록 ──")
for s in stubs:
print(f" {s['gas_file']:45} {s['gas_function']}")
print()
print(f"출력: {out}")
return 0
if __name__ == "__main__":
sys.exit(main())
-198
View File
@@ -1,198 +0,0 @@
"""
GAS_THIN_ADAPTER_POLICY_V1 — Phase 3: thin_adapter annotation
spec/39_gas_thin_adapter_policy.yaml 참조.
각 GAS forbidden 함수의 첫 번째 실행 라인 직전에
// THIN_ADAPTER: <responsibility> delegated to Python — <python_module>:<python_function>
한 줄 주석을 삽입한다. 기능 코드는 변경하지 않는다 (additive-only).
이 주석은 Phase 4 검증 도구가 "이 함수는 이전 대상으로 등록됨"을 확인하는 마커가 된다.
"""
from __future__ import annotations
import json
import re
import sys
from pathlib import Path
ROOT = Path(__file__).parent.parent
GAS_DIR = ROOT / "src" / "gas_adapter_parts"
# GAS forbidden 함수 → Python 대응 매핑 (phase2_extract.py에서 가져온 데이터)
THIN_ADAPTER_MAP: list[dict] = [
{"gas_file": "gdc_01_fetch_fundamentals.gs", "gas_function": "_mergePositionRecord_",
"responsibility": "stop_loss", "python_module": "src/quant_engine/convert_xlsx_to_json.py",
"python_function": "normalize_backdata_harness_payload"},
{"gas_file": "gdc_02_account_satellite.gs", "gas_function": "_addTickerRoute_",
"responsibility": "unknown", "python_module": "src/quant_engine/inject_computed_harness.py",
"python_function": "calc_semiconductor_cluster"},
{"gas_file": "gdf_01_price_metrics.gs", "gas_function": "calcApexTradePlan_",
"responsibility": "sizing/normalize", "python_module": "src/quant_engine/compute_formula_outputs.py",
"python_function": "compute_position_size"},
{"gas_file": "gdf_02_harness_assembly.gs", "gas_function": "assembleHarnessCoreLayers_",
"responsibility": "sizing", "python_module": "src/quant_engine/inject_computed_harness.py",
"python_function": "main"},
{"gas_file": "gdf_02_harness_assembly.gs", "gas_function": "applyApexCashPreservationSuite_",
"responsibility": "decision", "python_module": "src/quant_engine/inject_computed_harness.py",
"python_function": "cash_recovery"},
{"gas_file": "gdf_02_harness_assembly.gs", "gas_function": "applyApexFeedbackSignalSuite_",
"responsibility": "decision", "python_module": "src/quant_engine/compute_formula_outputs.py",
"python_function": "compute_final_decision"},
{"gas_file": "gdf_02_harness_assembly.gs", "gas_function": "applyProposal54BuyBlockLocks_",
"responsibility": "decision", "python_module": "src/quant_engine/inject_computed_harness.py",
"python_function": "main"},
{"gas_file": "gdf_02_harness_assembly.gs", "gas_function": "calcStopBreachAlert_",
"responsibility": "stop_loss", "python_module": "src/quant_engine/inject_computed_harness.py",
"python_function": "calc_stop_breach_alerts"},
{"gas_file": "gdf_02_harness_assembly.gs", "gas_function": "calcAbsoluteRiskStopV1_",
"responsibility": "stop_loss", "python_module": "src/quant_engine/compute_formula_outputs.py",
"python_function": "compute_stop_price_core"},
{"gas_file": "gdf_02_harness_assembly.gs", "gas_function": "calcTpTriggerAlert_",
"responsibility": "take_profit", "python_module": "src/quant_engine/compute_formula_outputs.py",
"python_function": "compute_tp_validity"},
{"gas_file": "gdf_03_portfolio_gates.gs", "gas_function": "calcTpQuantityLadder_",
"responsibility": "sizing/take_profit", "python_module": "src/quant_engine/compute_formula_outputs.py",
"python_function": "compute_position_size"},
{"gas_file": "gdf_03_portfolio_gates.gs", "gas_function": "scoreSellCandidate_",
"responsibility": "decision", "python_module": "src/quant_engine/inject_computed_harness.py",
"python_function": "check_sanity"},
{"gas_file": "gdf_03_portfolio_gates.gs", "gas_function": "calcPrices_",
"responsibility": "stop_loss/take_profit", "python_module": "src/quant_engine/compute_formula_outputs.py",
"python_function": "compute_stop_price_core"},
{"gas_file": "gdf_03_portfolio_gates.gs", "gas_function": "runRouteFlow_",
"responsibility": "stop_loss", "python_module": "tools/gas_thin_adapter_stubs_v1.py",
"python_function": "stub_run_route_flow"},
{"gas_file": "gdf_03_portfolio_gates.gs", "gas_function": "buildOrderBlueprint_",
"responsibility": "stop_loss/take_profit", "python_module": "src/quant_engine/compute_formula_outputs.py",
"python_function": "main (order_blueprint_json)"},
{"gas_file": "gdf_03_portfolio_gates.gs", "gas_function": "calcDistributionRiskRow_",
"responsibility": "risk_score", "python_module": "src/quant_engine/inject_computed_harness.py",
"python_function": "calc_distribution_detector_per_ticker"},
{"gas_file": "gdf_04_execution_quality.gs", "gas_function": "calcProfitPreservationRow_",
"responsibility": "stop_loss", "python_module": "src/quant_engine/inject_computed_harness.py",
"python_function": "trailing_stop_v2"},
{"gas_file": "gdf_04_execution_quality.gs", "gas_function": "calcSmartCashRaiseV2_",
"responsibility": "stop_loss", "python_module": "src/quant_engine/inject_computed_harness.py",
"python_function": "cash_recovery"},
{"gas_file": "gdf_04_execution_quality.gs", "gas_function": "calcApexExecutionHarness_",
"responsibility": "sizing/decision", "python_module": "src/quant_engine/inject_computed_harness.py",
"python_function": "main"},
{"gas_file": "gdf_04_execution_quality.gs", "gas_function": "calcCashPreservationSellEngineV2_",
"responsibility": "sizing", "python_module": "src/quant_engine/inject_computed_harness.py",
"python_function": "cash_recovery"},
{"gas_file": "gdf_04_execution_quality.gs", "gas_function": "calcExportGate_",
"responsibility": "unknown", "python_module": "tools/gas_thin_adapter_stubs_v1.py",
"python_function": "stub_calc_export_gate"},
{"gas_file": "gdf_04_execution_quality.gs", "gas_function": "buildWatchLedger_",
"responsibility": "stop_loss/take_profit", "python_module": "tools/gas_thin_adapter_stubs_v1.py",
"python_function": "stub_build_watch_ledger"},
{"gas_file": "gdf_05_alpha_engines.gs", "gas_function": "buildShadowLedger_",
"responsibility": "stop_loss/sizing/take_profit", "python_module": "src/quant_engine/compute_formula_outputs.py",
"python_function": "check_sell_price_sanity"},
]
MARKER_PREFIX = "// THIN_ADAPTER:"
def _build_annotation(entry: dict) -> str:
return (
f" {MARKER_PREFIX} [{entry['responsibility']}] delegated to Python "
f"{entry['python_module']}:{entry['python_function']}"
)
def _find_function_body_start(lines: list[str], func_name: str) -> int | None:
"""함수 선언 다음 줄 ({이 시작되는 줄 이후 첫 번째 실행 코드 라인 인덱스)를 반환한다."""
# function 선언 패턴: function funcName(... {
pattern = re.compile(
r"^(?:function\s+)" + re.escape(func_name) + r"\s*\("
)
for i, line in enumerate(lines):
if pattern.search(line):
# 선언 라인부터 { 를 찾아 함수 본문 시작 위치를 결정
for j in range(i, min(i + 10, len(lines))):
if "{" in lines[j]:
return j # { 가 있는 줄 인덱스 반환 (다음 줄에 주석 삽입)
return None
def annotate_file(gs_path: Path, entries: list[dict], dry_run: bool = False) -> dict:
original = gs_path.read_text(encoding="utf-8")
lines = original.splitlines(keepends=True)
annotated: list[tuple[int, str]] = [] # (insert-after-line-index, annotation)
already_annotated = 0
not_found = []
for entry in entries:
func_name = entry["gas_function"]
annotation = _build_annotation(entry)
body_start = _find_function_body_start(lines, func_name)
if body_start is None:
not_found.append(func_name)
continue
# 이미 마커가 있으면 건너뜀
next_few = "".join(lines[body_start : body_start + 3])
if MARKER_PREFIX in next_few:
already_annotated += 1
continue
annotated.append((body_start, annotation + "\n"))
if annotated and not dry_run:
# 역순 삽입 (라인 인덱스 밀림 방지)
for insert_after, text in sorted(annotated, reverse=True):
lines.insert(insert_after + 1, text)
gs_path.write_text("".join(lines), encoding="utf-8")
return {
"file": gs_path.name,
"annotated": len(annotated),
"already_annotated": already_annotated,
"not_found": not_found,
"modified": len(annotated) > 0 and not dry_run,
}
def main(dry_run: bool = False) -> int:
# 파일별로 그룹화
from collections import defaultdict
by_file: dict[str, list[dict]] = defaultdict(list)
for entry in THIN_ADAPTER_MAP:
by_file[entry["gas_file"]].append(entry)
total_annotated = 0
results = []
for fname, entries in by_file.items():
gs_path = GAS_DIR / fname
if not gs_path.exists():
print(f" SKIP (not found): {fname}")
continue
result = annotate_file(gs_path, entries, dry_run=dry_run)
results.append(result)
total_annotated += result["annotated"]
status = "DRY" if dry_run else ("MODIFIED" if result["modified"] else "SKIP")
print(f" [{status}] {fname}: +{result['annotated']} 주석, skip={result['already_annotated']}, not_found={result['not_found']}")
print()
print(f"=== Phase 3 thin_adapter annotation {'(dry-run)' if dry_run else '완료'} ===")
print(f"총 THIN_ADAPTER 주석 삽입: {total_annotated} / 23")
# 결과를 Temp에 기록
out = ROOT / "Temp" / "gas_thin_adapter_phase3_result.json"
out.parent.mkdir(exist_ok=True)
out.write_text(json.dumps({
"phase": "thin_adapter",
"dry_run": dry_run,
"total_annotated": total_annotated,
"results": results,
}, ensure_ascii=False, indent=2), encoding="utf-8")
print(f"결과 저장: {out}")
return 0
if __name__ == "__main__":
dry = "--dry-run" in sys.argv
sys.exit(main(dry_run=dry))
@@ -1,90 +0,0 @@
from __future__ import annotations
import argparse
from pathlib import Path
import yaml
ROOT = Path(__file__).resolve().parents[1]
CONTRACT = ROOT / "spec" / "postgresql_history_contract.yaml"
DEFAULT_SQL = ROOT / "Temp" / "postgresql_history_schema_v1.sql"
DEFAULT_JSON = ROOT / "Temp" / "postgresql_history_schema_v1.json"
def _columns(domain: dict) -> list[str]:
cols = domain.get("key_fields") or []
out: list[str] = []
for col in cols:
name = str(col)
if name in {"provenance"}:
continue
out.append(name)
return out
def _table_name(domain_name: str) -> str:
return domain_name
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--contract", default=str(CONTRACT))
ap.add_argument("--sql-out", default=str(DEFAULT_SQL))
ap.add_argument("--json-out", default=str(DEFAULT_JSON))
args = ap.parse_args()
contract_path = Path(args.contract)
data = yaml.safe_load(contract_path.read_text(encoding="utf-8"))
domains = data.get("domains") or {}
sql_lines = [
"-- PostgreSQL history-first schema",
"-- generated from spec/postgresql_history_contract.yaml",
"",
"CREATE SCHEMA IF NOT EXISTS engine_history;",
""
]
table_defs: dict[str, dict[str, object]] = {}
for domain_name, domain in domains.items():
if not isinstance(domain, dict):
continue
cols = _columns(domain)
table_name = _table_name(domain_name)
sql_lines.append(f"CREATE TABLE IF NOT EXISTS engine_history.{table_name} (")
sql_lines.append(" id BIGSERIAL PRIMARY KEY,")
for col in cols:
sql_lines.append(f" {col} TEXT NOT NULL,")
sql_lines.append(" provenance JSONB NOT NULL DEFAULT '{}'::jsonb,")
sql_lines.append(" created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()")
sql_lines.append(");")
sql_lines.append("")
sql_lines.append(f"CREATE INDEX IF NOT EXISTS idx_{table_name}_created_at ON engine_history.{table_name} (created_at DESC);")
sql_lines.append("")
table_defs[table_name] = {"columns": cols, "description": domain.get("description", "")}
sql_text = "\n".join(sql_lines).rstrip() + "\n"
sql_out = Path(args.sql_out)
json_out = Path(args.json_out)
sql_out.parent.mkdir(parents=True, exist_ok=True)
sql_out.write_text(sql_text, encoding="utf-8")
json_out.write_text(
yaml.safe_dump(
{
"formula_id": "POSTGRESQL_HISTORY_SCHEMA_V1",
"gate": "PASS",
"contract_path": str(contract_path.relative_to(ROOT)),
"tables": table_defs,
"sql_out": str(sql_out.relative_to(ROOT)),
},
allow_unicode=True,
sort_keys=False,
),
encoding="utf-8",
)
print(f"POSTGRESQL_HISTORY_SCHEMA_V1 gate=PASS tables={len(table_defs)}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
-179
View File
@@ -1,179 +0,0 @@
#!/usr/bin/env python3
"""
Gitea Actions Run Detail Inspector v2
AGENTS.md 지침 준수: Gitea API를 하네스로 조회. 직접 서버 접근 금지.
formula_id: GITEA_ACTIONS_RUN_DETAIL_V2
토큰 우선순위 (높은 것부터):
1. --token CLI 인자
2. GITEA_TOKEN_BAIK (사용자 지정 별칭)
3. GITEA_TOKEN_TAXBAIK (기존 표준)
4. GITEA_TOKEN (일반 fallback)
5. GITEA_TOKEN_HOME (레거시)
"""
from __future__ import annotations
import argparse
import json
import os
import urllib.error
import urllib.request
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
DEFAULT_BASE_URL = "https://gitea.taxbaik.com"
DEFAULT_OWNER = "kjh2064"
DEFAULT_REPO = "QuantEngineByItz"
def _get_token(cli_token: str = "") -> str:
"""토큰 우선순위: CLI → GITEA_TOKEN_BAIK → GITEA_TOKEN_TAXBAIK → GITEA_TOKEN → GITEA_TOKEN_HOME"""
if cli_token and cli_token.strip():
return cli_token.strip()
for env_key in ("GITEA_TOKEN_BAIK", "GITEA_TOKEN_TAXBAIK", "GITEA_TOKEN", "GITEA_TOKEN_HOME"):
val = os.environ.get(env_key, "").strip()
if val:
return val
return ""
def _request_json(url: str, token: str = "") -> tuple[int, str, Any]:
headers = {
"Accept": "application/json",
"User-Agent": "QuantEngine-Harness/2.0",
}
if token:
headers["Authorization"] = f"token {token}"
req = urllib.request.Request(url, headers=headers, method="GET")
try:
with urllib.request.urlopen(req, timeout=30) as resp:
raw = resp.read().decode("utf-8", errors="replace")
payload = json.loads(raw) if raw else None
return resp.status, resp.reason, payload
except urllib.error.HTTPError as exc:
raw = exc.read().decode("utf-8", errors="replace")
try:
payload = json.loads(raw)
except Exception:
payload = raw
return exc.code, exc.reason or "", payload
def _summary_run(run: dict) -> dict:
return {
"id": run.get("id"),
"run_number": run.get("run_number"),
"display_title": run.get("display_title"),
"workflow_path": run.get("path"),
"event": run.get("event"),
"status": run.get("status"),
"conclusion": run.get("conclusion"),
"head_branch": run.get("head_branch"),
"head_sha": run.get("head_sha"),
"started_at": run.get("started_at"),
"completed_at": run.get("completed_at"),
"actor": (run.get("actor") or {}).get("login"),
}
def main() -> int:
ap = argparse.ArgumentParser(description="Gitea Actions Run Detail Inspector v2")
ap.add_argument("--base-url", default=DEFAULT_BASE_URL)
ap.add_argument("--owner", default=DEFAULT_OWNER)
ap.add_argument("--repo", default=DEFAULT_REPO)
ap.add_argument("--run-id", type=int, default=2545, help="Gitea Actions run ID")
ap.add_argument("--token", default="", help="Gitea API Personal Access Token")
ap.add_argument("--all-runs", action="store_true", help="List recent N runs")
ap.add_argument("--limit", type=int, default=10, help="Number of recent runs to list when --all-runs")
args = ap.parse_args()
token = _get_token(args.token)
base = f"{args.base_url.rstrip('/')}/api/v1/repos/{args.owner}/{args.repo}"
errors: list[str] = []
evidence: dict[str, Any] = {
"owner": args.owner,
"repo": args.repo,
"target_run_id": args.run_id,
"token_provided": bool(token),
}
# 1. Target run details
run_url = f"{base}/actions/runs/{args.run_id}"
s1, _, run_payload = _request_json(run_url, token=token)
evidence["target_run_http_status"] = s1
if s1 != 200:
errors.append(f"Run #{args.run_id} fetch failed: HTTP {s1}")
target_run_summary = None
jobs_data = None
else:
target_run_summary = _summary_run(run_payload)
# 2. Fetch jobs for this run
jobs_url = f"{base}/actions/runs/{args.run_id}/jobs"
s2, _, jobs_payload = _request_json(jobs_url, token=token)
evidence["jobs_http_status"] = s2
if s2 == 200 and isinstance(jobs_payload, dict):
raw_jobs = jobs_payload.get("workflow_jobs") or []
jobs_data = []
for job in raw_jobs:
steps = job.get("steps") or []
failed_steps = [
{
"step_number": st.get("number"),
"name": st.get("name"),
"conclusion": st.get("conclusion"),
"started_at": st.get("started_at"),
"completed_at": st.get("completed_at"),
}
for st in steps
if st.get("conclusion") not in ("success", "skipped", None)
]
jobs_data.append({
"job_id": job.get("id"),
"job_name": job.get("name"),
"status": job.get("status"),
"conclusion": job.get("conclusion"),
"started_at": job.get("started_at"),
"completed_at": job.get("completed_at"),
"runner_name": job.get("runner_name"),
"total_steps": len(steps),
"failed_steps": failed_steps,
})
else:
jobs_data = None
if s2 != 200:
errors.append(f"Jobs fetch failed: HTTP {s2}")
# 3. Recent runs list (optional)
recent_runs = None
if args.all_runs:
runs_url = f"{base}/actions/runs?limit={args.limit}"
s3, _, runs_payload = _request_json(runs_url, token=token)
evidence["runs_list_http_status"] = s3
if s3 == 200 and isinstance(runs_payload, dict):
raw_runs = runs_payload.get("workflow_runs") or []
recent_runs = [_summary_run(r) for r in raw_runs]
else:
errors.append(f"Runs list fetch failed: HTTP {s3}")
result = {
"formula_id": "GITEA_ACTIONS_RUN_DETAIL_V2",
"gate": "PASS" if not errors else "FAIL",
"errors": errors,
"evidence": evidence,
"run_summary": target_run_summary,
"jobs": jobs_data,
"recent_runs": recent_runs,
}
out = ROOT / "Temp" / f"gitea_actions_run_{args.run_id}_detail_v2.json"
out.write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding="utf-8")
print(json.dumps(result, ensure_ascii=False, indent=2))
return 0 if not errors else 1
if __name__ == "__main__":
raise SystemExit(main())
-326
View File
@@ -1,326 +0,0 @@
#!/usr/bin/env python3
"""
WBS-9.6 Phase 1 & 2: LLM Radar Trust Tier + Loading Order
Phase 1: Trust 라벨 시스템 정의
Phase 2: 5-tier 로딩 순서 구현
"""
import yaml
import json
from pathlib import Path
from typing import Dict, List, Tuple
from datetime import datetime
import sys
class LLMRadarPhase12:
"""LLM Radar Trust Tier System (Phase 1 & 2)"""
def __init__(self):
self.trust_tier_spec = Path("spec/llm_radar_trust_tiers_v1.yaml")
self.context_cache = {}
self.load_order_sequence = []
self.results = {
"timestamp": datetime.now().isoformat(),
"phase_1": {},
"phase_2": {},
"summary": {}
}
def load_trust_tier_spec(self) -> Dict:
"""Trust tier 스펙 로드"""
if not self.trust_tier_spec.exists():
print(f"[ERROR] Trust tier spec not found: {self.trust_tier_spec}")
return {}
with open(self.trust_tier_spec, encoding='utf-8') as f:
spec = yaml.safe_load(f)
return spec
def phase_1_build_trust_labels(self) -> Dict:
"""Phase 1: 모든 문서에 trust 라벨 지정"""
spec = self.load_trust_tier_spec()
if not spec:
return {"status": "FAILED", "error": "Spec not loaded"}
# Trust tier 정보 추출
trust_tiers = spec.get("trust_tier_system", {})
doc_classification = spec.get("document_classification", {})
trust_labels = {}
# 각 분류에서 문서 추출
for category, config in doc_classification.items():
tier = config.get("tier")
documents = config.get("documents", [])
if tier not in trust_tiers:
print(f"[WARNING] Unknown tier: {tier} for category {category}")
continue
tier_info = trust_tiers[tier]
trust_level = tier_info.get("trust_level", 0)
for doc in documents:
trust_labels[doc] = {
"tier": tier,
"trust_level": trust_level,
"category": category,
"priority": tier_info.get("loading_priority", 0)
}
self.results["phase_1"]["total_documents"] = len(trust_labels)
self.results["phase_1"]["tier_distribution"] = self._count_by_tier(trust_labels)
self.results["phase_1"]["trust_labels"] = trust_labels
print(f"[Phase 1] Trust labels created for {len(trust_labels)} documents")
return {
"status": "SUCCESS",
"documents_labeled": len(trust_labels),
"tiers": list(set(label["tier"] for label in trust_labels.values()))
}
def _count_by_tier(self, labels: Dict) -> Dict:
"""Tier별 문서 개수 계산"""
counts = {}
for label in labels.values():
tier = label["tier"]
counts[tier] = counts.get(tier, 0) + 1
return counts
def phase_2_build_loading_order(self) -> Dict:
"""Phase 2: 5-tier 로딩 순서 정의"""
spec = self.load_trust_tier_spec()
if not spec:
return {"status": "FAILED", "error": "Spec not loaded"}
loading_strategy = spec.get("loading_strategy", {})
trust_tiers = spec.get("trust_tier_system", {})
# 로딩 순서 정의
load_sequence = []
# Phase 1: Canonical (trust_level=100)
canonical_docs = [
"spec/12_field_dictionary.yaml",
"spec/14_raw_workbook_mapping.yaml",
"spec/11_market_regime.yaml"
]
load_sequence.append({
"phase": 1,
"name": "Canonical References",
"tier": "canonical",
"trust_threshold": 100,
"documents": canonical_docs,
"action": "Always load"
})
# Phase 2: Adapter (trust_level=80)
adapter_docs = [
"spec/09_decision_flow.yaml",
"spec/13_formula_registry.yaml"
]
load_sequence.append({
"phase": 2,
"name": "Adapter Bridges",
"tier": "adapter",
"trust_threshold": 80,
"documents": adapter_docs,
"action": "Load if no conflict with canonical"
})
# Phase 3: Reference (trust_level=60)
reference_docs = [
"docs/WBS_9_1_F14_MIGRATION_COMPLETE_2026_06_22.md",
"docs/WBS_9_4_INCIDENT_RESPONSE_PLAYBOOK_2026_06_22.md"
]
load_sequence.append({
"phase": 3,
"name": "Reference Context",
"tier": "reference",
"trust_threshold": 60,
"documents": reference_docs,
"action": "Load as secondary context"
})
# Phase 4: Search-Based
load_sequence.append({
"phase": 4,
"name": "Search-Based Context",
"tier": "search",
"trust_threshold": 50,
"documents": [], # Dynamic - determined by query
"action": "Retrieve by relevance + tier"
})
# Phase 5: Fallback
load_sequence.append({
"phase": 5,
"name": "LLM Knowledge Fallback",
"tier": "fallback",
"trust_threshold": 0,
"documents": [], # LLM internal
"action": "Use LLM training data only"
})
self.load_order_sequence = load_sequence
self.results["phase_2"]["loading_phases"] = load_sequence
self.results["phase_2"]["total_phases"] = len(load_sequence)
print(f"[Phase 2] Loading order defined for {len(load_sequence)} phases")
return {
"status": "SUCCESS",
"phases": len(load_sequence),
"total_documents_to_load": sum(len(p.get("documents", [])) for p in load_sequence)
}
def generate_llm_context_builder_pseudo_code(self) -> str:
"""LLM context builder를 위한 의사 코드 생성"""
pseudo_code = """
// LLM Radar Phase 1 & 2 Context Builder
function buildContextWithTrustTiers(query, userContext) {
context = []
loadedDocs = set()
// Phase 1: Load Canonical (100% trust)
for (doc in canonicalDocuments) {
if (doc.exists()) {
content = loadDocument(doc)
context.append({
tier: "canonical",
trust_level: 100,
content: content,
loaded_at: phase_1
})
loadedDocs.add(doc)
}
}
// Phase 2: Load Adapter (80% trust)
for (doc in adapterDocuments) {
if (doc.exists() && doc not in loadedDocs) {
content = loadDocument(doc)
if (!conflictsWithCanonical(content, context)) {
context.append({
tier: "adapter",
trust_level: 80,
content: content,
loaded_at: phase_2
})
loadedDocs.add(doc)
}
}
}
// Phase 3: Load Reference (60% trust)
for (doc in referenceDocuments) {
if (doc.exists() && doc not in loadedDocs) {
content = loadDocument(doc)
context.append({
tier: "reference",
trust_level: 60,
content: content,
loaded_at: phase_3
})
loadedDocs.add(doc)
}
}
// Phase 4: Search-Based (50% trust)
relevantDocs = searchDocuments(query, threshold=50)
for (doc in relevantDocs) {
if (doc not in loadedDocs) {
content = loadDocument(doc)
context.append({
tier: "search",
trust_level: 50,
content: content,
relevance_score: doc.score,
loaded_at: phase_4
})
loadedDocs.add(doc)
}
}
// Phase 5: Fallback (0% trust - use LLM knowledge)
if (context.isEmpty()) {
context.append({
tier: "fallback",
trust_level: 0,
source: "llm_training_data",
loaded_at: phase_5
})
}
return context
}
// Conflict detection
function conflictsWithCanonical(adapterDoc, canonicalContext) {
for (canonical in canonicalContext) {
if (contradicts(adapterDoc, canonical)) {
logWarning("Adapter contradicts canonical")
return true
}
}
return false
}
"""
return pseudo_code
def generate_report(self) -> Dict:
"""전체 리포트 생성"""
print("\n" + "="*80)
print("WBS-9.6 Phase 1 & 2: LLM Radar Trust Tier System")
print("="*80)
# Phase 1
phase1_result = self.phase_1_build_trust_labels()
print(f"\n[Phase 1 Result] {phase1_result}")
# Phase 2
phase2_result = self.phase_2_build_loading_order()
print(f"[Phase 2 Result] {phase2_result}")
# Summary
self.results["summary"] = {
"phase_1_status": phase1_result.get("status"),
"phase_2_status": phase2_result.get("status"),
"total_trust_labels": self.results["phase_1"].get("total_documents", 0),
"loading_phases_defined": self.results["phase_2"].get("total_phases", 0),
"next_phase": "Phase 3: Dependency Graph",
"target_completion": "2026-08-15",
"error_rate_target": "50% reduction from baseline"
}
print("\n[Summary]")
print(f" Phase 1 (Trust Labels): {self.results['phase_1'].get('total_documents', 0)} docs labeled")
print(f" Phase 2 (Load Order): {self.results['phase_2'].get('total_phases', 0)} phases defined")
print(f" Tiers: {', '.join(self.results['phase_1'].get('tier_distribution', {}).keys())}")
return self.results
def save_report(self, output_file: str = None) -> None:
"""리포트 저장"""
if not output_file:
output_file = f"Temp/llm_radar_phase12_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
Path(output_file).parent.mkdir(parents=True, exist_ok=True)
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(self.results, f, indent=2, ensure_ascii=False)
print(f"\n[Save] Report saved: {output_file}")
if __name__ == "__main__":
radar = LLMRadarPhase12()
radar.generate_report()
radar.save_report()
# 의사 코드 출력
print("\n" + "="*80)
print("Pseudo Code: LLM Context Builder with Trust Tiers")
print("="*80)
print(radar.generate_llm_context_builder_pseudo_code())
@@ -1,96 +0,0 @@
from __future__ import annotations
from pathlib import Path
import yaml
ROOT = Path(__file__).resolve().parents[1]
def main() -> int:
field_dict_path = ROOT / "spec" / "12_field_dictionary.yaml"
if not field_dict_path.exists():
print("Field dictionary not found.")
return 1
field_data = yaml.safe_load(field_dict_path.read_text(encoding="utf-8")) or {}
fields = field_data.get("field_dictionary", {}).get("fields", {})
# Identify all collisions
alias_to_canonicals: dict[str, list[str]] = {}
for fid, info in fields.items():
if not info:
continue
canonical_name = info.get("canonical_name", fid)
aliases = info.get("aliases", [])
all_names = [canonical_name] + aliases
for name in all_names:
alias_to_canonicals.setdefault(name, []).append(fid)
collisions = {name: sorted(list(set(clist))) for name, clist in alias_to_canonicals.items() if len(set(clist)) > 1}
if not collisions:
print("No collisions to resolve.")
return 0
print(f"Resolving {len(collisions)} alias collisions...")
# We iterate and apply resolution rules
for name, clist in collisions.items():
# Rule 1: If name matches one of the canonical names exactly, keep it only there
exact_match = None
for fid in clist:
if fields[fid].get("canonical_name") == name:
exact_match = fid
break
if exact_match is not None:
# Remove from all other fields' aliases
for fid in clist:
if fid != exact_match:
aliases = fields[fid].get("aliases", [])
if name in aliases:
aliases.remove(name)
fields[fid]["aliases"] = aliases
continue
# Rule 2: Case-insensitive or close matching
# Assign to the field whose canonical name is closest to lowercase of the name
target_fid = None
lower_name = name.lower()
# Check if lowercase maps to a canonical name
for fid in clist:
if fields[fid].get("canonical_name") == lower_name:
target_fid = fid
break
# Suffix/prefix matching heuristic
if target_fid is None:
for fid in clist:
cname = fields[fid].get("canonical_name", "")
if cname in lower_name or lower_name in cname:
target_fid = fid
break
# Fallback: just pick the first one
if target_fid is None:
target_fid = clist[0]
# Keep alias in target_fid, remove from others
for fid in clist:
if fid != target_fid:
aliases = fields[fid].get("aliases", [])
if name in aliases:
aliases.remove(name)
fields[fid]["aliases"] = aliases
# Save cleaned fields back
field_data["field_dictionary"]["fields"] = fields
field_dict_path.write_text(yaml.safe_dump(field_data, sort_keys=False, allow_unicode=True), encoding="utf-8")
print("Resolved field alias collisions successfully.")
return 0
if __name__ == "__main__":
raise SystemExit(main())
-250
View File
@@ -1,250 +0,0 @@
#!/usr/bin/env python3
"""
WBS-8.1 모니터링 준비
T+20 레저 30건 목표 달성을 위한 모니터링 시스템 설정
"""
from datetime import datetime, timedelta
from pathlib import Path
import json
class WBS81MonitoringSetup:
"""WBS-8.1 모니터링 준비"""
def __init__(self):
self.today = datetime.now().date()
self.target_date = datetime(2026, 7, 15).date()
self.days_until_target = (self.target_date - self.today).days
self.results = {
"timestamp": datetime.now().isoformat(),
"monitoring_setup": {}
}
def calculate_milestones(self) -> dict:
"""마일스톤 계산"""
milestones = {
"phase": "WBS-8.1: T+20 레저 30건",
"target_date": str(self.target_date),
"days_remaining": self.days_until_target,
"current_progress": 0,
"target_trades": 30,
"timeline": {
"week_1": {
"date": str(self.today + timedelta(days=7)),
"target_accumulation": 4,
"note": "매일 ~0.5건 수집 추정"
},
"week_2": {
"date": str(self.today + timedelta(days=14)),
"target_accumulation": 7,
"note": "누적 진행률 23%"
},
"week_3": {
"date": str(self.today + timedelta(days=21)),
"target_accumulation": 11,
"note": "누적 진행률 37%"
},
"week_4": {
"date": str(self.today + timedelta(days=28)),
"target_accumulation": 15,
"note": "누적 진행률 50%, 중간점"
},
"target": {
"date": str(self.target_date),
"target_accumulation": 30,
"note": "최종 목표 달성"
}
}
}
return milestones
def define_monitoring_metrics(self) -> dict:
"""모니터링 메트릭 정의"""
metrics = {
"daily_collection": {
"metric": "entries_added_per_day",
"target": 0.5,
"unit": "entries",
"tracking": "kis_data_collection.db row count"
},
"t20_milestone": {
"metric": "trades_reaching_t20_date",
"target": 30,
"unit": "trades",
"tracking": "performance.t20_milestone IS NOT NULL",
"formula": "entry_date + 20 days <= today"
},
"data_quality": {
"metric": "completeness_score",
"target": 95,
"unit": "percent",
"tracking": "NULL values in critical columns"
},
"accuracy": {
"metric": "price_match_vs_kis_api",
"target": 100,
"unit": "percent",
"tracking": "kis_data_collection.close_price vs live KIS"
}
}
return metrics
def define_monitoring_tools(self) -> list:
"""모니터링 도구 정의"""
tools = [
{
"name": "auto_collect_t20_ledger_v1.py",
"frequency": "daily",
"schedule": "00:00 UTC",
"purpose": "T+20 경과 거래 자동 감지 및 기록"
},
{
"name": "monitor_wbs_progress_v1.py",
"frequency": "hourly",
"schedule": "*/1 * * * *",
"purpose": "WBS-8 진행률 모니터링"
},
{
"name": "validate_data_collection_v1.py",
"frequency": "daily",
"schedule": "12:00 UTC",
"purpose": "데이터 무결성 검증"
},
{
"name": "benchmark_snapshot_admin_performance_v1.py",
"frequency": "weekly",
"schedule": "sun 00:00 UTC",
"purpose": "성능 벤치마크 (WBS-9.2와 통합)"
}
]
return tools
def define_risk_factors(self) -> list:
"""리스크 팩터"""
risks = [
{
"risk": "낮은 수집 속도",
"current_rate": 0.5,
"required_rate": 0.5,
"threshold": "< 0.3 entries/day",
"mitigation": "KIS API 대역폭 확대 또는 추가 계정 활용"
},
{
"risk": "API 다운타임",
"impact": "데이터 수집 중단",
"mitigation": "Fallback to cached data (CACHED_ONLY mode)",
"recovery_time": "< 2 minutes"
},
{
"risk": "데이터 품질 저하",
"impact": "T+20 계산 부정확",
"mitigation": "NULL policy enforcement + CI gates",
"detection": "daily validation"
},
{
"risk": "거래 정체",
"impact": "30건 목표 미달성",
"threshold": "< 4 entries/week",
"mitigation": "거래 전략 검토 및 조정"
}
]
return risks
def setup_alerting(self) -> dict:
"""알림 규칙"""
alerts = {
"critical": {
"daily_collection_failed": {
"condition": "entries_added_per_day = 0",
"action": "Immediate: Check KIS API status + logs",
"escalation": "1 hour grace period, then escalate"
},
"no_t20_records_for_7_days": {
"condition": "No new t20_milestone for 7 days",
"action": "Review trade entry date distribution",
"escalation": "Check if T+20 threshold calculation is correct"
}
},
"warning": {
"collection_below_target": {
"condition": "entries_added_per_day < 0.3",
"action": "Warning: Below target collection rate",
"threshold": 3,
"unit": "consecutive days"
},
"progress_behind_schedule": {
"condition": "cumulative < (days_elapsed / total_days) * 30",
"action": "Warning: Progress behind linear schedule",
"recovery_plan": "Increase daily collection rate"
}
}
}
return alerts
def generate_report(self) -> dict:
"""모니터링 설정 리포트"""
print("\n" + "="*80)
print("WBS-8.1 모니터링 시스템 설정")
print("="*80)
# 마일스톤
milestones = self.calculate_milestones()
print(f"\n[목표]")
print(f" Phase: {milestones['phase']}")
print(f" Target Date: {milestones['target_date']}")
print(f" Days Remaining: {milestones['days_remaining']}")
print(f" Target Trades: {milestones['target_trades']} entries")
# 메트릭
metrics = self.define_monitoring_metrics()
print(f"\n[메트릭]")
for name, metric in metrics.items():
print(f" {name}:")
print(f" └─ Target: {metric['target']} {metric['unit']}")
print(f" └─ Tracking: {metric['tracking']}")
# 도구
tools = self.define_monitoring_tools()
print(f"\n[모니터링 도구]")
for tool in tools:
print(f" {tool['name']}")
print(f" └─ Schedule: {tool['frequency']} ({tool['schedule']})")
# 리스크
risks = self.define_risk_factors()
print(f"\n[리스크 팩터]")
for risk in risks:
print(f" {risk['risk']}")
print(f" └─ Mitigation: {risk.get('mitigation', 'TBD')}")
# 결과 저장
self.results["monitoring_setup"] = {
"milestones": milestones,
"metrics": metrics,
"tools": tools,
"risks": risks,
"alerts": self.setup_alerting()
}
return self.results
if __name__ == "__main__":
setup = WBS81MonitoringSetup()
setup.generate_report()
# 설정 저장
config_file = Path("Temp/wbs81_monitoring_config.json")
config_file.parent.mkdir(parents=True, exist_ok=True)
with open(config_file, 'w', encoding='utf-8') as f:
import json
json.dump(setup.results, f, indent=2, ensure_ascii=False)
print(f"\n[저장] 모니터링 설정: {config_file}")
print("[준비 완료] 2026-07-15 T+20 레저 30건 목표 달성을 위한 모니터링 시스템 준비됨")
-135
View File
@@ -1,135 +0,0 @@
#!/usr/bin/env python3
"""
WBS-8.7: spec-코드 동기화 확장 (has_code_implementation 태그 자동화)
"""
import yaml
from pathlib import Path
from typing import Dict, List
SPEC_DIR = Path("spec")
def get_code_reference_patterns() -> Dict[str, str]:
"""각 spec 파일이 참조하는 코드 패턴"""
return {
# Formula references
"formula_registry": "src/quant_engine",
"decision_flow": "tools/build_final_execution_decision",
"routing": "tools/validate_order_grammar",
"market_regime": "spec/11_market_regime",
"field_dictionary": "spec/14_raw_workbook_mapping",
# Performance/Data
"performance_contract": "tools/benchmark_snapshot_admin_performance",
"data_gaps": "tools/auto_fill",
"low_capability_llm": "tools/build_final_context_for_llm",
# Gas/KIS
"gas_adapter": "tools/validate_gitea_secrets_contract",
"kis": "tools/validate_gitea_secrets_contract",
# Storage
"release_dag": ".gitea/workflows",
# Strategy
"anti_late_entry": "tools/validate_anti_late_entry_gate",
"pre_distribution": "tools/validate_pre_distribution_early_warning",
"smart_money": "tools/validate_smart_money_liquidity_gate",
"cash_floor": "tools/validate_cash_floor_policy",
}
def should_have_code_reference(filename: str) -> bool:
"""파일이 코드 참조를 가져야 하는지 판단"""
patterns = get_code_reference_patterns()
filename_lower = filename.lower()
for pattern in patterns.keys():
if pattern in filename_lower:
return True
return False
def tag_spec_file(file_path: Path) -> bool:
"""spec 파일에 has_code_implementation 태그 추가"""
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = yaml.safe_load(f)
if content is None:
content = {}
# meta 섹션이 없으면 생성
if 'meta' not in content:
content['meta'] = {}
meta = content['meta']
# 이미 태그되어 있으면 스킵
if 'has_code_implementation' in meta:
return None # 이미 처리됨
# 코드 참조 여부 판단
has_reference = should_have_code_reference(file_path.stem)
# 태그 추가
if has_reference:
meta['has_code_implementation'] = True
meta['code_path'] = "tools/ or spec/ or .gitea/workflows"
else:
meta['has_code_implementation'] = False
# 파일 저장
with open(file_path, 'w', encoding='utf-8') as f:
yaml.dump(content, f, allow_unicode=True, default_flow_style=False)
return has_reference
except Exception as e:
print(f"Error processing {file_path}: {e}")
return None
def main():
"""메인 함수"""
print("WBS-8.7: spec-코드 동기화 확장")
print("="*80)
# 모든 spec 파일 찾기
spec_files = sorted(SPEC_DIR.glob("*.yaml"))
tagged_count = 0
with_code = 0
without_code = 0
already_tagged = 0
for spec_file in spec_files:
result = tag_spec_file(spec_file)
if result is None:
already_tagged += 1
elif result:
with_code += 1
tagged_count += 1
print(f"[+] {spec_file.name}: has_code_implementation=true")
else:
without_code += 1
tagged_count += 1
print(f"[-] {spec_file.name}: has_code_implementation=false")
print("\n" + "="*80)
print(f"[결과]")
print(f" 새로 태그된 파일: {tagged_count}")
print(f" 코드 참조 있음: {with_code}")
print(f" 코드 참조 없음: {without_code}")
print(f" 이미 태그됨: {already_tagged}")
print(f" 총 파일 수: {len(spec_files)}")
coverage = ((tagged_count + already_tagged) / len(spec_files)) * 100
print(f"\n[진행률] {coverage:.1f}% 완료")
if coverage >= 90:
print("[OK] WBS-8.7 목표 달성 (>90%)")
else:
print(f"[진행 중] 목표까지 {90-coverage:.1f}% 남음")
if __name__ == "__main__":
main()
-63
View File
@@ -1,63 +0,0 @@
#!/usr/bin/env python3
from __future__ import annotations
import json
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
if str(ROOT) not in sys.path:
sys.path.insert(0, str(ROOT))
def main() -> int:
errors: list[str] = []
spec_path = ROOT / "spec" / "02_data_contract.yaml"
server_path = ROOT / "src" / "quant_engine" / "snapshot_admin_server_v1.py"
collector_path = ROOT / "src" / "quant_engine" / "kis_data_collection_v1.py"
# Check if files exist before reading
spec_exists = spec_path.exists()
server_exists = server_path.exists()
collector_exists = collector_path.exists()
spec_text = spec_path.read_text(encoding="utf-8") if spec_exists else ""
server_text = server_path.read_text(encoding="utf-8") if server_exists else ""
collector_text = collector_path.read_text(encoding="utf-8") if collector_exists else ""
if not spec_exists:
print(f"Warning: {spec_path} not found, skipping validation")
if not server_exists:
print(f"Warning: {server_path} not found, skipping server validation")
if not collector_exists:
print(f"Warning: {collector_path} not found, skipping collector validation")
# Only check markers in files that exist
marker_checks = {
"spec/db-first": (spec_text, "DB 기반 수집 결과를 바탕으로 생성된 파생 보고서 증빙", spec_exists),
"spec/db-first-xlsx": (spec_text, "xlsx는 HTS 잔고·거래내역 판독 또는 DB 반영 이전의 보조 감사 소스", spec_exists),
"server/json-role": (server_text, "derived_report_evidence", server_exists),
"server/json-evidence": (server_text, "Derived JSON Evidence Preview", server_exists),
"server/collection-trend": (server_text, "collectionTrendChart", server_exists),
"collector/db-canonical": (collector_text, "SQLite as the canonical persistence layer", collector_exists),
}
for name, (haystack, marker, should_check) in marker_checks.items():
if should_check and marker not in haystack:
errors.append(f"missing marker: {name}")
# If no files exist, pass with warning
if not (spec_exists or server_exists or collector_exists):
print(json.dumps({"gate": "PASS", "errors": [], "note": "Legacy Python files not found, skipping DB pipeline validation"}, ensure_ascii=False, indent=2))
return 0
if errors:
print(json.dumps({"gate": "FAIL", "errors": errors}, ensure_ascii=False, indent=2))
return 1
print(json.dumps({"gate": "PASS", "errors": []}, ensure_ascii=False, indent=2))
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -1,130 +0,0 @@
from __future__ import annotations
import argparse
import json
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
DEFAULT_PATH = ROOT / "Temp" / "execution_readiness_matrix_v1.json"
REQUIRED_AXES = {
"data_integrity",
"routing_serving",
"serving_output_lock",
"decision_governance",
"final_judgment_lock",
"fundamental_basis",
"horizon_policy",
"smart_money_liquidity",
"cash_recovery_execution",
"execution_availability",
"performance_readiness",
"report_consistency",
}
def _load(path: Path) -> dict[str, Any]:
if not path.exists():
return {}
try:
obj = json.loads(path.read_text(encoding="utf-8"))
except Exception:
return {}
return obj if isinstance(obj, dict) else {}
def main() -> int:
ap = argparse.ArgumentParser(description="Validate EXECUTION_READINESS_MATRIX_V1.")
ap.add_argument("--json", default=str(DEFAULT_PATH))
args = ap.parse_args()
path = Path(args.json)
if not path.is_absolute():
path = ROOT / path
payload = _load(path)
errors: list[str] = []
if str(payload.get("formula_id") or "") != "EXECUTION_READINESS_MATRIX_V1":
errors.append("formula_id mismatch")
gate = str(payload.get("gate") or "")
if gate not in {"PASS_100", "WATCH_PENDING_SAMPLE", "BLOCK_EXECUTION"}:
errors.append(f"gate={gate}")
axes = payload.get("axes")
if not isinstance(axes, list) or not axes:
errors.append("axes must be non-empty list")
axes = []
seen: set[str] = set()
scores: list[float] = []
block_count = 0
for idx, row in enumerate(axes):
if not isinstance(row, dict):
errors.append(f"axes[{idx}] must be object")
continue
axis = str(row.get("axis") or "")
seen.add(axis)
score = row.get("score_0_100")
if not isinstance(score, (int, float)):
errors.append(f"axes[{idx}].score_0_100 must be numeric")
continue
score_f = float(score)
if score_f < 0 or score_f > 100:
errors.append(f"axes[{idx}].score_0_100 out of range")
scores.append(score_f)
row_gate = str(row.get("gate") or "")
if row_gate not in {"PASS_100", "WATCH", "BLOCK"}:
errors.append(f"axes[{idx}].gate={row_gate}")
if row_gate == "BLOCK":
block_count += 1
if row_gate != "PASS_100" and not str(row.get("blocking_reason") or "").strip():
errors.append(f"axes[{idx}].blocking_reason missing")
if not str(row.get("source_json") or "").strip():
errors.append(f"axes[{idx}].source_json missing")
if not str(row.get("formula_id") or "").strip():
errors.append(f"axes[{idx}].formula_id missing")
missing_axes = sorted(REQUIRED_AXES - seen)
if missing_axes:
errors.append(f"missing_axes={missing_axes}")
if scores:
expected_min = round(min(scores), 2)
actual_min = payload.get("min_axis_score")
if not isinstance(actual_min, (int, float)) or round(float(actual_min), 2) != expected_min:
errors.append(f"min_axis_score mismatch expected={expected_min} actual={actual_min}")
expected_avg = round(sum(scores) / len(scores), 2)
actual_avg = payload.get("average_axis_score")
if not isinstance(actual_avg, (int, float)) or round(float(actual_avg), 2) != expected_avg:
errors.append(f"average_axis_score mismatch expected={expected_avg} actual={actual_avg}")
actual_blocks = payload.get("hard_block_count")
if not isinstance(actual_blocks, int) or actual_blocks != block_count:
errors.append(f"hard_block_count mismatch expected={block_count} actual={actual_blocks}")
if gate == "PASS_100":
if block_count != 0:
errors.append("PASS_100 cannot have BLOCK axes")
if scores and min(scores) < 100.0:
errors.append("PASS_100 requires all axes score_0_100=100")
if gate == "BLOCK_EXECUTION" and block_count == 0:
errors.append("BLOCK_EXECUTION requires at least one BLOCK axis")
if errors:
print("EXECUTION_READINESS_MATRIX_V1_FAIL")
for err in errors:
print(f" {err}")
return 1
print("EXECUTION_READINESS_MATRIX_V1_OK")
print(f" gate: {gate}")
print(f" min_axis_score: {float(payload.get('min_axis_score')):.2f}")
print(f" hard_block_count: {block_count}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
-180
View File
@@ -1,180 +0,0 @@
from __future__ import annotations
import argparse
import json
from pathlib import Path
from typing import Any
from operational_report_contract import REPORT_SECTION_ORDER
ROOT = Path(__file__).resolve().parents[1]
DEFAULT_PATH = ROOT / "Temp" / "operational_report.json"
SCHEMA_PATH = ROOT / "schemas" / "operational_report.schema.json"
REQUIRED_TOP_LEVEL_KEYS = {"schema_version", "source_json", "section_count", "sections", "summary"}
REQUIRED_SECTIONS = [
"today_decision_summary_card",
"routing_serving_trace",
"QEH_AUDIT_BLOCK",
"investment_quality_headline",
"final_judgment_table",
"operational_truth_score",
"execution_readiness_matrix",
"pass_100_criteria",
"final_execution_decision",
"concise_hts_input_sheet",
"reference_price_ledger",
"watch_breakout_gate",
"anti_whipsaw_reentry_gate",
]
def safe_print(message: str) -> None:
try:
print(message)
except UnicodeEncodeError:
fallback = message.encode("cp949", errors="backslashreplace").decode("cp949", errors="ignore")
print(fallback)
def load_json(path: Path) -> dict[str, Any]:
payload = json.loads(path.read_text(encoding="utf-8"))
return payload if isinstance(payload, dict) else {}
def main() -> int:
parser = argparse.ArgumentParser(description="Validate operational report JSON structure.")
parser.add_argument("--json", default=str(DEFAULT_PATH))
args = parser.parse_args()
path = Path(args.json)
if not path.is_absolute():
path = ROOT / path
if not path.exists():
print("OPERATIONAL_REPORT_JSON_FAIL: missing file")
return 1
payload = load_json(path)
errors: list[str] = []
if not SCHEMA_PATH.exists():
print("OPERATIONAL_REPORT_JSON_FAIL: missing schema file")
return 1
schema = load_json(SCHEMA_PATH)
if not isinstance(schema, dict):
print("OPERATIONAL_REPORT_JSON_FAIL: invalid schema file")
return 1
if payload.get("schema_version") != schema.get("properties", {}).get("schema_version", {}).get("const"):
errors.append("schema_version const mismatch")
if payload.get("source_json") != schema.get("properties", {}).get("source_json", {}).get("const"):
errors.append("source_json const mismatch")
sections = payload.get("sections")
if not isinstance(sections, list):
errors.append("sections: must be array")
sections = []
else:
for idx, section in enumerate(sections):
if not isinstance(section, dict):
errors.append(f"sections[{idx}]: must be object")
continue
if not isinstance(section.get("name"), str) or not section.get("name").strip():
errors.append(f"sections[{idx}]: missing name")
if not isinstance(section.get("title"), str) or not section.get("title").strip():
errors.append(f"sections[{idx}]: missing title")
if not isinstance(section.get("markdown"), str) or not section.get("markdown").startswith(f"## {section.get('title')}"):
errors.append(f"sections[{idx}]: markdown/title mismatch")
missing_top = REQUIRED_TOP_LEVEL_KEYS - set(payload)
if missing_top:
errors.append(f"missing_top_level_keys={sorted(missing_top)}")
sections = payload.get("sections")
if not isinstance(sections, list) or not sections:
errors.append("sections: must be a non-empty list")
sections = []
if payload.get("section_count") != len(sections):
errors.append(f"section_count mismatch: stored={payload.get('section_count')} actual={len(sections)}")
names: list[str] = []
for idx, section in enumerate(sections):
if not isinstance(section, dict):
errors.append(f"sections[{idx}]: must be object")
continue
name = str(section.get("name") or "").strip()
title = str(section.get("title") or "").strip()
markdown = str(section.get("markdown") or "").strip()
if not name:
errors.append(f"sections[{idx}]: missing name")
if not title:
errors.append(f"sections[{idx}]: missing title")
if not markdown.startswith(f"## {title}"):
errors.append(f"sections[{idx}]: markdown/title mismatch")
names.append(name)
if len(names) != len(set(names)):
errors.append("sections: duplicate section names detected")
if names:
missing_canonical = [name for name in REPORT_SECTION_ORDER if name not in names]
if missing_canonical:
errors.append(f"sections: missing canonical sections={missing_canonical[:5]}")
for required in REQUIRED_SECTIONS:
if required not in names:
errors.append(f"missing_section={required}")
routing_idx = names.index("routing_serving_trace") if "routing_serving_trace" in names else -1
qeh_idx = names.index("QEH_AUDIT_BLOCK") if "QEH_AUDIT_BLOCK" in names else -1
if routing_idx < 0 or qeh_idx < 0 or routing_idx > qeh_idx:
errors.append("section_order_invalid:routing_serving_trace_before_QEH_AUDIT_BLOCK")
summary = payload.get("summary")
if not isinstance(summary, dict):
errors.append("summary: must be object")
else:
if not isinstance(summary.get("found_settlement"), bool):
errors.append("summary.found_settlement must be bool")
if not isinstance(summary.get("found_heat"), bool):
errors.append("summary.found_heat must be bool")
if not isinstance(summary.get("found_routing"), bool):
errors.append("summary.found_routing must be bool")
if not isinstance(summary.get("found_qeh"), bool):
errors.append("summary.found_qeh must be bool")
if not isinstance(summary.get("found_concise_hts_input_sheet"), bool):
errors.append("summary.found_concise_hts_input_sheet must be bool")
if not isinstance(summary.get("found_reference_price_ledger"), bool):
errors.append("summary.found_reference_price_ledger must be bool")
if not isinstance(summary.get("canonical_order_ok"), bool):
errors.append("summary.canonical_order_ok must be bool")
if summary.get("json_validation_status") is not None and not isinstance(summary.get("json_validation_status"), str):
errors.append("summary.json_validation_status must be string or null")
if summary.get("found_outcome_eval_window") is not None and not isinstance(summary.get("found_outcome_eval_window"), bool):
errors.append("summary.found_outcome_eval_window must be bool or null")
if summary.get("outcome_eval_gate") is not None and not isinstance(summary.get("outcome_eval_gate"), str):
errors.append("summary.outcome_eval_gate must be string or null")
if summary.get("outcome_root_cause_flags") is not None and not isinstance(summary.get("outcome_root_cause_flags"), list):
errors.append("summary.outcome_root_cause_flags must be list or null")
if summary.get("found_algorithm_guidance_proof") is not None and not isinstance(summary.get("found_algorithm_guidance_proof"), bool):
errors.append("summary.found_algorithm_guidance_proof must be bool or null")
if summary.get("algorithm_guidance_proof_score") is not None and not isinstance(summary.get("algorithm_guidance_proof_score"), (int, float)):
errors.append("summary.algorithm_guidance_proof_score must be number or null")
if summary.get("algorithm_guidance_proof_gate") is not None and not isinstance(summary.get("algorithm_guidance_proof_gate"), str):
errors.append("summary.algorithm_guidance_proof_gate must be string or null")
if errors:
for error in errors:
safe_print(error)
safe_print("OPERATIONAL_REPORT_JSON_FAIL")
return 1
safe_print("OPERATIONAL_REPORT_JSON_OK")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -1,69 +0,0 @@
"""validate_pass_100_authority_lock_v1.py — P0-002 수용 검증기
active PASS_100 산출물이 정확히 1개이고 v3가 그것임을 검증한다.
legacy v1/v2에는 legacy_reference_only 마킹이 있어야 한다.
"""
from __future__ import annotations
import json
from pathlib import Path
from v7_hardening_common import ROOT, TEMP, load_json, save_json
DEFAULT_OUT = TEMP / "pass_100_authority_lock_v1.json"
def main() -> int:
v3 = load_json(TEMP / "pass_100_criteria_v3.json")
v2 = load_json(TEMP / "pass_100_criteria_v2.json")
v1 = load_json(TEMP / "pass_100_criteria_v1.json")
errors: list[str] = []
# v3 must be active
if not v3:
errors.append("pass_100_criteria_v3.json not found — run build_pass_100_criteria_v3.py first")
elif not v3.get("is_active"):
errors.append("pass_100_criteria_v3.json.is_active != true")
# v3 must be the only active artifact
if v2 and v2.get("is_active"):
errors.append("pass_100_criteria_v2.json still has is_active=true — must be legacy_reference_only")
if v1 and v1.get("is_active"):
errors.append("pass_100_criteria_v1.json still has is_active=true — must be legacy_reference_only")
# active PASS_100 not achieving PASS_100 when failed_count>0 is correct behaviour
# but authority lock must exist
active_artifact_count = sum([
1 if (v3 and v3.get("is_active")) else 0,
1 if (v2 and v2.get("is_active")) else 0,
1 if (v1 and v1.get("is_active")) else 0,
])
if active_artifact_count != 1:
errors.append(f"active_artifact_count={active_artifact_count}, expected exactly 1 (pass_100_criteria_v3.json)")
status = "PASS" if not errors else "FAIL"
result = {
"formula_id": "PASS_100_AUTHORITY_LOCK_V1",
"status": status,
"active_artifact": "pass_100_criteria_v3.json",
"active_artifact_count": active_artifact_count,
"errors": errors,
"v3_gate": v3.get("gate") if v3 else "MISSING",
"v3_score_0_100": v3.get("score_0_100") if v3 else None,
"v3_failed_count": v3.get("failed_count") if v3 else None,
}
save_json(str(DEFAULT_OUT), result)
print(json.dumps(result, ensure_ascii=False, indent=2))
if status == "PASS":
print("PASS_100_AUTHORITY_LOCK_V1_OK")
else:
print("PASS_100_AUTHORITY_LOCK_V1_FAIL")
for e in errors:
print(f" ERROR: {e}")
return 0 if status == "PASS" else 1
if __name__ == "__main__":
raise SystemExit(main())
-109
View File
@@ -1,109 +0,0 @@
from __future__ import annotations
import argparse
import json
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
DEFAULT_PATH = ROOT / "Temp" / "pass_100_criteria_v1.json"
def _load(path: Path) -> dict[str, Any]:
if not path.exists():
return {}
try:
obj = json.loads(path.read_text(encoding="utf-8"))
except Exception:
return {}
return obj if isinstance(obj, dict) else {}
def main() -> int:
ap = argparse.ArgumentParser(description="Validate PASS_100_CRITERIA_V1.")
ap.add_argument("--json", default=str(DEFAULT_PATH))
args = ap.parse_args()
path = Path(args.json)
if not path.is_absolute():
path = ROOT / path
payload = _load(path)
errors: list[str] = []
_VALID_IDS = {"PASS_100_CRITERIA_V1", "PASS_100_CRITERIA_V3_ALIAS_V1"}
if str(payload.get("formula_id") or "") not in _VALID_IDS:
errors.append("formula_id mismatch")
gate = str(payload.get("gate") or "")
if gate not in {"PASS_100", "BLOCK_EXECUTION"}:
errors.append(f"gate={gate}")
if not isinstance(payload.get("pass_100_allowed"), bool):
errors.append("pass_100_allowed must be bool")
criteria = payload.get("criteria")
if not isinstance(criteria, list) or not criteria:
errors.append("criteria must be non-empty list")
criteria = []
passed_count = 0
failed: list[str] = []
for idx, row in enumerate(criteria):
if not isinstance(row, dict):
errors.append(f"criteria[{idx}] must be object")
continue
if not str(row.get("criterion_id") or "").strip():
errors.append(f"criteria[{idx}].criterion_id missing")
if not isinstance(row.get("passed"), bool):
errors.append(f"criteria[{idx}].passed must be bool")
continue
if row["passed"]:
passed_count += 1
else:
failed.append(str(row.get("criterion_id") or f"criteria[{idx}]"))
if not str(row.get("remediation") or "").strip() or str(row.get("remediation")) == "NONE":
errors.append(f"criteria[{idx}].remediation missing")
if not str(row.get("source_json") or "").strip():
errors.append(f"criteria[{idx}].source_json missing")
if not str(row.get("formula_id") or "").strip():
errors.append(f"criteria[{idx}].formula_id missing")
expected_score = round(passed_count / len(criteria) * 100.0, 2) if criteria else 0.0
actual_score = payload.get("score_0_100")
if not isinstance(actual_score, (int, float)) or round(float(actual_score), 2) != expected_score:
errors.append(f"score_0_100 mismatch expected={expected_score} actual={actual_score}")
if payload.get("passed_count") != passed_count:
errors.append(f"passed_count mismatch expected={passed_count} actual={payload.get('passed_count')}")
if payload.get("failed_count") != len(failed):
errors.append(f"failed_count mismatch expected={len(failed)} actual={payload.get('failed_count')}")
if payload.get("failed_criteria") != failed:
errors.append("failed_criteria mismatch")
pass_allowed = bool(payload.get("pass_100_allowed"))
if pass_allowed and failed:
errors.append("pass_100_allowed cannot be true with failed criteria")
if gate == "PASS_100" and failed:
errors.append("PASS_100 cannot have failed criteria")
if gate == "BLOCK_EXECUTION" and not failed:
errors.append("BLOCK_EXECUTION requires failed criteria")
if gate == "PASS_100" and not pass_allowed:
errors.append("PASS_100 requires pass_100_allowed=true")
if gate == "BLOCK_EXECUTION" and pass_allowed:
errors.append("BLOCK_EXECUTION requires pass_100_allowed=false")
if errors:
print("PASS_100_CRITERIA_V1_FAIL")
for err in errors:
print(f" {err}")
return 1
print("PASS_100_CRITERIA_V1_OK")
print(f" gate: {gate}")
print(f" score_0_100: {float(payload.get('score_0_100')):.2f}")
print(f" failed_count: {len(failed)}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -1,145 +0,0 @@
from __future__ import annotations
import argparse
import json
import hashlib
from pathlib import Path
from typing import Any
ROOT = Path(__file__).resolve().parents[1]
DEFAULT_INPUT = ROOT / "Temp" / "truthful_decision_ledger_v2.json"
DEFAULT_REPORT = ROOT / "Temp" / "operational_report.json"
def _load_json(path: Path) -> dict[str, Any]:
try:
data = json.loads(path.read_text(encoding="utf-8"))
except Exception:
return {}
return data if isinstance(data, dict) else {}
def _is_number(v: Any) -> bool:
return isinstance(v, (int, float)) and not isinstance(v, bool)
def _canonical(obj: Any) -> str:
return json.dumps(obj, ensure_ascii=False, sort_keys=True, separators=(",", ":"))
def _sha256_text(text: str) -> str:
return hashlib.sha256(text.encode("utf-8")).hexdigest()
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--input", default=str(DEFAULT_INPUT))
ap.add_argument("--report", default=str(DEFAULT_REPORT))
args = ap.parse_args()
input_path = Path(args.input)
if not input_path.is_absolute():
input_path = ROOT / input_path
report_path = Path(args.report)
if not report_path.is_absolute():
report_path = ROOT / report_path
payload = _load_json(input_path)
if not payload:
print("TRUTHFUL_DECISION_LEDGER_V2_FAIL")
print("- invalid input json")
return 1
errors: list[str] = []
if str(payload.get("formula_id") or "") != "TRUTHFUL_DECISION_LEDGER_V2":
errors.append("formula_id mismatch")
if payload.get("llm_numeric_generated_flag") is not False:
errors.append("llm_numeric_generated_flag must be false")
if not str(payload.get("input_hash") or "").strip():
errors.append("input_hash missing")
if not str(payload.get("route_id") or "").strip():
errors.append("route_id missing")
if not str(payload.get("report_hash") or "").strip():
errors.append("report_hash missing")
elif report_path.exists():
try:
report_payload = json.loads(report_path.read_text(encoding="utf-8"))
except Exception:
report_payload = None
if report_payload is None:
errors.append("report_json invalid")
else:
expected_report_hash = _sha256_text(_canonical(report_payload))
if payload.get("report_hash") != expected_report_hash:
errors.append("report_hash mismatch")
ledger_rows = payload.get("ledger_rows")
if not isinstance(ledger_rows, list):
errors.append("ledger_rows must be a list")
ledger_rows = []
elif not ledger_rows:
print("WARNING: ledger_rows is empty (no active proposals)")
# Do not append to errors to allow gate to pass
for i, row in enumerate(ledger_rows):
if not isinstance(row, dict):
errors.append(f"ledger_rows[{i}] must be object")
continue
required = [
"decision_id",
"proposal_id",
"ticker",
"action",
"state",
"formula_id",
"input_hash",
"output_hash",
"source_fields",
"source_snapshot_hash",
"price_basis",
"qty_basis",
"reason_codes",
"gate_stack",
"export_gate",
"renderer_section",
"llm_numeric_generated_flag",
"outcome_binding_id",
"record_type",
]
for key in required:
if key not in row:
errors.append(f"ledger_rows[{i}].{key} missing")
if row.get("llm_numeric_generated_flag") is not False:
errors.append(f"ledger_rows[{i}].llm_numeric_generated_flag must be false")
if not isinstance(row.get("source_fields"), list) or not row.get("source_fields"):
errors.append(f"ledger_rows[{i}].source_fields must be non-empty list")
if not isinstance(row.get("reason_codes"), list) or not row.get("reason_codes"):
errors.append(f"ledger_rows[{i}].reason_codes must be non-empty list")
if not isinstance(row.get("gate_stack"), list) or not row.get("gate_stack"):
errors.append(f"ledger_rows[{i}].gate_stack must be non-empty list")
if row.get("record_type") not in {"order_blueprint", "shadow_ledger"}:
errors.append(f"ledger_rows[{i}].record_type invalid")
if not str(row.get("output_hash") or "").strip():
errors.append(f"ledger_rows[{i}].output_hash missing")
else:
recompute = dict(row)
recompute.pop("output_hash", None)
expected = _sha256_text(_canonical(recompute))
if row.get("output_hash") != expected:
errors.append(f"ledger_rows[{i}].output_hash mismatch")
if errors:
print("TRUTHFUL_DECISION_LEDGER_V2_FAIL")
for e in errors:
print(f"- {e}")
return 1
print("TRUTHFUL_DECISION_LEDGER_V2_OK")
print(f"rows={len(ledger_rows)}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
-233
View File
@@ -1,233 +0,0 @@
#!/usr/bin/env python3
"""
WBS-9.2: snapshot_admin 성능 최적화
목표: P99 < 2초 달성
"""
import time
import json
from pathlib import Path
from datetime import datetime
from typing import Dict, List
class PerformanceOptimizer:
"""snapshot_admin 성능 최적화"""
def __init__(self):
self.results = {
"timestamp": datetime.now().isoformat(),
"optimizations": [],
"summary": {}
}
def identify_optimization_opportunities(self) -> List[Dict]:
"""최적화 기회 식별"""
opportunities = [
{
"name": "Query Indexing",
"current_impact": "테이블 스캔으로 인한 지연",
"optimization": "기존 인덱스 확인 및 추가",
"tables": ["data_feed", "performance", "positions"],
"expected_improvement": "3-5배 빠르기",
"complexity": "LOW",
"status": "IDENTIFIED"
},
{
"name": "Connection Pooling",
"current_impact": "매 요청마다 새로운 DB 연결",
"optimization": "SQLite 커넥션 풀 또는 WAL 모드",
"expected_improvement": "2-3배 빠르기",
"complexity": "MEDIUM",
"status": "IDENTIFIED"
},
{
"name": "Query Caching",
"current_impact": "반복적인 동일 쿼리 실행",
"optimization": "Redis 또는 메모리 캐시 추가",
"expected_improvement": "10-100배 빠르기 (캐시 히트)",
"complexity": "MEDIUM",
"status": "IDENTIFIED"
},
{
"name": "Table Partitioning",
"current_impact": "큰 테이블에서 느린 조회",
"optimization": "성능 메트릭별 파티셔닝",
"expected_improvement": "5-10배 빠르기",
"complexity": "HIGH",
"status": "IDENTIFIED"
},
{
"name": "WAL Mode",
"current_impact": "동시 접근 시 락 경합",
"optimization": "SQLite PRAGMA journal_mode=WAL",
"expected_improvement": "2-3배 빠르기 + 동시성 향상",
"complexity": "LOW",
"status": "READY_TO_IMPLEMENT"
},
{
"name": "PRAGMA Optimization",
"current_impact": "기본 설정으로 인한 오버헤드",
"optimization": "cache_size, synchronous, temp_store 조정",
"expected_improvement": "1.5-2배 빠르기",
"complexity": "LOW",
"status": "READY_TO_IMPLEMENT"
}
]
return opportunities
def implement_wal_mode(self) -> Dict:
"""WAL 모드 적용"""
import sqlite3
optimizations = []
for db_name, db_path in [
("kis_data_collection", "src/quant_engine/kis_data_collection.db"),
("snapshot_admin", "src/quant_engine/snapshot_admin.db")
]:
try:
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# WAL 모드 활성화
cursor.execute("PRAGMA journal_mode=WAL")
mode = cursor.fetchone()[0]
# 추가 최적화
cursor.execute("PRAGMA synchronous=NORMAL")
cursor.execute("PRAGMA cache_size=10000")
cursor.execute("PRAGMA temp_store=MEMORY")
conn.commit()
conn.close()
optimizations.append({
"database": db_name,
"optimization": "WAL mode enabled",
"journal_mode": mode,
"status": "SUCCESS"
})
print(f"[OK] {db_name}: WAL mode = {mode}")
except Exception as e:
optimizations.append({
"database": db_name,
"error": str(e),
"status": "FAILED"
})
print(f"[FAIL] {db_name}: {e}")
return {
"optimization": "WAL Mode",
"results": optimizations
}
def add_performance_indexes(self) -> Dict:
"""성능 인덱스 추가"""
import sqlite3
indexes = [
("kis_data_collection", "data_feed", "entry_date"),
("kis_data_collection", "data_feed", "ticker"),
("snapshot_admin", "performance", "entry_date"),
("snapshot_admin", "performance", "ticker"),
("snapshot_admin", "positions", "ticker"),
]
results = []
for db_name, table, column in indexes:
db_path = "src/quant_engine/" + db_name + ".db"
try:
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# 인덱스 생성
index_name = f"idx_{table}_{column}"
cursor.execute(f"CREATE INDEX IF NOT EXISTS {index_name} ON {table}({column})")
conn.commit()
conn.close()
results.append({
"database": db_name,
"table": table,
"column": column,
"index_name": index_name,
"status": "SUCCESS"
})
print(f"[OK] {db_name}.{table}.{column} indexed")
except Exception as e:
results.append({
"database": db_name,
"table": table,
"column": column,
"error": str(e),
"status": "FAILED"
})
print(f"[FAIL] {db_name}.{table}.{column}: {e}")
return {
"optimization": "Performance Indexes",
"results": results
}
def generate_report(self) -> Dict:
"""최적화 리포트"""
print("\n" + "="*80)
print("WBS-9.2: snapshot_admin 성능 최적화")
print("="*80)
opportunities = self.identify_optimization_opportunities()
print("\n[식별된 최적화 기회]")
for opp in opportunities:
status = "[READY]" if opp["status"] == "READY_TO_IMPLEMENT" else "[FUTURE]"
print(f" {status} {opp['name']}")
print(f" └─ 개선: {opp['expected_improvement']}")
# 즉시 적용 가능한 최적화
print("\n[즉시 적용 가능한 최적화]")
wal_result = self.implement_wal_mode()
self.results["optimizations"].append(wal_result)
index_result = self.add_performance_indexes()
self.results["optimizations"].append(index_result)
# 성능 목표
print("\n[성능 목표]")
print(" P99 < 2000ms (2초)")
print(" 동시 접근 10개 테이블")
print(" 데이터 무결성: 100%")
print("\n[예상 효과]")
print(" 1. WAL 모드: 2-3배 빠르기 + 동시성 향상")
print(" 2. 인덱싱: 3-5배 빠르기 (entry_date, ticker 조회)")
print(" 3. PRAGMA: 1.5-2배 빠르기 (캐시 최적화)")
print(" 4. 누적 효과: 5-10배 성능 개선 예상")
self.results["summary"] = {
"target_p99_ms": 2000,
"optimizations_applied": 2,
"opportunities_identified": len(opportunities),
"expected_improvement_factor": "5-10x",
"status": "IN_PROGRESS"
}
return self.results
if __name__ == "__main__":
optimizer = PerformanceOptimizer()
result = optimizer.generate_report()
# 결과 저장
output_file = Path("Temp/wbs92_optimization_report.json")
output_file.parent.mkdir(parents=True, exist_ok=True)
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(result, f, indent=2, ensure_ascii=False)
print(f"\n[저장] 최적화 리포트: {output_file}")
print("[완료] WBS-9.2 성능 최적화 적용 완료")
-247
View File
@@ -1,247 +0,0 @@
#!/usr/bin/env python3
"""
WBS-9.3: NULL Policy Enforcement
목표: 모든 DB 테이블에서 각 컬럼의 NULL 정책 강제
- Phase 1: NULL 정책 정의 (각 테이블별 컬럼)
- Phase 2: 제약조건 검증 (NOT NULL 강제)
- Phase 3: CI 게이트 (입력 데이터 검증)
- Phase 4: 자동 복구 (NULL 값 처리)
"""
import sqlite3
from pathlib import Path
from datetime import datetime
import json
class NullPolicyEnforcement:
"""NULL 정책 강제"""
def __init__(self):
self.kis_db = Path('src/quant_engine/kis_data_collection.db')
self.snapshot_db = Path('src/quant_engine/snapshot_admin.db')
self.results = {
"timestamp": datetime.now().isoformat(),
"phases": {}
}
def phase_1_define_null_policy(self) -> dict:
"""Phase 1: NULL 정책 정의"""
print("\n[Phase 1] NULL 정책 정의")
null_policy = {
"kis_data_collection": {
"data_feed": {
"NOT_NULL": ["ticker", "entry_price", "entry_date"],
"ALLOW_NULL": ["stop_price", "target_price", "ma20", "ma60", "rsi14"]
}
},
"snapshot_admin": {
"settings": {
"NOT_NULL": ["ordinal", "key"],
"ALLOW_NULL": ["value", "note"]
},
"account_snapshot": {
"NOT_NULL": ["captured_at", "account", "account_type"],
"ALLOW_NULL": ["stop_price", "highest_price_since_entry", "entry_date"]
},
"alpha_history": {
"NOT_NULL": ["entry_date", "ticker", "entry_price"],
"ALLOW_NULL": ["stop_price", "pnl_pct", "mae_pct"]
},
"event_calendar": {
"NOT_NULL": ["event_date", "event_name"],
"ALLOW_NULL": ["event_description", "impact_level"]
},
"core_satellite": {
"NOT_NULL": ["ticker", "name"],
"ALLOW_NULL": ["allocation_pct", "risk_score"]
}
}
}
print(f" 정의된 테이블: {sum(len(v) for v in null_policy.values())}")
for db, tables in null_policy.items():
for table, policy in tables.items():
print(f" {db}.{table}")
print(f" NOT_NULL: {len(policy['NOT_NULL'])}개 컬럼")
print(f" ALLOW_NULL: {len(policy['ALLOW_NULL'])}개 컬럼")
return null_policy
def phase_2_validate_constraints(self, null_policy: dict) -> dict:
"""Phase 2: 제약조건 검증"""
print("\n[Phase 2] 제약조건 검증")
validation_results = {}
# kis_data_collection 검증
conn = sqlite3.connect(self.kis_db)
cursor = conn.cursor()
for table, policy in null_policy["kis_data_collection"].items():
cursor.execute(f"PRAGMA table_info({table})")
columns = {col[1]: col[3] for col in cursor.fetchall()}
violations = []
for col in policy["NOT_NULL"]:
if col in columns and columns[col] == 0:
violations.append(f"{col} should be NOT NULL but is nullable")
status = "OK" if not violations else "VIOLATION"
validation_results[f"kis.{table}"] = {
"status": status,
"violations": violations
}
print(f" kis.{table}: {status}")
if violations:
for v in violations:
print(f" [!] {v}")
conn.close()
# snapshot_admin 검증
conn = sqlite3.connect(self.snapshot_db)
cursor = conn.cursor()
for table, policy in null_policy["snapshot_admin"].items():
if table not in ["settings", "account_snapshot", "alpha_history", "event_calendar", "core_satellite"]:
continue
try:
cursor.execute(f"PRAGMA table_info({table})")
columns = {col[1]: col[3] for col in cursor.fetchall()}
violations = []
for col in policy["NOT_NULL"]:
if col in columns and columns[col] == 0:
violations.append(f"{col} should be NOT NULL but is nullable")
status = "OK" if not violations else "VIOLATION"
validation_results[f"snapshot.{table}"] = {
"status": status,
"violations": violations
}
print(f" snapshot.{table}: {status}")
if violations:
for v in violations:
print(f" [!] {v}")
except sqlite3.OperationalError:
print(f" snapshot.{table}: SKIP (table not found)")
conn.close()
return validation_results
def phase_3_ci_gates(self, null_policy: dict) -> dict:
"""Phase 3: CI 게이트 (데이터 입력 검증)"""
print("\n[Phase 3] CI 게이트 (데이터 입력 검증)")
gates = {
"pre_insert_validation": {
"description": "INSERT/UPDATE 전 NULL 검증",
"check_required_columns": True,
"check_data_types": True,
"fail_on_violation": True
},
"post_insert_validation": {
"description": "INSERT/UPDATE 후 NULL 검증",
"check_row_count": True,
"check_integrity": True,
"fail_on_violation": True
},
"daily_audit": {
"description": "일일 NULL 값 감시",
"schedule": "00:00 UTC",
"alert_on_violation": True
}
}
print(f" CI 게이트: {len(gates)}")
for gate, config in gates.items():
print(f" {gate}: {config['description']}")
return gates
def phase_4_auto_recovery(self, null_policy: dict) -> dict:
"""Phase 4: 자동 복구 (NULL 값 처리)"""
print("\n[Phase 4] 자동 복구")
recovery_rules = {
"default_values": {
"ticker": "UNKNOWN",
"entry_date": "1900-01-01",
"account": "DEFAULT",
"event_date": "1900-01-01"
},
"fallback_strategies": {
"entry_price": "use_previous_value_or_fail",
"stop_price": "use_default_or_null",
"target_price": "calculate_from_entry"
},
"validation_levels": {
"CRITICAL": "fail_immediately",
"HIGH": "log_and_continue",
"MEDIUM": "auto_fix_and_log"
}
}
print(f" 기본값 규칙: {len(recovery_rules['default_values'])}")
print(f" 폴백 전략: {len(recovery_rules['fallback_strategies'])}")
print(f" 검증 레벨: {len(recovery_rules['validation_levels'])}")
return recovery_rules
def run(self) -> dict:
"""전체 실행"""
print("="*80)
print("WBS-9.3: NULL Policy Enforcement")
print("="*80)
# Phase 1: 정책 정의
null_policy = self.phase_1_define_null_policy()
self.results["phases"]["phase_1"] = null_policy
# Phase 2: 검증
validation = self.phase_2_validate_constraints(null_policy)
self.results["phases"]["phase_2"] = validation
# Phase 3: CI 게이트
ci_gates = self.phase_3_ci_gates(null_policy)
self.results["phases"]["phase_3"] = ci_gates
# Phase 4: 자동 복구
recovery = self.phase_4_auto_recovery(null_policy)
self.results["phases"]["phase_4"] = recovery
# 요약
print("\n" + "="*80)
print("[결과 요약]")
violations_count = sum(1 for v in validation.values() if v["status"] == "VIOLATION")
print(f" 검증 결과: {len(validation) - violations_count}/{len(validation)} PASS")
print(f" CI 게이트: {len(ci_gates)}개 구현")
print(f" 자동 복구: {len(recovery['default_values'])}개 규칙")
self.results["summary"] = {
"phase_1_status": "COMPLETE",
"phase_2_status": "VALIDATED",
"phase_3_status": "IMPLEMENTED",
"phase_4_status": "CONFIGURED",
"violations": violations_count,
"overall_status": "100%" if violations_count == 0 else "90% (violations to fix)"
}
return self.results
if __name__ == "__main__":
enforcer = NullPolicyEnforcement()
result = enforcer.run()
# 결과 저장
output_file = Path("Temp/wbs93_null_policy.json")
output_file.parent.mkdir(parents=True, exist_ok=True)
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(result, f, indent=2, ensure_ascii=False)
print(f"\n[저장] {output_file}")
print("[완료] WBS-9.3 NULL Policy Enforcement 구현 완료")
-305
View File
@@ -1,305 +0,0 @@
#!/usr/bin/env python3
"""
WBS-9.5: Sector Flow Reliability Measurement
섹터 흐름 신뢰도 측정
- 데이터 커버리지: 섹터별 데이터 가용도
- 신선도: 최신 데이터의 타이밍
- 일관성: 데이터 품질 이상치 감지
"""
import sqlite3
from pathlib import Path
from datetime import datetime, timedelta
import json
class SectorFlowReliability:
"""섹터 흐름 신뢰도 측정"""
def __init__(self):
self.snapshot_db = Path('src/quant_engine/snapshot_admin.db')
self.results = {
"timestamp": datetime.now().isoformat(),
"measurements": {}
}
def measure_data_coverage(self) -> dict:
"""데이터 커버리지 측정"""
print("\n[1. 데이터 커버리지]")
conn = sqlite3.connect(self.snapshot_db)
cursor = conn.cursor()
# 테이블 확인
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='sector_flow_history'")
if not cursor.fetchone():
print(" [!] sector_flow_history 테이블이 없음")
return {}
# 총 행 수
cursor.execute("SELECT COUNT(*) FROM sector_flow_history")
total_rows = cursor.fetchone()[0]
# 섹터별 행 수
cursor.execute("""
SELECT Sector, COUNT(*) as count
FROM sector_flow_history
GROUP BY Sector
ORDER BY count DESC
""")
sector_counts = cursor.fetchall()
coverage = {
"total_rows": total_rows,
"sectors": len(sector_counts),
"sector_distribution": {}
}
print(f" 총 행: {total_rows}")
print(f" 섹터 수: {len(sector_counts)}")
print(f" 섹터별 분포:")
for sector, count in sector_counts[:10]:
pct = (count / total_rows * 100) if total_rows > 0 else 0
coverage["sector_distribution"][sector] = {
"count": count,
"percentage": round(pct, 1)
}
print(f" {sector}: {count} ({pct:.1f}%)")
conn.close()
return coverage
def measure_data_freshness(self) -> dict:
"""데이터 신선도 측정"""
print("\n[2. 데이터 신선도]")
conn = sqlite3.connect(self.snapshot_db)
cursor = conn.cursor()
# 최신 날짜 확인
cursor.execute("""
SELECT MIN(Snapshot_Date) as earliest, MAX(Snapshot_Date) as latest
FROM sector_flow_history
""")
earliest, latest = cursor.fetchone()
freshness = {
"earliest_date": earliest,
"latest_date": latest,
"age_days": 0
}
if latest:
try:
latest_dt = datetime.fromisoformat(latest)
age = (datetime.now() - latest_dt).days
freshness["age_days"] = age
print(f" 최신 데이터: {latest} ({age}일 전)")
except:
print(f" 최신 데이터: {latest}")
if earliest:
print(f" 가장 오래된 데이터: {earliest}")
# 시간대별 분포
cursor.execute("""
SELECT
DATE(Snapshot_Date) as date,
COUNT(*) as count
FROM sector_flow_history
GROUP BY DATE(Snapshot_Date)
ORDER BY date DESC
LIMIT 10
""")
date_dist = cursor.fetchall()
print(f" 최근 10일 분포:")
for date, count in date_dist:
print(f" {date}: {count}")
conn.close()
return freshness
def measure_data_consistency(self) -> dict:
"""데이터 일관성 측정"""
print("\n[3. 데이터 일관성]")
conn = sqlite3.connect(self.snapshot_db)
cursor = conn.cursor()
consistency = {
"null_values": 0,
"outliers": 0,
"warnings": []
}
# NULL 값 확인
cursor.execute("""
SELECT
COUNT(*) as null_count,
COUNT(DISTINCT Sector) as sectors_with_nulls
FROM sector_flow_history
WHERE
Sector IS NULL
OR Snapshot_Date IS NULL
OR Sector_Score IS NULL
""")
null_count, sectors_null = cursor.fetchone()
consistency["null_values"] = null_count
if null_count > 0:
consistency["warnings"].append(f"NULL 값 발견: {null_count}")
print(f" [!] NULL 값: {null_count}")
# 이상치 감지 (극단값)
cursor.execute("""
SELECT
Sector,
MIN(Sector_Score) as min_val,
MAX(Sector_Score) as max_val,
AVG(Sector_Score) as avg_val
FROM sector_flow_history
WHERE Sector_Score IS NOT NULL
GROUP BY Sector
""")
anomalies = 0
for sector, min_val, max_val, avg_val in cursor.fetchall():
if min_val is None or max_val is None:
continue
# 극단값 감지 (평균의 5배 이상)
if avg_val and max_val > avg_val * 5:
anomalies += 1
consistency["warnings"].append(f"{sector}: 극단값 감지 ({max_val})")
consistency["outliers"] = anomalies
if anomalies > 0:
print(f" [!] 이상치: {anomalies}개 섹터")
# 데이터 완정성 (중요 컬럼)
cursor.execute("""
SELECT
(COUNT(*) - COUNT(Sector)) as missing_sectors,
(COUNT(*) - COUNT(Snapshot_Date)) as missing_dates,
(COUNT(*) - COUNT(Sector_Score)) as missing_values
FROM sector_flow_history
""")
missing_sectors, missing_dates, missing_values = cursor.fetchone()
print(f" 누락 데이터: sector={missing_sectors}, date={missing_dates}, value={missing_values}")
conn.close()
return consistency
def calculate_reliability_score(self, coverage: dict, freshness: dict, consistency: dict) -> float:
"""종합 신뢰도 점수 계산"""
print("\n[4. 종합 신뢰도]")
scores = {
"coverage_score": 0,
"freshness_score": 0,
"consistency_score": 0
}
# 커버리지 점수 (0-100)
if coverage.get("total_rows", 0) > 0:
sector_count = coverage.get("sectors", 0)
# 10개 이상 섹터: 100점
# 1개 미만: 0점
scores["coverage_score"] = min(100, sector_count * 10)
print(f" 커버리지: {scores['coverage_score']:.1f}/100 ({coverage.get('sectors', 0)} 섹터)")
# 신선도 점수 (0-100)
age_days = freshness.get("age_days", 9999)
if age_days <= 1:
scores["freshness_score"] = 100 # 1일 이내
elif age_days <= 7:
scores["freshness_score"] = 80 # 1주일 이내
elif age_days <= 30:
scores["freshness_score"] = 50 # 1개월 이내
else:
scores["freshness_score"] = 20 # 오래됨
print(f" 신선도: {scores['freshness_score']:.1f}/100 ({age_days}일 전)")
# 일관성 점수 (0-100)
null_violations = consistency.get("null_values", 0)
outlier_count = consistency.get("outliers", 0)
warnings = len(consistency.get("warnings", []))
consistency_score = 100
if null_violations > 0:
consistency_score -= min(20, null_violations / 10)
if outlier_count > 0:
consistency_score -= min(30, outlier_count * 3)
consistency_score = max(0, consistency_score)
scores["consistency_score"] = consistency_score
print(f" 일관성: {scores['consistency_score']:.1f}/100 ({warnings} 경고)")
# 종합 점수 (가중 평균)
# 커버리지 30%, 신선도 40%, 일관성 30%
overall = (
scores["coverage_score"] * 0.3 +
scores["freshness_score"] * 0.4 +
scores["consistency_score"] * 0.3
)
print(f"\n 종합 신뢰도: {overall:.1f}/100")
return overall
def run(self) -> dict:
"""전체 실행"""
print("="*80)
print("WBS-9.5: Sector Flow Reliability Measurement")
print("="*80)
# 측정
coverage = self.measure_data_coverage()
freshness = self.measure_data_freshness()
consistency = self.measure_data_consistency()
# 신뢰도 점수
reliability_score = self.calculate_reliability_score(coverage, freshness, consistency)
# 결과 저장
self.results["measurements"] = {
"coverage": coverage,
"freshness": freshness,
"consistency": consistency,
"reliability_score": reliability_score
}
# 신뢰도 판정
if reliability_score >= 80:
status = "HIGH (신뢰 가능)"
elif reliability_score >= 60:
status = "MEDIUM (주의 필요)"
else:
status = "LOW (개선 필요)"
print(f"\n[판정]")
print(f" 신뢰도 상태: {status}")
print(f" 권장사항: {'데이터 사용 가능' if reliability_score >= 60 else '데이터 보완 필요'}")
self.results["summary"] = {
"status": status,
"reliability_score": reliability_score,
"measurement_date": datetime.now().isoformat()
}
return self.results
if __name__ == "__main__":
measurer = SectorFlowReliability()
result = measurer.run()
# 결과 저장
output_file = Path("Temp/wbs95_sector_flow_reliability.json")
output_file.parent.mkdir(parents=True, exist_ok=True)
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(result, f, indent=2, ensure_ascii=False)
print(f"\n[저장] {output_file}")
print("[완료] WBS-9.5 Sector Flow Reliability Measurement 완료")
-251
View File
@@ -1,251 +0,0 @@
#!/usr/bin/env python3
"""
WBS-9.6: LLM Radar Phase 3-5 구현
Phase 3: Dependency Graph
Phase 4: Terminology Registry
Phase 5: Error Validation
"""
import json
from pathlib import Path
from datetime import datetime
class LLMRadarPhases3to5:
"""Phase 3-5 구현"""
def __init__(self):
self.results = {
"timestamp": datetime.now().isoformat(),
"phases": {}
}
def phase_3_dependency_graph(self) -> dict:
"""Phase 3: 개념 간 의존성 그래프"""
graph = {
"phase": 3,
"name": "Dependency Graph",
"purpose": "문서 간 개념 의존성 정의",
"nodes": {
"field_dictionary": {
"tier": "canonical",
"provides": ["canonical_name", "aliases", "types"],
"depends_on": []
},
"market_regime": {
"tier": "canonical",
"provides": ["regime_states", "transitions"],
"depends_on": ["field_dictionary"]
},
"decision_flow": {
"tier": "adapter",
"provides": ["routing_rules", "gates"],
"depends_on": ["field_dictionary", "market_regime"]
},
"formula_registry": {
"tier": "adapter",
"provides": ["formula_definitions", "formulas"],
"depends_on": ["field_dictionary"]
},
"migration_reports": {
"tier": "reference",
"provides": ["implementation_evidence"],
"depends_on": ["formula_registry", "decision_flow"]
},
"incident_playbooks": {
"tier": "reference",
"provides": ["recovery_procedures"],
"depends_on": ["field_dictionary"]
}
},
"edges": [
("decision_flow", "field_dictionary", "uses"),
("decision_flow", "market_regime", "depends"),
("formula_registry", "field_dictionary", "uses"),
("migration_reports", "formula_registry", "references"),
("migration_reports", "decision_flow", "references"),
],
"conflict_resolution": {
"circular_dependency": "BLOCK - hierarchy enforced by tier system",
"missing_dependency": "WARN - reference without implementation",
"stale_dependency": "WARN - outdated tier reference"
}
}
return graph
def phase_4_terminology_registry(self) -> dict:
"""Phase 4: 용어 통일 레지스트리"""
terminology = {
"phase": 4,
"name": "Terminology Registry",
"purpose": "개념 이름 충돌 제거",
"canonical_terms": {
"trade_entry": {
"canonical": "entry_date",
"aliases": ["trade_entry_date", "entry", "거래개시일"],
"definition": "거래 진입 시점 (ISO 8601)",
"context": ["data_feed", "performance"]
},
"position_size": {
"canonical": "quantity",
"aliases": ["size", "qty", "거래수량", "수량"],
"definition": "보유 주식 수량 (주식 단위)",
"context": ["positions", "data_feed"]
},
"exit_condition": {
"canonical": "stop_price",
"aliases": ["stop", "손절가", "exit_trigger"],
"definition": "손절매 기준 가격",
"context": ["decision_flow", "performance"]
},
"performance_metric": {
"canonical": "pnl_pct",
"aliases": ["return", "수익률", "profit_loss_percent"],
"definition": "손익 백분율 ((close-entry)/entry*100)",
"context": ["performance"]
}
},
"conflict_resolution": {
"method": "Canonical-first lookup",
"fallback": "Alias matching with warning",
"error": "Unknown term → DATA_MISSING"
}
}
return terminology
def phase_5_error_validation(self) -> dict:
"""Phase 5: 에러 검증 게이트"""
validation = {
"phase": 5,
"name": "Error Validation",
"purpose": "개념 혼동 자동 감지",
"validation_rules": [
{
"rule": "Canonical contradiction",
"description": "Canonical과 Reference가 모순",
"detection": "semantic diff check",
"action": "BLOCK - reject Reference",
"severity": "CRITICAL"
},
{
"rule": "Missing canonical",
"description": "Canonical 문서 부재",
"detection": "tier lookup failure",
"action": "WARN - fallback to adapter",
"severity": "HIGH"
},
{
"rule": "Stale alias",
"description": "사용 중단된 별칭 사용",
"detection": "alias version mismatch",
"action": "WARN - suggest canonical",
"severity": "MEDIUM"
},
{
"rule": "Circular definition",
"description": "개념이 자신을 정의",
"detection": "dependency graph cycle",
"action": "BLOCK - fix definition",
"severity": "CRITICAL"
}
],
"validation_gates": {
"pre_llm_invocation": {
"checks": ["canonical_available", "no_contradictions", "no_cycles"],
"threshold": "ALL PASS required",
"on_fail": "Use fallback tier"
},
"post_llm_output": {
"checks": ["output_matches_canonical", "no_new_aliases"],
"threshold": "95%+ match",
"on_fail": "Log discrepancy, suggest rerun"
}
}
}
return validation
def generate_implementation_plan(self) -> str:
"""구현 계획"""
plan = """
## WBS-9.6 Phase 3-5 구현 계획
### Phase 3: Dependency Graph
- 6 핵심 문서 그래프화
- 3계층 (canonical adapter reference)
- 순환 의존성 감지
### Phase 4: Terminology Registry
- 4 핵심 개념 정의
- 개념별 5-10 별칭
- Canonical-first 룩업
### Phase 5: Error Validation
- 4 검증 규칙
- Pre-LLM + Post-LLM 검증
- 자동 감지 복구
### 예상 효과
- 개념 혼동 감지율: 95%+
- 거짓 긍정율: <5%
- 오류율 감소: 50% (Phase 1&2 대비 추가 25%)
"""
return plan
def run(self) -> dict:
"""전체 실행"""
print("\n" + "="*80)
print("WBS-9.6: LLM Radar Phase 3-5 구현")
print("="*80)
# Phase 3
phase3 = self.phase_3_dependency_graph()
self.results["phases"]["phase_3"] = phase3
print(f"\n[Phase 3] Dependency Graph")
print(f" 노드: {len(phase3['nodes'])}")
print(f" 엣지: {len(phase3['edges'])}")
# Phase 4
phase4 = self.phase_4_terminology_registry()
self.results["phases"]["phase_4"] = phase4
print(f"\n[Phase 4] Terminology Registry")
print(f" 표준 용어: {len(phase4['canonical_terms'])}")
for term, info in phase4['canonical_terms'].items():
aliases = len(info['aliases'])
print(f" - {term}: {aliases}개 별칭")
# Phase 5
phase5 = self.phase_5_error_validation()
self.results["phases"]["phase_5"] = phase5
print(f"\n[Phase 5] Error Validation")
print(f" 검증 규칙: {len(phase5['validation_rules'])}")
print(f" 검증 게이트: {len(phase5['validation_gates'])}")
# 계획
plan = self.generate_implementation_plan()
print(plan)
self.results["summary"] = {
"total_phases_implemented": 5,
"error_rate_reduction_target": "50%",
"false_positive_rate_target": "<5%",
"confidence_threshold": "95%+",
"status": "COMPLETE"
}
return self.results
if __name__ == "__main__":
phases = LLMRadarPhases3to5()
result = phases.run()
# 저장
output_file = Path("Temp/wbs96_phase3to5.json")
output_file.parent.mkdir(parents=True, exist_ok=True)
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(result, f, indent=2, ensure_ascii=False)
print(f"\n[저장] Phase 3-5 구현: {output_file}")
print("[완료] WBS-9.6 모든 Phase 구현 완료 (1-5)")