# 章节切分泄漏问题根因分析报告

## 问题描述

**现象**: 第十章"其他资料"的内容（计算书、相关图纸等）被错误地合并到第九章的最后一个 chunk 中。

**验证结果**: 通过 `verify_leak.py` 脚本验证，确认这是**真实泄漏**，非误报。
- 泄漏内容：2359 字符的第十章内容
- 影响范围：特定测试文件 `测试模版-四川路桥专项施工方案框架以及编制说明（2025修订第三版）- v0.2.pdf`
- 批量测试结果：7 个文件中 6 个通过，仅该文件失败

## 根因分析

### 1. 标题位置查找异常

**涉及代码**: `core/construction_review/component/doc_worker/utils/title_matcher.py`

**发现的问题**:
- `_find_full_title_positions("第十章 其他资料", full_text)` 正确找到两个位置：[4524, 43321]
  - 4524: 目录中的"第十章 其他资料"
  - 43321: 正文中真实的"第十章 其他资料"标题
- 但 `_select_best_position` 最终返回的位置是：**32460**（错误！）

### 2. 位置 32460 的真相

**实际内容**: 位置 32460 所在的文本是：
```
XXXX 公司 XXX 专项施工方案
```

这是**页眉**内容，根本不包含"第十章"字符串！

**结论**: 存在**位置计算 bug**，`current_pos` 的累加逻辑或 `_find_pattern_in_line` 的返回值计算有误。

### 3. 代码分析

**可疑代码段** (`title_matcher.py`):

```python
def _find_full_title_positions(self, title: str, text: str) -> List[int]:
    positions = []
    lines = text.split('\n')
    current_pos = 0

    for line in lines:
        line_clean = self._remove_escape_chars(line)
        line_normalized = self._normalize_title(line_clean)

        if title_normalized in line_normalized:
            pos_in_line = line_normalized.find(title_normalized)
            line_pos = self._find_pattern_in_line(
                title_normalized, line, pos_in_line
            )
            if line_pos >= 0:
                found_pos = current_pos + line_pos  # <-- 可能的问题
                positions.append(found_pos)

        current_pos += len(line) + 1  # +1 for newline <-- 可能的问题
```

**可能的根本原因**:
1. `_find_pattern_in_line` 返回的 `line_pos` 是在原始行中的位置，但 `current_pos` 是基于清理前行计算的
2. 字符编码问题导致位置偏移（如中文标点、特殊空白字符）
3. `_remove_escape_chars` 前后的长度差异未正确处理

## 影响评估

| 维度 | 评估 |
|------|------|
| 严重性 | 高 - 导致章节内容错位，影响完整性审查准确性 |
| 范围 | 特定文件触发，可能与页眉格式有关 |
| 频率 | 批量测试 7 个文件中出现 1 次，约 14% 触发率 |

## 修复建议

### 方案 1: 修复位置计算逻辑（推荐）

统一使用原始文本进行位置计算，避免清理前后的位置映射问题：

```python
def _find_full_title_positions(self, title: str, text: str) -> List[int]:
    # 使用原始文本直接搜索，避免位置映射复杂性
    import re

    # 构建兼容空格变体的正则模式
    title_pattern = title.replace(' ', r'\s+')
    positions = []

    for m in re.finditer(title_pattern, text):
        # 验证这是否是真正的标题行（行首/独立）
        line_start = text.rfind('\n', 0, m.start()) + 1
        line_end = text.find('\n', m.start())
        line_text = text[line_start:line_end].strip()

        if self._is_valid_title_line(line_text, title):
            positions.append(m.start())

    return positions
```

### 方案 2: 增加页眉过滤

在标题查找前，先过滤掉页眉页脚内容，避免干扰：

```python
def _filter_headers_footers(self, pages_content: List[Dict]) -> str:
    """过滤每页的页眉页脚后再进行标题查找"""
    # 实现页眉页脚检测和过滤逻辑
```

### 方案 3: 基于 TOC 的标题定位增强

利用 PDF 目录中的页码信息，限制标题搜索范围：

```python
def find_title_positions(self, items, full_text, pages_content, toc_pages):
    # 对于每个标题，使用 TOC 中的页码信息缩小搜索范围
    # 只在 TOC 指示的页面范围内搜索对应标题
```

## 相关提交

- **commit 93bcdb9**: `fix(doc_worker): 修复章节标题定位错误导致的跨章节内容吞并`
  - 该提交添加了 `_get_toc_boundary_position` 方法用于 TOC 边界保护
  - 但本问题的根因在 `_find_full_title_positions` 的位置计算，该提交未修复

## 调试脚本

以下脚本可用于复现和验证问题：

1. `utils_test/Chunk_Split_Test/verify_leak.py` - 验证泄漏是否存在
2. `utils_test/Chunk_Split_Test/debug_split.py` - 跟踪 `_get_toc_boundary_position` 调用
3. `utils_test/Chunk_Split_Test/debug_split2.py` - 跟踪 `found_titles` 列表
4. `utils_test/Chunk_Split_Test/debug_actual_text.py` - 分析实际 PDF 文本
5. `utils_test/Chunk_Split_Test/debug_find_positions.py` - 手动跟踪位置查找逻辑

## 附录：关键位置数据

```
标题: "第十章 其他资料"

正确位置:
- 4524: 目录中出现
- 43321: 正文中真实标题位置

错误返回:
- 32460: 页眉文本中（不包含"第十章"）

位置 32460 所在行内容:
'XXXX 公司 XXX 专项施工方案'
```

---

**分析时间**: 2026-03-30
**分析人**: Claude Code
**状态**: 待修复
