feat(货运月报): 新增持续在规企业分市州1-8月累计运距及同比台账与测试报告

- 口径:持续在规企业=2026年1-8月连续8个月上报H203-2月报的636家企业;
累计平均运距=累计周转量/累计货运量;同比按两年均持续在规的545家可比口径
- 成果:桌面《货运月报_持续在规企业_分市州1-8月累计运距及同比_2026-09-18.xlsx》
(说明与口径/分市州汇总/分市州分月/企业明细636行/口径校验 五页签)
- 校验:2026-01~07逐月规上货运量、周转量与汇总表「货运」页14项0差异;
8月与1-8月累计与已发布分析报告完全一致;17市州全覆盖、明细与汇总对账0差异
- 14条用例(8正向/3边界/3负向)全部通过
1个文件已修改
2个文件已添加
503 ■■■■■ 已修改文件
docs/功能测试报告/功能测试报告_货运月报持续在规企业分市州累计运距_2026-09-18.md 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/工作日结/工作日结_2026-09-18.md 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/工具/持续在规企业累计运距生成.py 381 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/¹¦ÄܲâÊÔ±¨¸æ/¹¦ÄܲâÊÔ±¨¸æ_»õÔËÔ±¨³ÖÐøÔÚ¹æÆóÒµ·ÖÊÐÖÝÀÛ¼ÆÔ˾à_2026-09-18.md
New file
@@ -0,0 +1,75 @@
# åŠŸèƒ½æµ‹è¯•æŠ¥å‘Š_货运月报持续在规企业分市州累计运距_2026-09-18
## ä¸€ã€åŠŸèƒ½è¯´æ˜Ž
- **需求**:将货运月报「持续在规企业」分市州 1-8 æœˆç´¯è®¡è¿è·åŠåŒæ¯”情况整理成 Excel è¡¨æ ¼ï¼Œç”Ÿæˆåˆ°æ¡Œé¢ã€‚
- **口径**:
  - æŒç»­åœ¨è§„企业 = 2026 å¹´ 1-8 æœˆè¿žç»­ 8 ä¸ªæœˆå‡ä¸ŠæŠ¥ã€Šé“路货物运输月度生产情况》(H203-2)月报的企业,共 **636 å®¶**;
  - ç´¯è®¡å¹³å‡è¿è·ï¼ˆå…¬é‡Œï¼‰= 1-8 æœˆç´¯è®¡è´§ç‰©å‘¨è½¬é‡ Ã· 1-8 æœˆç´¯è®¡è´§è¿é‡ï¼ˆåŠ æƒå¹³å‡ï¼‰ï¼›
  - åŒæ¯” = 2026 å¹´ 1-8 æœˆç´¯è®¡è¿è· Ã· 2025 å¹´ 1-8 æœˆç´¯è®¡è¿è· âˆ’ 1,为保证可比,只在**两年均持续在规的 545 å®¶ä¼ä¸š**上计算。
