Files
orchestrator/docs/work-items/ORCH-020/08-data-requirements.md
claude-bot 6c204548a7
All checks were successful
CI / test (push) Successful in 1m14s
architect(ET): auto-commit from architect run_id=798
2026-06-17 21:16:50 +03:00

86 lines
6.3 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
work_item: ORCH-020
stage: architecture
author_agent: architect
status: proposed
created_at: 2026-06-17
model_used: claude-opus-4-8
---
# 08 — Требования к данным: ORCH-020 — Оценка задачи, запускаемая статусом «Оценка»
Work Item: **ORCH-020** · Repo: **orchestrator** · Стадия: architecture
> When-applicable / информационный (гейтом не парсится). Одна **новая аддитивная** таблица; существующие
> таблицы (`tasks`/`agent_runs`/`jobs`/…) — **не изменяются** (NFR-8).
## Изменения схемы БД
**Новая аддитивная таблица `task_estimates`** (`CREATE TABLE IF NOT EXISTS` в `db.init_db()`, паттерн
`coverage_baseline`/`lessons`/`transition_lease`; идемпотентно, restart-safe на общей прод-БД):
```sql
CREATE TABLE IF NOT EXISTS task_estimates (
id INTEGER PRIMARY KEY AUTOINCREMENT,
work_item_id TEXT NOT NULL UNIQUE, -- ключ/UPSERT-цель (issue может не иметь task на момент оценки)
task_id INTEGER, -- FK tasks.id; НУЛЛАБЕЛЕН до старта пайплайна
repo TEXT,
-- Прогноз (на момент перевода в «Оценка»):
forecast_tokens INTEGER,
forecast_seconds INTEGER,
forecast_cost_usd REAL,
forecast_story_points INTEGER, -- из {1,2,3,5,8}
-- Факт (на момент перехода задачи в `done`):
actual_tokens INTEGER,
actual_seconds INTEGER,
actual_cost_usd REAL,
actual_story_points INTEGER, -- из {1,2,3,5,8}
-- Метаданные:
source TEXT, -- 'status' | 'manual' | 'api'
estimate_count INTEGER NOT NULL DEFAULT 1, -- число пере-оценок (инкремент при UPSERT)
created_at TEXT NOT NULL DEFAULT (datetime('now')),
updated_at TEXT
);
CREATE INDEX IF NOT EXISTS idx_task_estimates_repo ON task_estimates (repo);
CREATE INDEX IF NOT EXISTS idx_task_estimates_task_id ON task_estimates (task_id);
```
- **`UNIQUE(work_item_id)`** — несущий инвариант идемпотентной пере-оценки (BR-T4/AC-T4): повторный перевод
в «Оценка» делает **UPSERT** (`INSERT … ON CONFLICT(work_item_id) DO UPDATE …`), обновляя одну строку и
инкрементируя `estimate_count`; дублей строк нет.
- **Дельта** прогноз↔факт **не хранится отдельной колонкой** — вычисляется на чтение из forecast/actual
(избегаем рассинхрона; калибровке достаточно обеих величин). При желании реализатор может добавить
материализованные `delta_*` — не обязательно (BR-10 требует «обе величины + дельту»; вычисляемая дельта
это удовлетворяет).
- **Индексы:** по `repo` (выборка/снапшот по проекту) и `task_id` (связь с задачей). По `work_item_id`
индекс создаётся автоматически (UNIQUE).
## Новые/изменённые сущности
- **Хелперы `db.py`** (каждый открывает/закрывает свою connection, паттерн `coverage_baseline`/`lessons`;
leaf `estimator`/вызывающие оборачивают в never-raise):
- `record_estimate(work_item_id, repo, task_id=None, forecast_*=…, source='status') -> int` — UPSERT
прогноза по `work_item_id`; инкремент `estimate_count`, стамп `updated_at`.
- `set_actual(work_item_id, actual_tokens, actual_seconds, actual_cost_usd, actual_story_points,
task_id=None) -> bool` — запись факта; **не трогает** forecast-поля.
- `get_estimate(work_item_id) -> dict | None` — текущая строка прогноз/факт.
- `estimates_snapshot(limit=…) -> dict` — read-only для блока `estimator` в `GET /queue`.
- **Read-only агрегат истории** `db.completed_task_stats(repo, track) -> {n, mean_tokens, mean_cost_usd,
mean_seconds}` — поверх `agent_runs` (токены/стоимость, как `task_usage_summary`) и `tasks`
(`stage='done'`, время = `updated_at created_at` с отсечкой `estimator_wall_cap_s`). **Только чтение**
существующих таблиц; новых колонок не вводит.
## Совместимость данных / миграции
- **Аддитивность (NFR-8):** только новая таблица + новые read/write-хелперы; **ни одной** правки
существующих таблиц/колонок/индексов. `STAGE_TRANSITIONS`/`QG_CHECKS`/machine-verdict-ключи независимы
от данных оценки.
- **Идемпотентность миграции:** `CREATE TABLE IF NOT EXISTS` + `CREATE INDEX IF NOT EXISTS` — no-op на уже
созданной таблице; безопасно на живой общей прод-БД (enduro не затронут — таблица общая, но писать в неё
будет только self-hosting-скоуп; строки enduro не появляются, пока репо вне `estimator_repos`).
- **Restart-safe:** строки `task_estimates` переживают рестарт; прогноз, сделанный на бэклоге (с
`task_id=NULL`), сохраняется до старта пайплайна и связывается с `task_id` позже (best-effort).
- **Влияние на общую прод-БД:** таблица малая (одна строка на оценённый issue), индексы лёгкие; нагрузка на
hot-path **нулевая** (claim/queue не читают `task_estimates`). Откат (`ORCH_ESTIMATOR_ENABLED=false`)
оставляет таблицу пустой/неиспользуемой — безвредно.