docs/¹¤¾ß/»ã×Ü´ó±í²îÒì±È¶Ô.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):
    """向上找最近一个"标题行"(B åˆ—为空且 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 å£å¾„)
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")