174 lines
7.2 KiB
Python
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."""
|