Files

174 lines
7.2 KiB
Python

"""Abstract store API for the external-task scheduled analysis feature."""
from __future__ import annotations
from abc import ABC, abstractmethod
from datetime import datetime
from typing import Any
class TaskAnalysisStore(ABC):
"""Tasks / bindings / runs / run-messages / content versions."""
# ── external tasks ──────────────────────────────────────────────────────
@abstractmethod
async def create_task(self, data: dict[str, Any]) -> dict[str, Any]:
"""Create a task. Raises ValueError when source_task_id already exists."""
@abstractmethod
async def get_task(self, task_id: str) -> dict[str, Any] | None: ...
@abstractmethod
async def get_task_by_source(self, source_task_id: str) -> dict[str, Any] | None: ...
@abstractmethod
async def list_tasks(self) -> list[dict[str, Any]]:
"""All tasks newest first, each augmented with its binding (if any)."""
@abstractmethod
async def delete_task_cascade(self, task_id: str) -> bool:
"""Delete task + binding + runs + messages + versions. True if existed."""
# ── bindings ────────────────────────────────────────────────────────────
@abstractmethod
async def get_binding(self, external_task_id: str) -> dict[str, Any] | None: ...
@abstractmethod
async def get_binding_by_id(self, binding_id: str) -> dict[str, Any] | None: ...
@abstractmethod
async def upsert_binding(self, external_task_id: str, data: dict[str, Any]) -> dict[str, Any]:
"""Create-or-update the 1:1 binding for a task (refresh chain snapshot)."""
@abstractmethod
async def delete_binding(self, external_task_id: str) -> bool: ...
@abstractmethod
async def due_bindings(self, now: datetime, *, limit: int = 20) -> list[dict[str, Any]]:
"""Enabled bindings with next_run_at <= now (execution candidates)."""
@abstractmethod
async def update_binding(self, binding_id: str, patch: dict[str, Any]) -> dict[str, Any] | None:
"""Patch mutable fields (next_run_at / last_status / direction_notes...)."""
@abstractmethod
async def last_bound_chain_id(self) -> str | None:
"""Most recently updated binding's chain id (default option for create API)."""
# ── runs ────────────────────────────────────────────────────────────────
@abstractmethod
async def create_run_once(self, data: dict[str, Any]) -> dict[str, Any]:
"""Idempotent create: same binding_id + scheduled_for returns existing row."""
@abstractmethod
async def get_run(self, run_id: str) -> dict[str, Any] | None: ...
@abstractmethod
async def list_runs(self, external_task_id: str, *, limit: int = 50) -> list[dict[str, Any]]: ...
@abstractmethod
async def update_run(self, run_id: str, patch: dict[str, Any]) -> dict[str, Any] | None: ...
@abstractmethod
async def stuck_runs(self, older_than: datetime, *, limit: int = 20) -> list[dict[str, Any]]:
"""Runs still pending/running past ``older_than`` (watchdog recovery)."""
# ── run messages ────────────────────────────────────────────────────────
@abstractmethod
async def append_run_messages(self, run_id: str, messages: list[dict[str, Any]]) -> int:
"""Append batch of {seat_agent_id, stage_index, event_type, content}."""
@abstractmethod
async def list_run_messages(
self, run_id: str, *, seat: str | None = None, limit: int = 500
) -> list[dict[str, Any]]: ...
# ── content versions (timeline) ─────────────────────────────────────────
@abstractmethod
async def create_version(self, data: dict[str, Any]) -> dict[str, Any]:
"""One version per run (idempotent on run_id: replaces existing)."""
@abstractmethod
async def list_versions(self, external_task_id: str, *, limit: int = 50, offset: int = 0) -> list[dict[str, Any]]:
"""Timeline versions newest first (对方详情接口数据源)."""
# ── data delivery: block defs / periods / submissions ────────────────────
@abstractmethod
async def ensure_block_defs(self, task_id: str) -> list[dict[str, Any]]:
"""Seed the 5 default block defs when absent (create/legacy tasks); return ordered."""
@abstractmethod
async def list_block_defs(self, task_id: str) -> list[dict[str, Any]]:
"""Block defs ordered by sort_order (empty list when never seeded)."""
@abstractmethod
async def update_block_defs(self, task_id: str, updates: list[dict[str, Any]]) -> list[dict[str, Any]]:
"""Patch title/sort_order per block_key. ValueError on unknown block_key."""
@abstractmethod
async def open_period(
self,
task_id: str,
*,
source: str,
run_id: str | None = None,
opened_at: datetime | None = None,
) -> dict[str, Any]:
"""Append a new timeline period (period_index = max+1). Idempotent on run_id.
``opened_at`` 默认取当前时间;演示/回填场景可显式指定刻度时刻。
"""
@abstractmethod
async def get_period(self, task_id: str, period_index: int) -> dict[str, Any] | None: ...
@abstractmethod
async def current_period(self, task_id: str) -> dict[str, Any] | None:
"""Latest period (submissions always land here)."""
@abstractmethod
async def list_periods(self, task_id: str, *, limit: int = 500) -> list[dict[str, Any]]:
"""Periods ascending by period_index (timeline ticks)."""
@abstractmethod
async def append_submission(
self,
task_id: str,
period_index: int,
block_key: str,
payload: dict[str, Any],
*,
submitted_by: str | None = None,
) -> dict[str, Any]:
"""Append to the submission log (seq per task+period+block)."""
@abstractmethod
async def list_submissions(
self, task_id: str, period_index: int, block_key: str, *, limit: int = 500
) -> list[dict[str, Any]]:
"""Submission log for one block of one period ordered by seq (merge source)."""
@abstractmethod
async def upsert_period_block(
self, task_id: str, period_index: int, block_key: str, payload: dict[str, Any]
) -> dict[str, Any]:
"""Write the merged payload for (task, period, block); bumps updated_at."""
@abstractmethod
async def list_task_blocks(self, task_id: str, *, limit: int = 2000) -> list[dict[str, Any]]:
"""All merged block rows of a task ({block_key, period_index, payload, updated_at})."""
@abstractmethod
async def get_setting(self, key: str) -> str | None:
"""Read one module setting value (``None`` when absent)."""
@abstractmethod
async def set_setting(self, key: str, value: str) -> None:
"""Upsert one module setting value."""