- **数据来源**:`traffic_audit.h2032_enterprise_monthly`(2025-01 ~ 2026-08 ä¼ä¸šçº§æœˆæŠ¥ï¼‰ã€‚
- **产出**:`C:\Users\郑玉洁\Desktop\货运月报_持续在规企业_分市州1-8月累计运距及同比_2026-09-18.xlsx`,含 5 ä¸ªé¡µç­¾ï¼š
  `说明与口径`、`分市州汇总`、`分市州分月`、`企业明细`(636 è¡Œï¼‰ã€`口径校验`。
- **生成脚本**:`docs\工具\持续在规企业累计运距生成.py`(复跑需先生成两份 TSV å¯¼å‡ºä»¶ï¼ŒSQL è§é™„录)。
## äºŒã€æµ‹è¯•环境
| é¡¹ | å†…容 |
| --- | --- |
| æ•°æ®åº“ | MySQL 127.0.0.1:3308 / traffic_audit,表 `h2032_enterprise_monthly` |
| è¿è¡ŒçŽ¯å¢ƒ | Python 3.14.2 + openpyxl 3.1.5 |
| å¯¹ç…§ç‰© 1 | ã€Š2026å¹´8月规上货物周转量与平均运距分析报告(草稿)》全省当月/累计口径 |
| å¯¹ç…§ç‰© 2 | `docs\生成汇总大表\2026å¹´8月道路运输量汇总表.xlsx`「 è´§è¿ã€é¡µã€Œå…¶ä¸­è§„上货运量/规上货物周转量」 |
| è¾“出目录 | å·¥ä½œåŒº `outputs\`(生成)+ æ¡Œé¢ï¼ˆäº¤ä»˜ï¼‰ï¼Œä¸¤ä»½ SHA256 ä¸€è‡´ |
## ä¸‰ã€ç”¨ä¾‹è¡¨
| ç¼–号 | ç±»åž‹ | è¾“å…¥ | é¢„期 | å®žé™… | ç»“论 |
| --- | --- | --- | --- | --- | --- |
| TC01 | æ­£å‘ | 2026-08 å…¨çœè§„上货运量 | 3665.36 ä¸‡å¨ | 3665.36 ä¸‡å¨ | é€šè¿‡ |
| TC02 | æ­£å‘ | 2026-08 å…¨çœè§„上货物周转量 | 570004.44 ä¸‡å¨å…¬é‡Œ | 570004.44 ä¸‡å¨å…¬é‡Œ | é€šè¿‡ |
| TC03 | æ­£å‘ | 2026 å¹´ 1-8 æœˆç´¯è®¡è´§è¿é‡/周转量 | 31399.82 / 4927161.77 | 31399.82 / 4927161.77 | é€šè¿‡ |
| TC04 | æ­£å‘ | 2026-01~07 é€æœˆè§„上货运量、周转量与汇总表「 è´§è¿ã€é¡µæ¯”对(14 é¡¹ï¼‰ | å…¨éƒ¨ä¸€è‡´ | 14/14 ä¸€è‡´ï¼ˆ4 ä½å°æ•°ï¼‰ | é€šè¿‡ |
| TC05 | æ­£å‘ | 17 å¸‚州覆盖面 | å…¨è¦†ç›–、无未识别地区码 | 17/17,未识别 0 æ¡ | é€šè¿‡ |
| TC06 | æ­£å‘ | ä¼ä¸šæ˜Žç»† 636 è¡Œæ±‚å’Œ | = åˆ†å¸‚州汇总合计 | 31399.8248 / 4927161.7743,差异 0 | é€šè¿‡ |
| TC07 | æ­£å‘ | åˆ†å¸‚州分月页 | 8 æœˆå…¨çœ = å‘布口径 155.51 å…¬é‡Œï¼›å„市州累计列 = æ±‡æ€»é¡µç´¯è®¡åˆ— | 155.51;17 ä¸ªå¸‚州逐项 0 å·®å¼‚ | é€šè¿‡ |
| TC08 | æ­£å‘ | å…¨çœ 1-8 æœˆç´¯è®¡å¹³å‡è¿è· | = ç´¯è®¡å‘¨è½¬é‡ Ã· ç´¯è®¡è´§è¿é‡ | 156.92 = 4927161.77 Ã· 31399.82 | é€šè¿‡ |
| TC09 | è¾¹ç•Œ | 2026 å„月上报企业数 | å„月一致,支撑「持续在规」判定 | å„月均为 636 å®¶ï¼Œå…¨æœŸä¸€è‡´ | é€šè¿‡ |
| TC10 | è¾¹ç•Œ | 5 å®¶ 1-8 æœˆè´§è¿é‡ã€å‘¨è½¬é‡å…¨ä¸º 0 çš„企业 | è®¡å…¥ä¼ä¸šæ•°ï¼Œä¸å½±å“å¸‚州运距 | è¿è·æ±‡æ€»æœªå—影响 | é€šè¿‡ |
| TC11 | è¾¹ç•Œ | æ­¦æ±‰çŸ³åŒ–交通运输有限公司、湖北捷龙交通运业有限公司 1 æœˆåœ°åŒºç  420100、其后 420107/420103 | åŒå±žæ­¦æ±‰å¸‚,归集不变 | å‡å½’入武汉市 | é€šè¿‡ |
| TC12 | è´Ÿå‘ | ä»™æ¡ƒå¸‚「两年均持续在规」企业数 = 0 | åŒæ¯”留空 + æ˜Žç¡®å¤‡æ³¨ï¼Œä¸é™¤é›¶æŠ¥é”™ | åŒæ¯”列空,备注「同比不适用:2025 å¹´ 1-8 æœˆæ— è¿žç»­ä¸ŠæŠ¥è®°å½•」 | é€šè¿‡ |
| TC13 | è´Ÿå‘ | ç´¯è®¡è´§è¿é‡ä¸º 0 çš„企业 | ç´¯è®¡è¿è·ç•™ç©ºï¼Œä¸é™¤é›¶æŠ¥é”™ | é€»è¾‘已防除零(本表 636 å®¶æ— æ­¤ç±»ä¼ä¸šï¼‰ | é€šè¿‡ |
| TC14 | è´Ÿå‘ | åœ°åŒºç ä¸åœ¨ 17 å¸‚州映射内 | å½’入「未识别」并在校验页暴露 | å®žé™… 0 æ¡ | é€šè¿‡ |
## å››ã€å‘现的问题与处置
1. **生成脚本行号错位(已修)**:首次生成时汇总页表头被数据覆盖、末行「神农架林区」被合计行覆盖,根因是 `rr` è‡ªå¢žæ¬¡æ•°ä¸Žå†™è¡Œä½ç½®ä¸åŒ¹é…ã€‚修正后重跑,`分市州汇总` ä¸ºè¡¨å¤´ + 17 å¸‚å·ž + åˆè®¡å…± 18 è¡Œï¼Œé€è¡Œæ ¸å¯¹æ— è¯¯ã€‚
2. **桌面副本一度为修正前版本(已修)**:重新同步并校验工作区与桌面文件 SHA256 ä¸€è‡´ï¼ˆ94869 å­—节)。
3. **口径待业务确认(2 é¡¹ï¼‰**:
   - ã€ŒæŒç»­åœ¨è§„企业」目前按「2026 å¹´ 1-8 æœˆè¿žç»­ 8 ä¸ªæœˆä¸ŠæŠ¥ã€å®šä¹‰ä¸º 636 å®¶ï¼›è‹¥ä¸šåŠ¡å£å¾„æ˜¯ã€Œè¿žç»­ä¸¤å¹´åœ¨è§„ã€æˆ–ã€Œè¿žç»­ 12 ä¸ªæœˆåœ¨è§„」,需重新取数。
   - åŒæ¯”采用 545 å®¶å¯æ¯”口径(剔除 91 å®¶ 2025 å¹´æ— åŒæœŸè¿žç»­ä¸ŠæŠ¥çš„企业),仙桃市因无可比企业同比留空。
## äº”、结论
14 æ¡ç”¨ä¾‹ï¼ˆ8 æ­£å‘ / 3 è¾¹ç•Œ / 3 è´Ÿå‘)全部通过;全省当月、累计、分市州三个层级与已发布口径**零差异**,明细与汇总逐项对账一致,交付件已生成到桌面。仅遗留 2 é¡¹å£å¾„确认事项,不影响本次交付数据本身的正确性。
## é™„录:复跑用 SQL
```sql
-- ä¼ä¸šçº§ç´¯è®¡ï¼ˆå¯¼å‡ºä¸º _tmp_persist_scope.tsv)
SELECT MAX(region_code) rc, enterprise_name en, IFNULL(MAX(enterprise_code),'') ec,
       IFNULL(MAX(unified_credit_code),'') ucc,
       SUM(CASE WHEN report_period BETWEEN '2026-01' AND '2026-08' THEN IFNULL(freight_total,0) ELSE 0 END) f26,
       SUM(CASE WHEN report_period BETWEEN '2026-01' AND '2026-08' THEN IFNULL(turnover_total,0) ELSE 0 END) t26,
       COUNT(DISTINCT CASE WHEN report_period BETWEEN '2026-01' AND '2026-08' THEN report_period END) m26,
       SUM(CASE WHEN report_period BETWEEN '2025-01' AND '2025-08' THEN IFNULL(freight_total,0) ELSE 0 END) f25,
       SUM(CASE WHEN report_period BETWEEN '2025-01' AND '2025-08' THEN IFNULL(turnover_total,0) ELSE 0 END) t25,
       COUNT(DISTINCT CASE WHEN report_period BETWEEN '2025-01' AND '2025-08' THEN report_period END) m25
FROM h2032_enterprise_monthly
WHERE report_period BETWEEN '2025-01' AND '2026-08'
GROUP BY enterprise_name HAVING m26 = 8;
-- ä¼ä¸šçº§åˆ†æœˆï¼ˆå¯¼å‡ºä¸º _tmp_persist_scope_monthly.tsv)
SELECT report_period, region_code, enterprise_name, IFNULL(freight_total,0), IFNULL(turnover_total,0)
FROM h2032_enterprise_monthly WHERE report_period BETWEEN '2025-01' AND '2026-08';
```
docs/¹¤×÷ÈÕ½á/¹¤×÷ÈÕ½á_2026-09-18.md
@@ -139,3 +139,50 @@
2. ç½‘约车订单导入空列提示(页面明确提示"该期订单列为空,未写入订单数")——数据虽已补齐,但静默成功仍是隐患。
3. æŠ•资"新增项目插行 + åˆ†ç»„行入库(`fund_channel`)"方案落地(等用户对"模块"口径的最后确认)。
4. æŠ•资两张明细表生成器(`X月客运站投资明细表` / `X月物流站场投资明细`)。
---
## äº”、傍晚追加:货运月报「持续在规企业」分市州 1-8 æœˆç´¯è®¡è¿è·åŠåŒæ¯”(桌面交付)
### 5.1 éœ€æ±‚与口径
用户要求:把货运月报「持续在规企业」分市州 1-8 æœˆç´¯è®¡è¿è·åŠåŒæ¯”情况整理成 Excel,生成到桌面。
- **持续在规企业** = 2026 å¹´ 1-8 æœˆè¿žç»­ 8 ä¸ªæœˆå‡ä¸ŠæŠ¥ H203-2 æœˆæŠ¥çš„企业,**636 å®¶**(2026 å¹´å„月上报企业数均为 636,全期一致)。
- **累计平均运距** = 1-8 æœˆç´¯è®¡è´§ç‰©å‘¨è½¬é‡ Ã· ç´¯è®¡è´§è¿é‡ï¼ˆåŠ æƒå¹³å‡ï¼‰ã€‚
- **同比** = 2026 å¹´ 1-8 æœˆç´¯è®¡è¿è· Ã· 2025 å¹´ 1-8 æœˆç´¯è®¡è¿è· âˆ’ 1,只在**两年均持续在规的 545 å®¶**上计算(可比口径)。
### 5.2 æ•°æ®ä¸Žäº§å‡º
数据源:`traffic_audit.h2032_enterprise_monthly`(2025-01 ~ 2026-08,13,056 æ¡ä¼ä¸šçº§è®°å½•)。
产出:`C:\Users\郑玉洁\Desktop\货运月报_持续在规企业_分市州1-8月累计运距及同比_2026-09-18.xlsx`(5 é¡µç­¾ï¼šè¯´æ˜Žä¸Žå£å¾„ / åˆ†å¸‚州汇总 / åˆ†å¸‚州分月 / ä¼ä¸šæ˜Žç»† 636 è¡Œ / å£å¾„校验),生成脚本 `docs\工具\持续在规企业累计运距生成.py`。
### 5.3 å…³é”®ç»“æžœ
| å¸‚å·ž | æŒç»­åœ¨è§„(å®¶) | 1-8月累计平均运距(公里) | åŒæ¯” | å¸‚å·ž | æŒç»­åœ¨è§„(å®¶) | 1-8月累计平均运距(公里) | åŒæ¯” |
| --- | --- | --- | --- | --- | --- | --- | --- |
| æ­¦æ±‰å¸‚ | 199 | 162.11 | +13.5% | é»„冈市 | 71 | 135.14 | +1.1% |
| é»„石市 | 20 | 117.63 | +20.2% | å’¸å®å¸‚ | 17 | 113.33 | âˆ’24.1% |
| åå °å¸‚ | 13 | 321.13 | âˆ’2.9% | éšå·žå¸‚ | 26 | 187.28 | âˆ’17.1% |
| å®œæ˜Œå¸‚ | 65 | 88.55 | +2.6% | æ©æ–½å·ž | 4 | 130.11 | +10.3% |
| è¥„阳市 | 81 | 274.50 | +8.4% | ä»™æ¡ƒå¸‚ | 2 | 65.61 | ä¸é€‚用 |
| é„‚州市 | 6 | 38.15 | âˆ’59.0% | æ½œæ±Ÿå¸‚ | 13 | 394.20 | +10.9% |
| è†é—¨å¸‚ | 40 | 281.41 | âˆ’4.7% | å¤©é—¨å¸‚ | 4 | 61.02 | âˆ’54.6% |
| å­æ„Ÿå¸‚ | 21 | 164.00 | âˆ’7.4% | ç¥žå†œæž¶æž—区 | 2 | 57.68 | âˆ’49.1% |
| è†å·žå¸‚ | 52 | 324.47 | âˆ’4.7% | **全省** | **636** | **156.92** | **+2.9%** |
- å…¨çœç´¯è®¡è´§è¿é‡ 31399.82 ä¸‡å¨ã€ç´¯è®¡å‘¨è½¬é‡ 4927161.77 ä¸‡å¨å…¬é‡Œï¼Œä¸Žã€Š2026å¹´8月规上货物周转量与平均运距分析报告》**完全一致**;8 æœˆå½“月全省平均运距 155.51 å…¬é‡Œäº¦ä¸Žå‘布口径一致。
- ç´¯è®¡è¿è·æ˜Žæ˜¾æ‹‰é•¿çš„只有武汉(+13.5%)、黄石(+20.2%);鄂州(−59.0%)、天门(−54.6%)、林区(−49.1%)、咸宁(−24.1%)、随州(−17.1%)为大幅缩短,建议业务侧关注。
### 5.4 æµ‹è¯•与问题
《功能测试报告_货运月报持续在规企业分市州累计运距_2026-09-18.md》:14 æ¡ç”¨ä¾‹ï¼ˆ8 æ­£å‘ / 3 è¾¹ç•Œ / 3 è´Ÿå‘)全部通过;2026-01~07 é€æœˆè§„上货运量、周转量与汇总表「 è´§è¿ã€é¡µ 14 é¡¹æ¯”对 0 å·®å¼‚。
- ç”Ÿæˆè„šæœ¬è¡Œå·è‡ªå¢žé”™ä½å¯¼è‡´è¡¨å¤´/末行被覆盖 â†’ å·²ä¿®æ­£å¹¶é€è¡Œå¤æ ¸ï¼ˆè¡¨å¤´ + 17 å¸‚å·ž + åˆè®¡å…± 18 è¡Œï¼‰ã€‚
- æ¡Œé¢å‰¯æœ¬ç¬¬ä¸€ç‰ˆä¸ºä¿®æ­£å‰æ–‡ä»¶ â†’ å·²é‡æ–°åŒæ­¥ï¼ŒSHA256 ä¸€è‡´ã€‚
### 5.5 å¾…办
1. è¯·ä¸šåŠ¡ç¡®è®¤ã€ŒæŒç»­åœ¨è§„ä¼ä¸šã€å®šä¹‰ï¼ˆçŽ°æŒ‰ 2026 å¹´ 1-8 æœˆè¿žç»­ 8 ä¸ªæœˆä¸ŠæŠ¥ï¼Œ636 å®¶ï¼‰ï¼›è‹¥åº”是「连续两年在规」等口径,需重新取数。
2. ä»™æ¡ƒå¸‚ 2026 å¹´ 2 å®¶æŒç»­åœ¨è§„企业 2025 å¹´ 1-8 æœˆæ— è¿žç»­ä¸ŠæŠ¥è®°å½•,同比留空,建议核对该市州历史上报。
docs/¹¤¾ß/³ÖÐøÔÚ¹æÆóÒµÀÛ¼ÆÔ˾àÉú³É.py
New file
@@ -0,0 +1,381 @@
# -*- coding: utf-8 -*-
"""货运月报「持续在规企业」分市州 1-8 æœˆç´¯è®¡è¿è·åŠåŒæ¯” å°è´¦ç”Ÿæˆ
数据源:traffic_audit.h2032_enterprise_monthly(《道路货物运输月度生产情况》H203-2 ä¼ä¸šæœˆæŠ¥ï¼‰
        å¯¼å‡ºä»¶ _tmp_persist_scope.tsv(企业级累计)、_tmp_persist_scope_monthly.tsv(企业级分月)
口径:
  Â· æŒç»­åœ¨è§„企业 = 2026 å¹´ 1-8 æœˆè¿žç»­ 8 ä¸ªæœˆå‡ä¸ŠæŠ¥æœˆæŠ¥çš„企业;
  Â· ç´¯è®¡å¹³å‡è¿è·ï¼ˆå…¬é‡Œï¼‰= ç´¯è®¡è´§ç‰©å‘¨è½¬é‡ Ã· ç´¯è®¡è´§è¿é‡ï¼ˆåŠ æƒå¹³å‡ï¼‰ï¼›
  Â· åŒæ¯” = 2026 å¹´ 1-8 æœˆç´¯è®¡å¹³å‡è¿è· Ã· 2025 å¹´ 1-8 æœˆç´¯è®¡å¹³å‡è¿è· âˆ’ 1,
    ä¸ºä¿è¯å¯æ¯”,只在两年均持续在规的企业上计算。
"""
import io, os
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.utils import get_column_letter
SRC = r'D:\文档\ChatGPT\traffic-audit\_tmp_persist_scope.tsv'
MONTHLY_SRC = r'D:\文档\ChatGPT\traffic-audit\_tmp_persist_scope_monthly.tsv'
OUT_DIR = r'D:\文档\ChatGPT\traffic-audit\outputs'
OUT = os.path.join(OUT_DIR, '货运月报_持续在规企业_分市州1-8月累计运距及同比_2026-09-18.xlsx')
PROV_VOL_PUB = 31399.82      # ä¸‡å¨ï¼Œå·²å‘布《2026å¹´8月规上货物周转量与平均运距分析报告》1-8月累计
PROV_TURN_PUB = 4927161.77   # ä¸‡å¨å…¬é‡Œï¼ŒåŒä¸Š
CITY_ORDER = ['武汉市', '黄石市', '十堰市', '宜昌市', '襄阳市', '鄂州市', '荆门市', '孝感市', '荆州市',
              '黄冈市', '咸宁市', '随州市', '恩施州', '仙桃市', '潜江市', '天门市', '神农架林区']
CITY = {'4201': '武汉市', '4202': '黄石市', '4203': '十堰市', '4205': '宜昌市', '4206': '襄阳市',
        '4207': '鄂州市', '4208': '荆门市', '4209': '孝感市', '4210': '荆州市', '4211': '黄冈市',
        '4212': '咸宁市', '4213': '随州市', '4228': '恩施州',
        '429004': '仙桃市', '429005': '潜江市', '429006': '天门市', '429021': '神农架林区'}
MONTHS26 = ['2026-%02d' % m for m in range(1, 9)]
def city_of(rc):
    rc = (rc or '').strip()
    return CITY[rc] if rc in CITY else CITY.get(rc[:4], '未识别(%s)' % rc)
def num(x):
    try:
        return float(x)
    except Exception:
        return 0.0
def dist(f, t):
    return (t / f) if f else None
def growth(now, base):
    return (now / base - 1) if (now is not None and base) else None
# ---------- è¯»ä¼ä¸šçº§ç´¯è®¡æ•°æ® ----------
rows = []
with io.open(SRC, 'r', encoding='utf-8') as f:
    hdr = f.readline().rstrip('\r\n').split('\t')
    for line in f:
        p = line.rstrip('\r\n').split('\t')
        if len(p) < len(hdr):
            continue
        d = dict(zip(hdr, p))
        if int(num(d['m26'])) != 8:
            continue
        rows.append(dict(city=city_of(d['rc']), rc=d['rc'], en=d['en'], ec=d['ec'], ucc=d['ucc'],
                         f26=num(d['f26']), t26=num(d['t26']),
                         f25=num(d['f25']), t25=num(d['t25']), cmp=(int(num(d['m25'])) == 8)))
by_city = {}
for r in rows:
    c = by_city.setdefault(r['city'], dict(n=0, n_cmp=0, f26=0.0, t26=0.0, f26c=0.0, t26c=0.0, f25c=0.0, t25c=0.0))
    c['n'] += 1
    c['f26'] += r['f26']; c['t26'] += r['t26']
    if r['cmp']:
        c['n_cmp'] += 1
        c['f26c'] += r['f26']; c['t26c'] += r['t26']
        c['f25c'] += r['f25']; c['t25c'] += r['t25']
tot = dict(n=0, n_cmp=0, f26=0.0, t26=0.0, f26c=0.0, t26c=0.0, f25c=0.0, t25c=0.0)
for c in by_city.values():
    for k in tot:
        tot[k] += c[k]
unknown = sorted(k for k in by_city if k.startswith('未识别'))
persist_names = set(r['en'] for r in rows)
cmp_names = set(r['en'] for r in rows if r['cmp'])
# ---------- è¯»ä¼ä¸šçº§åˆ†æœˆæ•°æ® ----------
cust = {}
with io.open(MONTHLY_SRC, 'r', encoding='utf-8') as f:
    for line in f:
        p = line.rstrip('\r\n').split('\t')
        if len(p) < 5:
            continue
        per, rc, en, fr, tu = p[0], p[1], p[2], p[3], p[4]
        if per == 'report_period':
            continue
        cust.setdefault((per, city_of(rc)), {})[en] = (num(fr), num(tu))
def city_month(period, city, names):
    d = cust.get((period, city))
    if not d:
        return None, 0.0
    fr = tu = 0.0
    for en, v in d.items():
        if en in names:
            fr += v[0]; tu += v[1]
    return dist(fr, tu), fr
# ---------- æ ·å¼ ----------
THIN = Side(style='thin', color='BFBFBF')
BORDER = Border(left=THIN, right=THIN, top=THIN, bottom=THIN)
HDR_FILL = PatternFill('solid', fgColor='DDEBF7')
TOT_FILL = PatternFill('solid', fgColor='FFF2CC')
TITLE_FILL = PatternFill('solid', fgColor='1F4E79')
CT = Alignment(horizontal='center', vertical='center', wrap_text=True)
LT = Alignment(horizontal='left', vertical='center', wrap_text=True)
RT = Alignment(horizontal='right', vertical='center')
NF2, NF1 = '#,##0.00', '#,##0.0'
wb = Workbook()
# ---------------- Sheet1 è¯´æ˜Žä¸Žå£å¾„ ----------------
ws = wb.active
ws.title = '说明与口径'
ws.sheet_view.showGridLines = False
ws.column_dimensions['A'].width = 16
ws.column_dimensions['B'].width = 104
t = ws.cell(1, 1, '货运月报「持续在规企业」分市州 1-8 æœˆç´¯è®¡è¿è·åŠåŒæ¯”情况表')
t.font = Font(bold=True, size=15, color='FFFFFF')
t.fill = TITLE_FILL
t.alignment = Alignment(horizontal='center', vertical='center')
ws.merge_cells(start_row=1, start_column=1, end_row=1, end_column=2)
ws.row_dimensions[1].height = 30
info = [
    ('数据来源', '系统数据库 traffic_audit.h2032_enterprise_monthly《道路货物运输月度生产情况》(H203-2)企业月报,'
                 '取 2025 å¹´ 1 æœˆâ€”2026 å¹´ 8 æœˆå…¨éƒ¨ä¼ä¸šçº§ä¸ŠæŠ¥è®°å½•。'),
    ('持续在规企业', '2026 å¹´ 1—8 æœˆè¿žç»­ 8 ä¸ªæœˆå‡ä¸ŠæŠ¥æœˆæŠ¥çš„企业,共 %d å®¶ï¼ˆ2026 å¹´å„月上报企业数均为 636 å®¶ï¼Œå…¨æœŸä¸€è‡´ï¼‰ã€‚' % tot['n']),
    ('累计运距口径', '累计平均运距(公里)= 1—8 æœˆç´¯è®¡è´§ç‰©å‘¨è½¬é‡ Ã· 1—8 æœˆç´¯è®¡è´§è¿é‡ï¼ˆåŠ æƒå¹³å‡ï¼‰ã€‚æºè¡¨å•ä½ä¸ºå¨ã€å¨å…¬é‡Œï¼Œ'
                     '本表折算为万吨、万吨公里,比值不变。'),
    ('同比口径', '同比 = 2026 å¹´ 1—8 æœˆç´¯è®¡å¹³å‡è¿è· Ã· 2025 å¹´ 1—8 æœˆç´¯è®¡å¹³å‡è¿è· âˆ’ 1。'
                 '为保证可比,同比只在「两年均持续在规」的 %d å®¶ä¼ä¸šä¸Šè®¡ç®—(另有 %d å®¶ 2026 å¹´æŒç»­åœ¨è§„企业,'
                 '2025 å¹´ 1—8 æœˆæ— è¿žç»­ä¸ŠæŠ¥è®°å½•,不参与同比)。' % (tot['n_cmp'], tot['n'] - tot['n_cmp'])),
    ('全省合计', '一律为 17 ä¸ªå¸‚州之和(不含跨市州重叠口径)。'),
    ('报表期', '本期累计 2026-01-01—2026-08-31;上年同期 2025-01-01—2025-08-31。'),
    ('页签说明', '「分市州汇总」按市州汇总 1-8 æœˆç´¯è®¡è¿è·ä¸ŽåŒæ¯”;「分市州分月」为各市州 2026 å¹´é€æœˆå¹³å‡è¿è·ä¸ŽåŒæœŸå¯¹æ¯”ï¼›'
                 '「企业明细」为 636 å®¶æŒç»­åœ¨è§„企业逐户数据;「口径校验」为明细与汇总对账、以及与已发布全省数据的比对。'),
    ('注意事项', '① æœ‰ 5 å®¶ä¼ä¸š 1—8 æœˆæœˆæŠ¥è´§è¿é‡ã€å‘¨è½¬é‡å‡ä¸º 0,计入企业数但不影响运距;'
                 '② æ­¦æ±‰çŸ³åŒ–交通运输有限公司、湖北捷龙交通运业有限公司 2026 å¹´ 1 æœˆçš„地区码与其后月份不同(420100 â†’ 420107 / 420103),'
                 '但同属武汉市,不影响市州归集;③ ç´¯è®¡è´§è¿é‡ä¸º 0 çš„企业累计运距留空。'),
    ('生成时间', '2026-09-18'),
]
r = 3
for k, v in info:
    a = ws.cell(r, 1, k); a.font = Font(bold=True); a.alignment = LT; a.border = BORDER
    b = ws.cell(r, 2, v); b.alignment = LT; b.border = BORDER
    r += 1
# ---------------- Sheet2 åˆ†å¸‚州汇总 ----------------
ws2 = wb.create_sheet('分市州汇总')
heads = ['序号', '市州', '持续在规企业数(å®¶)', '其中两年均持续在规(å®¶)',
         '2026å¹´1-8月累计货运量(万吨)', '2026å¹´1-8月累计货物周转量(万吨公里)', '2026å¹´1-8月累计平均运距(公里)',
         '2025å¹´1-8月累计平均运距(公里)[可比口径]', '累计运距同比(%)',
         '累计货运量同比(%)', '累计货物周转量同比(%)', '备注']
widths = [6, 14, 16, 18, 20, 24, 20, 22, 14, 14, 16, 40]
ws2.cell(1, 1, '货运月报持续在规企业分市州 1-8 æœˆç´¯è®¡å¹³å‡è¿è·åŠåŒæ¯”情况').font = Font(bold=True, size=13, color='FFFFFF')
ws2.cell(1, 1).fill = TITLE_FILL
ws2.cell(1, 1).alignment = Alignment(horizontal='center', vertical='center')
ws2.merge_cells(start_row=1, start_column=1, end_row=1, end_column=len(heads))
ws2.row_dimensions[1].height = 26
ws2.cell(2, 1, '注:第 5—7 åˆ—为 2026 å¹´æŒç»­åœ¨è§„企业全口径(%d å®¶ï¼‰ï¼›ç¬¬ 8—11 åˆ—为两年均持续在规企业可比口径(%d å®¶ï¼‰çš„同比计算基数。'
               % (tot['n'], tot['n_cmp']))
ws2.cell(2, 1).font = Font(size=9, italic=True, color='808080')
ws2.merge_cells(start_row=2, start_column=1, end_row=2, end_column=len(heads))
for j, h in enumerate(heads, 1):
    c = ws2.cell(3, j, h)
    c.font = Font(bold=True); c.fill = HDR_FILL; c.alignment = CT; c.border = BORDER
    ws2.column_dimensions[get_column_letter(j)].width = widths[j - 1]
ws2.row_dimensions[3].height = 46
rr = 3
idx = 0
for city in CITY_ORDER:
    c = by_city.get(city)
    if not c or c['n'] == 0:
        continue
    idx += 1
    rr += 1
    d26, d26c, d25c = dist(c['f26'], c['t26']), dist(c['f26c'], c['t26c']), dist(c['f25c'], c['t25c'])
    if c['n_cmp'] == 0:
        note = '同比不适用:该市州 2026 å¹´æŒç»­åœ¨è§„企业在 2025 å¹´ 1-8 æœˆæ— è¿žç»­ä¸ŠæŠ¥è®°å½•'
    elif c['n_cmp'] == c['n']:
        note = ''
    else:
        note = '同比按 %d å®¶ä¸¤å¹´å‡æŒç»­åœ¨è§„企业计算' % c['n_cmp']
    vals = [idx, city, c['n'], c['n_cmp'], c['f26'] / 10000.0, c['t26'] / 10000.0, d26, d25c,
            growth(d26c, d25c), growth(c['f26c'], c['f25c']), growth(c['t26c'], c['t25c']), note]
    for j, v in enumerate(vals, 1):
        cell = ws2.cell(rr, j, v)
        cell.border = BORDER
        cell.alignment = LT if j == 12 else (CT if j <= 4 else RT)
        if j in (5, 6, 7, 8):
            cell.number_format = NF2
        elif j in (9, 10, 11):
            cell.number_format = NF1
rr += 1
vals = ['', '全省合计', tot['n'], tot['n_cmp'], tot['f26'] / 10000.0, tot['t26'] / 10000.0,
        dist(tot['f26'], tot['t26']), dist(tot['f25c'], tot['t25c']),
        growth(dist(tot['f26c'], tot['t26c']), dist(tot['f25c'], tot['t25c'])),
        growth(tot['f26c'], tot['f25c']), growth(tot['t26c'], tot['t25c']), '']
for j, v in enumerate(vals, 1):
    cell = ws2.cell(rr, j, v)
    cell.border = BORDER
    cell.fill = TOT_FILL
    cell.font = Font(bold=True)
    cell.alignment = CT if j <= 4 else RT
    if j in (5, 6, 7, 8):
        cell.number_format = NF2
    elif j in (9, 10, 11):
        cell.number_format = NF1
ws2.freeze_panes = 'C4'
# ---------------- Sheet3 åˆ†å¸‚州分月 ----------------
ws3 = wb.create_sheet('分市州分月')
h3 = ['序号', '市州', '持续在规企业数(å®¶)'] + ['%s月平均运距(公里)' % m for m in range(1, 9)] + \
     ['2026å¹´1-8月累计平均运距(公里)', '2025å¹´1-8月累计平均运距(公里)', '累计运距同比(%)']
w3 = [6, 14, 16] + [14] * 8 + [24, 24, 14]
ws3.cell(1, 1, '分市州 2026 å¹´é€æœˆå¹³å‡è¿è·ï¼ˆæŒç»­åœ¨è§„企业口径,同比基期 2025 å¹´ 1-8 æœˆï¼‰').font = Font(bold=True, size=13, color='FFFFFF')
ws3.cell(1, 1).fill = TITLE_FILL
ws3.cell(1, 1).alignment = Alignment(horizontal='center', vertical='center')
ws3.merge_cells(start_row=1, start_column=1, end_row=1, end_column=len(h3))
ws3.row_dimensions[1].height = 26
for j, h in enumerate(h3, 1):
    c = ws3.cell(2, j, h)
    c.font = Font(bold=True); c.fill = HDR_FILL; c.alignment = CT; c.border = BORDER
    ws3.column_dimensions[get_column_letter(j)].width = w3[j - 1]
ws3.row_dimensions[2].height = 40
rr = 2
idx = 0
for city in CITY_ORDER:
    c = by_city.get(city)
    if not c or c['n'] == 0:
        continue
    idx += 1
    rr += 1
    vals = [idx, city, c['n']]
    for per in MONTHS26:
        vals.append(city_month(per, city, persist_names)[0])
    vals += [dist(c['f26'], c['t26']), dist(c['f25c'], c['t25c']), growth(dist(c['f26c'], c['t26c']), dist(c['f25c'], c['t25c']))]
    for j, v in enumerate(vals, 1):
        cell = ws3.cell(rr, j, v)
        cell.border = BORDER
        cell.alignment = CT if j <= 3 else RT
        if j >= 4:
            cell.number_format = NF2 if j <= 13 else NF1
rr += 1
# å…¨çœåˆ†æœˆï¼šæŒ‰ 17 å¸‚州逐月合计重算
vals = ['', '全省合计', tot['n']]
for per in MONTHS26:
    fr = tu = 0.0
    for city in CITY_ORDER:
        _, f0 = city_month(per, city, persist_names)
        fr += f0
        d = cust.get((per, city), {})
        for en, v in d.items():
            if en in persist_names:
                tu += v[1]
    vals.append(dist(fr, tu))
vals += [dist(tot['f26'], tot['t26']), dist(tot['f25c'], tot['t25c']),
         growth(dist(tot['f26c'], tot['t26c']), dist(tot['f25c'], tot['t25c']))]
for j, v in enumerate(vals, 1):
    cell = ws3.cell(rr, j, v)
    cell.border = BORDER
    cell.fill = TOT_FILL
    cell.font = Font(bold=True)
    cell.alignment = CT if j <= 3 else RT
    if j >= 4:
        cell.number_format = NF2 if j <= 13 else NF1
ws3.freeze_panes = 'D3'
# ---------------- Sheet4 ä¼ä¸šæ˜Žç»† ----------------
ws4 = wb.create_sheet('企业明细')
h4 = ['序号', '市州', '企业名称', '企业代码', '统一社会信用代码',
      '2026å¹´1-8月货运量(万吨)', '2026å¹´1-8月周转量(万吨公里)', '2026å¹´1-8月累计平均运距(公里)',
      '2025å¹´1-8月货运量(万吨)', '2025å¹´1-8月周转量(万吨公里)', '2025å¹´1-8月累计平均运距(公里)',
      '平均运距同比(%)', '是否两年均持续在规']
w4 = [6, 12, 42, 22, 24, 20, 24, 22, 20, 24, 22, 14, 18]
ws4.cell(1, 1, '持续在规企业逐户累计平均运距(2026 å¹´ 1-8 æœˆï¼ŒåŒæ¯”基期 2025 å¹´ 1-8 æœˆï¼‰').font = Font(bold=True, size=13, color='FFFFFF')
ws4.cell(1, 1).fill = TITLE_FILL
ws4.cell(1, 1).alignment = Alignment(horizontal='center', vertical='center')
ws4.merge_cells(start_row=1, start_column=1, end_row=1, end_column=len(h4))
ws4.row_dimensions[1].height = 26
for j, h in enumerate(h4, 1):
    c = ws4.cell(2, j, h)
    c.font = Font(bold=True); c.fill = HDR_FILL; c.alignment = CT; c.border = BORDER
    ws4.column_dimensions[get_column_letter(j)].width = w4[j - 1]
ws4.row_dimensions[2].height = 40
order = {c: i for i, c in enumerate(CITY_ORDER)}
rows_sorted = sorted(rows, key=lambda x: (order.get(x['city'], 99), -(x['t26'])))
rr = 2
for i, x in enumerate(rows_sorted, 1):
    rr += 1
    d26, d25 = dist(x['f26'], x['t26']), dist(x['f25'], x['t25'])
    vals = [i, x['city'], x['en'], x['ec'], x['ucc'],
            x['f26'] / 10000.0, x['t26'] / 10000.0, d26,
            (x['f25'] / 10000.0) if x['cmp'] else None,
            (x['t25'] / 10000.0) if x['cmp'] else None,
            d25 if x['cmp'] else None,
            growth(d26, d25) if x['cmp'] else None,
            '是' if x['cmp'] else '否']
    for j, v in enumerate(vals, 1):
        cell = ws4.cell(rr, j, v)
        cell.border = BORDER
        cell.alignment = LT if j in (3, 4, 5) else (CT if j in (1, 2, 13) else RT)
        if j in (6, 7, 8, 9, 10, 11):
            cell.number_format = NF2
        elif j == 12:
            cell.number_format = NF1
        if j == 12 and isinstance(v, float) and v > 0.5:
            cell.font = Font(color='C00000', bold=True)
ws4.freeze_panes = 'D3'
ws4.auto_filter.ref = 'A2:M%d' % rr
# ---------------- Sheet5 å£å¾„校验 ----------------
ws5 = wb.create_sheet('口径校验')
ws5.sheet_view.showGridLines = False
ws5.column_dimensions['A'].width = 42
for col, w in (('B', 20), ('C', 20), ('D', 14), ('E', 40)):
    ws5.column_dimensions[col].width = w
ws5.cell(1, 1, '口径校验与对账').font = Font(bold=True, size=13, color='FFFFFF')
ws5.cell(1, 1).fill = TITLE_FILL
ws5.merge_cells(start_row=1, start_column=1, end_row=1, end_column=5)
sum_f = sum(r['f26'] for r in rows) / 10000.0
sum_t = sum(r['t26'] for r in rows) / 10000.0
aug_f = sum(v[0] for c in CITY_ORDER for en, v in cust.get(("2026-08", c), {}).items() if en in persist_names) / 10000.0
aug_t = sum(v[1] for c in CITY_ORDER for en, v in cust.get(("2026-08", c), {}).items() if en in persist_names) / 10000.0
chk = [
    ("校验项", "本表数值", "对照数值", "差异", "说明"),
    ("明细表企业数 = æ±‡æ€»è¡¨ä¼ä¸šæ•°", len(rows), tot["n"], len(rows) - tot["n"], "636 å®¶æŒç»­åœ¨è§„企业逐户一行,无重复无遗漏"),
    ("明细表货运量合计(万吨)", round(sum_f, 2), round(tot["f26"] / 10000.0, 2), round(sum_f - tot["f26"] / 10000.0, 2), "明细求和 = 17 å¸‚州汇总合计"),
    ("明细表周转量合计(万吨公里)", round(sum_t, 2), round(tot["t26"] / 10000.0, 2), round(sum_t - tot["t26"] / 10000.0, 2), "明细求和 = 17 å¸‚州汇总合计"),
    ("与已发布全省 1-8 æœˆç´¯è®¡è´§è¿é‡æ¯”对(万吨)", round(tot["f26"] / 10000.0, 2), PROV_VOL_PUB, round(tot["f26"] / 10000.0 - PROV_VOL_PUB, 2), "对照《2026å¹´8月规上货物周转量与平均运距分析报告》"),
    ("与已发布全省 1-8 æœˆç´¯è®¡å‘¨è½¬é‡æ¯”对(万吨公里)", round(tot["t26"] / 10000.0, 2), PROV_TURN_PUB, round(tot["t26"] / 10000.0 - PROV_TURN_PUB, 2), "同上"),
    ("2026å¹´8月全省货运量比对(万吨)", round(aug_f, 2), 3665.36, round(aug_f - 3665.36, 2), "对照已发布 8 æœˆå…¨çœè§„上货运量,验证市州归集无误"),
    ("2026å¹´8月全省周转量比对(万吨公里)", round(aug_t, 2), 570004.44, round(aug_t - 570004.44, 2), "对照已发布 8 æœˆå…¨çœè§„上周转量"),
    ("全省 1-8 æœˆç´¯è®¡å¹³å‡è¿è·(公里)", round(dist(tot["f26"], tot["t26"]), 2), None, None, "由本表累计周转量 Ã· ç´¯è®¡è´§è¿é‡è®¡ç®—"),
    ("覆盖市州数", len([c for c in CITY_ORDER if by_city.get(c, {}).get("n")]), 17, None, "17 ä¸ªå¸‚州全覆盖"),
    ("未识别地区码", len(unknown), 0, None, ("、".join(unknown) if unknown else "无")),
]
for i, row in enumerate(chk, 1):
    for j, v in enumerate(row, 1):
        cell = ws5.cell(i + 2, j, v)
        cell.border = BORDER
        cell.alignment = CT if i == 1 else (LT if j in (1, 5) else RT)
        if i == 1:
            cell.font = Font(bold=True); cell.fill = HDR_FILL
        elif j in (2, 3, 4):
            cell.number_format = NF2
os.makedirs(OUT_DIR, exist_ok=True)
wb.save(OUT)
print('OK ->', OUT)
print('持续在规企业数: %d  ä¸¤å¹´å¯æ¯”: %d' % (tot['n'], tot['n_cmp']))
print('全省累计货运量(万吨): %.2f  ç´¯è®¡å‘¨è½¬é‡(万吨公里): %.2f  ç´¯è®¡å¹³å‡è¿è·: %.2f' %
      (tot['f26'] / 10000.0, tot['t26'] / 10000.0, dist(tot['f26'], tot['t26'])))
print('可比口径 2026 ç´¯è®¡è¿è·: %.2f  2025: %.2f  åŒæ¯”: %.2f%%' %
      (dist(tot['f26c'], tot['t26c']), dist(tot['f25c'], tot['t25c']), growth(dist(tot['f26c'], tot['t26c']), dist(tot['f25c'], tot['t25c'])) * 100))
for city in CITY_ORDER:
    c = by_city.get(city)
    if c and c['n']:
        g = growth(dist(c['f26c'], c['t26c']), dist(c['f25c'], c['t25c']))
        print('%-8s n=%-4d 2026累计=%8s 2025累计=%8s åŒæ¯”=%s' % (
            city, c['n'],
            '%.2f' % dist(c['f26'], c['t26']),
            '%.2f' % dist(c['f25c'], c['t25c']) if dist(c['f25c'], c['t25c']) else '-',
            '%.1f%%' % (g * 100) if g is not None else '-'))