feat: postgres history-first 계약과 적재 경로 추가

- PostgreSQL history contract와 schema/validator를 추가했습니다.
- .NET history store, snapshot reader, repository, migration을 연결했습니다.
- history-first 운영 모델 문서와 daily signal tracking 문구를 정리했습니다.
This commit is contained in:
2026-06-26 14:17:04 +09:00
parent 7e0c0b6c8f
commit 8f13bb4a48
17 changed files with 707 additions and 17 deletions
@@ -0,0 +1,45 @@
from __future__ import annotations
import argparse
import json
from pathlib import Path
from src.quant_engine.postgresql_history_store_v1 import DOMAIN_TABLES
ROOT = Path(__file__).resolve().parents[1]
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("--dsn", required=True)
ap.add_argument("--out", default=str(ROOT / "Temp" / "postgresql_history_snapshot_v1.json"))
ap.add_argument("--limit", type=int, default=200)
args = ap.parse_args()
try:
from src.quant_engine.postgresql_history_store_v1 import connect, snapshot_table
except Exception as exc:
raise SystemExit(f"import_failed: {exc}")
conn = connect(args.dsn)
try:
payload = {
"formula_id": "POSTGRESQL_HISTORY_SNAPSHOT_V1",
"gate": "PASS",
"domains": {
domain: snapshot_table(conn, domain, limit=args.limit)
for domain in DOMAIN_TABLES
},
}
finally:
conn.close()
out = Path(args.out)
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(json.dumps(payload, ensure_ascii=False, indent=2, default=str), encoding="utf-8")
print(f"POSTGRESQL_HISTORY_SNAPSHOT_V1 gate=PASS out={out}")
return 0
if __name__ == "__main__":
raise SystemExit(main())
@@ -0,0 +1,90 @@
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())
@@ -0,0 +1,44 @@
from __future__ import annotations
import json
from pathlib import Path
import yaml
ROOT = Path(__file__).resolve().parents[1]
CONTRACT = ROOT / "spec" / "postgresql_history_contract.yaml"
OUT = ROOT / "Temp" / "postgresql_history_contract_v1.json"
def main() -> int:
errors: list[str] = []
if not CONTRACT.exists():
errors.append("contract_missing")
else:
try:
data = yaml.safe_load(CONTRACT.read_text(encoding="utf-8"))
except Exception as exc:
errors.append(f"yaml_parse_error:{exc}")
data = {}
if not isinstance(data, dict):
errors.append("contract_not_mapping")
else:
for key in ("market_raw_history", "factor_version_history", "factor_output_history", "decision_result_history", "market_vs_engine_gap_history"):
if key not in (data.get("domains") or {}):
errors.append(f"missing_domain:{key}")
if "PostgreSQL" not in json.dumps(data, ensure_ascii=False):
errors.append("postgresql_not_mentioned")
result = {
"formula_id": "POSTGRESQL_HISTORY_CONTRACT_V1",
"gate": "PASS" if not errors else "FAIL",
"errors": errors,
"contract_path": str(CONTRACT.relative_to(ROOT)),
}
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())