Przeglądaj źródła

refactor: review_pipeline 适配 v6 schema——无评分,结构化问题清单

lingfengQAQ 2 miesięcy temu
rodzic
commit
b4884019ae

+ 39 - 53
webnovel-writer/scripts/data_modules/tests/test_webnovel_unified_cli.py

@@ -233,33 +233,25 @@ def test_review_pipeline_builds_artifacts(tmp_path):
     review_results_path.write_text(
         json.dumps(
             {
-                "continuity-checker": {
-                    "agent": "continuity-checker",
-                    "chapter": 20,
-                    "overall_score": 76,
-                    "pass": True,
-                    "issues": [
-                        {
-                            "id": "TIME_001",
-                            "type": "TIMELINE_ISSUE",
-                            "severity": "high",
-                            "location": "第2段",
-                            "description": "时间线回跳",
-                            "suggestion": "补时间锚点",
-                        }
-                    ],
-                    "metrics": {},
-                    "summary": "存在时间线问题",
-                },
-                "consistency-checker": {
-                    "agent": "consistency-checker",
-                    "chapter": 20,
-                    "overall_score": 84,
-                    "pass": True,
-                    "issues": [],
-                    "metrics": {},
-                    "summary": "设定稳定",
-                },
+                "issues": [
+                    {
+                        "severity": "critical",
+                        "category": "timeline",
+                        "location": "第2段",
+                        "description": "时间线回跳",
+                        "evidence": "上章深夜,本章突然中午",
+                        "fix_hint": "补时间过渡",
+                        "blocking": True,
+                    },
+                    {
+                        "severity": "medium",
+                        "category": "ai_flavor",
+                        "location": "第5段",
+                        "description": "'稳住心神'出现2次",
+                        "fix_hint": "替换为具体动作",
+                    },
+                ],
+                "summary": "1个阻断,1个中等",
             },
             ensure_ascii=False,
         ),
@@ -273,10 +265,12 @@ def test_review_pipeline_builds_artifacts(tmp_path):
         report_file="审查报告/第20章.md",
     )
 
-    assert payload["aggregated"]["overall_score"] == 80.0
-    assert payload["aggregated"]["timeline_gate"]["blocked"] is True
-    assert payload["review_metrics"]["report_file"] == "审查报告/第20章.md"
-    assert "timeline_gate:block=True;count=1" in payload["review_metrics"]["notes"]
+    assert payload["review_result"]["blocking_count"] == 1
+    assert payload["review_result"]["has_blocking"] is True
+    assert payload["review_result"]["issues_count"] == 2
+    assert payload["metrics"]["issues_count"] == 2
+    assert payload["metrics"]["blocking_count"] == 1
+    assert payload["metrics"]["report_file"] == "审查报告/第20章.md"
 
 
 def test_review_pipeline_forwards_with_resolved_project_root(monkeypatch, tmp_path):
@@ -308,10 +302,8 @@ def test_review_pipeline_forwards_with_resolved_project_root(monkeypatch, tmp_pa
             "18",
             "--review-results",
             str(review_results),
-            "--aggregated-out",
-            str(tmp_path / "aggregated.json"),
-            "--review-metrics-out",
-            str(tmp_path / "review_metrics.json"),
+            "--metrics-out",
+            str(tmp_path / "metrics.json"),
             "--report-file",
             "审查报告/第18章.md",
         ],
@@ -329,10 +321,8 @@ def test_review_pipeline_forwards_with_resolved_project_root(monkeypatch, tmp_pa
         "18",
         "--review-results",
         str(review_results),
-        "--aggregated-out",
-        str(tmp_path / "aggregated.json"),
-        "--review-metrics-out",
-        str(tmp_path / "review_metrics.json"),
+        "--metrics-out",
+        str(tmp_path / "metrics.json"),
         "--report-file",
         "审查报告/第18章.md",
     ]
@@ -350,22 +340,21 @@ def test_review_pipeline_main_creates_output_directories(tmp_path):
     review_results_path.write_text(
         json.dumps(
             {
-                "consistency-checker": {
-                    "agent": "consistency-checker",
-                    "chapter": 9,
-                    "overall_score": 88,
-                    "pass": True,
-                    "issues": [],
-                    "metrics": {},
-                    "summary": "稳定",
-                }
+                "issues": [
+                    {
+                        "severity": "low",
+                        "category": "other",
+                        "location": "p1",
+                        "description": "小问题",
+                    }
+                ],
+                "summary": "轻微",
             },
             ensure_ascii=False,
         ),
         encoding="utf-8",
     )
 
-    aggregated_out = project_root / ".webnovel" / "tmp" / "review" / "aggregated.json"
     metrics_out = project_root / ".webnovel" / "tmp" / "review" / "metrics.json"
 
     old_argv = sys.argv
@@ -377,9 +366,7 @@ def test_review_pipeline_main_creates_output_directories(tmp_path):
         "9",
         "--review-results",
         str(review_results_path),
-        "--aggregated-out",
-        str(aggregated_out),
-        "--review-metrics-out",
+        "--metrics-out",
         str(metrics_out),
     ]
     try:
@@ -387,5 +374,4 @@ def test_review_pipeline_main_creates_output_directories(tmp_path):
     finally:
         sys.argv = old_argv
 
-    assert aggregated_out.is_file()
     assert metrics_out.is_file()

+ 4 - 7
webnovel-writer/scripts/data_modules/webnovel.py

@@ -251,9 +251,8 @@ def main() -> None:
 
     p_review_pipeline = sub.add_parser("review-pipeline", help="转发到 review_pipeline.py")
     p_review_pipeline.add_argument("--chapter", type=int, required=True, help="目标章节号")
-    p_review_pipeline.add_argument("--review-results", required=True, help="checker 原始结果 JSON 文件")
-    p_review_pipeline.add_argument("--aggregated-out", help="聚合结果输出文件")
-    p_review_pipeline.add_argument("--review-metrics-out", help="review_metrics 输出文件")
+    p_review_pipeline.add_argument("--review-results", required=True, help="reviewer 原始结果 JSON 文件")
+    p_review_pipeline.add_argument("--metrics-out", default="", help="metrics 输出文件")
     p_review_pipeline.add_argument("--report-file", default="", help="审查报告路径")
 
     # 兼容:允许 `--project-root` 出现在任意位置(减少 agents/skills 拼命令的出错率)
@@ -316,10 +315,8 @@ def main() -> None:
             "--chapter", str(args.chapter),
             "--review-results", str(args.review_results),
         ]
-        if args.aggregated_out:
-            return_args.extend(["--aggregated-out", str(args.aggregated_out)])
-        if args.review_metrics_out:
-            return_args.extend(["--review-metrics-out", str(args.review_metrics_out)])
+        if args.metrics_out:
+            return_args.extend(["--metrics-out", str(args.metrics_out)])
         if args.report_file:
             return_args.extend(["--report-file", str(args.report_file)])
         raise SystemExit(_run_script("review_pipeline.py", return_args))

+ 34 - 37
webnovel-writer/scripts/review_pipeline.py

@@ -1,15 +1,11 @@
 #!/usr/bin/env python3
 # -*- coding: utf-8 -*-
 """
-review_pipeline.py - Step 3 审查结果文件流辅助脚本
+Step 3 审查结果处理。
 
-职责:
-- 读取 checker 原始结果文件
-- 调用 index_manager 聚合审查结果
-- 物化为 review_metrics 结构
-- 可选写入 review_metrics 到 index.db
+读取 reviewer agent 的原始输出 JSON,解析为 ReviewResult,
+生成 metrics 用于 index.db 沉淀。
 """
-
 from __future__ import annotations
 
 import argparse
@@ -27,43 +23,42 @@ def _ensure_scripts_path() -> None:
         sys.path.insert(0, str(scripts_dir))
 
 
-def _load_json(path: Path) -> Dict[str, Any]:
-    return json.loads(path.read_text(encoding="utf-8"))
+_ensure_scripts_path()
 
+from data_modules.review_schema import parse_review_output
 
-def _write_json(path: Path, payload: Dict[str, Any]) -> None:
-    path.parent.mkdir(parents=True, exist_ok=True)
-    path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
 
-def build_review_artifacts(project_root: Path, chapter: int, review_results_path: Path, report_file: str = "") -> Dict[str, Any]:
-    _ensure_scripts_path()
-    from data_modules.config import DataModulesConfig
-    from data_modules.index_manager import _aggregate_checker_results, ReviewAggregateResult
+def build_review_artifacts(
+    project_root: Path,
+    chapter: int,
+    review_results_path: Path,
+    report_file: str = "",
+) -> Dict[str, Any]:
+    raw = json.loads(review_results_path.read_text(encoding="utf-8"))
+    result = parse_review_output(chapter=chapter, raw=raw)
+    metrics = result.to_metrics_dict()
+    if report_file:
+        metrics["report_file"] = report_file
 
-    _ = DataModulesConfig.from_project_root(project_root)
-    raw_data = _load_json(review_results_path)
-    aggregated = ReviewAggregateResult(**_aggregate_checker_results(chapter, raw_data))
-    review_metrics = aggregated.to_review_metrics(report_file=report_file)
     return {
         "chapter": chapter,
-        "review_results": raw_data,
-        "aggregated": aggregated.__dict__,
-        "review_metrics": review_metrics.__dict__,
+        "review_result": result.to_dict(),
+        "metrics": metrics,
     }
 
 
 def main() -> None:
-    parser = argparse.ArgumentParser(description="构建 Step 3 审查中间产物")
-    parser.add_argument("--project-root", required=True, help="项目根目录")
-    parser.add_argument("--chapter", type=int, required=True, help="章节号")
-    parser.add_argument("--review-results", required=True, help="checker 原始结果 JSON 文件")
-    parser.add_argument("--aggregated-out", help="聚合结果输出文件")
-    parser.add_argument("--review-metrics-out", help="review_metrics 输出文件")
-    parser.add_argument("--report-file", default="", help="审查报告路径")
+    parser = argparse.ArgumentParser(description="Review pipeline v6")
+    parser.add_argument("--project-root", required=True)
+    parser.add_argument("--chapter", type=int, required=True)
+    parser.add_argument("--review-results", required=True)
+    parser.add_argument("--metrics-out", default="")
+    parser.add_argument("--report-file", default="")
+
     args = parser.parse_args()
+    project_root = Path(args.project_root)
+    review_results_path = Path(args.review_results)
 
-    project_root = Path(args.project_root).resolve()
-    review_results_path = Path(args.review_results).resolve()
     payload = build_review_artifacts(
         project_root=project_root,
         chapter=args.chapter,
@@ -71,11 +66,13 @@ def main() -> None:
         report_file=args.report_file,
     )
 
-    if args.aggregated_out:
-        _write_json(Path(args.aggregated_out), payload["aggregated"])
-
-    if args.review_metrics_out:
-        _write_json(Path(args.review_metrics_out), payload["review_metrics"])
+    if args.metrics_out:
+        out_path = Path(args.metrics_out)
+        out_path.parent.mkdir(parents=True, exist_ok=True)
+        out_path.write_text(
+            json.dumps(payload["metrics"], ensure_ascii=False, indent=2),
+            encoding="utf-8",
+        )
 
     print(json.dumps(payload, ensure_ascii=False, indent=2))