|
@@ -7,11 +7,115 @@
|
|
|
@IDE : Cursor
|
|
@IDE : Cursor
|
|
|
@Author : AI Assistant
|
|
@Author : AI Assistant
|
|
|
@Date : 2026-05-27
|
|
@Date : 2026-05-27
|
|
|
-@Description: 审查文本预处理 — 合并 PDF 物理折行,消除排版换行导致的审查误报
|
|
|
|
|
|
|
+@Description: 审查文本预处理 — 合并 PDF 物理折行 + 异体字/形近字标准化,消除排版与输入法导致的审查误报
|
|
|
"""
|
|
"""
|
|
|
|
|
|
|
|
import re
|
|
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'[。!?]$')
|
|
_SENTENCE_END_RE = re.compile(r'[。!?]$')
|
|
@@ -62,6 +166,10 @@ def preprocess_review_text(text: str) -> str:
|
|
|
if not text:
|
|
if not text:
|
|
|
return text
|
|
return text
|
|
|
|
|
|
|
|
|
|
+ # 异体字/形近字标准化:在 PDF 换行合并前执行,
|
|
|
|
|
+ # 因为这些罕见字符可能导致后续 LLM 审查误判
|
|
|
|
|
+ text = normalize_variant_chars(text)
|
|
|
|
|
+
|
|
|
# 统一换行符
|
|
# 统一换行符
|
|
|
text = text.replace('\r\n', '\n').replace('\r', '\n')
|
|
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()):
|
|
elif _SENTENCE_END_RE.search(prev.rstrip()):
|
|
|
result.append(line)
|
|
result.append(line)
|
|
|
|
|
|
|
|
|
|
+ # 上一行是结构化编号 → 保留(标题/图表后不合并正文)
|
|
|
|
|
+ elif _is_structural_line(prev.rstrip()):
|
|
|
|
|
+ result.append(line)
|
|
|
|
|
+
|
|
|
# 当前行是结构化编号 → 保留(章节/条款/图表标题)
|
|
# 当前行是结构化编号 → 保留(章节/条款/图表标题)
|
|
|
elif _is_structural_line(line):
|
|
elif _is_structural_line(line):
|
|
|
result.append(line)
|
|
result.append(line)
|