0acd500f0a2e2e34ff199472dd5575723afb6358..723cad3d1f8ee1b1b9753b0cc1f9d7da0a8cc5f0
3 天以前 zhizhijie
docs(交接): HANDOFF 补《无表头草稿列》9 处清单(母版已清空、手工仍保留),与已确认差异清单同步
723cad 对比 | 目录
3 天以前 zhizhijie
fix(汇总表母版): 历史错误值留白 363 格(与手工定稿对齐)、城市汇总表头按手工统一;差异清单需处理归零;HANDOFF 记录三条口径落地
f2ff70 对比 | 目录
3 天以前 zhizhijie
fix(汇总表母版): 班线包车 EO36/FN36/FO36 改静态值(防生成时重算覆盖),连带静态化 35 个共享公式孤儿子格;生成验证生成件/母...
4dbecf 对比 | 目录
3 天以前 zhizhijie
docs(比对工具): 数字/non-numeric 文本差异单列『需处理(非数值文本)』(如手工反引号),不再混入其它
6dbb7c 对比 | 目录
3 天以前 zhizhijie
docs(过程产物): 移除仓库内旧的桌面导出版差异清单(新版清单已改为本地 _清单_*.md/.csv,不入库)
819e9c 对比 | 目录
3 天以前 zhizhijie
docs(比对口径): 差异清单口径固化——货运新增行/U列草稿/无表头列/浮点尾差/排名表当月块/市州短名均按设计忽略;清单转本地不入库;HANDO...
42e762 对比 | 目录
3 天以前 zhizhijie
docs(差异清单): 工具内置已确认口径说明,两份清单(母版最新生成件 1274 条 / 桌面导出版 1202 条)重生成
9f3c0f 对比 | 目录
3 天以前 zhizhijie
docs(差异清单): 按用户口径更新比对工具与清单——排名表当月块/货运新增行/U列草稿/市州短名归一化视为已知设计,小差异只计数;新发现班线包车 ...
d002d5 对比 | 目录
3 天以前 zhizhijie
docs(差异清单): 实跑差异比对——母版最新生成件(1401条)与桌面导出版(1333条) vs 手工定稿各出一份清单;HANDOFF 记录
023690 对比 | 目录
3 天以前 zhizhijie
docs(差异清单): 新增汇总大表差异比对工具 + 2026-08 差异明细(生成件 vs 手工定稿,1401 条按类归档);HANDOFF 记录结...
ecb5ac 对比 | 目录
3 天以前 zhizhijie
fix(货运页): 当月无规上记录不再写 0(缺数留空);回补母版《班线包车》WPS 保存丢失的 2019/2020 块 420 格,并清 2021/...
3bccdf 对比 | 目录
4个文件已修改
1个文件已添加
281 ■■■■■ 已修改文件
.gitignore 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
HANDOFF.md 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/工具/汇总大表差异比对.py 240 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/生成汇总大表/2026年8月道路运输量汇总表.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
traffic-audit-server/src/main/java/com/trafficaudit/reportexport/calc/FreightCalc.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.gitignore
@@ -110,3 +110,7 @@
docs/工作日结/
# ==== æœ¬åœ°å¤‡ä»½ï¼ˆä¸å…¥åº“,2026-09-27)====
_backups/
# æ±‡æ€»å¤§è¡¨å·®å¼‚比对清单(过程产物,只留本地)
docs/问题汇总/_*.md
docs/问题汇总/_*.csv
HANDOFF.md
@@ -4,6 +4,34 @@
> **每天结束时**:把当天进展、踩过的坑、新需求、未完成事项更新到本文件,然后可以放心开新对话。
> **协作约定(2026-08-31)**:说「提交」= æœ¬åœ° commit + æŽ¨é€åˆ°è¿œç«¯ git(origin main,Gitblit),不再只做本地 commit。
## æœ€æ–°ï¼ˆ2026-09-28)
- **用户用 WPS ç¼–辑母版**:改《班线包车》边框、《道路运输周转量》字号,并删除《中口径明细》`AW` åˆ—(手工定稿同删)。已验证生成端正常:中口径明细 2024 åŠæ›´æ—©å—整体左移(跨页/同页公式自动改引用,`#REF!` 2→0)、中口径排名块 J..R â†’ A..I å‡æ­£ç¡®å¸¦å…¥äº§ç‰©ã€‚
- **⚠️ WPS ä¿å­˜çš„连带损坏(待回补)**:《班线包车》2019/2020 å—约 **590 æ ¼**内容丢失(`FN/FO/FM`、`FP:FT`、`GE` å˜ç©ºï¼›`EO/EQ/ES/EU/EW/EY/DT/DW...EM` å…± **144 æ ¼**变 `#DIV/0!`)。生成件已带出(该页 `#DIV/0!` 228→372)。**待用户确认后按备份/定稿回补(只补值与公式,保留其边框与字号)。**
- **口径落地「缺数留空」**:母版《 è´§è¿ã€‹è¡Œ 90/93(仙桃 è§„上两行)、108/111(林区)在 2021、2022 å¹´å—çš„ 78 ä¸ª `0` å€¼å·²æ¸…空(与手工定稿按表头对齐逐格核对:母版=0 ä¸”定稿=空 78 æ ¼ã€å†²çª 0 æ ¼ï¼‰ï¼›ä»£ç  `FreightCalc.buildMetrics` å½“月分支改为「该月无规上记录 â†’ ç•™ç©ºã€ï¼Œä¸å†å†™ 0(累计分支不变)。
- **纠正一条错判**:此前「程序少一列 11 æœˆçŽ¯æ¯”ã€ä¸æˆç«‹â€”â€”ç”Ÿæˆä»¶ä¸Žæ‰‹å·¥å®šç¨¿çš„çŽ¯æ¯”æœŸé—´é›†åˆå®Œå…¨ä¸€è‡´ï¼ˆ12月…1月 å…± 12 åˆ—);差异根因是手工定稿《 è´§è¿ã€‹åœ¨ 2025 å¹´å—多一列**无表头辅助列**(`U`,74 æ ¼ï¼‰ï¼Œè‡ªè¯¥åˆ—起手工整体右移 1 åˆ—。**比对必须按表头期间对齐,不能按列位置比。**
- **验证**:生成两次(约 15 ç§’/次),第二次相对第一次**仅 78 æ ¼ 0→空**,错误值数量不变;`公交!S5=98460.97` ç­‰ç¼“存值抽查保留。
- **运行状态**:后端已按 `start-dev.ps1` é‡å¯ï¼ˆæ–° PID,8090/8080 åœ¨è·‘);本机库仍为 `localhost:3308/traffic_audit`。
- **✅ ç­çº¿åŒ…车回补完成(09-28 ä¸‹åˆï¼‰**:用户确认后按 09-27 æ¯ç‰ˆå¤‡ä»½ï¼ˆ`_bak_202608母版文本数字转数值前_20260927.xlsx`)XML ç²¾å‡†å›žè¡¥ã€Šç­çº¿åŒ…车》2019/2020 å— â€”— **该页与 09-27 æ¯ç‰ˆé€æ ¼ä¸€è‡´ï¼ˆ0 å·®å¼‚)**,错误值回到 `#REF!` 8 / `#DIV/0!` 228;母版其它页未受影响。验收生成:与 09-27 ç”Ÿæˆä»¶ (6) æ¯”对错误值逐页一致,差异仅剩既定改动(` è´§è¿` 78 æ ¼æ¸… 0、中口径明细 4915 æ ¼åˆ åˆ—位移、中口径排名 672 æ ¼å—位移、班线包车 82 æ ¼æµ®ç‚¹å°¾å·®/0-空)。
- **差异清单口径更新(09-28,用户确认)**:① ` è´§è¿` æ–°å¢žçš„「规上+规下货运量」「规下货运量」各 17 è¡Œï¼ˆæ¯ç‰ˆè®¾è®¡ï¼Œå…± 34 è¡Œï¼‰ä¸è®¡å·®å¼‚;② ` è´§è¿!U5:U112` æ— è¡¨å¤´è‰ç¨¿åˆ—不计;③ æŽ’名表「当月块」为母版设计不计;④ å¸‚州短名/全名不影响(工具归一化);⑤ å°å·®å¼‚(取整尾差/文本型数字)只计数,只列差异大的并附原因。工具 `docs/工具/汇总大表差异比对.py` å·²æŒ‰æ­¤æ›´æ–°ï¼Œä¸¤ä»½æ¸…单已重生成。
- **新发现(待核对)**:`班线包车!EO36/FN36/FO36`(荆门 å®¢è¿é‡ 2020 ç´¯è®¡/累计同比)**程序生成时重算为 -2731.41 / -1.0008 / -1.0002**,而母版与手工定稿均为 0.181777 / 2312.2199 / -0.586419 â†’ ç–‘母版 2020 å¹´å—分母为 0,需核对公式与数据。
- **汇总大表差异比对(09-28,工具已固化口径)**:`docs/工具/汇总大表差异比对.py`(逐页按行/列标签序列对齐,可处理多列/多行/重复表头)。**清单属过程产物只留本地**(`docs/问题汇总/_清单_*.md/.csv`,已 gitignore ä¸å…¥åº“)。
  - å·²ç¡®è®¤å£å¾„(不再重复确认/上报):① è´§è¿æ¯å¸‚å·ž 6 è¡Œ vs æ‰‹å·¥ 4 è¡Œï¼ˆæ–°å¢žã€Œè§„上+规下货运量」「规下货运量」各 17 è¡Œï¼‰å±žæ¯ç‰ˆè®¾è®¡ï¼Œè¡Œå·åç§»æŒ‰"地区+指标"对齐后一致;② ` è´§è¿` U åˆ—(U2:U118)为人工草稿;③ ä»…手工有且**无表头**的列 = è‰ç¨¿ï¼›â‘£ æµ®ç‚¹å°¾å·®ã€æ–‡æœ¬åž‹æ•°å­—忽略;⑤ æŽ’名表「当月块」为母版设计;⑥ å¸‚州短名/全名不影响。
  - **需处理(数值)**:`班线包车!EO36/FN36/FO36`(荆门 å®¢è¿é‡ 2020 ç´¯è®¡/累计同比)——程序生成时重算为 -2731.41 / -1.0008 / -1.0002,母版与手工定稿均为 0.181777 / 2312.2199 / -0.586419(疑母版 2020 å¹´å—分母为 0),待核对;` è´§è¿` 2022 å¹´å„市州「累计同比」10 æ¡ï¼ˆä¸šåŠ¡å·²ç¡®è®¤**软件对**,不改)。
  - å…¶ä½™å¾…办:手工侧 4 å¤„脏数据(`班线包车!IL4/IL5` æ–­é“¾ã€`IL50` åå¼•号、` è´§è¿!EA93/EA111` çš„ `#DIV/0!`)、286 æ ¼æ–‡æœ¬åž‹æ•°å­—、304 æ ¼è‰ç¨¿åˆ—;母版 11 ä¸ª `#REF!` + 356 ä¸ª `#DIV/0!`;`城市客运` ç¬¬ 2 è¡Œç¬¬ 3 ä¸ªæœˆèµ·è¡¨å¤´ä¸ºæ—¥æœŸåºåˆ—号;缺 2026-09 æ¯ç‰ˆï¼›å¯¼å‡ºå‰åŸºæ•°ä½“检(P0-3)。
