"""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."""