docs/¹¤¾ß/³ÖÐøÔÚ¹æÆóÒµÀÛ¼ÆÔ˾àÉú³É.py
@@ -1,13 +1,14 @@
# -*- coding: utf-8 -*-
"""货运月报「持续在规企业」分市州 1-8 æœˆç´¯è®¡è¿è·åŠåŒæ¯” å°è´¦ç”Ÿæˆ
"""货运月报「持续在规企业」分市州 1-8 æœˆç´¯è®¡è¿è·åŠåŒæ¯” å°è´¦ç”Ÿæˆï¼ˆv2:同比基期改为官方全口径)
数据源:traffic_audit.h2032_enterprise_monthly(《道路货物运输月度生产情况》H203-2 ä¼ä¸šæœˆæŠ¥ï¼‰
        å¯¼å‡ºä»¶ _tmp_persist_scope.tsv(企业级累计)、_tmp_persist_scope_monthly.tsv(企业级分月)
口径:
  Â· æŒç»­åœ¨è§„企业 = 2026 å¹´ 1-8 æœˆè¿žç»­ 8 ä¸ªæœˆå‡ä¸ŠæŠ¥æœˆæŠ¥çš„企业;
口径(v2 ä¿®è®¢ï¼‰ï¼š
  Â· æŒç»­åœ¨è§„企业 = 2026 å¹´ 1-8 æœˆè¿žç»­ 8 ä¸ªæœˆå‡ä¸ŠæŠ¥æœˆæŠ¥çš„企业(636 å®¶ï¼Œç”¨æˆ· 2026-09-18 ç¡®è®¤ï¼‰ï¼›
  Â· ç´¯è®¡å¹³å‡è¿è·ï¼ˆå…¬é‡Œï¼‰= ç´¯è®¡è´§ç‰©å‘¨è½¬é‡ Ã· ç´¯è®¡è´§è¿é‡ï¼ˆåŠ æƒå¹³å‡ï¼‰ï¼›
  Â· åŒæ¯” = 2026 å¹´ 1-8 æœˆç´¯è®¡å¹³å‡è¿è· Ã· 2025 å¹´ 1-8 æœˆç´¯è®¡å¹³å‡è¿è· âˆ’ 1,
    ä¸ºä¿è¯å¯æ¯”,只在两年均持续在规的企业上计算。
  Â· åŒæ¯” = 2026 å¹´ 1-8 æœˆç´¯è®¡å€¼ Ã· 2025 å¹´ 1-8 æœˆç´¯è®¡å€¼ âˆ’ 1,**基期为 2025 å¹´ 1-8 æœˆå…¨éƒ¨è§„上企业上报数**
    ï¼ˆå…¨çœ 664 å®¶ï¼‰ï¼Œä¸Žæ±‡æ€»è¡¨ã€Œè´§è¿ã€é¡µ T åˆ— `=S/SUM(V7,X7,…,AJ7)-1` åŒä¸€å£å¾„ï¼›
    v1 è¯¯ç”¨ã€Œä¸¤å¹´å‡æŒç»­åœ¨è§„çš„ 545 å®¶ã€ä½œåŸºæœŸï¼Œå¯¼è‡´åŒæ¯”偏小,已修正。
"""
import io, os
from openpyxl import Workbook
@@ -19,8 +20,12 @@
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   # ä¸‡å¨å…¬é‡Œï¼ŒåŒä¸Š
PROV = {
    'vol26': 31399.82, 'turn26': 4927161.77,       # å·²å‘布:2026 å¹´ 1-8 æœˆç´¯è®¡
    'vol25': 33994.45, 'turn25': 5102518.95,       # æ±‡æ€»è¡¨ã€Œè´§è¿ã€é¡µ 2025 å¹´ 1-8 æœˆï¼ˆåŒæ¯”基期)
    'vol26_08': 3665.36, 'turn26_08': 570004.44,   # å·²å‘布:2026 å¹´ 8 æœˆå½“月
    'yoy_vol': -0.0763, 'yoy_turn': -0.0344,       # å·²å‘布:1-8 æœˆç´¯è®¡åŒæ¯”
}
CITY_ORDER = ['武汉市', '黄石市', '十堰市', '宜昌市', '襄阳市', '鄂州市', '荆门市', '孝感市', '荆州市',
              '黄冈市', '咸宁市', '随州市', '恩施州', '仙桃市', '潜江市', '天门市', '神农架林区']
@@ -68,46 +73,64 @@
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 = by_city.setdefault(r['city'], dict(n=0, f26=0.0, t26=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)
tot = dict(n=0, f26=0.0, t26=0.0)
for c in by_city.values():
    for k in tot:
        tot[k] += c[k]
by_city_cmp = {}
for r in rows:
    if not r['cmp']:
        continue
    c = by_city_cmp.setdefault(r['city'], dict(n=0, f26c=0.0, t26c=0.0, f25c=0.0, t25c=0.0))
    c['n'] += 1
    c['f26c'] += r['f26']; c['t26c'] += r['t26']
    c['f25c'] += r['f25']; c['t25c'] += r['t25']
tot_cmp = dict(n=0, f26c=0.0, t26c=0.0, f25c=0.0, t25c=0.0)
for c in by_city_cmp.values():
    for k in tot_cmp:
        tot_cmp[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:
        if len(p) < 5 or p[0] == 'report_period':
            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):
def city_month(period, city, names=None):
    d = cust.get((period, city))
    if not d:
        return None, 0.0
        return None, 0.0, 0.0
    fr = tu = 0.0
    for en, v in d.items():
        if en in names:
        if names is None or en in names:
            fr += v[0]; tu += v[1]
    return dist(fr, tu), fr
    return dist(fr, tu), fr, tu
# ---------- 2025 å¹´ 1-8 æœˆå…¨å£å¾„基期 ----------
B25 = {}
for (per, city), d in cust.items():
    if not ('2025-01' <= per <= '2025-08'):
        continue
    c = B25.setdefault(city, dict(ents=set(), f=0.0, t=0.0))
    for en, v in d.items():
        c['ents'].add(en)
        c['f'] += v[0]; c['t'] += v[1]
tot25 = dict(ents=set(), f=0.0, t=0.0)
for c in B25.values():
    tot25['ents'] |= c['ents']
    tot25['f'] += c['f']; tot25['t'] += c['t']
# ---------- æ ·å¼ ----------
THIN = Side(style='thin', color='BFBFBF')
@@ -118,7 +141,7 @@
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'
NF2, NF1, NFP = '#,##0.00', '#,##0.0', '0.00%'
wb = Workbook()
@@ -127,7 +150,7 @@
ws.title = '说明与口径'
ws.sheet_view.showGridLines = False
ws.column_dimensions['A'].width = 16
ws.column_dimensions['B'].width = 104
ws.column_dimensions['B'].width = 106
t = ws.cell(1, 1, '货运月报「持续在规企业」分市州 1-8 æœˆç´¯è®¡è¿è·åŠåŒæ¯”情况表')
t.font = Font(bold=True, size=15, color='FFFFFF')
t.fill = TITLE_FILL
@@ -138,19 +161,23 @@
info = [
    ('数据来源', '系统数据库 traffic_audit.h2032_enterprise_monthly《道路货物运输月度生产情况》(H203-2)企业月报,'
                 '取 2025 å¹´ 1 æœˆâ€”2026 å¹´ 8 æœˆå…¨éƒ¨ä¼ä¸šçº§ä¸ŠæŠ¥è®°å½•。'),
    ('持续在规企业', '2026 å¹´ 1—8 æœˆè¿žç»­ 8 ä¸ªæœˆå‡ä¸ŠæŠ¥æœˆæŠ¥çš„企业,共 %d å®¶ï¼ˆ2026 å¹´å„月上报企业数均为 636 å®¶ï¼Œå…¨æœŸä¸€è‡´ï¼‰ã€‚' % tot['n']),
    ('持续在规企业', '2026 å¹´ 1—8 æœˆè¿žç»­ 8 ä¸ªæœˆå‡ä¸ŠæŠ¥æœˆæŠ¥çš„企业,共 %d å®¶ï¼ˆ2026 å¹´å„月上报企业数均为 636 å®¶ï¼Œå…¨æœŸä¸€è‡´ï¼›'
                 '口径经用户 2026-09-18 ç¡®è®¤ï¼‰ã€‚' % 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'])),
    ('同比口径', '同比 = 2026 å¹´ 1—8 æœˆç´¯è®¡å€¼ Ã· 2025 å¹´ 1—8 æœˆç´¯è®¡å€¼ âˆ’ 1。**基期为 2025 å¹´ 1—8 æœˆå…¨éƒ¨è§„上企业上报数**'
                 '(全省 %d å®¶ï¼‰ï¼Œä¸Žæ±‡æ€»è¡¨ã€Œè´§è¿ã€é¡µ T åˆ—公式 =S/SUM(V7,X7,…,AJ7)-1 åŒä¸€å£å¾„,可与发布报告直接对齐:'
                 '全省累计货运量同比 âˆ’7.63%%、周转量同比 âˆ’3.44%%、平均运距同比 +4.54%%。' % len(tot25['ents'])),
    ('全省合计', '一律为 17 ä¸ªå¸‚州之和(不含跨市州重叠口径)。'),
    ('报表期', '本期累计 2026-01-01—2026-08-31;上年同期 2025-01-01—2025-08-31。'),
    ('页签说明', '「分市州汇总」按市州汇总 1-8 æœˆç´¯è®¡è¿è·ä¸ŽåŒæ¯”;「分市州分月」为各市州 2026 å¹´é€æœˆå¹³å‡è¿è·ä¸ŽåŒæœŸå¯¹æ¯”ï¼›'
                 '「企业明细」为 636 å®¶æŒç»­åœ¨è§„企业逐户数据;「口径校验」为明细与汇总对账、以及与已发布全省数据的比对。'),
    ('报表期', '本期累计 2026-01-01—2026-08-31;同比基期 2025-01-01—2025-08-31。'),
    ('页签说明', '「分市州汇总」按市州汇总 1-8 æœˆç´¯è®¡è¿è·ä¸ŽåŒæ¯”;「分市州分月」为各市州 2026 å¹´é€æœˆå¹³å‡è¿è·ï¼›'
                 '「企业明细」为 636 å®¶æŒç»­åœ¨è§„企业逐户数据(企业级同比仅对两年均连续上报的 545 å®¶ç»™å‡ºï¼‰ï¼›'
                 '「口径对比」并列官方口径与同企业口径的同比;「口径校验」为明细与汇总对账,以及与汇总表、已发布报告的比对。'),
    ('注意事项', '① æœ‰ 5 å®¶ä¼ä¸š 1—8 æœˆæœˆæŠ¥è´§è¿é‡ã€å‘¨è½¬é‡å‡ä¸º 0,计入企业数但不影响运距;'
                 '② æ­¦æ±‰çŸ³åŒ–交通运输有限公司、湖北捷龙交通运业有限公司 2026 å¹´ 1 æœˆçš„地区码与其后月份不同(420100 â†’ 420107 / 420103),'
                 '但同属武汉市,不影响市州归集;③ ç´¯è®¡è´§è¿é‡ä¸º 0 çš„企业累计运距留空。'),
    ('版本', 'v2(2026-09-18):修正同比基期口径——v1 è¯¯ç”¨ã€Œä¸¤å¹´å‡æŒç»­åœ¨è§„çš„ 545 å®¶ã€ä½œåŸºæœŸï¼Œå…¨çœç´¯è®¡è¿è·åŒæ¯”ç®—å¾— +2.90%%,'
             '官方口径应为 +4.54%%。'),
    ('生成时间', '2026-09-18'),
]
r = 3
@@ -161,18 +188,18 @@
# ---------------- Sheet2 åˆ†å¸‚州汇总 ----------------
ws2 = wb.create_sheet('分市州汇总')
heads = ['序号', '市州', '持续在规企业数(å®¶)', '其中两年均持续在规(å®¶)',
heads = ['序号', '市州', '2026å¹´1-8月持续在规企业数(å®¶)', '2025å¹´1-8月规上企业数(å®¶)',
         '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')
         '2025å¹´1-8月累计货运量(万吨)', '2025å¹´1-8月累计货物周转量(万吨公里)', '2025å¹´1-8月累计平均运距(公里)',
         '累计平均运距同比(%)', '累计货运量同比(%)', '累计货物周转量同比(%)']
widths = [6, 14, 20, 18, 20, 24, 20, 20, 24, 20, 15, 15, 17]
ws2.cell(1, 1, '货运月报持续在规企业分市州 1-8 æœˆç´¯è®¡å¹³å‡è¿è·åŠåŒæ¯”情况(同比基期:2025 å¹´ 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, '注:第 5—7 åˆ—为 2026 å¹´æŒç»­åœ¨è§„企业(636 å®¶ï¼‰ç´¯è®¡å€¼ï¼›ç¬¬ 8—10 åˆ—为 2025 å¹´ 1-8 æœˆå…¨éƒ¨è§„上企业(全省 664 å®¶ï¼‰ç´¯è®¡å€¼ï¼Œ'
               '即汇总表「货运」页 T åˆ—同比公式的基期;第 11—13 åˆ—同比口径与发布报告一致。')
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):
@@ -189,46 +216,44 @@
        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]
    b = B25.get(city, dict(ents=set(), f=0.0, t=0.0))
    d26, d25 = dist(c['f26'], c['t26']), dist(b['f'], b['t'])
    vals = [idx, city, c['n'], len(b['ents']),
            c['f26'] / 10000.0, c['t26'] / 10000.0, d26,
            b['f'] / 10000.0, b['t'] / 10000.0, d25,
            growth(d26, d25), growth(c['f26'], b['f']), growth(c['t26'], b['t'])]
    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.alignment = CT if j <= 4 else RT
        if j in (5, 6, 7, 8, 9, 10):
            cell.number_format = NF2
        elif j in (9, 10, 11):
            cell.number_format = NF1
        elif j in (11, 12, 13):
            cell.number_format = NFP
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']), '']
d26p, d25p = dist(tot['f26'], tot['t26']), dist(tot25['f'], tot25['t'])
vals = ['', '全省合计', tot['n'], len(tot25['ents']),
        tot['f26'] / 10000.0, tot['t26'] / 10000.0, d26p,
        tot25['f'] / 10000.0, tot25['t'] / 10000.0, d25p,
        growth(d26p, d25p), growth(tot['f26'], tot25['f']), growth(tot['t26'], tot25['t'])]
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):
    if j in (5, 6, 7, 8, 9, 10):
        cell.number_format = NF2
    elif j in (9, 10, 11):
        cell.number_format = NF1
    elif j in (11, 12, 13):
        cell.number_format = NFP
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')
w3 = [6, 14, 18] + [15] * 8 + [24, 24, 15]
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))
@@ -247,31 +272,19 @@
        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']))]
    b = B25.get(city, dict(ents=set(), f=0.0, t=0.0))
    d26, d25 = dist(c['f26'], c['t26']), dist(b['f'], b['t'])
    vals = [idx, city, c['n']] + [city_month(p, city, persist_names)[0] for p in MONTHS26] + \
           [d26, d25, growth(d26, d25)]
    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
            cell.number_format = NFP if j == 14 else NF2
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']))]
vals = ['', '全省合计', tot['n']] + [city_month(p, None)[0] for p in MONTHS26] + \
       [d26p, d25p, growth(d26p, d25p)]
for j, v in enumerate(vals, 1):
    cell = ws3.cell(rr, j, v)
    cell.border = BORDER
@@ -279,7 +292,7 @@
    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
        cell.number_format = NFP if j == 14 else NF2
ws3.freeze_panes = 'D3'
# ---------------- Sheet4 ä¼ä¸šæ˜Žç»† ----------------
@@ -289,7 +302,7 @@
      '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, '持续在规企业逐户累计平均运距(2026 å¹´ 1-8 æœˆï¼›åŒæœŸå€¼ä¸ŽåŒæ¯”仅对两年均持续在规的 545 å®¶ç»™å‡ºï¼‰').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))
@@ -320,37 +333,114 @@
        if j in (6, 7, 8, 9, 10, 11):
            cell.number_format = NF2
        elif j == 12:
            cell.number_format = NF1
            cell.number_format = NFP
        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 å£å¾„对比 ----------------
ws6 = wb.create_sheet('口径对比')
h6 = ['序号', '市州', '2026å¹´1-8月累计平均运距(公里)\n[持续在规企业636å®¶]',
      '2025å¹´1-8月累计平均运距(公里)\n[全部规上企业]', '官方口径同比(%)',
      '2025å¹´1-8月累计平均运距(公里)\n[同企业545å®¶]', '同企业口径同比(%)', '两者差异(百分点)', '说明']
w6 = [6, 14, 26, 24, 15, 24, 15, 16, 46]
ws6.cell(1, 1, '两种同比口径对比(官方口径 = ä¸Žæ±‡æ€»è¡¨ã€Œè´§è¿ã€é¡µ T åˆ—一致;同企业口径 = ä»…两年均持续在规企业)').font = Font(bold=True, size=13, color='FFFFFF')
ws6.cell(1, 1).fill = TITLE_FILL
ws6.cell(1, 1).alignment = Alignment(horizontal='center', vertical='center')
ws6.merge_cells(start_row=1, start_column=1, end_row=1, end_column=len(h6))
ws6.row_dimensions[1].height = 26
ws6.cell(2, 1, '注:交付主表(分市州汇总)采用官方口径;本页两个口径并列,差异大的市州多为两年间规上企业名单变动(停产、新增、拆分)所致,供业务核对参考。')
ws6.cell(2, 1).font = Font(size=9, italic=True, color='808080')
ws6.merge_cells(start_row=2, start_column=1, end_row=2, end_column=len(h6))
for j, h in enumerate(h6, 1):
    c = ws6.cell(3, j, h)
    c.font = Font(bold=True); c.fill = HDR_FILL; c.alignment = CT; c.border = BORDER
    ws6.column_dimensions[get_column_letter(j)].width = w6[j - 1]
ws6.row_dimensions[3].height = 48
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
    b = B25.get(city, dict(ents=set(), f=0.0, t=0.0))
    g = by_city_cmp.get(city)
    d26, d25 = dist(c['f26'], c['t26']), dist(b['f'], b['t'])
    g_off = growth(d26, d25)
    if g:
        dc26, dc25 = dist(g['f26c'], g['t26c']), dist(g['f25c'], g['t25c'])
        g_same = growth(dc26, dc25)
    else:
        dc25, g_same = None, None
    diff = (g_off - g_same) if (g_off is not None and g_same is not None) else None
    note = ''
    if diff is not None and abs(diff) > 0.10:
        note = '两口径差异超 10 ä¸ªç™¾åˆ†ç‚¹ï¼Œä¸»è¦å› ä¸¤å¹´è§„上企业名单变动,建议核对该市州企业变动情况'
    vals = [idx, city, d26, d25, g_off, dc25, g_same, (diff * 100 if diff is not None else None), note]
    for j, v in enumerate(vals, 1):
        cell = ws6.cell(rr, j, v)
        cell.border = BORDER
        cell.alignment = LT if j in (1, 9) else (CT if j == 2 else RT)
        if j in (3, 4, 6):
            cell.number_format = NF2
        elif j in (5, 7):
            cell.number_format = NFP
        elif j == 8:
            cell.number_format = NF1
rr += 1
g_cmp = growth(dist(tot_cmp['f26c'], tot_cmp['t26c']), dist(tot_cmp['f25c'], tot_cmp['t25c']))
vals = ['', '全省合计', d26p, d25p, growth(d26p, d25p), dist(tot_cmp['f25c'], tot_cmp['t25c']), g_cmp,
        (growth(d26p, d25p) - g_cmp) * 100 if g_cmp is not None else None, '']
for j, v in enumerate(vals, 1):
    cell = ws6.cell(rr, j, v)
    cell.border = BORDER
    cell.fill = TOT_FILL
    cell.font = Font(bold=True)
    cell.alignment = CT if j == 2 else RT
    if j in (3, 4, 6):
        cell.number_format = NF2
    elif j in (5, 7):
        cell.number_format = NFP
    elif j == 8:
        cell.number_format = NF1
ws6.freeze_panes = 'C4'
# ---------------- 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['A'].width = 46
for col, w in (('B', 20), ('C', 20), ('D', 14), ('E', 46)):
    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
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 "无")),
    ('校验项', '本表数值', '对照数值', '差异', '说明'),
    ('明细表企业数 = æ±‡æ€»è¡¨ä¼ä¸šæ•°', 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 å¸‚州汇总合计'),
    ('2026å¹´1-8月累计货运量(万吨)', round(tot['f26'] / 10000.0, 2), PROV['vol26'], round(tot['f26'] / 10000.0 - PROV['vol26'], 2), '对照已发布报告 1-8 æœˆç´¯è®¡'),
    ('2026å¹´1-8月累计周转量(万吨公里)', round(tot['t26'] / 10000.0, 2), PROV['turn26'], round(tot['t26'] / 10000.0 - PROV['turn26'], 2), '同上'),
    ('2026å¹´8月全省货运量(万吨)', round(aug_f, 2), PROV['vol26_08'], round(aug_f - PROV['vol26_08'], 2), '验证市州归集无误'),
    ('2026å¹´8月全省周转量(万吨公里)', round(aug_t, 2), PROV['turn26_08'], round(aug_t - PROV['turn26_08'], 2), '同上'),
    ('2025å¹´1-8月累计货运量(万吨)【同比基期】', round(tot25['f'] / 10000.0, 2), PROV['vol25'], round(tot25['f'] / 10000.0 - PROV['vol25'], 2), '对照汇总表「货运」页 V7..AJ7(缓存值)'),
    ('2025å¹´1-8月累计周转量(万吨公里)【同比基期】', round(tot25['t'] / 10000.0, 2), PROV['turn25'], round(tot25['t'] / 10000.0 - PROV['turn25'], 2), '对照汇总表「货运」页 V8..AJ8(缓存值)'),
    ('2025å¹´1-8月全省规上企业数(å®¶)', len(tot25['ents']), 664, len(tot25['ents']) - 664, '同比基期企业范围'),
    ('累计货运量同比', growth(tot['f26'], tot25['f']), PROV['yoy_vol'], round(growth(tot['f26'], tot25['f']) - PROV['yoy_vol'], 4), '对照已发布报告 1-8 æœˆç´¯è®¡åŒæ¯”'),
    ('累计周转量同比', growth(tot['t26'], tot25['t']), PROV['yoy_turn'], round(growth(tot['t26'], tot25['t']) - PROV['yoy_turn'], 4), '同上'),
    ('2025å¹´1-8月累计平均运距(公里)', round(d25p, 2), None, None, '= 2025 ç´¯è®¡å‘¨è½¬é‡ Ã· ç´¯è®¡è´§è¿é‡'),
    ('2026å¹´1-8月累计平均运距(公里)', round(d26p, 2), None, None, '= 2026 ç´¯è®¡å‘¨è½¬é‡ Ã· ç´¯è®¡è´§è¿é‡'),
    ('全省累计平均运距同比', growth(d26p, d25p), None, None, '= 156.92 Ã· 150.10 âˆ’ 1'),
    ('覆盖市州数', 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):
@@ -360,22 +450,19 @@
        if i == 1:
            cell.font = Font(bold=True); cell.fill = HDR_FILL
        elif j in (2, 3, 4):
            cell.number_format = NF2
            cell.number_format = NFP if row[0].endswith('同比') else 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))
print('2026 1-8月: %.4f ä¸‡å¨ / %.4f ä¸‡å¨å…¬é‡Œ / %.4f å…¬é‡Œ' % (tot['f26'] / 10000.0, tot['t26'] / 10000.0, d26p))
print('2025 1-8月: %.4f ä¸‡å¨ / %.4f ä¸‡å¨å…¬é‡Œ / %.4f å…¬é‡Œ (%d å®¶)' % (tot25['f'] / 10000.0, tot25['t'] / 10000.0, d25p, len(tot25['ents'])))
print('全省同比: è´§è¿é‡ %.2f%%  å‘¨è½¬é‡ %.2f%%  å¹³å‡è¿è· %.2f%%' %
      (growth(tot['f26'], tot25['f']) * 100, growth(tot['t26'], tot25['t']) * 100, growth(d26p, d25p) * 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 '-'))
        b = B25.get(city, dict(ents=set(), f=0.0, t=0.0))
        d1, d0 = dist(c['f26'], c['t26']), dist(b['f'], b['t'])
        print('%-8s n=%-4d 2026=%8.2f 2025=%8s åŒæ¯”=%s' % (
            city, c['n'], d1, ('%.2f' % d0) if d0 else '-', ('%.1f%%' % (growth(d1, d0) * 100)) if d0 else '-'))