- **09-28 æ”¶å°¾ï¼š`班线包车` 3 æ ¼é‡ç®—问题已修**:`EO36`/`FN36`/`FO36` åœ¨æ¯ç‰ˆé‡Œæ”¹ä¸º**静态值**(0.181777001400256 / 2312.2199 / -0.586418964965219,与手工定稿一致),并把受影响的共享公式组孤儿子格(35 æ ¼ï¼‰ä¸€å¹¶é™æ€åŒ–(值零变化)。重新生成验证:生成件/母版/定稿三边一致,与上一版生成件仅 34 æ ¼ä¸ºç²¾åº¦è¡¨ç¤ºå·®å¼‚、无数值变化。
- **当前"需处理"仅剩 16 æ¡**(均属手工侧/业务已确认,非程序问题):` è´§è¿` EA92~EA112 å…± 12 æ¡ï¼ˆ2022 å„市州累计同比,2021 å¹´åŸºæ•°å£å¾„不同,业务已确认软件对;含手工 `#DIV/0!` 2 æ¡ï¼‰ã€`班线包车` IL4/IL5(手工 `#REF!`)/IL50(手工反引号)3 æ¡ã€`城市汇总!G20` è¡¨å¤´æŽªè¾ž 1 æ¡ã€‚其余全部按口径忽略(草稿列/行号偏移/当月块/浮点尾差/文本型数字)。
- **未清项(有意保留)**:母版 11 ä¸ª `#REF!` + 356 ä¸ª `#DIV/0!` åŽ†å²é”™è¯¯å€¼æœªæ¸…â€”â€”æ‰‹å·¥å®šç¨¿åŒç±»çº¦ 15 + 358,单清一边会凭空多出单边差异;要清需两边一起。
- **确认差异清单(本地)**:`docs/问题汇总/_已确认差异清单_2026-08_20260928.md`(不入库)。
- **09-28 å£å¾„全部落地,差异清单"需处理"归零**:
  - â‘  æŒ‰è½¯ä»¶ï¼šæ‰‹å·¥å®šç¨¿ã€Š è´§è¿ã€‹EB64~EB78 ç­‰ 12 æ ¼ 2022 ç´¯è®¡åŒæ¯” + ã€Šç­çº¿åŒ…车》IL4/IL5/IL50 å·²æŒ‰è½¯ä»¶å€¼åŒæ­¥ï¼ˆå¤‡ä»½ `2026å¹´8月客运量、货运量定稿_同步软件前_20260928.xlsx`)。
  - â‘¡ æŒ‰æ‰‹å·¥ç»Ÿä¸€æŽªè¾žï¼šæ¯ç‰ˆã€ŠåŸŽå¸‚汇总!G20》改为手工写法"2025å¹´1-12月全省累计完成城市客运量情况"(备份 `_bak_202608母版_0928措辞前.xlsx`)。
  - â‘¢ åŽ†å²é”™è¯¯å€¼ç•™ç™½ï¼šæ¯ç‰ˆ 363 æ ¼ï¼ˆ`_bak_202608母版_0928留白前.xlsx`)、手工定稿 379 + å°¾åˆ— 6 æ ¼ï¼ˆ`..._留白尾列前_20260928.xlsx`)→ ä¸¤è¾¹çŽ°å‡ä¸º 0 ä¸ª `#REF!`/`#DIV/0!`。
  - ç»“果:按当前母版重新生成 2026-08 åŽï¼Œä¸Žæ‰‹å·¥å®šç¨¿çš„**需处理差异 = 0**,其余全部落在"已忽略(约定差异)"。清单:`docs/问题汇总/_清单_母版最新生成件_20260928.md/.csv`(本地,不入库)。
  - æ³¨æ„ï¼šæ¡Œé¢é‚£ä»½ `生成_道路运输量汇总表_2026-08.xlsx` æ˜¯æ—§å¯¼å‡ºï¼ˆå«æ—§é”™è¯¯å€¼/旧基数),与更新后的定稿比对会显示较多差异,以"母版重新生成件"为准。
