123 lines
4.6 KiB
Python
123 lines
4.6 KiB
Python
"""Architect research timeline dashboard.
|
|
|
|
Shows weekly external research findings as a traceable architecture-impact flow:
|
|
research target -> finding -> impact -> recommendation -> version decision ->
|
|
governance event.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import asdict, dataclass, field
|
|
from datetime import datetime, timezone
|
|
from typing import Any
|
|
|
|
|
|
def utc_now() -> str:
|
|
return datetime.now(timezone.utc).isoformat()
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ResearchTimelineStep:
|
|
id: str
|
|
title: str
|
|
step_type: str
|
|
status: str
|
|
source: str
|
|
timestamp: str
|
|
impact: str = "none"
|
|
version_bump: str = "none"
|
|
detail: dict[str, Any] = field(default_factory=dict)
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
return asdict(self)
|
|
|
|
|
|
class ArchitectResearchTimeline:
|
|
ORDER = {
|
|
"research_planned": 10,
|
|
"finding_created": 20,
|
|
"impact_analyzed": 30,
|
|
"recommendation_created": 40,
|
|
"version_decision_created": 50,
|
|
"architecture_review_event_created": 60,
|
|
}
|
|
|
|
def build(self, findings: list[dict[str, Any]] | None = None, decisions: list[dict[str, Any]] | None = None) -> dict[str, Any]:
|
|
findings = findings or []
|
|
decisions = decisions or []
|
|
steps: list[ResearchTimelineStep] = [
|
|
ResearchTimelineStep(
|
|
id="research-plan",
|
|
title="每周技术情报调研计划",
|
|
step_type="research_planned",
|
|
status="ok",
|
|
source="architect-agent",
|
|
timestamp=utc_now(),
|
|
detail={"cadence": "weekly"},
|
|
)
|
|
]
|
|
for idx, finding in enumerate(findings):
|
|
steps.append(
|
|
ResearchTimelineStep(
|
|
id=finding.get("id", f"finding-{idx}"),
|
|
title=finding.get("title", "research finding"),
|
|
step_type="finding_created",
|
|
status="ok",
|
|
source=finding.get("source", "unknown"),
|
|
timestamp=finding.get("created_at", utc_now()),
|
|
impact=finding.get("impact", "none"),
|
|
detail=finding,
|
|
)
|
|
)
|
|
steps.append(
|
|
ResearchTimelineStep(
|
|
id=f"impact-{finding.get('id', idx)}",
|
|
title="架构影响分析",
|
|
step_type="impact_analyzed",
|
|
status="attention_required" if finding.get("impact") in {"architecture", "breaking", "new_capability"} else "ok",
|
|
source="architect-agent",
|
|
timestamp=utc_now(),
|
|
impact=finding.get("impact", "none"),
|
|
detail={"relevance": finding.get("relevance"), "recommendation": finding.get("recommendation")},
|
|
)
|
|
)
|
|
for idx, decision in enumerate(decisions):
|
|
steps.append(
|
|
ResearchTimelineStep(
|
|
id=decision.get("id", f"decision-{idx}"),
|
|
title="C/R/V 版本建议",
|
|
step_type="version_decision_created",
|
|
status=decision.get("status", "ok"),
|
|
source="architect-agent",
|
|
timestamp=decision.get("created_at", utc_now()),
|
|
version_bump=decision.get("version_bump", "none"),
|
|
detail=decision,
|
|
)
|
|
)
|
|
steps.sort(key=lambda s: (self.ORDER.get(s.step_type, 999), s.timestamp))
|
|
return {
|
|
"module": "architect-research-timeline",
|
|
"route": "/admin/ai-evolution/architect/research/timeline",
|
|
"title": "架构师技术情报时间线",
|
|
"status": self._status(steps),
|
|
"generated_at": utc_now(),
|
|
"steps": [step.to_dict() for step in steps],
|
|
"summary": self._summary(steps),
|
|
}
|
|
|
|
def _status(self, steps: list[ResearchTimelineStep]) -> str:
|
|
if any(step.status == "attention_required" for step in steps):
|
|
return "attention_required"
|
|
return "healthy"
|
|
|
|
def _summary(self, steps: list[ResearchTimelineStep]) -> dict[str, Any]:
|
|
return {
|
|
"findings": len([s for s in steps if s.step_type == "finding_created"]),
|
|
"architecture_impacts": len([s for s in steps if s.impact in {"architecture", "breaking", "new_capability"}]),
|
|
"version_decisions": len([s for s in steps if s.step_type == "version_decision_created"]),
|
|
"recommended_bumps": sorted({s.version_bump for s in steps if s.version_bump != "none"}),
|
|
}
|
|
|
|
|
|
__all__ = ["ResearchTimelineStep", "ArchitectResearchTimeline"]
|