Skip to content

Commit da1fbb1

Browse files
Pigbibicodex
andcommitted
fix: fail closed on quant monitor delivery
Co-Authored-By: Codex <noreply@openai.com>
1 parent 2e6c739 commit da1fbb1

5 files changed

Lines changed: 212 additions & 10 deletions

File tree

ops/quant-monitor/scripts/daily_briefing_builder.py

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,20 @@
1515
DOMAINS = ("cn_equity", "hk_equity", "us_equity", "crypto")
1616

1717

18+
def _collect_drift_results(run_drift_detection, *, domains=DOMAINS):
19+
results: dict[str, list[Any]] = {}
20+
errors: dict[str, dict[str, str]] = {}
21+
for domain in domains:
22+
try:
23+
results[domain] = list(run_drift_detection(domain))
24+
except Exception as exc:
25+
errors[domain] = {
26+
"code": "drift_data_unavailable",
27+
"error_type": type(exc).__name__,
28+
}
29+
return results, errors
30+
31+
1832
def _status_counts(strategies: list[dict[str, Any]]) -> dict[str, int]:
1933
counts = {"healthy": 0, "watch": 0, "review": 0, "critical": 0}
2034
for row in strategies:
@@ -33,9 +47,10 @@ def main() -> int:
3347
from quant_platform_kit.strategy_lifecycle.drift_detector import run_drift_detection
3448
from quant_platform_kit.strategy_lifecycle.health_dashboard import build_dashboard
3549

50+
drift_results, drift_errors = _collect_drift_results(run_drift_detection)
3651
drift_by_key: dict[tuple[str, str], float] = {}
37-
for domain in DOMAINS:
38-
for drift in run_drift_detection(domain):
52+
for domain, domain_results in drift_results.items():
53+
for drift in domain_results:
3954
drift_by_key[(domain, drift.strategy_profile)] = float(drift.drift_score or 0.0)
4055

4156
with tempfile.TemporaryDirectory() as tmp:
@@ -58,13 +73,19 @@ def main() -> int:
5873
for domain in DOMAINS:
5974
strategies = by_domain.get(domain, [])
6075
summary = _status_counts(strategies)
76+
domain_errors = [drift_errors[domain]] if domain in drift_errors else []
6177
report = {
6278
"domain": domain,
63-
"ok": True,
79+
"ok": not domain_errors,
80+
"data_status": "unavailable" if domain_errors else "ready",
6481
"as_of": datetime.now(timezone.utc).isoformat(),
6582
"strategies": strategies,
6683
"summary": summary,
84+
"errors": domain_errors,
6785
}
86+
if domain_errors:
87+
error = domain_errors[0]
88+
report["error"] = f"{error['code']}:{error['error_type']}"
6889
path = out_dir / f"{domain}.json"
6990
path.write_text(json.dumps(report, ensure_ascii=False, indent=2) + "\n", encoding="utf-8")
7091
print(f"[briefing] wrote {path}")

ops/quant-monitor/scripts/health_cycle.py

Lines changed: 85 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from __future__ import annotations
55

6+
import hashlib
67
import json
78
import os
89
import subprocess
@@ -16,6 +17,66 @@
1617
SCORE_ALERT = 60.0
1718
DRIFT_REVIEW = 0.50
1819
DRIFT_CRITICAL = 0.75
20+
_ALERT_STATE_RELATIVE_PATH = Path("data/alert-state/health_cycle.json")
21+
22+
23+
def _collect_drift_results(run_drift_detection, *, domains=DOMAINS):
24+
results: dict[str, list[Any]] = {}
25+
errors: list[dict[str, str]] = []
26+
for domain in domains:
27+
try:
28+
results[domain] = list(run_drift_detection(domain))
29+
except Exception as exc:
30+
errors.append(
31+
{
32+
"domain": domain,
33+
"code": "drift_data_unavailable",
34+
"error_type": type(exc).__name__,
35+
}
36+
)
37+
return results, errors
38+
39+
40+
def _alert_fingerprint(lines: list[str]) -> str:
41+
payload = "\n".join(sorted(str(line) for line in lines))
42+
return hashlib.sha256(payload.encode("utf-8")).hexdigest()
43+
44+
45+
def _alert_state_path(root: Path) -> Path:
46+
return root / _ALERT_STATE_RELATIVE_PATH
47+
48+
49+
def _is_duplicate_alert(root: Path, fingerprint: str) -> bool:
50+
try:
51+
payload = json.loads(_alert_state_path(root).read_text(encoding="utf-8"))
52+
except (OSError, json.JSONDecodeError):
53+
return False
54+
return str(payload.get("fingerprint") or "") == fingerprint
55+
56+
57+
def _record_alert(root: Path, fingerprint: str) -> None:
58+
path = _alert_state_path(root)
59+
path.parent.mkdir(parents=True, exist_ok=True)
60+
temp_path = path.with_suffix(".tmp")
61+
temp_path.write_text(
62+
json.dumps(
63+
{
64+
"schema_version": "quant_monitor_alert_state.v1",
65+
"fingerprint": fingerprint,
66+
},
67+
sort_keys=True,
68+
)
69+
+ "\n",
70+
encoding="utf-8",
71+
)
72+
temp_path.replace(path)
73+
74+
75+
def _clear_alert(root: Path) -> None:
76+
try:
77+
_alert_state_path(root).unlink()
78+
except FileNotFoundError:
79+
pass
1980

