Skip to content

重构: 文档审核独立子域(正交双轴 + 可扩展待审原因) + 导出 MRF 信号列 — #284 #287#288

Merged
duguankui merged 9 commits into
mainfrom
feature/document-review-redesign
Jun 9, 2026
Merged

重构: 文档审核独立子域(正交双轴 + 可扩展待审原因) + 导出 MRF 信号列 — #284 #287#288
duguankui merged 9 commits into
mainfrom
feature/document-review-redesign

Conversation

@duguankui

Copy link
Copy Markdown
Member

概述

Document 的"审核状态"重建为独立子域(#284):从多义的单 ReviewStatus enum 拆为两个正交轴 + 可扩展原因集合,并落地新需求"必填字段缺失进操作员队列、但不阻断下游消费"。顺带补齐导出对原因轴的透出(#287)。

  • 可用性轴 LifecycleStatus(不动)— 对下游放行
  • 处置轴 ReviewDisposition(由 ReviewStatus 重命名收敛:NotReviewed / Confirmed / Rejected,删 PendingReview)— 只由操作员动作写
  • 原因轴 ReviewReasons(新增 [Flags]UnresolvedClassification 阻断 Ready / MissingRequiredFields non-blocking)— 只由 pipeline / evaluator 写
  • 拒绝理由 RejectionReason(独立必填,替代寄生在分类字段上的 ClassificationReason

"需操作员关注"统一判据:ReviewReasons != None && ReviewDisposition != RejectedReviewReasonPolicy.RequiresAttention),服务端算 / 客户端纯渲染。

提交分解(可独立审阅)

commit 内容
6aa4dc5 PR1 领域核心:Domain.Shared 新类型 + enum 重命名 + Document 改造 + ReviewStateEvaluator + Ready 闸门改 !HasBlocking
2431a49 PR2 EF 迁移 Refactor_DocumentReview(回填顺序敏感:RejectionReason 早于 DROP;旧 PendingReview 拆 NotReviewed+UC)
e64f76e / 54a753f PR3 字段抽取/补录接 MRF 评估 + non-blocking 不挡 Ready 端到端断言
f9f869e PR4 DTO 出口审核字段 + 结构化明细(列表薄/详情厚)+ RejectReviewInput 必填 + ETO 回归守护
aca3e8a PR5 客户端双轴渲染 + 单队列原因标签 + proxy 重生 + proxy-contract.spec
0a99218 code-review 复审收尾:判据统一收进 policy + 删死 API + 清陈旧 RejectionReason + 回归测试
f273bf2 术语漂移清理(docs + 注释 PendingReview/ClassificationReason → 双轴)+ 拒绝理由必填文案
8ca1a05 #287 导出 ReviewReasons 系统列透出 MissingRequiredFields(解封 EFCore.Tests 编译)

质量保障

  • 经两轮多智能体对抗式 code-review(最近一轮 45 agents / 9 维度,确认核心修复正确,仅余 low/nit 已就地清理)。
  • 测试全绿:Domain.Tests 157 · Application.Tests 229 · EFCore.Tests 49(本 PR 解封)· Angular vitest 4/4 · paperbase 包 build 通过。

边界合规

Closes #284
Closes #287

🤖 Generated with Claude Code

duguankui and others added 9 commits June 9, 2026 12:03
把"文档审核"建模为独立子域,消除 ReviewStatus/ClassificationReason 多义等补丁病。

- Domain.Shared: 新增 DocumentReviewReasons([Flags]) + ReviewReasonPolicy(blocking 单点声明);
  DocumentReviewStatus 重命名为 DocumentReviewDisposition(删 PendingReview,迁为原因位);
  DocumentConsts 删 MaxClassificationReasonLength、加 MaxRejectionReasonLength
- Domain: Document 拆 ReviewDisposition + ReviewReasons 正交双轴 + 独立必填 RejectionReason
  + 按位 SetReviewReason 唯一写入口; 删 ClassificationReason; 新增 ReviewStateEvaluator(纯函数);
  DeriveLifecycle 闸门改 !HasBlocking(ReviewReasons)(等价且可扩展); 低置信路径不再持久化 reason
- 横切: DTO/Input/AppService(审核队列 HasReviewReasons 过滤,排除已拒绝)/Export/EF 配置
  (ReviewDisposition 用 HasColumnName("ReviewStatus") 复用旧列名,迁移不重命名列)
- 测试: 现有审核测试适配 ReviewDisposition/ReviewReasons + 新增 11 个 evaluator/policy 单测

分阶段实施(#284)的 PR1。EF 迁移 / 字段抽取接 MRF / DTO 出口新字段 / 客户端 留后续 PR。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- RenameColumn ClassificationReason → RejectionReason(Rejected 行拒绝理由保留)
- AddColumn ReviewReasons int NOT NULL default 0(不溯及存量,#284 决策)
- 数据回填(顺序敏感): 清非 Rejected 行残留的旧 AI 理由; 旧 PendingReview(10) 拆 NotReviewed(0)+UC(1)
- DB 列名 ReviewStatus 保留(HasColumnName),不重命名列

经 ef-migration-safety-reviewer 审查: 无高风险——RenameColumn=sp_rename 元数据操作、
AddColumn NOT NULL default=SQL Server 元数据级、列名/索引零物理变更、模型快照三件套同步。
部署提示: 两条全表 UPDATE 在大表需低峰执行; 如需留底旧 AI 分类理由请迁移前自行归档。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- FieldExtractionEventHandler: 注入 ReviewStateEvaluator; 写字段同一 UoW 内(stale 守卫之后)
  复用 currentDefinitions 筛 IsRequired + ExtractedFieldValues 评估, 物化 MissingRequiredFields;
  空字段定义路径清 MRF
- DocumentAppService.UpdateExtractedFieldsAsync: 操作员补录后同样评估 MRF(补齐→clear, 退出队列闭环)
- Document.SetReviewReason 改 public(MRF 写入点在 Application 层, 跨程序集; 仍约定每 bit 单阶段维护)
- 测试: 加 必填缺失→置 MRF / 必填齐全→不置 MRF 两个 handler 用例

此后"必填缺失进操作员队列、不阻断下游(non-blocking, 不影响 Ready/DocumentReadyEto)"后端端到端可用。
Application.Tests 223 / Domain.Tests 150 全绿。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
直接验证重构核心需求: 带 MissingRequiredFields 的文档, 关键流水线成功 + 类型确认后
仍跃迁 Ready(必填缺失只进操作员队列, 不阻断下游 DocumentReadyEto), MRF 原因位保留。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- DocumentDto 加 ReviewReasons/RequiresReview/RejectionReason/ReviewReasonDetails;
  DocumentListItemDto 加 ReviewReasons/RequiresReview(列表薄, 不带明细)
- 新增 ReviewReasonDetailDto{Reason,IsBlocking,MissingFieldNames?}——服务端算/客户端纯渲染:
  IsBlocking 按 ReviewReasonPolicy 填; MissingFieldNames 读时算(required\extracted 的 DisplayName)
- Mapper: ReviewReasons/RejectionReason 自动映射, RequiresReview/ReviewReasonDetails ignore + AppService 填
- MapToDtoAsync 详情厚(组装明细), FillListReferencesAsync 列表薄(仅 RequiresReview, 避免 N+1)
- 测试: 详情 DTO 出口必填缺失明细(含 MissingFieldNames + IsBlocking=false)端到端

Application.Tests 224 全绿。ETO/MCP 下游契约未动(审核轴不入下游)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- generate-proxy 重生: 删 DocumentReviewStatus enum, 新增 DocumentReviewDisposition + DocumentReviewReasons,
  models 加 reviewDisposition/reviewReasons/requiresReview/reviewReasonDetails/rejectionReason, 删 classificationReason
- document-list: 消除 reviewStatus===PendingReview 自推断; 队列过滤改 hasReviewReasons; 双 badge(生命周期+审核);
  needsConfirmation 按 UnresolvedClassification; isProcessing 回归纯可用性轴
- document-detail: needsReview→requiresReview; header badge 纯生命周期; 详情区渲染 reviewReasonDetails 明细
  (含缺失必填字段名 + IsBlocking); 删 classificationReason 块改 rejectionReason
- document-review-queue: 队列查询改 hasReviewReasons; 拒绝理由必填校验
- public-api re-export 换新 enum; proxy-contract.spec 断言 Disposition/Reasons 值;
  本地化(en/zh): 删旧 DocumentReviewStatus key, 加 ReviewReason/ReviewDisposition/NeedsReview/RejectionReason

vitest 4 全绿; nx build 全成功(paperbase + host)。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- #1 把"需关注"收敛为唯一判据 ReviewReasonPolicy.RequiresAttention(reasons, disposition),
  出口 RequiresReview/列表/明细/前端 needsConfirmation 全用之;删除已无生产消费方的单参重载(footgun)
- #2 离开 Rejected 即清 RejectionReason(ApplyAutomaticClassificationResult/RequestClassificationReview/ConfirmClassification)
- #4 MRF 缺失字段名为空时跳过空壳明细;明细整体为空返回 null(与"无明细"语义统一)
- 注释对齐双轴模型:DTO RequiresReview 定义、ApplyManualClassification(Reviewed→Confirmed)、测试 Scenario 6/7
- #3 核验:UC 文档 DocumentTypeId=null 被类型绑定导出过滤,复合映射是死代码——仅改测试断言 None→NotReviewed
- #5 核验驳回:两处字段定义查询 soft-delete 语义相反,不合并(加注释固化)
- 回归测试:RequiresAttention 真值表、(MRF,Confirmed)→需关注、空 MRF 明细跳过、拒绝空理由校验、RejectionReason 清理

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>


- docs/classification.md、docs/text-extraction.md:把已删的 ReviewStatus/PendingReview/Reviewed 三态、
  ClassificationReason 描述改写为双轴模型(ReviewDisposition 处置轴 + ReviewReasons 原因轴)
- 代码注释/日志统一术语:ReclassifyDocumentInput / DocumentReadyEventHandler / DocumentClassificationWorkflow /
  DocumentClassificationBackgroundJob / DocumentType / DocumentAppService 里"PendingReview 状态/队列"→"待人工审核(UnresolvedClassification)"
- 拒绝理由文案纠正:RejectReasonHint 由"选填/Optional"改为"必填/Required"(与 [Required] + 客户端非空校验一致),
  审核队列拒绝弹窗 label 加必填星号

迁移说明类引用(DocumentReviewDisposition/DocumentReviewReasons 的"旧 X 已迁为 Y")保留不动。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
#284 双轴后导出固定系统列只跟处置轴(ReviewStatus=ReviewDisposition),丢了原因轴。
non-blocking 的 MissingRequiredFields 文档照常进类型绑定导出(DocumentTypeId 非空、Ready),
缺必填的行在导出文件里与完好行不可区分。补 ReviewReasons 系统列:

- ExportProjection + ExportTemplateAppService:新增固定列 ReviewReasons
  (顺序 LifecycleStatus, ReviewStatus, ReviewReasons, Title);None→空单元格,否则 [Flags].ToString()
- ExportTemplateExport_Tests:表头/行断言加 ReviewReasons 列 + 新增 Confirmed+MRF 文档导出用例
- 顺带解封 EFCore.Tests 编译:ExportColumn 旧 3 参调用(#207 遗留)改 2 参(Guid,int),
  SeedSchemaAsync seed 真实 DisplayName(导出表头取 FieldDefinition.DisplayName)

边界:仅补 CSV/XLSX 导出信道;REST DocumentDto 已完整出口 MRF;ETO/MCP 下游契约不动。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@duguankui duguankui merged commit b11cfed into main Jun 9, 2026
2 checks passed
@duguankui duguankui deleted the feature/document-review-redesign branch June 9, 2026 10:24
duguankui pushed a commit that referenced this pull request Jun 9, 2026
集成 #288#284 文档审核两轴重构)。解决冲突 + 迁移重处理代码到新审核模型:

- FieldExtractionService 纳入 #284 的 ReviewStateEvaluator + MissingRequiredFields
  评估(抽取完成物化必填缺失信号;空字段路径一并清 MissingRequiredFields)——
  与 origin/main 的事件 handler 行为对齐,handler 仍委托引擎。
- 重处理范围查询从已删除的 DocumentReviewStatus 迁到两轴模型:
  excludeManuallyConfirmed → ReviewDisposition != Confirmed;
  「待审核队列」范围 → ReviewReasons 含 UnresolvedClassification(旧 PendingReview)。
  仓储参数 reviewStatus → withReason(DocumentReviewReasons?),贯穿 AppService /
  dispatcher args / 测试。
- ExportTemplateExport_Tests 取 origin/main 版(#288 已独立修复,撤回本分支的并行修复)。

全绿:Domain 157 / Application 239 / EFCore 56;host 构建 0 错误。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
duguankui pushed a commit that referenced this pull request Jun 9, 2026
合并 origin/main(#288/#284) 后这些迁移编辑漏 git add,致合并提交编译失败(仍引用已删除的
DocumentReviewStatus)。本提交补齐:

- FieldExtractionService 纳入 ReviewStateEvaluator + MissingRequiredFields 评估
- 仓储/AppService/dispatcher 范围参数 reviewStatus → withReason(DocumentReviewReasons?);
  excludeManuallyConfirmed → ReviewDisposition != Confirmed;待审核队列 → UnresolvedClassification
- 测试同步迁移

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant