From d002d5c5b46a20efb767c5e74374b4a25bd84f35 Mon Sep 17 00:00:00 2001
From: zhizhijie <zhizhijie@users.noreply.gitee.com>
Date: 星期一, 28 九月 2026 16:14:33 +0800
Subject: [PATCH] docs(差异清单): 按用户口径更新比对工具与清单——排名表当月块/货运新增行/U列草稿/市州短名归一化视为已知设计,小差异只计数;新发现班线包车 EO36/FN36/FO36 生成时被重算

---
 docs/工具/汇总大表差异比对.py |   77 +++++++++++++++++++++++++++++++++-----
 1 files changed, 66 insertions(+), 11 deletions(-)

diff --git "a/docs/\345\267\245\345\205\267/\346\261\207\346\200\273\345\244\247\350\241\250\345\267\256\345\274\202\346\257\224\345\257\271.py" "b/docs/\345\267\245\345\205\267/\346\261\207\346\200\273\345\244\247\350\241\250\345\267\256\345\274\202\346\257\224\345\257\271.py"
index 15358e5..b81099a 100644
--- "a/docs/\345\267\245\345\205\267/\346\261\207\346\200\273\345\244\247\350\241\250\345\267\256\345\274\202\346\257\224\345\257\271.py"
+++ "b/docs/\345\267\245\345\205\267/\346\261\207\346\200\273\345\244\247\350\241\250\345\267\256\345\274\202\346\257\224\345\257\271.py"
@@ -4,7 +4,7 @@
 璇存槑: 閫愰〉鎸�"琛屾爣绛�/鍒楄〃澶�"鍋氬簭鍒楀榻愶紙difflib锛夛紝鍙纭鐞嗗鍒�/澶氳/閲嶅琛ㄥご锛�
       姣忎釜涓嶄竴鑷存牸褰掑叆 缁撴瀯浣嶇Щ|鍙栨暣灏惧樊|鏂囨湰鍨嬫暟瀛梶鑽夌/杈呭姪鍒梶浠呯▼搴忔湁|浠呮墜宸ユ湁|鏂囨湰涓嶅悓|閿欒鍊紎瀹炶川鏁板�煎樊寮傘��
 """
-import datetime, io, os, sys
+import datetime, io, os, re, sys
 from difflib import SequenceMatcher
 from openpyxl import load_workbook
 from openpyxl.utils import get_column_letter
@@ -17,17 +17,59 @@
             return "%d-%02d" % (d.year, d.month)
         if isinstance(v, str) and v.strip() and not v.startswith("="):
             return v.strip()[:20]
-    return ""
+    return "(鏃犺〃澶村垪)"
 
 def row_label(ws):
     out = []; cur = ""; seen = {}
     for r in range(1, ws.max_row+1):
         a = ws.cell(row=r, column=1).value; b = ws.cell(row=r, column=2).value
         if isinstance(a, str) and a.strip(): cur = a.strip()
-        lab = ("%s|%s" % (cur, b.strip())) if (isinstance(b, str) and b.strip()) else (cur or "")
+        if isinstance(b, str) and b.strip():
+            lab = "%s|%s|%s" % (block_of(ws, r), norm_region(cur), norm_indicator(b))
+        else:
+            lab = "%s|%s|" % (block_of(ws, r), norm_region(cur))
         seen[lab] = seen.get(lab, 0) + 1
         out.append("%s#%d" % (lab, seen[lab]))
     return out
+
+def norm(s):
+    """鏍囩褰掍竴鍖栵細鍘荤┖鐧姐�佸幓鍏ㄨ绌烘牸銆佺粺涓�寮曞彿銆佸幓鎺夆�樺競/鐪�/宸炩�欏悗缂�宸紓"""
+    if not isinstance(s, str): return ""
+    t = s.replace("\u3000", " ").replace("锛�", "(").replace("锛�", ")").strip()
+    t = re.sub(r"[\s]+", "", t)
+    return t
+
+def norm_region(s):
+    t = norm(s)
+    for suf in ("甯�", "鐪�", "鑷不宸�", "宸�"):
+        if t.endswith(suf) and len(t) > len(suf): t = t[:-len(suf)]
+    return t
+
+def norm_indicator(s):
+    t = norm(s).replace("(涓囧惃鍏噷)", "").replace("(涓囧惃)", "").replace("(涓囦汉鍏噷)", "").replace("(涓囦汉)", "")
+    t = t.replace("鍏朵腑瑙勪笂", "瑙勪笂").replace("鍏朵腑瑙勪笅", "瑙勪笅")
+    return t
+
+def block_of(ws, r):
+    """鍚戜笂鎵炬渶杩戜竴涓�"鏍囬琛�"锛圔 鍒椾负绌轰笖 A/鏂囧瓧杈冮暱锛夊垽鏂墍灞炲潡锛氱疮璁� / 褰撴湀 / 鍏跺畠"""
+    for rr in range(r, 0, -1):
+        a = ws.cell(row=rr, column=1).value; b = ws.cell(row=rr, column=2).value
+        txt = " ".join(str(x) for x in (a, b) if isinstance(x, str))
+        if not isinstance(b, str) or not b.strip():
+            if len(norm(txt)) >= 6:
+                if "绱" in txt: return "绱鍧�"
+                if "鍚勫競宸�" in txt or "鎺掑悕" in txt or "鏄庣粏琛�" in txt: return "褰撴湀鍧�"
+    return "鍏跺畠"
+
+# 宸茬煡璁捐宸紓锛氫笉璁″叆闂锛堟寜鐢ㄦ埛 2026-09-28 鍙e緞锛�
+KNOWN_ROWS = {" 璐ц繍": ("瑙勪笂+瑙勪笅璐ц繍閲�", "瑙勪笅璐ц繍閲�")}
+KNOWN_SHEETS_ANY_BLOCK = ()   # 鎺掑悕琛ㄧ殑褰撴湀鍧楃敱 block_of 澶勭悊
+
+def is_known_extra_row(sheet, indicator, side):
+    if sheet in KNOWN_ROWS and side == "绋嬪簭":
+        for pat in KNOWN_ROWS[sheet]:
+            if norm_indicator(indicator) == norm_indicator(pat): return True
+    return False
 
 def num(v):
     return isinstance(v, (int, float)) and not isinstance(v, bool)
@@ -69,13 +111,26 @@
         unmatched_rows_g = [r-1 for r in range(1, len(rl_g)+1) if r-1 not in rowmap]
         unmatched_rows_d = [r-1 for r in range(1, len(rl_d)+1) if r-1 not in set(rowmap.values())]
         for c in unmatched_cols_g:
-            if c >= 2: bump("浠呯▼搴忔湁(鍒�)"); rows_out.append([sh, "%s*" % get_column_letter(c+1), "锛堟暣鍒楋級", cl_g[c], "浠呯▼搴忔湁(鍒�)", "", "", "鐢熸垚浠跺鍑烘鍒�"])
+            if c < 2: continue
+            if cl_g[c] == "(鏃犺〃澶村垪)":
+                bump("宸茬煡璁捐(鏃犺〃澶磋崏绋垮垪)"); continue
+            bump("浠呯▼搴忔湁(鍒�)"); rows_out.append([sh, "%s*" % get_column_letter(c+1), "锛堟暣鍒楋級", cl_g[c], "浠呯▼搴忔湁(鍒�)", "", "", "鐢熸垚浠跺鍑烘鍒�"])
         for c in unmatched_cols_d:
-            if c >= 2: bump("浠呮墜宸ユ湁(鍒�)"); rows_out.append([sh, "锛堟棤锛�", "锛堟暣鍒楋級", cl_d[c], "浠呮墜宸ユ湁(鍒�)", "", "", "鎵嬪伐瀹氱澶氬嚭姝ゅ垪"])
+            if c < 2: continue
+            if cl_d[c] == "(鏃犺〃澶村垪)":
+                bump("宸茬煡璁捐(鏃犺〃澶磋崏绋垮垪)"); continue
+            bump("浠呮墜宸ユ湁(鍒�)"); rows_out.append([sh, "锛堟棤锛�", "锛堟暣鍒楋級", cl_d[c], "浠呮墜宸ユ湁(鍒�)", "", "", "鎵嬪伐瀹氱澶氬嚭姝ゅ垪"])
         for r in unmatched_rows_g:
-            if r >= 4: bump("浠呯▼搴忔湁(琛�)"); rows_out.append([sh, "绗�%d琛�" % (r+1), rl_g[r], "锛堟暣琛岋級", "浠呯▼搴忔湁(琛�)", "", "", "鐢熸垚浠跺鍑烘鍖烘"])
+            if r < 4: continue
+            rowb = "%s" % wg.cell(row=r+1, column=2).value
+            if is_known_extra_row(sh, rowb, "绋嬪簭"):
+                bump("宸茬煡璁捐(姣嶇増鏂板琛�)"); continue
+            bump("浠呯▼搴忔湁(琛�)"); rows_out.append([sh, "绗�%d琛�" % (r+1), rl_g[r], "锛堟暣琛岋級", "浠呯▼搴忔湁(琛�)", "", "", "鐢熸垚浠跺鍑烘鍖烘"])
         for r in unmatched_rows_d:
-            if r >= 4: bump("浠呮墜宸ユ湁(琛�)"); rows_out.append([sh, "锛堟棤锛�", rl_d[r], "锛堟暣琛岋級", "浠呮墜宸ユ湁(琛�)", "", "", "鎵嬪伐瀹氱澶氬嚭姝ゅ尯娈�"])
+            if r < 4: continue
+            if "褰撴湀鍧�" in rl_d[r] or "绱鍧�" in rl_d[r]:
+                bump("宸茬煡璁捐(鎺掑悕琛ㄥ綋鏈�/绱鍧�)"); continue
+            bump("浠呮墜宸ユ湁(琛�)"); rows_out.append([sh, "锛堟棤锛�", rl_d[r], "锛堟暣琛岋級", "浠呮墜宸ユ湁(琛�)", "", "", "鎵嬪伐瀹氱澶氬嚭姝ゅ尯娈�"])
         for i, j in sorted(colmap.items()):
             for p, q in sorted(rowmap.items()):
                 vg = wg.cell(row=p+1, column=i+1).value; vd = wd.cell(row=q+1, column=j+1).value
@@ -89,7 +144,7 @@
                     diff = abs(vg - vd)
                     if diff <= 1e-6: continue
                     rel = diff / max(abs(vd), 1e-9)
-                    if diff <= 0.01 or rel <= 1e-4: cat = "鍙栨暣灏惧樊"
+                    if diff <= 0.01 or rel <= 1e-4: cat = "鍙栨暣灏惧樊(鍙拷鐣�)"
                     else: cat = "瀹炶川鏁板�煎樊寮�"
                     note = "宸�=%.6g" % diff
                 elif num(vg) and (vd is None):
@@ -97,9 +152,9 @@
                 elif num(vd) and (vg is None):
                     cat = "浠呮墜宸ユ湁"
                 elif tg is not None and num(vd) and abs(tg - vd) <= 1e-9:
-                    cat = "鏂囨湰鍨嬫暟瀛�"; note = "鎵嬪伐涓烘枃鏈�'%s'" % vg
+                    cat = "鏂囨湰鍨嬫暟瀛�(鍙拷鐣�)"; note = "鎵嬪伐涓烘枃鏈�'%s'" % vg
                 elif td is not None and num(vg) and abs(td - vg) <= 1e-9:
-                    cat = "鏂囨湰鍨嬫暟瀛�"; note = "绋嬪簭涓烘枃鏈�'%s'" % vd
+                    cat = "鏂囨湰鍨嬫暟瀛�(鍙拷鐣�)"; note = "绋嬪簭涓烘枃鏈�'%s'" % vd
                 elif isinstance(vg, str) and isinstance(vd, str):
                     cat = "鏂囨湰涓嶅悓"; note = "绋嬪簭'%s' / 鎵嬪伐'%s'" % (vg[:18], vd[:18])
                 elif isinstance(vg, str) and vg.startswith("#") :
@@ -120,7 +175,7 @@
         for x in rows_out:
             f.write(",".join('"%s"' % str(v).replace('"', "'") for v in x) + "\n")
     # 杈撳嚭 MD
-    cats = ["瀹炶川鏁板�煎樊寮�", "浠呮墜宸ユ湁", "浠呯▼搴忔湁", "鍙栨暣灏惧樊", "鏂囨湰鍨嬫暟瀛�", "鏂囨湰涓嶅悓", "閿欒鍊�(绋嬪簭)", "閿欒鍊�(鎵嬪伐)", "浠呯▼搴忔湁(鍒�)", "浠呮墜宸ユ湁(鍒�)", "浠呯▼搴忔湁(琛�)", "浠呮墜宸ユ湁(琛�)", "鍏跺畠"]
+    cats = ["瀹炶川鏁板�煎樊寮�", "閿欒鍊�(绋嬪簭)", "閿欒鍊�(鎵嬪伐)", "鏂囨湰涓嶅悓", "浠呮墜宸ユ湁", "浠呯▼搴忔湁", "浠呯▼搴忔湁(鍒�)", "浠呮墜宸ユ湁(鍒�)", "浠呯▼搴忔湁(琛�)", "浠呮墜宸ユ湁(琛�)", "鍙栨暣灏惧樊(鍙拷鐣�)", "鏂囨湰鍨嬫暟瀛�(鍙拷鐣�)", "宸茬煡璁捐(姣嶇増鏂板琛�)", "宸茬煡璁捐(鏃犺〃澶磋崏绋垮垪)", "宸茬煡璁捐(鎺掑悕琛ㄥ綋鏈�/绱鍧�)", "鍏跺畠"]
     md = prefix + ".md"
     with io.open(md, "w", encoding="utf-8", newline="\n") as f:
         f.write("# 姹囨�诲ぇ琛ㄥ樊寮傛槑缁嗭紙鐢熸垚浠� vs 鎵嬪伐瀹氱锛塡n\n")

--
Gitblit v1.9.1