jarvis / scripts /check_quality_trends.py
Jonathan Haas
feat: complete wave 113 no-hardware reliability expansion
b28a7b2
Raw
History Blame Contribute Delete
7.32 kB
#!/usr/bin/env python
from __future__ import annotations
import argparse
import json
import subprocess
import time
from pathlib import Path
from typing import Any
def _load_json(path: Path) -> dict[str, Any]:
payload = json.loads(path.read_text(encoding="utf-8"))
if isinstance(payload, dict):
return payload
raise ValueError(f"Expected object JSON at {path}")
def _mean(values: list[float]) -> float:
if not values:
return 0.0
return sum(values) / float(len(values))
def _phase_duration(summary: dict[str, Any], phase_name: str) -> float:
for row in summary.get("results", []):
if not isinstance(row, dict):
continue
if str(row.get("phase", "")) != phase_name:
continue
try:
return float(row.get("duration_sec", 0.0) or 0.0)
except (TypeError, ValueError):
return 0.0
return 0.0
def _metric_snapshot(soak: dict[str, Any], fault: dict[str, Any]) -> dict[str, float]:
soak_durations = [
float(row.get("duration_sec", 0.0) or 0.0)
for row in soak.get("results", [])
if isinstance(row, dict)
]
fault_durations = [
float(row.get("duration_sec", 0.0) or 0.0)
for row in fault.get("results", [])
if isinstance(row, dict)
]
return {
"soak_failed_count": float(int(soak.get("failed_count", 0) or 0)),
"soak_avg_phase_duration_sec": _mean(soak_durations),
"soak_sim_baseline_duration_sec": _phase_duration(soak, "sim_baseline"),
"fault_failed_count": float(int(fault.get("failed_count", 0) or 0)),
"fault_avg_phase_duration_sec": _mean(fault_durations),
"fault_quick_duration_sec": _phase_duration(fault, "fault_quick"),
}
def _delta_check(*, name: str, actual: float, baseline: float, max_delta: float) -> dict[str, Any]:
delta = actual - baseline
limit = baseline + max_delta
passed = delta <= max_delta
return {
"name": name,
"actual": actual,
"baseline": baseline,
"delta": delta,
"max_delta": max_delta,
"limit": limit,
"passed": passed,
}
def _max_check(*, name: str, actual: float, max_allowed: float) -> dict[str, Any]:
passed = actual <= max_allowed
return {
"name": name,
"actual": actual,
"max_allowed": max_allowed,
"passed": passed,
}
def _build_checks(
*,
metrics: dict[str, float],
baseline: dict[str, Any],
) -> list[dict[str, Any]]:
soak = baseline.get("soak", {}) if isinstance(baseline, dict) else {}
fault = baseline.get("fault", {}) if isinstance(baseline, dict) else {}
checks: list[dict[str, Any]] = [
_max_check(
name="soak_failed_count",
actual=float(metrics.get("soak_failed_count", 0.0)),
max_allowed=float(soak.get("max_failed_count", 0.0) or 0.0),
),
_delta_check(
name="soak_avg_phase_duration_sec",
actual=float(metrics.get("soak_avg_phase_duration_sec", 0.0)),
baseline=float(soak.get("baseline_avg_phase_duration_sec", 0.0) or 0.0),
max_delta=float(soak.get("max_avg_phase_duration_delta_sec", 0.0) or 0.0),
),
_delta_check(
name="soak_sim_baseline_duration_sec",
actual=float(metrics.get("soak_sim_baseline_duration_sec", 0.0)),
baseline=float(soak.get("baseline_sim_baseline_duration_sec", 0.0) or 0.0),
max_delta=float(soak.get("max_sim_baseline_duration_delta_sec", 0.0) or 0.0),
),
_max_check(
name="fault_failed_count",
actual=float(metrics.get("fault_failed_count", 0.0)),
max_allowed=float(fault.get("max_failed_count", 0.0) or 0.0),
),
_delta_check(
name="fault_avg_phase_duration_sec",
actual=float(metrics.get("fault_avg_phase_duration_sec", 0.0)),
baseline=float(fault.get("baseline_avg_phase_duration_sec", 0.0) or 0.0),
max_delta=float(fault.get("max_avg_phase_duration_delta_sec", 0.0) or 0.0),
),
_delta_check(
name="fault_quick_duration_sec",
actual=float(metrics.get("fault_quick_duration_sec", 0.0)),
baseline=float(fault.get("baseline_quick_duration_sec", 0.0) or 0.0),
max_delta=float(fault.get("max_quick_duration_delta_sec", 0.0) or 0.0),
),
]
return checks
def _run_if_missing(path: Path, command: list[str]) -> None:
if path.exists():
return
path.parent.mkdir(parents=True, exist_ok=True)
subprocess.run(command, check=True)
def main() -> int:
parser = argparse.ArgumentParser(description="Quality trend gate using soak/fault artifacts.")
parser.add_argument(
"--soak-artifact",
default=".artifacts/quality/soak-profile-fast.json",
help="Soak profile artifact JSON path",
)
parser.add_argument(
"--fault-artifact",
default=".artifacts/quality/fault-campaign-quick-repeat1.json",
help="Fault campaign artifact JSON path",
)
parser.add_argument(
"--baseline",
default="config/quality-trend-baselines.json",
help="Baseline thresholds JSON path",
)
parser.add_argument(
"--output",
default=".artifacts/quality/quality-trend-gate.json",
help="Output summary JSON path",
)
parser.add_argument(
"--no-build-missing",
action="store_true",
help="Do not auto-generate missing artifacts.",
)
args = parser.parse_args()
soak_artifact = Path(args.soak_artifact)
fault_artifact = Path(args.fault_artifact)
baseline_path = Path(args.baseline)
output_path = Path(args.output)
if not args.no_build_missing:
_run_if_missing(
soak_artifact,
[
"./scripts/run_soak_profile.py",
"--profile",
"fast",
"--repeat",
"1",
"--output",
str(soak_artifact),
],
)
_run_if_missing(
fault_artifact,
[
"./scripts/run_fault_campaign.py",
"--profiles",
"quick",
"--repeat",
"1",
"--output",
str(fault_artifact),
],
)
soak = _load_json(soak_artifact)
fault = _load_json(fault_artifact)
baseline = _load_json(baseline_path)
metrics = _metric_snapshot(soak, fault)
checks = _build_checks(metrics=metrics, baseline=baseline)
accepted = all(bool(check.get("passed")) for check in checks)
summary = {
"accepted": accepted,
"checks": checks,
"metrics": metrics,
"artifacts": {
"soak_artifact": str(soak_artifact),
"fault_artifact": str(fault_artifact),
"baseline": str(baseline_path),
},
"generated_at": time.time(),
}
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text(json.dumps(summary, indent=2), encoding="utf-8")
print(json.dumps(summary, indent=2))
return 0 if accepted else 1
if __name__ == "__main__":
raise SystemExit(main())