153 lines
5.5 KiB
Python
153 lines
5.5 KiB
Python
"""作者明确圈选的正文及保护范围;通用改写不制造机器味诊断。"""
|
|
|
|
from dataclasses import asdict
|
|
from typing import Literal
|
|
|
|
from pydantic import ConfigDict, StrictInt
|
|
from pydantic.dataclasses import dataclass
|
|
|
|
from muse.作品规划.接口 import 读取章节归属
|
|
from muse.正式变更.接口 import 固定哈希
|
|
from muse.正文写作.存储 import 正文存储
|
|
from muse.正文写作.模型 import 正文错误
|
|
from muse.正文写作.正文格式 import 文稿身份
|
|
from muse.正文写作.选段编辑 import 应用选段, 段落文本, 选段修改
|
|
|
|
|
|
@dataclass(frozen=True, config=ConfigDict(extra="forbid"))
|
|
class 原文选段:
|
|
paragraph_id: str
|
|
paragraph_hash: str
|
|
start: StrictInt
|
|
end: StrictInt
|
|
original: str
|
|
|
|
def __post_init__(self):
|
|
if not 0 <= self.start < self.end or not self.original:
|
|
raise 正文错误("圈选需要非空原文和明确码点范围")
|
|
self.重放项()
|
|
|
|
def 重放项(self):
|
|
return 选段修改(
|
|
self.paragraph_id,
|
|
self.paragraph_hash,
|
|
self.start,
|
|
self.end,
|
|
self.original,
|
|
self.original,
|
|
)
|
|
|
|
@property
|
|
def selection_id(self):
|
|
return 固定哈希(asdict(self))
|
|
|
|
|
|
@dataclass(frozen=True, config=ConfigDict(extra="forbid"))
|
|
class 选区坐标:
|
|
paragraph_id: str
|
|
start: StrictInt
|
|
end: StrictInt
|
|
original: str
|
|
|
|
|
|
def 定位原文选区(连, 作者, work_id, chapter_id, branch_id, revision, document_hash, 坐标):
|
|
from muse.正文写作.选段编辑 import 段落哈希
|
|
|
|
chapter = 读取章节归属(连, 作者, chapter_id)
|
|
row = 正文存储(连).读取(文稿身份(chapter_id, branch_id))
|
|
if (
|
|
chapter["work_id"] != work_id
|
|
or row is None
|
|
or (row["revision"], row["document_hash"]) != (revision, document_hash)
|
|
):
|
|
raise 正文错误("圈选正文版本已变化")
|
|
doc = 正文存储.恢复草稿(row)
|
|
if not 1 <= len(坐标) <= 32:
|
|
raise 正文错误("需要1至32个确切选段")
|
|
by_id = {p.paragraph_id: p for p in doc.paragraphs}
|
|
out = []
|
|
for s in 坐标:
|
|
if s.paragraph_id not in by_id:
|
|
raise 正文错误("圈选段落不存在")
|
|
out.append(
|
|
原文选段(s.paragraph_id, 段落哈希(by_id[s.paragraph_id]), s.start, s.end, s.original)
|
|
)
|
|
应用选段(doc, tuple(s.重放项() for s in out))
|
|
return out
|
|
|
|
|
|
@dataclass(frozen=True, config=ConfigDict(extra="forbid"))
|
|
class 选段写作授权:
|
|
work_id: str
|
|
chapter_id: str
|
|
branch_id: str
|
|
revision: StrictInt
|
|
document_hash: str
|
|
mode: Literal["expand", "compress", "polish", "rewrite"]
|
|
purpose: str
|
|
selections: tuple[原文选段, ...]
|
|
protected_ranges: tuple[原文选段, ...] = ()
|
|
references: tuple[str, ...] = ()
|
|
|
|
def __post_init__(self):
|
|
if self.revision < 1 or not self.purpose.strip() or not 1 <= len(self.selections) <= 32:
|
|
raise 正文错误("选段改写需已保存版本、目的和1至32个选段")
|
|
if not self.work_id or not self.chapter_id or not self.branch_id:
|
|
raise 正文错误("选段改写需明确作品、章节及分支")
|
|
if len({s.selection_id for s in self.selections}) != len(self.selections):
|
|
raise 正文错误("圈选不能重复")
|
|
if len(self.references) > 8:
|
|
raise 正文错误("参考方法不超过8个")
|
|
if len(set(self.references)) != len(self.references):
|
|
raise 正文错误("参考方法版本不能重复")
|
|
if not all(type(r) is str and r.strip() for r in self.references):
|
|
raise 正文错误("参考方法需要非空版本身份")
|
|
|
|
|
|
def 准备选段改写(连, 作者, 授权: 选段写作授权):
|
|
chapter = 读取章节归属(连, 作者, 授权.chapter_id)
|
|
store = 正文存储(连)
|
|
row = store.读取(文稿身份(授权.chapter_id, 授权.branch_id))
|
|
if (
|
|
chapter["work_id"] != 授权.work_id
|
|
or row is None
|
|
or (row["revision"], row["document_hash"]) != (授权.revision, 授权.document_hash)
|
|
):
|
|
raise 正文错误("圈选的正文版本已改变或不属于该作品")
|
|
doc = store.恢复草稿(row)
|
|
patches = tuple(s.重放项() for s in 授权.selections)
|
|
应用选段(doc, patches)
|
|
应用选段(doc, tuple(s.重放项() for s in 授权.protected_ranges))
|
|
for s in 授权.selections:
|
|
for p in 授权.protected_ranges:
|
|
if s.paragraph_id == p.paragraph_id and max(s.start, p.start) < min(s.end, p.end):
|
|
raise 正文错误("自动改写范围与作者保护区重叠")
|
|
slots = []
|
|
for s in 授权.selections:
|
|
paragraph = next(p for p in doc.paragraphs if p.paragraph_id == s.paragraph_id)
|
|
text = 段落文本(paragraph)
|
|
slots.append(
|
|
{
|
|
"selection_id": s.selection_id,
|
|
"original": s.original,
|
|
"context": text[max(0, s.start - 160) : s.end + 160],
|
|
}
|
|
)
|
|
return (
|
|
{
|
|
"work_id": 授权.work_id,
|
|
"chapter_id": 授权.chapter_id,
|
|
"branch_id": 授权.branch_id,
|
|
"revision": row["revision"],
|
|
"document_hash": row["document_hash"],
|
|
"document": doc,
|
|
},
|
|
patches,
|
|
{
|
|
"mode": 授权.mode,
|
|
"purpose": 授权.purpose,
|
|
"slots": slots,
|
|
"protected_ranges": [asdict(p) for p in 授权.protected_ranges],
|
|
},
|
|
)
|