Ver código fonte

fix: 修复PDF换行合并导致标题/图表与正文假重复,异体字审查前置为预处理

- text_preprocessor: 新增上一行结构化判断,标题/图表后不再合并正文,
  消除"二、工程概况工程概况"类假重复
- text_preprocessor: 新增异体字/形近字标准化(normalize_variant_chars),
  在审查前统一替换,替代原prompt中的异体字审查规则
- basic_reviewers: 移除异体字审查规则(已前置),新增施工复合词误判防护
- inter_tool: 展平check_result嵌套字段到顶层,防止前端undefined显示

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
WangXuMing 3 horas atrás
pai
commit
ea40e3224d

+ 2 - 12
core/construction_review/component/infrastructure/parent_tool.py

@@ -83,7 +83,7 @@ def fetch_parent_chunks_by_parent_id(
         子块片段列表,按 pk 排序,如果不存在返回 None
     """
     if output_fields is None:
-        output_fields = ["pk", "text", "parent_id", "file_name", "title"]
+        output_fields = ["pk", "text", "parent_id"]
 
     if collection_name is None:
         collection_name = config_handler.get('rag_collections', 'PARENT_COLLECTION', 'rag_parent_hybrid')
@@ -117,13 +117,6 @@ def fetch_parent_chunks_by_parent_id(
         return chunks
 
     except Exception as e:
-        # 如果包含 file_name/title 的查询失败(字段不存在),回退到基础字段
-        if output_fields != ["pk", "text", "parent_id"]:
-            logger.warning(f"[父文档工具] 查询扩展字段失败 ({e}),回退到基础字段")
-            return fetch_parent_chunks_by_parent_id(
-                milvus_manager, parent_id, collection_name,
-                output_fields=["pk", "text", "parent_id"]
-            )
         logger.error(f"[父文档工具] 召回 parent_id {parent_id} 的片段失败: {e}")
         return None
 
@@ -301,11 +294,8 @@ def enhance_with_parent_docs_grouped(
                     start_cut = (original_length - target_length) // 2
                     compressed_text = text[start_cut:start_cut + target_length]
 
-                    # 添加压缩提示
-                    compressed_text = f"...[已按比例压缩{compression_ratio:.2%}]...\n{compressed_text}\n...[已压缩]..."
-                    parent_id_to_doc[pid] = compressed_text
-
                     logger.info(f"[分组增强] parent_id={str(pid)[:8]}... 压缩前={original_length}字符,压缩后={len(compressed_text)}字符")
+                    parent_id_to_doc[pid] = compressed_text
 
             # 重新计算总长度
             total_length_after = sum(len(text) for text in parent_id_to_doc.values())

+ 8 - 8
core/construction_review/component/reviewers/prompt/basic_reviewers.yaml

@@ -10,7 +10,7 @@ grammar_check:
     ## workflow
     - 负责检查文本中的错别字和重复字词等语法问题。
     - 检查待审查的词句语法标点格式是否符合规则。
-    - 检查错别字、异体字、多字、少字、重复字词等语法错误。
+    - 检查错别字、多字、少字、重复字词等语法错误。
     - 如果发现一个句子有错误,请正确的定位其错误位置,而不是想当然的去找问题。如句子末尾未加句号不要定位到句中去了。
     - 给出了最终建议后,需要再次考虑如果按照建议去做了,原文是否通顺合理。
     - 请减少对标点符号的严格审查,如果有问题请三思你所说的位置有没有符号,符号对不对。
@@ -24,6 +24,10 @@ grammar_check:
     4. 对于条款编号而言,'一)'这样的结构是正确的,符合中文规范
     5. 如果你对某个词或表述犹豫不决、无法确定它是否真的是错误,说明原文可能是正确的,此时应直接输出”无明显问题”,而不是勉强凑出一个修改建议。
     6. 请对汉语中经典易错字如”辩”与”辨”等等的混用请多加注意。
+    7. **施工领域复合词误判警示**:施工方案中存在大量你不认识的专业复合词(如”挡防措施””锚喷支护””仰拱衬砌””湿喷工艺”等),这些词在通用词典中不存在,但在施工领域是标准术语。当你遇到一个你不认识的二字或多字组合时,请遵循以下判定流程:
+       - 第一步:该组合中的每个字是否能独立表意且在上下文中语义合理?(如”挡”=挡土/阻挡,”防”=防护/防御 → “挡防”=挡土防护)
+       - 第二步:该组合是否出现在施工/工程语境中?(如前后文有”安全网””衬砌””支护”等工程词汇)
+       - 如果以上两步均为”是”,则该组合极大概率是施工专业术语,**不得将其判为错别字**,直接跳过。
 
     ## rule
     - 不需要强求要输出问题,除非是非常明显的错误。小问题可以忽略。
@@ -38,7 +42,7 @@ grammar_check:
     - 遵循以下**强制**规范:
         1. 务必结合语境进行分析检查。
         2. 对于**表格制表符**、**表格内容**不需要检查。
-        3. 对于术语概念不得曲解。
+        3. 对于术语概念不得曲解。**施工方案中的任何你不认识的复合词(如"挡防""锚喷""仰拱"等),默认都是专业术语,不是错别字。** 禁止因为你没见过某个词就将其判定为错别字。
         4. 没有明显词句语法错误、标点错误的内容不予检查,输出无明显问题。
         5. 已检查出的问题项仅输出一次检查结果,禁止对同一内容重复检查。
         6. 统一解释:如果表格中出现了多列相同的表头标题,不是错误,而是解析时这几个是合并的表头。
@@ -66,12 +70,8 @@ grammar_check:
     10. **语义逻辑问题**:任何与语义、逻辑、事实相关的内容
     11. **技术操作规程**:操作步骤的顺序是否正确、工艺参数是否合理、安全操作规范的技术正确性 —— 这些由专业技术审查流程处理,你只检查其中的文字书写错误(如错别字、漏字)
 
-    **你的职责范围仅限于**:错别字(如”混泥土”→”混凝土”)、异体字(如”毎”→”每”、”出”→”凸出”)、多字/少字、重复字词(如”公司公司”)、标点符号错误、”的地得”混用、明显的语法结构错误。
+    **你的职责范围仅限于**:错别字(如”混泥土”→”混凝土”)、多字/少字、重复字词(如”公司公司”)、标点符号错误、”的地得”混用、明显的语法结构错误。必须给出具体类别(如”错别字”)的修改建议,而不是笼统的说”有个错别字”,你需要指出哪个字是错别字,并给出正确的字是什么。
 
-    ### 异体字处理规则(强制执行)
-    1. 异体字属于审查范围,应当报告。
-    2. **issue_point 命名规范**:异体字问题必须以`[异体字]`开头(如`[异体字]”毎”应为”每”`),错别字以`[错别字]`开头,禁止混用。
-    3. 如果不确定某个字是否为异体字,按”拿不准就不报”处理。
     超出以上范围的所有问题,请忽略并输出”无明显问题”。
 
     ## output
@@ -90,7 +90,7 @@ grammar_check:
     - 低风险:形式问题、不影响实质内容的词句错误。
 
   user_prompt_template: |
-    请审查以下内容的词句语法错误,**仅限**错别字、异体字、多字、少字、重复字词、标点符号错误、"的地得"混用、明显的语法结构错误。
+    请审查以下内容的词句语法错误,**仅限**错别字、多字、少字、重复字词、标点符号错误、"的地得"混用、明显的语法结构错误。
 
     【待检查文本】
     {review_content}

+ 9 - 0
core/construction_review/component/reviewers/utils/inter_tool.py

@@ -474,6 +474,15 @@ class InterTool:
                             logger.warning(f"第 {idx} 个问题格式不符合预期:{item}")
                             processed_issues.append(item)
 
+                    # 展平 check_result 内嵌套字段到顶层,避免前端读到 undefined 显示 unknown
+                    FLATTEN_FIELDS = ["location", "suggestion", "reason", "issue_point",
+                                      "review_references", "reference_source"]
+                    for flat_item in processed_issues:
+                        if isinstance(flat_item, dict) and isinstance(flat_item.get("check_result"), dict):
+                            for field in FLATTEN_FIELDS:
+                                if field in flat_item["check_result"] and field not in flat_item:
+                                    flat_item[field] = flat_item["check_result"][field]
+
                     # 计算风险等级并添加到review_lists
                     for processed_issue in processed_issues:
                         if isinstance(processed_issue, dict):

+ 113 - 1
core/construction_review/component/reviewers/utils/text_preprocessor.py

@@ -7,11 +7,115 @@
 @IDE       : Cursor
 @Author    : AI Assistant
 @Date      : 2026-05-27
-@Description: 审查文本预处理 — 合并 PDF 物理折行,消除排版换行导致的审查误报
+@Description: 审查文本预处理 — 合并 PDF 物理折行 + 异体字/形近字标准化,消除排版与输入法导致的审查误报
 """
 
 import re
 
+# ============================================================================
+# 异体字 / 形近字标准化映射表
+# ============================================================================
+# 施工方案多由五笔输入法编写,字形相近的罕见异体字或形近字容易被误输入,
+# LLM(尤其是 qwen 系列)对这些罕见字符的 token 级识别能力弱,会将其误读为
+# 另一个形近字并编造错误理由。在文本进入审查流水线前做字符级标准化替换,
+# 从源头消除此类误报。
+#
+# 映射来源:
+#   1. 《第一批异体字整理表》中在现代文档中仍可能出现的异体字
+#   2. 五笔输入法形近字误输入(编码相近,候选窗中选错)
+#   3. 日语汉字变体(日文文献 OCR / 日文输入法混入)
+# ============================================================================
+
+_VARIANT_CHAR_MAP = {
+    # ── 山部异体 / 五笔形近 ──────────────────────────────
+    # 岀 (U+5C80) 是"出"的异体字,LLM(尤其是 qwen 系列)极易将其误读为"岌"
+    # 然后编造出"应为岌"的虚假审查理由。这是本映射表要解决的核心问题。
+    '岀': '出',
+    '峯': '峰',
+    '峩': '峨',
+
+    # ── 常用高频异体字(第一批异体字整理表)────────────────
+    '爲': '为',
+    '啓': '启',
+    '卽': '即',
+    '旣': '既',
+    '衞': '卫',
+    '眞': '真',
+    '衆': '众',
+    '竝': '并',
+    '並': '并',
+    '幷': '并',
+
+    # ── 五笔形近误输入(编码相近,候选窗中选错)───────────
+    '畧': '略',
+    '皁': '皂',
+    '牀': '床',
+    '觧': '解',
+    '舩': '船',
+    '袐': '秘',
+
+    # ── 传统 / 繁简对应(现代文档中不应出现但偶尔残留)─────
+    '靑': '青',
+    '隣': '邻',
+    '陞': '升',
+    '閙': '闹',
+    '滙': '汇',
+    '溼': '湿',
+    '洩': '泄',
+    '遊': '游',
+
+    # ── 施工文档场景常见的异体字 ──────────────────────────
+    '迺': '乃',
+    '逈': '迥',
+    '邨': '村',
+    '躰': '体',
+    '軆': '体',
+    '靁': '雷',
+    '麤': '粗',
+    '羣': '群',
+
+    # ── 日语汉字变体(日文输入法 / OCR 混入)──────────────
+    '黒': '黑',
+    '査': '查',
+    '汚': '污',
+    '涙': '泪',
+    '淸': '清',
+    '渕': '渊',
+    '弾': '弹',
+    '鶏': '鸡',
+    '鉱': '矿',
+
+    # ── 常见五笔末笔识别码错误(横竖撇捺差一笔)───────────
+    # 以下为极易混淆对,其中罕见字映射到常用字
+    '妺': '妹',  # 妺(mò, U+59BA) vs 妹(mèi, U+59B9), 五笔 VFY vs VFIY
+}
+
+
+
+def _build_variant_trans_table():
+    """构建 str.translate 用的字符映射表,过滤掉占位条目"""
+    return str.maketrans({k: v for k, v in _VARIANT_CHAR_MAP.items() if k != v})
+
+
+# 预编译转换表(模块加载时一次性构建)
+_VARIANT_TRANS_TABLE = _build_variant_trans_table()
+
+
+def normalize_variant_chars(text: str) -> str:
+    """
+    将文本中的异体字、形近字替换为标准常用字。
+
+    覆盖场景:
+    - 异体字(如 岀→出、峯→峰、啓→启)
+    - 五笔输入形近误输入(如 畧→略、皁→皂)
+    - 日语汉字变体(如 査→查、汚→污)
+
+    所有映射均为单字符→单字符,不改变文本长度和位置索引。
+    """
+    if not text:
+        return text
+    return text.translate(_VARIANT_TRANS_TABLE)
+
 
 # 句末标点:这些字符后的换行保留(段落/句子边界)
 _SENTENCE_END_RE = re.compile(r'[。!?]$')
@@ -62,6 +166,10 @@ def preprocess_review_text(text: str) -> str:
     if not text:
         return text
 
+    # 异体字/形近字标准化:在 PDF 换行合并前执行,
+    # 因为这些罕见字符可能导致后续 LLM 审查误判
+    text = normalize_variant_chars(text)
+
     # 统一换行符
     text = text.replace('\r\n', '\n').replace('\r', '\n')
 
@@ -84,6 +192,10 @@ def preprocess_review_text(text: str) -> str:
         elif _SENTENCE_END_RE.search(prev.rstrip()):
             result.append(line)
 
+        # 上一行是结构化编号 → 保留(标题/图表后不合并正文)
+        elif _is_structural_line(prev.rstrip()):
+            result.append(line)
+
         # 当前行是结构化编号 → 保留(章节/条款/图表标题)
         elif _is_structural_line(line):
             result.append(line)