feat(incident): Phase 27 frequency_snapshot DB 持久化 — incidents 表新增 JSONB 欄位
Some checks failed
CD Pipeline / build-and-deploy (push) Has been cancelled
Some checks failed
CD Pipeline / build-and-deploy (push) Has been cancelled
frequency_stats 原僅存 Redis(TTL 35天),Pod 重啟或超期即失 - incidents.frequency_snapshot JSONB:建立 incident 時寫入快照,永久保存 - incident_repository: _record_to_incident 還原 IncidentFrequencyStats - _incident_to_record_data 序列化 frequency_stats 快照到 DB - Migration: phase27_incident_frequency_snapshot.sql 已執行完成 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -565,6 +565,15 @@ class IncidentRecord(Base):
|
||||
comment="事件結果與人類回饋",
|
||||
)
|
||||
|
||||
# === 頻率快照 (Phase 27, 2026-04-10 ogt) ===
|
||||
# frequency_stats 原本只存記憶體/Redis(TTL=35天),Pod重啟或超期即失
|
||||
# 此欄位在 incident 建立時寫入快照,永久保存當時的頻率統計
|
||||
frequency_snapshot: Mapped[dict[str, Any] | None] = mapped_column(
|
||||
JSON,
|
||||
nullable=True,
|
||||
comment="建立時刻的 AnomalyFrequency 快照,永久保存 (Phase 27)",
|
||||
)
|
||||
|
||||
# === 時間軸 ===
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True),
|
||||
|
||||
@@ -19,7 +19,7 @@ from sqlalchemy import select
|
||||
|
||||
from src.db.base import get_db_context
|
||||
from src.db.models import IncidentRecord
|
||||
from src.models.incident import Incident, IncidentStatus, Severity
|
||||
from src.models.incident import Incident, IncidentFrequencyStats, IncidentStatus, Severity
|
||||
from src.repositories.interfaces import IIncidentRepository
|
||||
|
||||
logger = structlog.get_logger(__name__)
|
||||
@@ -31,6 +31,14 @@ logger = structlog.get_logger(__name__)
|
||||
|
||||
def _record_to_incident(record: IncidentRecord) -> Incident:
|
||||
"""Convert DB IncidentRecord to Pydantic Incident"""
|
||||
# Phase 27: 從 DB 快照還原 frequency_stats
|
||||
frequency_stats = None
|
||||
if getattr(record, "frequency_snapshot", None):
|
||||
try:
|
||||
frequency_stats = IncidentFrequencyStats(**record.frequency_snapshot)
|
||||
except Exception:
|
||||
pass # 欄位結構不符時跳過,不影響主流程
|
||||
|
||||
return Incident(
|
||||
incident_id=record.incident_id,
|
||||
status=IncidentStatus(record.status),
|
||||
@@ -42,11 +50,17 @@ def _record_to_incident(record: IncidentRecord) -> Incident:
|
||||
updated_at=record.updated_at,
|
||||
resolved_at=record.resolved_at,
|
||||
closed_at=record.closed_at,
|
||||
frequency_stats=frequency_stats,
|
||||
)
|
||||
|
||||
|
||||
def _incident_to_record_data(incident: Incident) -> dict[str, Any]:
|
||||
"""Convert Pydantic Incident to dict for DB record"""
|
||||
# Phase 27: 序列化 frequency_stats 快照到 DB
|
||||
frequency_snapshot = None
|
||||
if incident.frequency_stats:
|
||||
frequency_snapshot = incident.frequency_stats.model_dump()
|
||||
|
||||
return {
|
||||
"incident_id": incident.incident_id,
|
||||
"status": incident.status.value,
|
||||
@@ -58,6 +72,7 @@ def _incident_to_record_data(incident: Incident) -> dict[str, Any]:
|
||||
"updated_at": incident.updated_at,
|
||||
"resolved_at": incident.resolved_at,
|
||||
"closed_at": incident.closed_at,
|
||||
"frequency_snapshot": frequency_snapshot,
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user