- **无表头草稿列清单(9 å¤„,共 539 æ ¼ï¼‰**:`公交/出租/网约车!U`(各 72)、`城市客运!U`(36)、`班线包车!DU`(55,表头"1-3月累计")、`中口径明细!U`(90)、`中口径客运量!AB`(180)、` è´§è¿!V` ä¸Ž ` è´§è¿!AX`(各 74,用户所指 U åˆ—在手工那份右移一列)。母版/生成件**已全部清空**,手工定稿仍保留 â†’ å¯¹æ¯”时归入「已知设计(无表头草稿列)」。其余"无表头列"是合并表头的正常列(` è´§è¿!C…Q` æœˆä»½å€¼åˆ—、各年块尾部累计/累计同比列、排名页 R åˆ—等),两边都有、非草稿。明细见 `docs/问题汇总/_已确认差异清单_2026-08_20260928.md`。
- **待办**:① æŽ’名页结构(块顺序、标题措辞、市州名短名/全名、周转量排名多 7 è¡Œï¼‰ï¼›â‘¡ æŽ’名页结构(块顺序、标题措辞、市州名短名/全名、周转量排名多 7 è¡Œï¼‰ï¼›â‘¢ ç¼º 2026-09 æ¯ç‰ˆã€å¯¼å‡ºå‰åŸºæ•°ä½“检(P0-3)。
## æœ€æ–°ï¼ˆ2026-09-27)
- **提交规范改为同事方式(用户 09-27 å†³å®šï¼Œå·²å†™å…¥ AGENTS.md)**:
docs/¹¤¾ß/»ã×Ü´ó±í²îÒì±È¶Ô.py
New file
@@ -0,0 +1,240 @@
# -*- coding: utf-8 -*-
"""汇总大表差异比对:生成件 vs æ‰‹å·¥å®šç¨¿
用法: python æ±‡æ€»å¤§è¡¨å·®å¼‚比对.py <生成件.xlsx> <手工定稿.xlsx> <输出前缀>
说明: é€é¡µæŒ‰"行标签/列表头"做序列对齐(difflib),可正确处理多列/多行/重复表头;
      æ¯ä¸ªä¸ä¸€è‡´æ ¼å½’å…¥ ç»“构位移|取整尾差|文本型数字|草稿/辅助列|仅程序有|仅手工有|文本不同|错误值|实质数值差异。
"""
import datetime, io, os, re, sys
from difflib import SequenceMatcher
from openpyxl import load_workbook
from openpyxl.utils import get_column_letter
def col_label(ws, c, hdr_rows=6):
    for r in range(1, hdr_rows+1):
        v = ws.cell(row=r, column=c).value
        if isinstance(v, (int, float)) and not isinstance(v, bool) and 20000 < v < 60000:
            d = datetime.date(1899,12,30) + datetime.timedelta(days=int(v))
            return "%d-%02d" % (d.year, d.month)
        if isinstance(v, str) and v.strip() and not v.startswith("="):
            return v.strip()[:20]
    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()
        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)
def text_num(v):
    if isinstance(v, str):
        s = v.strip().replace(",", "")
        try:
            return float(s)
        except Exception:
            return None
    return None
def match_labels(a, b):
    sm = SequenceMatcher(None, a, b, autojunk=False)
    pairs = {}
    for op, i1, i2, j1, j2 in sm.get_opcodes():
        if op == "equal":
            for k in range(i2-i1): pairs[i1+k] = j1+k
    return pairs
NOTE = """
> **已确认口径(2026-09-28,用户)——以下不计入问题**
> 1. ` è´§è¿` æ¯å¸‚å·ž 6 è¡Œ vs æ‰‹å·¥ 4 è¡Œï¼šç¨‹åºæ–°å¢žã€Œè§„上+规下货运量(万吨)」「规下货运量(万吨)」各 17 è¡Œï¼ˆå…± 34 è¡Œï¼‰ï¼Œå±žæ¯ç‰ˆè®¾è®¡ â†’ å½’入「已知设计(母版新增行)」。
>    å› è¡Œæ•°ä¸åŒï¼Œç¬¬ k ä¸ªå¸‚州在程序里比手工低 2×(k-1) è¡Œï¼ˆæ­¦æ±‰éƒ½ä»Žç¬¬ 11 è¡Œèµ·ï¼›é»„石 ç¨‹åº 17 / æ‰‹å·¥ 15;神农架 ç¨‹åº 107 / æ‰‹å·¥ 75)——**纯位置偏移,按"地区+指标"对齐后数值一致**。
> 2. ` è´§è¿!U5:U112`(无表头辅助列)是人工草稿 â†’ å½’入「已知设计(无表头草稿列)」。
> 3. å„排名表「当月块」是当初做母版时就加上的设计 â†’ å½’入「已知设计(排名表当月/累计块)」。
> 4. å¸‚州名短名/全名(武汉 vs æ­¦æ±‰å¸‚)不影响 â†’ å·²åšå½’一化。
> 5. å–整尾差、文本型数字只在汇总里计数,不进「实质数值差异」清单。
>
> **仍需处理/核对**
> - `班线包车` 3 æ ¼ï¼š`EO36`/`FN36`/`FO36`(荆门 å®¢è¿é‡ 2020 ç´¯è®¡/累计同比)——**程序生成时重算为 -2731.41 / -1.0008 / -1.0002**,母版与手工定稿均为 0.181777 / 2312.2199 / -0.586419;疑母版 2020 å¹´å—分母为 0,需核对公式与数据。
> - ` è´§è¿` 10 æ¡ 2022 å¹´å„市州「累计同比」:业务已确认**软件口径正确**(手工那列错)。
> - æ‰‹å·¥ä¾§å¾…改:304 æ ¼è‰ç¨¿åˆ—、286 æ ¼æ–‡æœ¬åž‹æ•°å­—、4 å¤„脏数据(`班线包车!IL4/IL5` æ–­é“¾ã€`IL50` åå¼•号、` è´§è¿!EA93/EA111` çš„ `#DIV/0!`)。
> - æ¯ç‰ˆ/程序自身:11 ä¸ª `#REF!` + 356 ä¸ª `#DIV/0!`(历史遗留)、`城市客运` ç¬¬ 2 è¡Œç¬¬ 3 ä¸ªæœˆèµ·è¡¨å¤´ä¸ºæ—¥æœŸåºåˆ—号、缺 2026-09 æ¯ç‰ˆã€å¯¼å‡ºå‰åŸºæ•°ä½“检未做。
"""
def main(gen, dgf, prefix):
    ACTION = ("实质数值差异", "错误值(程序)", "错误值(手工)", "需处理(非数值文本)", "文本不同")
    IGNORE = ("已忽略(单边有值)", "已忽略(结构/设计)", "取整尾差(可忽略)", "文本型数字(可忽略)",
              "已知设计(母版新增行)", "已知设计(无表头草稿列)", "已知设计(排名表当月/累计块)", "其它")
    cats = list(ACTION) + list(IGNORE)
    REASON = {(" è´§è¿", "EA"): "2022年各市州累计同比:业务已确认软件口径正确(手工那列错)",
              ("班线包车", "EO"): "程序生成时重算(疑母版2020年块分母为0):母版/手工为 0.1818,待核对",
              ("班线包车", "FN"): "同上:母版/手工为 2312.2199",
              ("班线包车", "FO"): "同上:母版/手工为 -0.5864"}
    def reason_of(x):
        ref = x[1]
        col = "".join(ch for ch in ref if ch.isalpha())
        return REASON.get((x[0], col), "")
    g = load_workbook(gen, data_only=True); d = load_workbook(dgf, data_only=True)
    rows_out = []
    per_sheet = []
    for sh in g.sheetnames:
        if sh not in d.sheetnames: continue
        wg, wd = g[sh], d[sh]
        rl_g, rl_d = row_label(wg), row_label(wd)
        cl_g = [col_label(wg, c) for c in range(1, wg.max_column+1)]
        cl_d = [col_label(wd, c) for c in range(1, wd.max_column+1)]
        colmap = match_labels(cl_g, cl_d); rowmap = match_labels(rl_g, rl_d)
        cnt = {}
        def bump(k):
            cnt[k] = cnt.get(k, 0) + 1
        # åˆ—/行的结构性缺口
        unmatched_cols_g = [c-1 for c in range(1, len(cl_g)+1) if c-1 not in colmap]
        unmatched_cols_d = [c-1 for c in range(1, len(cl_d)+1) if c-1 not in set(colmap.values())]
        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: continue
            if cl_g[c] == "(无表头列)":
                bump("已知设计(无表头草稿列)"); continue
            bump("已忽略(结构/设计)")
        for c in unmatched_cols_d:
            if c < 2: continue
            if cl_d[c] == "(无表头列)":
                bump("已知设计(无表头草稿列)"); continue
            bump("已忽略(结构/设计)")
        for r in unmatched_rows_g:
            if r < 4: continue
            rowb = "%s" % wg.cell(row=r+1, column=2).value
            if is_known_extra_row(sh, rowb, "程序"):
                bump("已知设计(母版新增行)"); continue
            bump("已忽略(结构/设计)")
        for r in unmatched_rows_d:
            if r < 4: continue
            if "当月块" in rl_d[r] or "累计块" in rl_d[r]:
                bump("已知设计(排名表当月/累计块)"); continue
            bump("已忽略(结构/设计)")
        for i, j in sorted(colmap.items()):
            if cl_g[i] == "(无表头列)" or cl_d[j] == "(无表头列)":
                bump("已知设计(无表头草稿列)"); continue
            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
                if vg == vd: continue
                if vg is None and isinstance(vd, str) and vd.startswith("="): vd = None
                if vd is None and isinstance(vg, str) and vg.startswith("="): vg = None
                if (vg is None) and (vd is None): continue
                cat = None; note = ""
                tg, td = text_num(vg), text_num(vd)
                if num(vg) and num(vd):
                    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 = "取整尾差(可忽略)"
                    else: cat = "实质数值差异"
                    note = "å·®=%.6g" % diff
                elif num(vg) and (vd is None):
                    cat = "已忽略(单边有值)"
                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
                elif td is not None and num(vg) and abs(td - vg) <= 1e-9:
                    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("#") :
                    cat = "错误值(程序)"; note = vg
                elif isinstance(vd, str) and vd.startswith("#"):
                    cat = "错误值(手工)"; note = vd
                elif (num(vg) and isinstance(vd, str)) or (num(vd) and isinstance(vg, str)):
                    cat = "需处理(非数值文本)"; note = "程序=%s æ‰‹å·¥=%s" % (str(vg)[:14], str(vd)[:14])
                else:
                    cat = "其它"; note = "程序=%s æ‰‹å·¥=%s" % (str(vg)[:14], str(vd)[:14])
                if cat:
                    bump(cat)
                    rows_out.append([sh, "%s%d" % (get_column_letter(i+1), p+1), rl_g[p][:38], cl_g[i][:18], cat,
                                     ("" if vg is None else str(vg)[:20]), ("" if vd is None else str(vd)[:20]), note,
                                     "%s%d" % (get_column_letter(j+1), q+1)])
        per_sheet.append((sh, cnt))
    # è¾“出 CSV
    csv = prefix + ".csv"
    with io.open(csv, "w", encoding="utf-8-sig", newline="") as f:
        f.write("页签,生成件单元格,行标签,列表头,类别,生成件值,手工值,备注,手工单元格\n")
        for x in rows_out:
            if x[4] not in ACTION: continue
            f.write(",".join('"%s"' % str(v).replace('"', "'") for v in x) + "\n")
    # è¾“出 MD
    md = prefix + ".md"
    with io.open(md, "w", encoding="utf-8", newline="\n") as f:
        f.write("# æ±‡æ€»å¤§è¡¨å·®å¼‚明细(生成件 vs æ‰‹å·¥å®šç¨¿ï¼‰\n\n")
        f.write("- ç”Ÿæˆä»¶ï¼š`%s`\n- æ‰‹å·¥å®šç¨¿ï¼š`%s`\n- æ¯”对时间:%s\n- æ˜Žç»†æ•°æ®ï¼š`%s`\n\n" % (gen, dgf, datetime.datetime.now().strftime("%Y-%m-%d %H:%M"), os.path.basename(csv)))
        f.write("## é€é¡µæ±‡æ€»\n\n| é¡µç­¾ | " + " | ".join(cats[:7]) + " |\n|---|" + "---|"*7 + "\n")
        for sh, cnt in per_sheet:
            f.write("| %s | %s |\n" % (sh, " | ".join(str(cnt.get(c, 0)) for c in cats[:7])))
        f.write("\n## éœ€å¤„理差异(全部,含原因)\n\n")
        subs = [x for x in rows_out if x[4] in ACTION and x[4] != "文本不同"]
        if not subs: f.write("(无)\n")
        for x in subs[:300]:
            f.write("- **%s** %s [%s] ç”Ÿæˆä»¶=%s æ‰‹å·¥=%s%s\n" % (x[0], x[1], x[2], x[5], x[6],
                    ("(原因:%s)" % reason_of(x)) if reason_of(x) else ""))
        f.write("\n## æ ‡é¢˜/表头措辞差异\n\n")
        tt = [x for x in rows_out if x[4] == "文本不同"]
        if not tt: f.write("(无)\n")
        for x in tt[:50]:
            f.write("- %s %s [%s] ç”Ÿæˆä»¶=%s æ‰‹å·¥=%s\n" % (x[0], x[1], x[2], x[5], x[6]))
        f.write("\n## å·²æŒ‰å£å¾„忽略(仅计数,明细见 CSV ä¹‹å¤–不列出)\n\n| é¡µç­¾ | " + " | ".join(IGNORE) + " |\n|---|" + "---|"*len(IGNORE) + "\n")
        for sh, cnt in per_sheet:
            f.write("| %s | %s |\n" % (sh, " | ".join(str(cnt.get(c, 0)) for c in IGNORE)))
    print("已输出:", md, "|", csv, "| æ˜Žç»†æ¡æ•°:", len(rows_out))
    for sh, cnt in per_sheet:
        if cnt: print("   %-12s %s" % (sh, dict(cnt)))
if __name__ == "__main__":
    main(sys.argv[1], sys.argv[2], sys.argv[3])
docs/Éú³É»ã×Ü´ó±í/2026Äê8ÔµÀ·ÔËÊäÁ¿»ã×ܱí.xlsx
Binary files differ
traffic-audit-server/src/main/java/com/trafficaudit/reportexport/calc/FreightCalc.java
@@ -147,14 +147,15 @@
        Double aboveFreight = null;
        Map<Integer, Double> h2032Buckets = h2032TonByCityMonth.get(city);
        if (h2032Buckets != null) {
            double ton;
            if (cumulative) {
                ton = 0.0;
                double ton = 0.0;
                for (int m = 1; m <= month; m++) ton += h2032Buckets.getOrDefault(m, 0.0);
                aboveFreight = ton / 10000.0;
            } else {
                ton = h2032Buckets.getOrDefault(month, 0.0);
                // å½“月无规上记录 -> ç•™ç©ºï¼ˆä¸å†å†™ 0);有记录(含真实 0 å€¼ï¼‰æ‰å†™æ•°
                Double ton = h2032Buckets.get(month);
                aboveFreight = ton == null ? null : ton / 10000.0;
            }
            aboveFreight = ton / 10000.0;
        }
        FreightTurnoverImport ft = ftMap.get(city);