66import hashlib
77import json
88import os
9+ import re
910import subprocess
1011import sys
1112import tempfile
12- from datetime import datetime , timezone
13+ from datetime import datetime , timedelta , timezone
1314from pathlib import Path
1415from typing import Any
1516
1819DRIFT_REVIEW = 0.50
1920DRIFT_CRITICAL = 0.75
2021_ALERT_STATE_RELATIVE_PATH = Path ("data/alert-state/health_cycle.json" )
22+ _ARTIFACT_STATUS_RELATIVE_PATH = Path ("data/lifecycle-artifacts/status.json" )
23+ _ARTIFACT_STATUS_SCHEMA = "quant_monitor_lifecycle_artifact_status.v1"
24+ _SAFE_TOKEN = re .compile (r"^[A-Za-z][A-Za-z0-9_]{0,99}$" )
2125
2226
2327def _collect_drift_results (run_drift_detection , * , domains = DOMAINS ):
@@ -37,6 +41,111 @@ def _collect_drift_results(run_drift_detection, *, domains=DOMAINS):
3741 return results , errors
3842
3943
44+ def _refresh_and_collect_drift (run_monitor , run_drift_detection , * , domains = DOMAINS ):
45+ snapshots : dict [str , list [Any ]] = {}
46+ results : dict [str , list [Any ]] = {}
47+ errors : list [dict [str , str ]] = []
48+ for domain in domains :
49+ try :
50+ domain_snapshots = list (run_monitor (domain ))
51+ if not domain_snapshots :
52+ raise RuntimeError ("monitor produced no snapshots" )
53+ snapshots [domain ] = domain_snapshots
54+ except Exception as exc :
55+ errors .append (
56+ {
57+ "domain" : domain ,
58+ "code" : "monitor_data_unavailable" ,
59+ "error_type" : type (exc ).__name__ ,
60+ }
61+ )
62+ continue
63+ try :
64+ results [domain ] = list (run_drift_detection (domain ))
65+ except Exception as exc :
66+ errors .append (
67+ {
68+ "domain" : domain ,
69+ "code" : "drift_data_unavailable" ,
70+ "error_type" : type (exc ).__name__ ,
71+ }
72+ )
73+ return snapshots , results , errors
74+
75+
76+ def _artifact_status_error (
77+ domain : str ,
78+ * ,
79+ code : str = "artifact_sync_status_unavailable" ,
80+ error_type : str = "RuntimeError" ,
81+ ) -> dict [str , str ]:
82+ safe_code = code if _SAFE_TOKEN .fullmatch (code ) else "artifact_sync_status_unavailable"
83+ safe_error_type = error_type if _SAFE_TOKEN .fullmatch (error_type ) else "RuntimeError"
84+ return {"domain" : domain , "code" : safe_code , "error_type" : safe_error_type }
85+
86+
87+ def _load_lifecycle_artifact_status (
88+ root : Path ,
89+ * ,
90+ domains = DOMAINS ,
91+ now : datetime | None = None ,
92+ max_age : timedelta = timedelta (hours = 2 ),
93+ ) -> tuple [tuple [str , ...], list [dict [str , str ]]]:
94+ path = root / _ARTIFACT_STATUS_RELATIVE_PATH
95+ try :
96+ payload = json .loads (path .read_text (encoding = "utf-8" ))
97+ as_of = datetime .fromisoformat (str (payload ["as_of" ]).replace ("Z" , "+00:00" ))
98+ if as_of .tzinfo is None :
99+ raise ValueError ("status timestamp has no timezone" )
100+ current = (now or datetime .now (timezone .utc )).astimezone (timezone .utc )
101+ age = current - as_of .astimezone (timezone .utc )
102+ domain_statuses = payload ["domains" ]
103+ if (
104+ not isinstance (payload , dict )
105+ or payload .get ("schema_version" ) != _ARTIFACT_STATUS_SCHEMA
106+ or not isinstance (domain_statuses , dict )
107+ or age > max_age
108+ or age < - timedelta (minutes = 5 )
109+ ):
110+ raise ValueError ("artifact status is invalid or stale" )
111+ except (OSError , KeyError , TypeError , ValueError , json .JSONDecodeError ) as exc :
112+ return (), [
113+ _artifact_status_error (domain , error_type = type (exc ).__name__ )
114+ for domain in domains
115+ ]
116+
117+ ready : list [str ] = []
118+ errors : list [dict [str , str ]] = []
119+ for domain in domains :
120+ status = domain_statuses .get (domain )
121+ if not isinstance (status , dict ):
122+ errors .append (_artifact_status_error (domain ))
123+ continue
124+ profiles = status .get ("profiles" )
125+ valid_ready = (
126+ status .get ("status" ) == "ready"
127+ and isinstance (status .get ("artifact_id" ), int )
128+ and status ["artifact_id" ] > 0
129+ and isinstance (status .get ("run_id" ), int )
130+ and status ["run_id" ] > 0
131+ and re .fullmatch (r"[0-9a-f]{40}" , str (status .get ("head_sha" ) or "" ))
132+ and isinstance (profiles , list )
133+ and bool (profiles )
134+ and all (isinstance (profile , str ) and profile for profile in profiles )
135+ )
136+ if valid_ready :
137+ ready .append (domain )
138+ continue
139+ errors .append (
140+ _artifact_status_error (
141+ domain ,
142+ code = str (status .get ("code" ) or "artifact_sync_status_unavailable" ),
143+ error_type = str (status .get ("error_type" ) or "RuntimeError" ),
144+ )
145+ )
146+ return tuple (ready ), errors
147+
148+
40149def _alert_fingerprint (lines : list [str ]) -> str :
41150 payload = "\n " .join (sorted (str (line ) for line in lines ))
42151 return hashlib .sha256 (payload .encode ("utf-8" )).hexdigest ()
@@ -161,8 +270,15 @@ def main() -> int:
161270 from quant_platform_kit .strategy_lifecycle .codex_integration import create_issues_for_domain
162271 from quant_platform_kit .strategy_lifecycle .drift_detector import run_drift_detection
163272 from quant_platform_kit .strategy_lifecycle .health_dashboard import build_dashboard
273+ from quant_platform_kit .strategy_lifecycle .performance_monitor import run_monitor
164274
165- drift_results , drift_errors = _collect_drift_results (run_drift_detection )
275+ ready_domains , artifact_errors = _load_lifecycle_artifact_status (root )
276+ snapshot_results , drift_results , lifecycle_errors = _refresh_and_collect_drift (
277+ run_monitor ,
278+ run_drift_detection ,
279+ domains = ready_domains ,
280+ )
281+ data_errors = artifact_errors + lifecycle_errors
166282 build_dashboard (output_dir = str (dash_dir ), output_format = "json" )
167283
168284 strategies : list [dict [str , Any ]] = []
@@ -250,7 +366,7 @@ def main() -> int:
250366 )
251367
252368 data_error_lines : list [str ] = []
253- for error in drift_errors :
369+ for error in data_errors :
254370 data_error_lines .append (
255371 f"[{ error ['domain' ]} ] { error ['code' ]} ({ error ['error_type' ]} )"
256372 )
@@ -281,7 +397,8 @@ def main() -> int:
281397 "telegram_alerts" : notify_lines ,
282398 "telegram_sent" : telegram_sent ,
283399 "duplicate_alert_suppressed" : duplicate_alert_suppressed ,
284- "data_errors" : drift_errors ,
400+ "data_errors" : data_errors ,
401+ "snapshot_count" : sum (len (rows ) for rows in snapshot_results .values ()),
285402 "issues_created" : len ([r for r in issue_results if r .get ("issue_url" )]),
286403 "ok" : not notify_lines and not collector_payload_invalid ,
287404 "collector_payload_valid" : not collector_payload_invalid ,
0 commit comments