2081

2182
def _send_telegram(text: str) -> bool:
@@ -26,8 +87,7 @@ def _send_telegram(text: str) -> bool:
2687
try:
2788
from quant_platform_kit.notifications.telegram import send_telegram_message
2889

29-
send_telegram_message(bot_token=token, chat_ids=chat, text=text)
30-
return True
90+
return bool(send_telegram_message(bot_token=token, chat_ids=chat, text=text))
3191
except Exception:
3292
return False
3393

@@ -72,6 +132,7 @@ def main() -> int:
72132
from quant_platform_kit.strategy_lifecycle.drift_detector import run_drift_detection
73133
from quant_platform_kit.strategy_lifecycle.health_dashboard import build_dashboard
74134

135+
drift_results, drift_errors = _collect_drift_results(run_drift_detection)
75136
build_dashboard(output_dir=str(dash_dir), output_format="json")
76137

77138
strategies: list[dict[str, Any]] = []
@@ -139,7 +200,7 @@ def main() -> int:
139200

140201
issue_results: list[dict[str, Any]] = []
141202
for domain in DOMAINS:
142-
drifts = run_drift_detection(domain)
203+
drifts = drift_results.get(domain, [])
143204
for drift in drifts:
144205
score = float(drift.drift_score or 0.0)
145206
label = f"[{domain}] {drift.strategy_profile}: drift_score={score:.2f}"
@@ -156,18 +217,36 @@ def main() -> int:
156217
body=f"Quant-monitor detected critical drift.\n\n- {line}",
157218
)
158219

159-
notify_lines = telegram_lines + critical_lines
220+
data_error_lines = [
221+
f"[{error['domain']}] {error['code']} ({error['error_type']})"
222+
for error in drift_errors
223+
]
224+
if collector_payload_invalid:
225+
data_error_lines.append("[collector] dashboard_data_unavailable")
226+
notify_lines = telegram_lines + critical_lines + data_error_lines
227+
telegram_sent = False
228+
duplicate_alert_suppressed = False
160229
if notify_lines:
161230
body = "🚨 quant-monitor health_cycle\n" + "\n".join(f"• {line}" for line in notify_lines)
162-
_send_telegram(body)
231+
fingerprint = _alert_fingerprint(notify_lines)
232+
duplicate_alert_suppressed = _is_duplicate_alert(root, fingerprint)
233+
if not duplicate_alert_suppressed:
234+
telegram_sent = _send_telegram(body)
235+
if telegram_sent:
236+
_record_alert(root, fingerprint)
237+
else:
238+
_clear_alert(root)
163239

