Files
writer-work-flow/apps/api/ww_api/schemas/projects.py
Yaojia Wang d028c790c9 feat(agents,api): 一致性冲突一键采纳补丁对 original/replacement + SSE/契约透传
- Conflict schema 增 original/replacement(可缺):可局部修复时审稿提议精确补丁
- 续审 prompt 指示填逐字 original + 改后 replacement,不可定位则留空
- orchestrator SSE conflict 事件 + apps/api ReviewConflictView 透传两字段
- 重生成 TS 客户端 schema.d.ts;memory/contracts.md 记录契约变更(向后兼容)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-22 18:28:04 +02:00

168 lines
5.3 KiB
Python
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.

"""项目(立项)与章节草稿的请求/响应 schemaC3 / ARCH §7.2)。
snake_case前端经 OpenAPI 生成 TS 类型消费。改字段 → 前端必须 `pnpm gen:api`。
"""
from __future__ import annotations
import uuid
from typing import Any, Literal
from pydantic import BaseModel, Field
class ProjectCreateRequest(BaseModel):
"""POST /projects立项向导字段owner_id 由后端补 stub不入参"""
title: str = Field(min_length=1)
genre: str | None = None
logline: str | None = None
premise: str | None = None
theme: str | None = None
selling_points: list[str] = Field(default_factory=list)
structure: str | None = None
class ProjectResponse(BaseModel):
"""项目视图(创建/列表/详情共用)。"""
id: uuid.UUID
title: str
genre: str | None = None
logline: str | None = None
premise: str | None = None
theme: str | None = None
selling_points: list[str] = Field(default_factory=list)
structure: str | None = None
class ProjectListResponse(BaseModel):
"""GET /projects项目列表。"""
projects: list[ProjectResponse] = Field(default_factory=list)
class DraftStreamRequest(BaseModel):
"""POST /projects/:id/chapters/:no/draft本章生成的可选输入T4-b
`directive` = 作者本章指令(覆盖/补充大纲节拍)。临时输入,不持久化、无迁移;
后端只入 volatile守缓存前缀不变量 #9。无 body 时退化为纯按大纲生成(向后兼容)。
"""
directive: str | None = None
class DraftSaveRequest(BaseModel):
"""PUT /projects/:id/chapters/:no/draft自动保存草稿正文。"""
text: str
class DraftResponse(BaseModel):
"""草稿保存结果(脱敏:只回元信息 + 长度,不回灌正文以外的衍生)。"""
project_id: uuid.UUID
chapter_no: int
volume: int
status: str
version: int
length: int
class DraftView(BaseModel):
"""GET .../draft回灌已保存草稿含正文供工作台重载编辑器
与 PUT 的 `DraftResponse` 同元信息,**额外带 `content`**——读侧需要正文以重建编辑器,
保存侧不回灌正文故不带。`length` 仍按 content 字符数派生。
"""
project_id: uuid.UUID
chapter_no: int
volume: int
status: str
version: int
content: str
length: int
# ---- 审稿T2.5----
class ReviewRequest(BaseModel):
"""POST /projects/:id/chapters/:no/review可选携带待审草稿正文。
不传 `draft` 时端点回退到已保存的草稿chapter_repo
"""
draft: str | None = None
class ReviewConflictView(BaseModel):
"""单条一致性冲突chapter_reviews.conflicts JSONB 子项;形对齐 SSE `conflict` 事件)。
强类型化此前的裸 `dict[str, Any]`,给 TS 客户端真实字段类型。容忍历史/缺省(默认值)。
"""
type: str = ""
where: str = ""
refs: list[str] = Field(default_factory=list)
suggestion: str = ""
# 一键采纳补丁对(可缺):留痕里带则前端「采纳改法」可把 original→replacement 改入终稿。
original: str | None = None
replacement: str | None = None
class ReviewHistoryItem(BaseModel):
"""单条审稿留痕GET .../reviews 历史项snake_case"""
id: uuid.UUID
project_id: uuid.UUID
chapter_no: int
chapter_version: int | None = None
conflicts: list[ReviewConflictView] = Field(default_factory=list)
foreshadow_sug: list[dict[str, Any]] = Field(default_factory=list)
style: dict[str, Any] | None = None
pace: dict[str, Any] | None = None
health_score: int | None = None
decisions: dict[str, Any] | None = None
class ReviewHistoryResponse(BaseModel):
"""GET /projects/:id/chapters/:no/reviews审稿历史新→旧"""
reviews: list[ReviewHistoryItem] = Field(default_factory=list)
# ---- 验收T2.4----
# 每个冲突的裁决:采纳改法 / 忽略 / 手改R5
Verdict = Literal["accept", "ignore", "manual"]
class ConflictDecision(BaseModel):
"""对最近一次审稿留痕里**某个冲突**(按其在 conflicts 列表的下标定位)的裁决。"""
conflict_index: int = Field(ge=0, description="冲突在最近审稿 conflicts 列表中的下标")
verdict: Verdict = Field(description="采纳改法 / 忽略 / 手改")
note: str | None = Field(default=None, description="可选裁决备注(如手改说明)")
class AcceptRequest(BaseModel):
"""POST /projects/:id/chapters/:no/accept裁决清单 + 可能改过的终稿。"""
final_text: str = Field(min_length=1, description="作者裁决/改稿后的最终验收文本")
decisions: list[ConflictDecision] = Field(
default_factory=list, description="对最近审稿每个冲突的裁决每冲突必有其一R5"
)
class AcceptResponse(BaseModel):
"""验收「本次将更新」清单ARCH §7.2 写回结果)。"""
project_id: uuid.UUID
chapter_no: int
accepted_version: int = Field(description="晋升到的 accepted 版次max+1")
digest_added: bool = Field(description="是否新增了一行 chapter_digests")
decisions_recorded: int = Field(description="本次写回的裁决条数")
review_id: uuid.UUID | None = Field(default=None, description="写回裁决的审稿留痕行 id")