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>
149 lines
6.3 KiB
Markdown
149 lines
6.3 KiB
Markdown
# **RVW V2.0 表格提取疑难杂症专项解决方案 (v1.1 务实版)**
|
||
|
||
**问题焦点:** Word 表格“隐性多行”(单元格内多段落)导致的提取与验证错位 **核心策略:** **提取层保持原貌,验证层“懒分裂” (Lazy Split)** **技术栈:** Python (python-docx, pandas)
|
||
|
||
## **1\. 核心判断:技术选型定调**
|
||
|
||
| 维度 | 方案 A: 视觉模型 (VLM) | 方案 B: 结构重组 (预分裂) | 方案 C: 懒分裂 (推荐) |
|
||
| :---- | :---- | :---- | :---- |
|
||
| **原理** | 用 GPT-4V 截图识别 | 提取时把 Table 拆成 N 倍行 | **提取保持 \\n,验证时 split** |
|
||
| **准确性** | 低 (幻觉/小数点风险) | 中 (容易破坏合并单元格结构) | **高 (数据无损,逻辑灵活)** |
|
||
| **复杂度** | 高 (GPU/Prompt) | 高 (重构 DataFrame 结构) | **低 (仅在 Validator 中处理)** |
|
||
| **前端适配** | 难 (无法定位) | 难 (需定制虚拟行渲染) | **易 (原生 HTML \<br\>)** |
|
||
|
||
**最终决策:**
|
||
|
||
1. **坚决不用视觉模型**:数值准确性是底线。
|
||
2. **放弃“预分裂”**:不在提取阶段破坏表格的物理结构(Row/Span),避免引入元数据丢失风险。
|
||
3. **采用“懒分裂”**:在验证逻辑中,针对特定单元格内容进行 split('\\n'),实现细粒度验证。
|
||
|
||
## **2\. 提取层规范 (Extractor Layer)**
|
||
|
||
**目标**:忠实还原 Word 文档的物理结构,不自作聪明地拆行。
|
||
|
||
### **2.1 Python 实现逻辑**
|
||
|
||
在 DocxTableExtractor 中,对于单元格内的多段落,直接使用换行符 \\n 连接。
|
||
|
||
def extract\_cell\_text(cell):
|
||
"""
|
||
提取单元格文本,保留段落结构
|
||
"""
|
||
\# 过滤掉完全空白的段落,保留有内容的段落
|
||
paragraphs \= \[p.text.strip() for p in cell.paragraphs if p.text.strip()\]
|
||
return "\\n".join(paragraphs)
|
||
|
||
**输出数据结构示例 (JSON)**:
|
||
|
||
{
|
||
"row\_index": 3,
|
||
"cells": \[
|
||
{ "text": "并发症\\n颅内出血\\n牙龈出血" }, // Col 0
|
||
{ "text": "277 (14.65)\\n85 (4.49)\\n94 (4.97)" }, // Col 1
|
||
{ "text": "χ²=5.687\\nχ²=0.003\\nχ²=13.745" }, // Col 3 (统计值)
|
||
{ "text": "0.017\\n0.01\\n\<0.001" } // Col 4 (P值)
|
||
\]
|
||
}
|
||
|
||
## **3\. 验证层规范 (Validator Layer)**
|
||
|
||
**核心逻辑:** 验证器在读取数据时,动态检测是否存在多行内容。如果存在,则在内存中“临时分裂”并逐一验证。
|
||
|
||
### **3.1 懒分裂验证算法 (Lazy Verification Logic)**
|
||
|
||
def verify\_row\_statistics(row\_data, col\_map):
|
||
"""
|
||
验证单行数据的统计逻辑(支持隐性多行)
|
||
"""
|
||
issues \= \[\]
|
||
|
||
\# 1\. 获取目标单元格的原始文本
|
||
\# 假设我们要验证 Col 1 (Group A) vs Col 2 (Group B) \-\> P Value
|
||
cell\_a\_text \= row\_data\[col\_map\['group\_a'\]\]
|
||
cell\_b\_text \= row\_data\[col\_map\['group\_b'\]\]
|
||
cell\_p\_text \= row\_data\[col\_map\['p\_value'\]\]
|
||
|
||
\# 2\. 懒分裂 (Lazy Split)
|
||
lines\_a \= cell\_a\_text.split('\\n')
|
||
lines\_b \= cell\_b\_text.split('\\n')
|
||
lines\_p \= cell\_p\_text.split('\\n')
|
||
|
||
\# 3\. 确定对齐基准(取最大行数)
|
||
max\_lines \= max(len(lines\_a), len(lines\_b), len(lines\_p))
|
||
|
||
\# 4\. 逐行验证 (Line-by-Line Validation)
|
||
for i in range(max\_lines):
|
||
\# 安全获取当前行的数据(处理长度不一致情况)
|
||
val\_a \= lines\_a\[i\] if i \< len(lines\_a) else ""
|
||
val\_b \= lines\_b\[i\] if i \< len(lines\_b) else ""
|
||
|
||
\# P 值匹配策略:
|
||
\# 如果 P 值列只有 1 行,但数据有 N 行 \-\> 广播机制 (Broadcast)
|
||
\# 如果 P 值列有 N 行 \-\> 一一对应 (One-to-One)
|
||
if len(lines\_p) \== 1 and max\_lines \> 1:
|
||
val\_p \= lines\_p\[0\] \# 策略 A: 共享 P 值
|
||
else:
|
||
val\_p \= lines\_p\[i\] if i \< len(lines\_p) else "" \# 策略 B: 独立 P 值
|
||
|
||
\# 跳过空行
|
||
if not val\_a or not val\_b or not val\_p:
|
||
continue
|
||
|
||
\# 执行具体的统计验证
|
||
\# 传入 line\_index=i 以便报错时定位
|
||
error \= validate\_single\_line(val\_a, val\_b, val\_p, line\_index=i)
|
||
if error:
|
||
issues.append(error)
|
||
|
||
return issues
|
||
|
||
### **3.2 优势分析**
|
||
|
||
1. **兼容性强**:完美支持您截图中的 颅内出血 | 85 | 90 | P=0.01 这种每行独立 P 值的场景。
|
||
2. **鲁棒性**:如果只有第一行有 P 值(合并单元格视觉效果),代码中的 Broadcast 逻辑也能兜底。
|
||
3. **定位精准**:报错信息可以包含 line\_index,告诉前端是单元格里的第几行出错了。
|
||
|
||
## **4\. 前端渲染规范 (Frontend Layer)**
|
||
|
||
**目标**:使用最简单的 Web 技术还原 Word 样式,避免过度设计。
|
||
|
||
### **4.1 HTML 渲染策略**
|
||
|
||
后端返回的 html 字段中,直接将 \\n 替换为 \<br\>。
|
||
|
||
**Python 端处理:**
|
||
|
||
def generate\_html\_cell(text):
|
||
\# 转义 HTML 特殊字符,并将换行转为 \<br\>
|
||
safe\_text \= html.escape(text)
|
||
return safe\_text.replace("\\n", "\<br\>")
|
||
|
||
**前端展示效果:**
|
||
|
||
\<td\>
|
||
277 (14.65)\<br\>
|
||
85 (4.49)\<br\>
|
||
94 (4.97)
|
||
\</td\>
|
||
|
||
### **4.2 错误高亮策略**
|
||
|
||
由于我们不再拆分表格行(DOM 结构),高亮的最小单位是 **Cell(单元格)**。
|
||
|
||
* **交互设计**:
|
||
* 当发现第 2 行子数据错误时,**高亮整个单元格**。
|
||
* **Tooltip 提示**:鼠标悬停时,显示具体错误信息:“第 2 行数据 P 值校验不通过”。
|
||
* **进阶优化(V2.1 可选)**:
|
||
* 如果确实需要高亮某一行,Python 生成 HTML 时可以用 \<span\> 包裹每一行: \<span id="r3c2\_L0"\>277 (14.65)\</span\>\<br\>\<span id="r3c2\_L1"\>85 (4.49)\</span\>
|
||
* 但 MVP 阶段建议**只高亮单元格**,性价比最高。
|
||
|
||
## **5\. 总结**
|
||
|
||
| 模块 | 核心动作 | 复杂度 |
|
||
| :---- | :---- | :---- |
|
||
| **Python 提取** | 保持 \\n,不拆行,输出标准 JSON | ⭐ (低) |
|
||
| **Python 验证** | split('\\n'),循环对齐,独立计算 | ⭐⭐ (中) |
|
||
| **前端渲染** | 使用 \<br\> 换行,CSS 控制对齐 | ⭐ (低) |
|
||
| **前端高亮** | 高亮整个单元格,Tooltip 说明行号 | ⭐ (低) |
|
||
|
||
**这是目前最务实、风险最低的实施路径。** 请开发团队以此为准。 |