feat(rvw): Complete V2.0 Week 3 - Statistical validation extension and UX improvements

Week 3 Development Summary:

- Implement negative sign normalization (6 Unicode variants)

- Enhance T-test validation with smart sample size extraction

- Enhance SE triangle and CI-P consistency validation with subrow support

- Add precise sub-cell highlighting for P-values in multi-line cells

- Add frontend issue type Chinese translations (6 new types)

- Add file format tips for PDF/DOC uploads

Technical improvements:

- Add _clean_statistical_text() in extractor.py

- Add _safe_float() wrapper in validator.py

- Add ForensicsReport.tsx component

- Update ISSUE_TYPE_LABELS translations

Documentation:

- Add 2026-02-18 development record

- Update RVW module status (v5.1)

- Update system status (v5.2)

Status: Week 3 complete, ready for Week 4 testing
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-02-18 18:26:16 +08:00
parent 9f256c4a02
commit f9ed0c2528
36 changed files with 2790 additions and 501 deletions

View File

@@ -0,0 +1,137 @@
# **RVW V2.0 表格提取疑难杂症专项解决方案**
**问题焦点:** Word 表格“假行”现象(单元格内多段落)导致的提取错位
**核心策略:** 从“视觉模型”回归“DOM 深度解析”
**技术栈:** Python (python-docx)
## **1\. 核心判断:为什么不建议全量上视觉模型?**
您提到用视觉模型Vision Model如 GPT-4V, Qwen-VL来识别这听起来很诱人所见即所得但在**数据侦探**场景下有致命缺陷:
| 维度 | 视觉模型 (VLM/OCR) | 原生解析 (python-docx) | 结论 |
| :---- | :---- | :---- | :---- |
| **数值准确性** | **95%\~99%** (存在幻觉风险) | **100%** (直接读取 XML) | ❌ 审计场景不能有 1% 的误差 |
| **小数点敏感度** | 可能漏读小数点 (0.05 \-\> 005\) | 绝对精准 | ❌ P 值验证的核心 |
| **对齐能力** | 强 (能看懂视觉对齐) | 弱 (需算法辅助) | ✅ 视觉模型优势 |
| **成本/速度** | 高/慢 (需 GPU 推理) | 极低/极快 (CPU 解析) | ❌ 影响并发性能 |
**决策:**
**“数据”必须信赖 XML代码“结构”可以用算法还原。** 我们不需要视觉模型来看数字,我们只需要一段更聪明的 Python 代码来拆解段落。
## **2\. 现象诊断:什么是“隐性多行”?**
在您的截图中Word 表格的一行Row内部用户使用了 **回车键 (Enter)****软回车 (Shift+Enter)** 进行了换行。
**python-docx 的默认行为:**
cell.text 会把这些段落拼接成一个字符串,例如 "DNT时间段\\n\<45 min\\n45\~60 min"。前端 HTML 渲染时,如果没有处理 \\n或者对应列的行数不匹配就会导致错位。
## **3\. 解决方案:行分裂算法 (Row Explosion)**
我们需要在提取阶段,检测这种情况,并将“逻辑上的一行”分裂成“视觉上的多行”。
### **3.1 算法逻辑**
1. **扫描 (Scan)**:遍历表格的每一行。
2. **检测 (Detect)**:检查该行每一列的 **段落数量 (Paragraph Count)**
* 例如Col 1 有 4 个段落Col 2 有 4 个段落Col 3 只有 1 个段落(如 P 值)。
3. **分裂 (Explode)**
* 取最大段落数 max\_para (如 4)。
* 如果 max\_para \> 1则将此行**分裂**为 4 个新行。
4. **填充 (Fill)**
* 对于原本有多段落的列:按顺序填充到新行。
* 对于只有 1 个段落的列(如 P 值 0.001
* *策略 A重复*:每行都填 0.001。
* *策略 B首行/合并)*:只填第一行,后面留空(前端处理为合并单元格)。
### **3.2 代码实现 Demo**
请让 Python 工程师在 DocxTableExtractor 中加入以下逻辑:
from docx import Document
import pandas as pd
def explode\_word\_table\_rows(table):
"""
高级表格提取:处理单元格内的多段落(隐性多行)
"""
structured\_data \= \[\]
for row in table.rows:
\# 1\. 获取该行每一列的段落内容列表
\# cells\_content 结构: \[ \['DNT时间段', '\<45min', ...\], \['1299', '881', ...\], \['X2=..'\] \]
cells\_content \= \[\]
for cell in row.cells:
\# 过滤掉空段落,获取真实文本行
paras \= \[p.text.strip() for p in cell.paragraphs if p.text.strip()\]
if not paras:
paras \= \[""\] \# 保持占位
cells\_content.append(paras)
\# 2\. 计算该行“分裂”的最大高度
max\_height \= max(len(c) for c in cells\_content)
\# 3\. 如果是标准单行,直接添加
if max\_height \<= 1:
flat\_row \= \[c\[0\] if c else "" for c in cells\_content\]
structured\_data.append(flat\_row)
continue
\# 4\. 执行分裂 (Row Explosion)
\# 针对每一层visual\_row\_index构建一行数据
for i in range(max\_height):
new\_row \= \[\]
for col\_idx, cell\_paras in enumerate(cells\_content):
\# 策略:如何填充?
if len(cell\_paras) \> 1:
\# 情况 A该列有多行按顺序取
\# 如果当前层级超过了该列的行数,填空(或填最后一行)
val \= cell\_paras\[i\] if i \< len(cell\_paras) else ""
else:
\# 情况 B该列只有一行通常是统计值 P值
\# 只有第一行填值,模拟“合并单元格”的视觉效果
\# 或者val \= cell\_paras\[0\] (全部重复填充) \-\> 方便后续计算
val \= cell\_paras\[0\] if i \== 0 else ""
new\_row.append(val)
structured\_data.append(new\_row)
return pd.DataFrame(structured\_data)
\# 使用示例
\# doc \= Document("sample.docx")
\# df \= explode\_word\_table\_rows(doc.tables\[0\])
\# print(df)
## **4\. 前端渲染的配合**
为了让“数据侦探”的高亮定位准确,后端返回的数据结构必须包含**分裂后的坐标映射**。
**推荐的数据结构升级:**
{
"row\_id": "r4\_exploded\_0", // 原始第4行分裂后的第0子行
"is\_virtual": true, // 标记这是分裂出来的行
"cells": \[
{ "text": "\<45 min", "source\_cell": "R4C1", "paragraph\_index": 1 },
{ "text": "881 (46.59)", "source\_cell": "R4C2", "paragraph\_index": 1 },
{ "text": "", "source\_cell": "R4C3", "is\_merged\_placeholder": true } // P值列留空
\]
}
**前端展示逻辑:**
* 当后端返回 is\_merged\_placeholder: true 时,前端渲染时不显示内容,或者通过 CSS 渲染为合并单元格的样式(即不画上边框)。
## **5\. 总结**
1. **别用视觉模型**:准确率风险太大,得不偿失。
2. **用代码“分裂”段落**Word 的 cell.paragraphs 是您的救星。
3. **对齐策略**:通常临床表格中,如果一列有多行,另一列只有一行(如 P 值),那一行 P 值通常是对齐第一行或者居中的。在做\*\*数据验证L1/L2\*\*时,我们需要编写逻辑:*“如果检测到分裂行,且 P 值列为空,自动向上寻找最近的一个 P 值作为本行的验证依据。”*
**实施建议:**
请 Python 工程师立即测试上述 explode\_word\_table\_rows 逻辑。这能解决您 90% 的“HTML 只有一行”的问题。