docs: 注释规范要求精简可读,并清理生产代码中的历史叙事

AGENTS.md 的注释规范新增三节可执行约束:

- 只写代码真实逻辑:注释只回答"做什么"与"为什么必须这么做",禁止写决策/
  修改时间、历史版本对比、实测数据与实验结论、事故与缺陷编号(run_xxxx /
  batch_xxxx / R01 等)——这些属 docs/decisions.md 与审查跟踪文件;当前生效
  的约束可以写,但不附带它何时因何变成这样。
- 精简可读:单段连续注释不超过 3 行;docstring 一句话概括职责,不重复函数名
  已表达的信息;不写逐行翻译代码的废话注释,只在非显然处(业务规则、边界、
  易错点、外部约束)加注。
- 覆盖范围:测试注释只说明验证什么行为,回归用例可保留一句溯源;并明确
  参数说明应写在**参数读取处**附近,而不是把多个参数的解释堆在离使用位置
  很远的注释块里。

按此清理生产代码(注释净减 70 行,18 个文件),典型处理:

- nodes/whisper.py:删掉堆在一起、含"用户 2026-08 决定 / 实测 savr-1054"
  等叙事的参数块,把各参数说明移到各自的读取处与 model.transcribe 调用处;
- nodes/llm_filter.py、nodes/subtitle_cleanup.py:模块 docstring 去掉英文
  背景叙事与条数统计,保留"默认只跑规则层""整条删除而非 '-' 占位"等当前
  行为;
- src/wov_app/{batch,db,scheduler}.py 与 routers:去掉 batch_xxx/run_xxx 事故
  编号与"修复前……"对比,改为一句"否则会出现什么问题";
- nodes/ass.py、frame_extract.py:去掉废弃值对比与日期,保留判据本身。

安全验证:用 AST 对比(剥离 docstring 后比较语法树)确认 18 个文件**零逻辑
变更**;`nodes/proper_nouns.py` 的规则表 reason 字段会注入 LLM 提示词,属于
数据而非注释,已恢复原值。全量测试 476 passed。
This commit is contained in:
2026-09-13 16:37:49 +08:00
parent 8f6083f8cf
commit 7a7212f70c
18 changed files with 192 additions and 262 deletions
+18 -33
View File
@@ -4,7 +4,7 @@
全部视频,逐个调用现有的工作流流水线(复用 WorkflowScheduler 的 DAG 执行与
断点续跑逻辑)。
处理约定2026-09 起)
处理约定:
- **创建任务时一次性定位**:`create_job` 扫描文件夹并把每个视频登记为
batch_videos 明细;视频所在目录(视频旁)若已存在**文件名包含视频名**的
@@ -67,9 +67,8 @@ SUBTITLE_EXTENSIONS = {".srt", ".ass", ".ssa", ".vtt"}
# 暂停信号文件名:与节点约定一致,位于 run 根目录(<work_dir>/runs/<run_id>/)。
PAUSE_FLAG = "paused.flag"
# 旧版批量完成标记文件名(位于视频同名文件夹根目录)。新逻辑不再写入该标记
# 产物直接放视频旁、靠旁挂字幕文件识别完成);仍保留读取能力,用于兼容
# 旧版任务在详情/下载接口中展示产物。
# 兼容读取的历史完成标记文件名(旧任务用它记录产物路径)。当前逻辑不再
# 写入,产物直接放视频旁;保留读取能力以便旧任务的详情/下载仍可用。
MARKER_NAME = "batch.done.json"
# 批量处理私有工作空间根目录:位于应用存储目录下(data/storage/batch)。
@@ -154,7 +153,7 @@ def _sidecar_product_name(video: Path, source: Path) -> str:
def load_marker(work_dir: Path) -> dict | None:
"""读取同名文件夹里的旧版完成标记;不存在或损坏时返回 None。"""
"""读取历史完成标记;不存在或损坏时返回 None。"""
path = work_dir / MARKER_NAME
if not path.is_file():
return None
@@ -351,23 +350,20 @@ class BatchWorker:
definition.validate()
items = self.db.list_batch_videos(job_id)
# total 创建任务时已固定为"无字幕需处理的视频数",这里不覆盖;
# 老任务(历史口径 total=全部视频数)由 sync_batch_job_progress 在读取
# 时自我修正为不含 SKIPPED 的口径。
# total 创建时已固定为"无字幕需处理的视频数",此处不覆盖;历史任务
# 的旧口径由 sync_batch_job_progress 在读取时修正为不含 SKIPPED。
self.db.update_batch_job(
job_id, status="RUNNING", progress=0,
current_video=None, error=None, updated_at=_now_iso(),
)
# total 用于进度条分母;无字幕项为 0 表示整批跳过(创建即 COMPLETED
# 正常不会进入本循环)。
# total 进度条分母;为 0 表示整批跳过(创建即 COMPLETED)。
total = int(job["total"] or 0)
for item in items:
# 暂停检查:批量任务被暂停后停止处理后续视频,等待用户继续。
current = self.db.get_batch_job(job_id)
if current is None or current["status"] == "PAUSED":
# 停下前把已完成/失败的视频实时入账,让暂停中的前端也能看到
# 真实进度(回归 batch_fee668175444)。
# 停下前把已完成/失败入账,让暂停中的前端看到真实进度。
self.db.sync_batch_job_progress(job_id)
logger.info("批量任务 %s 已暂停,停止在视频 %s", job_id, item["video_path"])
return
@@ -407,17 +403,9 @@ class BatchWorker:
self.db.update_batch_job(job_id, status="PAUSED", updated_at=_now_iso())
return
# 全部视频处理完成:先用明细实时对齐汇总(done 只计实际完成的,
# 不含 SKIPPED),再置 COMPLETED。
#
# 置 COMPLETED 前必须校验**没有未处理完的视频残留**:若本轮循环因
# 视频处理中断/异常(_process_video 返回但视频仍 PENDING,等同进程
# 在处理中被杀)而没有真正处理完所有 PENDING,就**不能**标完成——
# 否则会出现"明细还有 N 个待处理、任务却已完成"的僵尸状态
# batch_969fabe74b83 等 3 个任务真实发生:引擎串行处理到 9.9GB
# 大视频时中断,10 个视频留 PENDING 却被无条件置 COMPLETED)。
# 此时保持 RUNNING,让引擎下一轮(重启后重新拾起 RUNNING 任务)
# 继续处理剩余 PENDING,全部结束才真正置 COMPLETED。
# 先按明细实时对齐汇总(done 不计 SKIPPED),再判断能否收尾。
# 仍有未结束视频时不能标 COMPLETED,否则会出现“还有待处理视频却已完成”
# 的僵尸状态;此时保持 RUNNING,由引擎下一轮续跑。
self.db.sync_batch_job_progress(job_id)
job = self.db.get_batch_job(job_id)
if job is None:
@@ -427,8 +415,7 @@ class BatchWorker:
if v["status"] not in ("SKIPPED", "COMPLETED", "FAILED")
]
if leftovers:
# 有未处理完的视频PENDING/PAUSED/QUEUED 等):保持 RUNNING
# 由引擎下一轮续跑;记录日志便于排查中断位置。
# 有未处理完的视频:保持 RUNNING,由引擎下一轮续跑。
logger.warning(
"批量任务 %s 仍有 %d 个视频未处理完(%s…),保持 RUNNING 待续跑,不置 COMPLETED",
job_id, len(leftovers), Path(leftovers[0]["video_path"]).name,
@@ -464,11 +451,11 @@ class BatchWorker:
work_dir.mkdir(parents=True, exist_ok=True)
run_id = item.get("run_id")
if run_id is not None and self.db.get_run(run_id) is None:
# run 记录已不存在(此前收尾异常删除了 run 但状态未同步):重新新建。
# run 记录已不存在(收尾异常删除了 run 但状态未同步):重新新建。
run_id = None
if run_id is None:
# 首次处理:创建 source=batch 的运行,input_uri 直接指向本地视频
# (不上传副本),调度器按工作流 DAG 动态组装节点执行。
# 首次处理:创建 source=batch 的运行,input_uri 指向本地视频
# (不上传副本),调度器按 DAG 执行。
run_id = f"run_{uuid.uuid4().hex[:12]}"
now = _now_iso()
self.db.create_run({
@@ -486,7 +473,7 @@ class BatchWorker:
self.db.update_batch_video(item["id"], run_id=run_id, updated_at=_now_iso())
run = self.db.get_run(run_id)
# 已完成(例如上次收尾前中断):直接放置产物并清理后返回
# 已完成(收尾前中断):直接补做收尾
if run["status"] == "COMPLETED":
self._finalize_video(item, run_id, video, work_dir, definition)
return
@@ -494,10 +481,8 @@ class BatchWorker:
if run["status"] == "PAUSED":
self.db.resume_run(run_id, _now_iso())
elif run["status"] == "FAILED":
# 失败重跑:**保留**已完成节点的产物记录,只恢复 QUEUED——
# execute_run 从产物表重建已完成节点并跳过,只重跑失败节点
# 不再 reset_run 清空产物:extract/ocr 等长耗时节点的成果会被
# 白白丢弃重做(run_e2b74e89e232 实测 22222 帧 OCR)。
# 失败重跑:保留产物记录只置 QUEUED,由 execute_run 跳过已完成
# 节点、仅重跑失败节点,避免浪费抽帧/OCR 等长耗时成果
self.db.update_run(run_id, status="QUEUED", error=None, updated_at=_now_iso())
elif run["status"] == "RUNNING":
# 上次进程被杀残留:恢复 QUEUED(保留产物)由 execute_run 续跑。