feat(governance): surface stale km burndown
All checks were successful
CD Pipeline / tests (push) Successful in 5m28s
Code Review / ai-code-review (push) Successful in 12s
Type Sync Check / check-type-sync (push) Successful in 25s
CD Pipeline / build-and-deploy (push) Successful in 4m6s
CD Pipeline / post-deploy-checks (push) Successful in 1m32s

This commit is contained in:
Your Name
2026-05-24 22:11:33 +08:00
parent f4253f22f8
commit ded2223d14
7 changed files with 720 additions and 7 deletions

View File

@@ -15,6 +15,7 @@ Unit Tests — AI Governance Endpoints (PR 1)
from __future__ import annotations
from datetime import datetime, timedelta, timezone
from types import SimpleNamespace
from unittest.mock import AsyncMock, patch
import pytest
@@ -40,6 +41,8 @@ from src.models.governance import (
KnowledgeStaleOwnerReviewBatchItem,
KnowledgeStaleOwnerReviewBatchQueueRequest,
KnowledgeStaleOwnerReviewBatchQueueResponse,
KnowledgeStaleOwnerReviewBurnDownItem,
KnowledgeStaleOwnerReviewBurnDownResponse,
KnowledgeStaleOwnerReviewCompleteRequest,
KnowledgeStaleOwnerReviewCompleteResponse,
KnowledgeStaleOwnerReviewInboxItem,
@@ -61,10 +64,12 @@ from src.services.governance_km_stale_review_service import (
_build_batch_owner_review_decision_context,
_build_batch_queue_plan_fingerprint,
_build_completion_plan_fingerprint,
_build_owner_review_burndown_items,
_build_owner_review_completion_audit_context,
_build_owner_review_inbox_item,
_build_stale_owner_review_decision_context,
_completion_stage_for_outcome,
_entries_to_stale_threshold,
)
from src.services.governance_km_stale_review_service import (
_build_stale_ratio_recheck_context as _build_stale_owner_review_recheck_context,
@@ -999,6 +1004,146 @@ class TestKmReviewDraftDedupe:
assert item.recommended_action == "refresh_with_evidence"
assert item.correlation_sources == ["incident", "playbook", "sentry", "signoz"]
def test_owner_review_burndown_endpoint_returns_completion_progress(self, client):
"""Burn-down endpoint 應把 pending、completion audit 與 recheck snapshot 集中呈現。"""
fake = KnowledgeStaleOwnerReviewBurnDownResponse(
project_id="awoooi",
burn_down_status="above_threshold",
current_snapshot=KnowledgeReviewDraftStaleRatioSnapshot(
stale_count=118,
total_count=200,
stale_ratio=0.59,
threshold=0.2,
stale_days=7,
),
entries_to_threshold=78,
pending_owner_reviews=9,
completed_owner_reviews=1,
completion_audit_total=1,
stale_ratio_recheck_total=1,
latest_stale_count_delta=-1,
latest_stale_ratio_delta=-0.005,
returned=1,
items=[
KnowledgeStaleOwnerReviewBurnDownItem(
completion_dispatch_id="dispatch-audit-001",
governance_event_id="event-001",
source_dispatch_id="dispatch-001",
recheck_dispatch_id="dispatch-recheck-001",
entry_id="km-001",
project_id="awoooi",
dispatch_status="succeeded",
workflow_stage="km_writeback_after_approval",
review_outcome="refresh_with_evidence",
owner="operator_console",
completed_at=NOW,
stale_ratio_snapshot=KnowledgeReviewDraftStaleRatioSnapshot(
stale_count=118,
total_count=200,
stale_ratio=0.59,
threshold=0.2,
stale_days=7,
),
stale_count_delta=-1,
stale_ratio_delta=-0.005,
above_threshold=True,
)
],
generated_at=NOW,
)
captured: dict = {}
async def mock_burndown(**kwargs):
captured.update(kwargs)
return fake
with patch(
"src.api.v1.ai_governance.query_km_stale_owner_review_burndown",
new=mock_burndown,
):
r = client.get(
"/api/v1/ai/governance/km-stale-owner-review-burndown"
"?project_id=awoooi&limit=12"
)
assert r.status_code == 200
assert captured == {"project_id": "awoooi", "limit": 12}
data = r.json()
assert data["schema_version"] == "km_stale_owner_review_burndown_v1"
assert data["burn_down_status"] == "above_threshold"
assert data["writes_on_read"] is False
assert data["manual_review_required"] is True
assert data["pending_owner_reviews"] == 9
assert data["completion_audit_total"] == 1
assert data["stale_ratio_recheck_total"] == 1
assert data["latest_stale_count_delta"] == -1
assert data["items"][0]["recheck_dispatch_id"] == "dispatch-recheck-001"
def test_owner_review_burndown_items_compute_chronological_delta(self):
older_snapshot = {
"stale_count": 119,
"total_count": 200,
"stale_ratio": 0.595,
"threshold": 0.2,
"stale_days": 7,
}
newer_snapshot = {
"stale_count": 118,
"total_count": 200,
"stale_ratio": 0.59,
"threshold": 0.2,
"stale_days": 7,
}
rows = [
SimpleNamespace(
id="dispatch-audit-new",
governance_event_id="event-001",
dispatch_status="succeeded",
completed_at=NOW,
decision_context={
"project_id": "awoooi",
"owner": "operator_console",
"review_outcome": "refresh_with_evidence",
"workflow": {
"project_id": "awoooi",
"entry_id": "km-002",
"source_dispatch_id": "dispatch-002",
"current_stage": "km_writeback_after_approval",
"stale_ratio_snapshot": newer_snapshot,
},
"stale_ratio_recheck": {"dispatch_id": "dispatch-recheck-new"},
},
),
SimpleNamespace(
id="dispatch-audit-old",
governance_event_id="event-001",
dispatch_status="succeeded",
completed_at=NOW - timedelta(minutes=10),
decision_context={
"project_id": "awoooi",
"owner": "operator_console",
"review_outcome": "refresh_with_evidence",
"workflow": {
"project_id": "awoooi",
"entry_id": "km-001",
"source_dispatch_id": "dispatch-001",
"current_stage": "km_writeback_after_approval",
"stale_ratio_snapshot": older_snapshot,
},
"stale_ratio_recheck": {"dispatch_id": "dispatch-recheck-old"},
},
),
]
items = _build_owner_review_burndown_items(rows, project_id="awoooi")
assert items[0].completion_dispatch_id == "dispatch-audit-new"
assert items[0].stale_count_delta == -1
assert items[0].stale_ratio_delta == pytest.approx(-0.005)
assert items[0].above_threshold is True
assert items[1].stale_count_delta is None
assert _entries_to_stale_threshold(items[0].stale_ratio_snapshot) == 78
def test_stale_owner_review_batch_context_is_operator_visible(self):
request = KnowledgeStaleOwnerReviewBatchQueueRequest(
project_id="awoooi",