fix(api): add quality summary slo metric
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
from src.services.adr100_slo_metrics_service import (
|
||||
Adr100SloMetricsSnapshot,
|
||||
AutomationOperationSample,
|
||||
QualitySummaryObservation,
|
||||
VerificationSample,
|
||||
render_adr100_slo_metrics,
|
||||
)
|
||||
@@ -38,6 +39,17 @@ def test_render_adr100_slo_metrics_outputs_required_series() -> None:
|
||||
knowledge_entries_created_24h=25,
|
||||
high_confidence_total=9,
|
||||
high_confidence_success_total=7,
|
||||
quality_summary_observations=[
|
||||
QualitySummaryObservation(
|
||||
project_id="awoooi",
|
||||
hours=24,
|
||||
limit=8,
|
||||
cache_status="miss",
|
||||
success=True,
|
||||
duration_seconds=1.234567,
|
||||
observed_at=1_778_756_100,
|
||||
),
|
||||
],
|
||||
emitted_at=1_778_756_000,
|
||||
)
|
||||
|
||||
@@ -58,6 +70,18 @@ def test_render_adr100_slo_metrics_outputs_required_series() -> None:
|
||||
assert "approval_records_high_confidence_total 9" in rendered
|
||||
assert "approval_records_high_confidence_success_total 7" in rendered
|
||||
assert "adr100_slo_emitter_last_success_timestamp 1778756000" in rendered
|
||||
assert (
|
||||
'awooop_truth_chain_quality_summary_last_duration_seconds{project_id="awoooi",'
|
||||
'hours="24",limit="8",cache_status="miss",success="true"} 1.234567'
|
||||
) in rendered
|
||||
assert (
|
||||
'awooop_truth_chain_quality_summary_last_success{project_id="awoooi",'
|
||||
'hours="24",limit="8",cache_status="miss",success="true"} 1'
|
||||
) in rendered
|
||||
assert (
|
||||
'awooop_truth_chain_quality_summary_observed_timestamp{project_id="awoooi",'
|
||||
'hours="24",limit="8",cache_status="miss",success="true"} 1778756100'
|
||||
) in rendered
|
||||
|
||||
|
||||
def test_render_adr100_slo_metrics_emits_zero_series_when_empty() -> None:
|
||||
@@ -71,6 +95,10 @@ def test_render_adr100_slo_metrics_emits_zero_series_when_empty() -> None:
|
||||
assert 'post_execution_verification_created_24h{outcome="none"} 0' in rendered
|
||||
assert "knowledge_entries_total 0" in rendered
|
||||
assert "knowledge_entries_created_24h 0" in rendered
|
||||
assert (
|
||||
'awooop_truth_chain_quality_summary_last_duration_seconds{project_id="none",'
|
||||
'hours="0",limit="0",cache_status="none",success="false"} 0'
|
||||
) in rendered
|
||||
|
||||
|
||||
def test_render_adr100_slo_metrics_escapes_labels() -> None:
|
||||
|
||||
@@ -10,6 +10,12 @@ from src.services.adr100_slo_status_service import (
|
||||
)
|
||||
|
||||
|
||||
QUALITY_SUMMARY_LATENCY_QUERY = (
|
||||
'max(awooop_truth_chain_quality_summary_last_duration_seconds{'
|
||||
'project_id="awoooi",limit="8",success="true"})'
|
||||
)
|
||||
|
||||
|
||||
class _FakePrometheusResponse:
|
||||
def __init__(self, payload: dict[str, Any]) -> None:
|
||||
self._payload = payload
|
||||
@@ -89,6 +95,7 @@ async def test_fetch_report_marks_ratio_slos_low_volume(monkeypatch):
|
||||
'sum(rate(automation_operation_log_total{outcome="auto_executed"}[5m]))': "0",
|
||||
"sum(rate(approval_records_high_confidence_total[1h]))": "0",
|
||||
"max(knowledge_entries_created_24h) or max(sli:km_growth_rate:24h)": "24",
|
||||
QUALITY_SUMMARY_LATENCY_QUERY: "1.2",
|
||||
}
|
||||
|
||||
monkeypatch.setattr(
|
||||
@@ -109,6 +116,8 @@ async def test_fetch_report_marks_ratio_slos_low_volume(monkeypatch):
|
||||
assert by_name["confidence_calibration"]["status"] == "skipped_low_volume"
|
||||
assert by_name["km_growth_rate"]["status"] == "ok"
|
||||
assert by_name["km_growth_rate"]["value"] == 24
|
||||
assert by_name["truth_chain_quality_summary_latency"]["status"] == "ok"
|
||||
assert by_name["truth_chain_quality_summary_latency"]["direction"] == "below"
|
||||
assert report["overall_status"] == "partial"
|
||||
assert report["overall_compliance"] == 1.0
|
||||
|
||||
@@ -121,6 +130,7 @@ async def test_fetch_report_classifies_hard_red_line_violation(monkeypatch):
|
||||
'sum(rate(automation_operation_log_total{outcome="auto_executed"}[5m]))': "0",
|
||||
"sum(rate(approval_records_high_confidence_total[1h]))": "0",
|
||||
"max(knowledge_entries_created_24h) or max(sli:km_growth_rate:24h)": "3",
|
||||
QUALITY_SUMMARY_LATENCY_QUERY: "1.2",
|
||||
}
|
||||
|
||||
monkeypatch.setattr(
|
||||
@@ -142,6 +152,36 @@ async def test_fetch_report_classifies_hard_red_line_violation(monkeypatch):
|
||||
assert report["overall_status"] == "violated"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_fetch_report_classifies_below_direction_slo(monkeypatch):
|
||||
values = {
|
||||
"sum(rate(automation_operation_log_total[5m]))": "0",
|
||||
'sum(rate(automation_operation_log_total{outcome="auto_executed"}[5m]))': "0",
|
||||
"sum(rate(approval_records_high_confidence_total[1h]))": "0",
|
||||
"max(knowledge_entries_created_24h) or max(sli:km_growth_rate:24h)": "24",
|
||||
QUALITY_SUMMARY_LATENCY_QUERY: "9.5",
|
||||
}
|
||||
|
||||
monkeypatch.setattr(
|
||||
"httpx.AsyncClient",
|
||||
lambda *args, **kwargs: _FakePrometheusClient(values),
|
||||
)
|
||||
monkeypatch.setattr(
|
||||
Adr100SloStatusService,
|
||||
"_fetch_verification_coverage",
|
||||
_low_volume_coverage,
|
||||
)
|
||||
|
||||
report = await Adr100SloStatusService().fetch_report()
|
||||
|
||||
by_name = {metric["name"]: metric for metric in report["metrics"]}
|
||||
latency = by_name["truth_chain_quality_summary_latency"]
|
||||
assert latency["status"] == "violated"
|
||||
assert latency["direction"] == "below"
|
||||
assert latency["value"] == 9.5
|
||||
assert report["overall_status"] == "violated"
|
||||
|
||||
|
||||
def test_verification_coverage_payload_flags_backlog():
|
||||
payload = _build_verification_coverage_payload(
|
||||
{
|
||||
|
||||
@@ -797,6 +797,12 @@ class TestRunSelfCheckGlobalFailureAlert:
|
||||
assert "governance_self_failure" not in calls
|
||||
|
||||
|
||||
QUALITY_SUMMARY_LATENCY_QUERY = (
|
||||
'max(awooop_truth_chain_quality_summary_last_duration_seconds{'
|
||||
'project_id="awoooi",limit="8",success="true"})'
|
||||
)
|
||||
|
||||
|
||||
class _FakePrometheusResponse:
|
||||
def __init__(self, value: str) -> None:
|
||||
self._value = value
|
||||
@@ -809,7 +815,7 @@ class _FakePrometheusResponse:
|
||||
|
||||
|
||||
class _FakePrometheusClient:
|
||||
def __init__(self, value: str) -> None:
|
||||
def __init__(self, value: str | dict[str, str]) -> None:
|
||||
self._value = value
|
||||
self.queries: list[str] = []
|
||||
|
||||
@@ -820,7 +826,10 @@ class _FakePrometheusClient:
|
||||
return False
|
||||
|
||||
async def get(self, *args, **kwargs): # noqa: ANN002, ANN003
|
||||
self.queries.append(str(kwargs.get("params", {}).get("query", "")))
|
||||
query = str(kwargs.get("params", {}).get("query", ""))
|
||||
self.queries.append(query)
|
||||
if isinstance(self._value, dict):
|
||||
return _FakePrometheusResponse(self._value.get(query, "NaN"))
|
||||
return _FakePrometheusResponse(self._value)
|
||||
|
||||
|
||||
@@ -838,6 +847,7 @@ class TestCheckSloCompliance:
|
||||
"decision_accuracy",
|
||||
"confidence_calibration",
|
||||
"km_growth_rate",
|
||||
"truth_chain_quality_summary_latency",
|
||||
):
|
||||
assert result[name]["status"] == "skipped"
|
||||
assert result[name]["reason"] == "prometheus_nan_or_inf"
|
||||
@@ -847,7 +857,13 @@ class TestCheckSloCompliance:
|
||||
async def test_km_growth_prefers_db_derived_24h_gauge(self):
|
||||
"""KM SLO 要優先使用 DB 24h gauge,避免新 counter 暖機時誤報 0."""
|
||||
agent = _make_agent()
|
||||
client = _FakePrometheusClient("25")
|
||||
client = _FakePrometheusClient({
|
||||
"sli:autonomy_rate:5m": "0.95",
|
||||
"sli:decision_accuracy:5m": "0.96",
|
||||
"sli:confidence_calibration:1h": "0.97",
|
||||
"max(knowledge_entries_created_24h) or max(sli:km_growth_rate:24h)": "25",
|
||||
QUALITY_SUMMARY_LATENCY_QUERY: "1.1",
|
||||
})
|
||||
|
||||
with patch("httpx.AsyncClient", return_value=client):
|
||||
result = await agent.check_slo_compliance()
|
||||
@@ -855,3 +871,4 @@ class TestCheckSloCompliance:
|
||||
assert "max(knowledge_entries_created_24h) or max(sli:km_growth_rate:24h)" in client.queries
|
||||
assert result["km_growth_rate"]["status"] == "ok"
|
||||
assert result["km_growth_rate"]["value"] == 25
|
||||
assert result["truth_chain_quality_summary_latency"]["status"] == "ok"
|
||||
|
||||
Reference in New Issue
Block a user