Files
ss-tools/specs/034-task-status-center/data-model.md
busya 33ee976c48 feat(reports): Task Status Center — unified /reports dashboard
Страница /reports трансформирована в Центр статусов задач:

Backend:
- GET /api/reports/summary — агрегированные счётчики тип×статус (5 корзин)
- GET/PUT /api/settings/reports — глобальные настройки отчётов
- _filter_tasks_by_rbac() — row-level фильтрация по роли
- normalize_task_report: LLM-валидация с ошибками → FAILED/PARTIAL
- get_summary(): 5 корзин pending/running/awaiting_input/success/failed

Frontend:
- TaskCenterModel.svelte.ts (400 строк) — Screen Model
- SummaryPanel — сводная панель с цветовым кодированием и active filter
- ReportCard — humanized labels, duration, task_id, failed border
- FilterBar — search + sort + time range с label'ами
- Pagination — showingText, уникальные id для select
- Quick views: «Упавшие», «В работе», «Успешные»
- TaskDrawer: scroll-to-error, footer скрыт для terminal, «Н/Д» fix

Тесты: 48 backend + 38 frontend (2521 всего)
Build:   Console errors: 0
2026-07-02 18:53:58 +03:00

297 lines
13 KiB
Markdown

#region DataModel [C:4] [TYPE ADR] [SEMANTICS data-model, schema, pydantic, typescript, dto, task-status-center]
@BRIEF Data model for the Task Status Center feature — backend Pydantic schemas (new + modified), frontend TypeScript DTOs, and the cross-stack contract bridge.
@RATIONALE The data model defines WHAT flows between backend and frontend. Without explicit DTO contracts, type drift between Python and TypeScript is inevitable over long-horizon development.
@REJECTED Auto-generating TypeScript from Pydantic was rejected (R7) — the schema set is small enough that manual sync is proportionate and avoids build-step complexity.
## 1. Entity Relationship Summary
```
TaskRecord (SQLAlchemy, persisted)
└── Task (Pydantic, in-memory via TaskManager)
└── TaskReport (Pydantic, normalized for UI)
├── ReportCollection (paginated list)
├── ReportDetailView (single task detail)
└── TaskSummary (aggregated counts) ← NEW
TaskSummary
└── TaskTypeSummary[] (one per task_type)
└── StatusCounts (pending, running, awaiting_input, success, failed)
```
## 2. New Backend Schemas
### 2.1 TaskSummary (NEW)
```python
# backend/src/models/report.py (add to existing module)
# #region TaskSummary [C:3] [TYPE Class] [SEMANTICS summary, aggregation, task, status, count]
# @ingroup ReportModels
# @BRIEF Aggregated task counts by type and status for the summary dashboard.
# @DATA_CONTRACT Output: TaskSummary → frontend interface TaskSummary
# @RELATION DEPENDS_ON -> [TaskReport]
# @TEST_CONTRACT TaskSummaryModel -> { invariants: ["all counts >= 0", "total == sum of all status counts across all types"] }
class StatusCounts(BaseModel):
"""Counts per status for a single task type."""
pending: int = Field(default=0, ge=0)
running: int = Field(default=0, ge=0)
awaiting_input: int = Field(default=0, ge=0)
success: int = Field(default=0, ge=0)
failed: int = Field(default=0, ge=0)
class TaskTypeSummary(BaseModel):
"""Summary for one task type."""
task_type: TaskType
display_label: str
icon_token: str
counts: StatusCounts
total: int = Field(default=0, ge=0)
class TaskSummary(BaseModel):
"""Full summary response for the summary dashboard."""
by_type: list[TaskTypeSummary]
total_tasks: int = Field(default=0, ge=0)
active_tasks: int = Field(default=0, ge=0) # pending + running + awaiting_input
# #endregion TaskSummary
```
### 2.2 Enhanced ReportQuery (MODIFIED)
The existing `ReportQuery` already supports `task_types[]`, `statuses[]`, `search`, `time_from`, `time_to`, `sort_by`, `sort_order`. No schema changes needed — the filtering logic in the service method will be enhanced to use these parameters more thoroughly.
**Change:** `sort_by` validator adds `"created_at"` to allowed values (currently only `updated_at`, `status`, `task_type`).
### 2.3 Enhanced TaskReport (MODIFIED)
No new fields needed. The existing `TaskReport` already carries all required fields for the spec:
- `report_id` → task identifier in list
- `task_type` → filter/summary grouping
- `status` → filter/summary grouping + color coding
- `started_at` → temporal display
- `updated_at` → sort default
- `summary` → display text
- `error_context` → error display + next actions
- `source_ref` → linked resource (dashboard, dataset, config)
### 2.4 No DB Schema Changes
No new database tables or columns. The feature works entirely with:
- Existing `task_records` table (persisted task metadata)
- Existing `task_logs` table (persisted logs for Task Drawer)
- In-memory `TaskManager.get_all_tasks()` as the data source
## 3. New Frontend TypeScript DTOs
```typescript
// frontend/src/types/reports.ts (NEW FILE)
// #region ReportsTypes [C:3] [TYPE Module] [SEMANTICS types, dto, reports, task-status-center]
// @BRIEF TypeScript DTOs matching backend Pydantic schemas for the Task Status Center.
// @DATA_CONTRACT Input: TypeScript interfaces → Output: typed API responses
// @RELATION DEPENDS_ON -> [ReportModels] via @DATA_CONTRACT cross-stack
// @RATIONALE Typed API responses prevent field mismatch bugs and enable IDE autocomplete.
/** Canonical task types matching backend TaskType enum. */
export type TaskType =
| 'llm_verification'
| 'backup'
| 'migration'
| 'documentation'
| 'clean_release'
| 'unknown';
/** Normalized report status matching backend ReportStatus enum. */
export type ReportStatus =
| 'success'
| 'failed'
| 'in_progress'
| 'partial';
/** Error/recovery context for failed tasks. */
export interface ErrorContext {
code: string | null;
message: string;
next_actions: string[];
}
/** Single task report — matches backend TaskReport. */
export interface TaskReport {
report_id: string;
task_id: string;
task_type: TaskType;
status: ReportStatus;
started_at: string | null;
updated_at: string;
summary: string;
details?: Record<string, unknown> | null;
validation_record?: Record<string, unknown> | null;
error_context: ErrorContext | null;
source_ref?: Record<string, unknown> | null;
}
/** Query parameters for report list filtering — matches backend ReportQuery. */
export interface ReportQuery {
page: number;
page_size: number;
task_types: TaskType[];
statuses: ReportStatus[];
time_from?: string | null;
time_to?: string | null;
search?: string | null;
sort_by: 'updated_at' | 'created_at' | 'status' | 'task_type';
sort_order: 'asc' | 'desc';
}
/** Paginated collection — matches backend ReportCollection. */
export interface ReportCollection {
items: TaskReport[];
total: number;
page: number;
page_size: number;
has_next: boolean;
applied_filters: ReportQuery;
}
/** Detailed view — matches backend ReportDetailView. */
export interface ReportDetailView {
report: TaskReport;
timeline: Record<string, unknown>[];
diagnostics: Record<string, unknown> | null;
next_actions: string[];
}
// --- NEW for Task Status Center ---
/** Status counts for one task type in the summary. */
export interface StatusCounts {
pending: number;
running: number;
awaiting_input: number;
success: number;
failed: number;
}
/** Per-type summary entry. */
export interface TaskTypeSummary {
task_type: TaskType;
display_label: string;
icon_token: string;
counts: StatusCounts;
total: number;
}
/** Full summary response — matches backend TaskSummary. */
export interface TaskSummary {
by_type: TaskTypeSummary[];
total_tasks: number;
active_tasks: number;
}
/** WebSocket task status event from /ws/task-events. */
export interface TaskStatusEvent {
type: 'task_status';
task_id: string;
task: {
id: string;
plugin_id: string;
status: string;
started_at: string | null;
finished_at: string | null;
user_id: string | null;
params: Record<string, unknown> | null;
result: Record<string, unknown> | null;
};
}
/** Filter state for the Task Center UI (broader than ReportQuery). */
export interface TaskCenterFilter {
task_types: TaskType[];
statuses: ReportStatus[];
search: string;
time_range: '1h' | '24h' | '7d' | '30d' | 'all';
sort_by: 'updated_at' | 'created_at' | 'status' | 'task_type';
sort_order: 'asc' | 'desc';
}
/** UI screen states for the Task Center page. */
export type ScreenState =
| 'loading'
| 'ready'
| 'empty'
| 'error'
| 'disconnected'
| 'reconnecting';
// #endregion ReportsTypes
```
## 4. Cross-Stack Contract Bridge
```
Backend (Pydantic) Frontend (TypeScript)
───────────────── ─────────────────────
TaskType (str Enum) ←──→ TaskType (union type)
ReportStatus (str Enum) ←──→ ReportStatus (union type)
TaskReport (BaseModel) ←──→ interface TaskReport
ReportQuery (BaseModel) ←──→ interface ReportQuery
ReportCollection ←──→ interface ReportCollection
ReportDetailView ←──→ interface ReportDetailView
TaskSummary (NEW) ←──→ interface TaskSummary
StatusCounts (NEW) ←──→ interface StatusCounts
TaskTypeSummary (NEW) ←──→ interface TaskTypeSummary
```
Each bridge pair carries `@DATA_CONTRACT` annotation referencing the counterpart:
- Backend: `@DATA_CONTRACT Output: TaskSummary → frontend interface TaskSummary`
- Frontend: `@DATA_CONTRACT Input: TypeScript interface → Output: typed API response`
## 5. Data Flow Diagram
```
┌─────────────────────────────────────────────────────────────────┐
│ BROWSER │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ TaskCenterModel (.svelte.ts) │ │
│ │ │ │
│ │ loadInitialData() │ │
│ │ ├─ GET /api/reports/summary ──────► TaskSummary │ │
│ │ └─ GET /api/reports?page=1&... ──► ReportCollection │ │
│ │ │ │
│ │ connectWebSocket() │ │
│ │ └─ ws://host/ws/task-events ────► TaskStatusEvent x N │ │
│ │ │ │ │
│ │ └─ 100ms debounce buffer │ │
│ │ └─ apply to tasks[] ──► $derived summary │ │
│ │ │ │
│ │ selectTask(taskId) │ │
│ │ └─ openDrawerForTask(taskId) ──► TaskDrawer + logs WS │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ +page.svelte (thin render layer ~60 lines) │ │
│ │ ├─ SummaryPanel ← m.summary │ │
│ │ ├─ FilterBar ← m.filters, m.applyFilter() │ │
│ │ └─ TaskList ← m.filteredTasks, m.selectTask() │ │
│ └──────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ BACKEND (FastAPI) │
│ │
│ GET /api/reports/summary ───► ReportsService.get_summary() │
│ └─ task_manager.get_all_tasks() │
│ └─ filter by RBAC (role-based) │
│ └─ normalize → aggregate → TaskSummary │
│ │
│ GET /api/reports?... ───► ReportsService.list_reports() │
│ └─ same flow → filter, sort, paginate → ReportCollection │
│ │
│ ws://host/ws/task-events ───► EventBus.broadcast_status() │
│ └─ already broadcasts on every task status transition │
│ └─ NO CODE CHANGES NEEDED │
└─────────────────────────────────────────────────────────────────┘
```
#endregion DataModel