116 lines
3.8 KiB
Python
116 lines
3.8 KiB
Python
"""AIOS Task Center domain model.
|
|
|
|
The Task Center gives owners and authorized users a single Web UI page showing:
|
|
who assigned a task, who is executing it, progress, elapsed time, remaining
|
|
work, and whether completion notifications were sent.
|
|
"""
|
|
|
|
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()
|
|
|
|
|
|
def parse_time(value: str | None) -> datetime | None:
|
|
if not value:
|
|
return None
|
|
return datetime.fromisoformat(value.replace("Z", "+00:00"))
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TaskNotificationStatus:
|
|
required: bool = True
|
|
sent: bool = False
|
|
channel: str | None = None
|
|
sent_at: str | None = None
|
|
recipient_scope: str | None = None
|
|
last_error: str | None = None
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
return asdict(self)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class TaskProgress:
|
|
percent: int = 0
|
|
current_step: str = "pending"
|
|
completed_steps: list[str] = field(default_factory=list)
|
|
remaining_steps: list[str] = field(default_factory=list)
|
|
blocked_reason: str | None = None
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
return asdict(self)
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class AIOSTaskRecord:
|
|
task_id: str
|
|
title: str
|
|
assigned_by: str
|
|
executor: str
|
|
source: str
|
|
status: str = "pending"
|
|
priority: str = "normal"
|
|
created_at: str = field(default_factory=utc_now)
|
|
started_at: str | None = None
|
|
updated_at: str = field(default_factory=utc_now)
|
|
completed_at: str | None = None
|
|
progress: TaskProgress = field(default_factory=TaskProgress)
|
|
notification: TaskNotificationStatus = field(default_factory=TaskNotificationStatus)
|
|
owner_gate_required: bool = False
|
|
owner_gate_status: str | None = None
|
|
evidence: list[str] = field(default_factory=list)
|
|
|
|
def elapsed_seconds(self) -> int:
|
|
start = parse_time(self.started_at) or parse_time(self.created_at)
|
|
end = parse_time(self.completed_at) or datetime.now(timezone.utc)
|
|
return int((end - start).total_seconds()) if start else 0
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
data = asdict(self)
|
|
data["elapsed_seconds"] = self.elapsed_seconds()
|
|
data["progress"] = self.progress.to_dict()
|
|
data["notification"] = self.notification.to_dict()
|
|
return data
|
|
|
|
|
|
class TaskCenter:
|
|
def summarize(self, tasks: list[AIOSTaskRecord] | list[dict[str, Any]] | None = None) -> dict[str, Any]:
|
|
raw_tasks = tasks or []
|
|
task_dicts = [t.to_dict() if hasattr(t, "to_dict") else dict(t) for t in raw_tasks]
|
|
active = [t for t in task_dicts if t.get("status") in {"running", "pending", "blocked"}]
|
|
blocked = [t for t in task_dicts if t.get("status") == "blocked"]
|
|
completed = [t for t in task_dicts if t.get("status") == "completed"]
|
|
notification_pending = [
|
|
t for t in completed
|
|
if t.get("notification", {}).get("required") and not t.get("notification", {}).get("sent")
|
|
]
|
|
return {
|
|
"module": "task-center",
|
|
"route": "/admin/ai-evolution/tasks",
|
|
"title": "AIOS 任务中心",
|
|
"status": "attention_required" if blocked or notification_pending else "healthy",
|
|
"generated_at": utc_now(),
|
|
"summary": {
|
|
"total": len(task_dicts),
|
|
"active": len(active),
|
|
"blocked": len(blocked),
|
|
"completed": len(completed),
|
|
"notification_pending": len(notification_pending),
|
|
},
|
|
"tasks": sorted(task_dicts, key=lambda t: t.get("updated_at", ""), reverse=True),
|
|
}
|
|
|
|
|
|
__all__ = [
|
|
"TaskNotificationStatus",
|
|
"TaskProgress",
|
|
"AIOSTaskRecord",
|
|
"TaskCenter",
|
|
]
|