164240
summary = {
165241
"as_of": datetime.now(timezone.utc).isoformat(),
166242
"domains": list(DOMAINS),
167243
"strategy_count": len(strategies),
168244
"telegram_alerts": notify_lines,
245+
"telegram_sent": telegram_sent,
246+
"duplicate_alert_suppressed": duplicate_alert_suppressed,
247+
"data_errors": drift_errors,
169248
"issues_created": len([r for r in issue_results if r.get("issue_url")]),
170-
"ok": not notify_lines,
249+
"ok": not notify_lines and not collector_payload_invalid,
171250
"collector_payload_valid": not collector_payload_invalid,
172251
"snapshot_data_status": normalized_payload.get("data_status"),
173252
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import importlib.util
2+
import tempfile
3+
import unittest
4+
from pathlib import Path
5+
6+
7+
ROOT = Path(__file__).resolve().parents[1]
8+
9+
10+
def _load_script(name: str):
11+
spec = importlib.util.spec_from_file_location(name, ROOT / "scripts" / f"{name}.py")
12+
module = importlib.util.module_from_spec(spec)
13+
assert spec.loader is not None
14+
spec.loader.exec_module(module)
15+
return module
16+
17+
18+
HEALTH_CYCLE = _load_script("health_cycle")
19+
DAILY_BRIEFING = _load_script("daily_briefing_builder")
20+
21+
22+
class MonitorFailClosedTests(unittest.TestCase):
23+
def test_health_cycle_collects_drift_errors_without_aborting(self) -> None:
24+
def unavailable(_domain):
25+
raise RuntimeError("sensitive path must not escape")
26+
27+
results, errors = HEALTH_CYCLE._collect_drift_results(
28+
unavailable,
29+
domains=("cn_equity", "us_equity"),
30+
)
31+
32+
self.assertEqual(results, {})
33+
self.assertEqual(
34+
errors,
35+
[
36+
{
37+
"domain": "cn_equity",
38+
"code": "drift_data_unavailable",
39+
"error_type": "RuntimeError",
40+
},
41+
{
42+
"domain": "us_equity",
43+
"code": "drift_data_unavailable",
44+
"error_type": "RuntimeError",
45+
},
46+
],
47+
)
48+
self.assertNotIn("sensitive path", str(errors))
49+
50+
def test_daily_briefing_collects_drift_errors_without_aborting(self) -> None:
51+
def unavailable(_domain):
52+
raise RuntimeError("sensitive path must not escape")
53+
54+
results, errors = DAILY_BRIEFING._collect_drift_results(
55+
unavailable,
56+
domains=("crypto",),
57+
)
58+
59+
self.assertEqual(results, {})
60+
self.assertEqual(
61+
errors,
62+
{
63+
"crypto": {
64+
"code": "drift_data_unavailable",
65+
"error_type": "RuntimeError",
66+
}
67+
},
68+
)
69+
self.assertNotIn("sensitive path", str(errors))
70+
71+
def test_health_cycle_alert_fingerprint_is_deduplicated_until_recovery(self) -> None:
72+
with tempfile.TemporaryDirectory() as tmp:
73+
root = Path(tmp)
74+
fingerprint = HEALTH_CYCLE._alert_fingerprint(["same failure"])
75+
76+
self.assertFalse(HEALTH_CYCLE._is_duplicate_alert(root, fingerprint))
77+
HEALTH_CYCLE._record_alert(root, fingerprint)
78+
self.assertTrue(HEALTH_CYCLE._is_duplicate_alert(root, fingerprint))
79+
80+
HEALTH_CYCLE._clear_alert(root)
81+
self.assertFalse(HEALTH_CYCLE._is_duplicate_alert(root, fingerprint))
82+
83+
84+
if __name__ == "__main__":
85+
unittest.main()

service/briefing_consumer.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,12 @@ def _classify_report_payload(
167167

168168
if payload.get("ok") is False:
169169
error = str(payload.get("error") or "ok=false")
170-
level = BriefingAction.TELEGRAM if "circuit" in error.lower() else BriefingAction.GITHUB_ISSUE
170+
data_unavailable = str(payload.get("data_status") or "").strip().lower() == "unavailable"
171+
level = (
172+
BriefingAction.TELEGRAM
173+
if data_unavailable or "circuit" in error.lower()
174+
else BriefingAction.GITHUB_ISSUE
175+
)
171176
findings.append(
172177
BriefingFinding(source=source, level=level, reason=error, domain=str(payload.get("domain") or ""))
173178
)

tests/test_briefing_model_router.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,18 @@ def test_telegram_for_critical_drift(self) -> None:
9797
)
9898
self.assertEqual(findings[0].level, BriefingAction.TELEGRAM)
9999

100+
def test_telegram_when_briefing_data_is_unavailable(self) -> None:
101+
findings = consume_briefing_report(
102+
{
103+
"ok": False,
104+
"data_status": "unavailable",
105+
"domain": "us_equity",
106+
"error": "drift_data_unavailable:RuntimeError",
107+
}
108+
)
109+
self.assertEqual(len(findings), 1)
110+
self.assertEqual(findings[0].level, BriefingAction.TELEGRAM)
111+
100112
def test_consume_briefing_dir_reads_files(self) -> None:
101113
import json
102114
import tempfile

0 commit comments

Comments
 (0)