fix(governance): normalize event and dispatch queries
All checks were successful
Code Review / ai-code-review (push) Successful in 9s
CD Pipeline / tests (push) Successful in 1m0s
CD Pipeline / build-and-deploy (push) Successful in 3m18s
CD Pipeline / post-deploy-checks (push) Successful in 1m17s

This commit is contained in:
Your Name
2026-05-14 18:31:11 +08:00
parent 6571260dd2
commit 08d28dc44b
4 changed files with 118 additions and 5 deletions

View File

@@ -85,8 +85,38 @@ def _extract_impact(details: dict) -> str:
return ""
def _extract_remediation(details: dict) -> str | None:
"""
將治理事件 details.remediation 正規化為前端可顯示的短字串。
Production 事件已出現 dict 形態(例如 {"items": [...]}API response
schema 則是字串。這裡做 read-side normalization避免歷史資料讓
/governance events 變成 500。
"""
remediation = details.get("remediation")
if remediation is None:
return None
if isinstance(remediation, str):
return remediation[:160]
if isinstance(remediation, dict):
for key in ("summary", "message", "reason", "action"):
value = remediation.get(key)
if isinstance(value, str) and value:
return value[:160]
items = remediation.get("items")
if isinstance(items, list):
normalized = [str(item) for item in items if item is not None]
if normalized:
return "".join(normalized[:3])[:160]
return str(remediation)[:160]
if isinstance(remediation, list):
normalized = [str(item) for item in remediation if item is not None]
return "".join(normalized[:3])[:160] if normalized else None
return str(remediation)[:160]
def _to_governance_event(row: AiGovernanceEvent) -> GovernanceEvent:
details = row.details or {}
details = row.details if isinstance(row.details, dict) else {}
return GovernanceEvent(
id=row.id,
event_type=row.event_type,
@@ -96,7 +126,7 @@ def _to_governance_event(row: AiGovernanceEvent) -> GovernanceEvent:
resolved_at=row.resolved_at,
impact=_extract_impact(details),
details=details,
remediation=details.get("remediation"),
remediation=_extract_remediation(details),
dispatch_ids=details.get("dispatch_ids", []),
)
@@ -239,14 +269,14 @@ async def _query_dispatch_table(
d.dispatch_status,
d.decision_context,
d.playbook_id,
d.created_at,
d.dispatched_at AS created_at,
d.dispatched_at,
d.completed_at,
d.operator_note
NULL::text AS operator_note
FROM governance_remediation_dispatch d
JOIN ai_governance_events e ON e.id = d.governance_event_id
WHERE d.dispatch_status = :dispatch_status
ORDER BY d.created_at DESC
ORDER BY d.dispatched_at DESC
""")
count_sql = text("""