docs/Ͷ×Ê/¹¤¾ß/ÊÐÖÝÔ±¨½âÎö.py
@@ -76,21 +76,45 @@
    return str(v)
def pick_columns(cell, nrows, ncols):
    """按表头关键字定位列;同一字段命中多次时,取“行号最小、其次是命中词最短”的那个。"""
def pick_columns(cell, nrows, ncols, merges=None):
    """按表头关键字定位列;同一字段命中多次时,取“行号最小、其次是命中词最短”的那个。
    ä¸¤çº§è¡¨å¤´ï¼ˆç»„表头)处理:若某字段的命中落在**跨多列的合并单元格**里,说明它是组表头,
    ä¸èƒ½ç›´æŽ¥å½“成该字段的列;此时在本合并区内排除“其它字段更深的子表头”所占的列,
    è‹¥åªå‰©å”¯ä¸€ä¸€åˆ—,则该列就是本字段的列。
    ä¾‹ï¼šé„‚州《企业月报汇总》R5 ç»„表头「自年初累计完成投资(万元)」合并 I:J:K,
        å­è¡¨å¤´ I=本年计划投资(万元)、J=合计、K=其中:本月完成投资 â†’
        ã€Œè‡ªå¹´åˆç´¯è®¡ã€åº”落在 J(与投资系统 G åˆ— 5507.5 ä¸€è‡´ï¼‰ï¼Œè€Œä¸æ˜¯ I(8000,那其实是本年计划投资)。
    """
    hits = F.scan_fields(cell, nrows, ncols)
    cols = {}
    best = {}
    for f in FIELDS:
        if f not in hits:
            continue
        best = sorted(hits[f], key=lambda h: (h[0], len(h[2])))[0]
        cols[f] = F.colindex(best[1])
        if f in hits:
            best[f] = sorted(hits[f], key=lambda h: (h[0], len(h[2])))[0]
    cols = {}
    for f, h in best.items():
        row1, letter, _txt = h
        c0 = F.colindex(letter)
        span = F.merge_span(merges, row1, c0)
        if span and (span[1] - span[0]) > 1:
            taken = set()
            for f2, h2 in best.items():
                if f2 == f or h2[0] <= row1:
                    continue
                c2 = F.colindex(h2[1])
                if span[0] <= c2 < span[1]:
                    taken.add(c2)
            free = [c for c in range(span[0], span[1]) if c not in taken]
            if len(free) == 1:
                cols[f] = free[0]
                continue
        cols[f] = c0
    return cols, hits
def parse_sheet(cell, nrows, ncols, city=None):
def parse_sheet(cell, nrows, ncols, city=None, merges=None):
    """解析一张工作表 â†’ (项目行列表, æ®µä¿¡æ¯, æ ¡éªŒä¿¡æ¯)"""
    cols, hits = pick_columns(cell, nrows, ncols)
    cols, hits = pick_columns(cell, nrows, ncols, merges)
    namec = cols.get('项目名称')
    if namec is None:
        return [], {}, {}
@@ -181,7 +205,7 @@
    bodies = [a.body] if a.body else ['客运(客运站)', '物流(物流园区)']
    md = '%d月' % int(a.month.split('-')[1])
    records, report = [], []
    records, report, checks = [], [], []
    report.append('# å¸‚州月报解析报告(%s)' % a.month)
    report.append('')
    report.append('(只读解析;一行一个项目行,明细见同名 `.tsv`)')
@@ -207,23 +231,37 @@
                sel = F.pick_sheet(sheets, ym=ym)
                if not sel:
                    report.append('| %s | ï¼ˆæœªé€‰ä¸­å·¥ä½œè¡¨ï¼‰ | | | | | |' % f[:44])
                    checks.append({'品类': body_key, '市州': city_file or '', '文件': f, '工作表': '',
                                   '状态': '未选中工作表', '项目行数': 0, '一致性': '—'})
                    continue
                nm, nr, nc, cell, _hits = sel
                rows, seg, extra = parse_sheet(cell, nr, nc, city=city_file)
                merges = F.sheet_merges(path).get(nm, [])
                rows, seg, extra = parse_sheet(cell, nr, nc, city=city_file, merges=merges)
                city = seg.get('市州') or city_file or city_by_override(f) or ''
                sums = {k: sum(num(r.get(k)) or 0 for r in rows) for k in NUM_FIELDS}
                sub = extra.get('小计合计行') or {}
                sub_txt, ok = [], '—'
                if not sub and extra.get('市州行值'):
                    sub = {'R%s(市州行)' % (seg.get('市州行') or '?'): extra['市州行值']}
                tok = tval = None
                if sub:
                    key, val = pick_total(sub)
                    tok, tval = key, val
                    sub_txt.append('%s=%s' % (key, '/'.join(pretty(val.get(k)) for k in ('自开始建设累计', '自年初累计', '本月完成'))))
                    if len(sub) > 1:
                        sub_txt.append('(另有 %d è¡Œå°è®¡/合计)' % (len(sub) - 1))
                    same = all(abs((val.get(k) or 0) - sums[k]) < 0.05
                               for k in ('自开始建设累计', '自年初累计', '本月完成'))
                    ok = '✅ ä¸€è‡´' if same else '⚠️ ä¸ä¸€è‡´'
                ck = {'品类': body_key, '市州': city, '文件': f, '工作表': nm, '状态': 'OK',
                      '项目行数': len(rows), '合计行': '', '一致性': ok}
                for k in ('自开始建设累计', '自年初累计', '本月完成'):
                    ck['求和_' + k] = sums[k]
                if sub:
                    ck['合计行'] = tok
                    for k in ('自开始建设累计', '自年初累计', '本月完成'):
                        ck['合计_' + k] = tval.get(k)
                checks.append(ck)
                report.append('| %s | %s | %s | %d | %s | %s | %s |' % (
                    f[:44], nm[:14], city, len(rows),
                    '/'.join(pretty(sums[k]) for k in ('自开始建设累计', '自年初累计', '本月完成')),
@@ -234,6 +272,9 @@
                    records.append(rec)
            except Exception as e:
                report.append('| %s | ï¼ˆè§£æžå¤±è´¥ï¼š%s: %s) | | | | | |' % (f[:44], type(e).__name__, str(e)[:60]))
                checks.append({'品类': body_key, '市州': city_file or '', '文件': f, '工作表': '',
                               '状态': '解析失败:%s: %s' % (type(e).__name__, str(e)[:80]),
                               '项目行数': 0, '一致性': '—'})
        report.append('')
    # å¸‚州汇总(跨文件合并同市州)
@@ -254,6 +295,17 @@
            *(pretty(s[f]) for f in ('总投资', '自开始建设累计', '本年计划投资', '自年初累计', '本月完成'))))
    report.append('')
    ck_cols = ['品类', '市州', '文件', '工作表', '状态', '项目行数', '一致性', '合计行',
               '求和_自开始建设累计', '求和_自年初累计', '求和_本月完成',
               '合计_自开始建设累计', '合计_自年初累计', '合计_本月完成']
    p_ck = os.path.join(out, '解析校验_%s.tsv' % a.month)
    with io.open(p_ck, 'w', encoding='utf-8-sig') as fh:
        fh.write('\t'.join(ck_cols) + '\n')
        for c in checks:
            fh.write('\t'.join(pretty(c.get(k, '')) if isinstance(c.get(k), float) else str(c.get(k, ''))
                                for k in ck_cols) + '\n')
    print('校验记录数 = %d' % len(checks))
    cols = ['品类', '市州', '文件', '工作表', '行号', '序号', '分类'] + FIELDS
    tsv = os.path.join(out, '解析结果_%s.tsv' % a.month)
    with io.open(tsv, 'w', encoding='utf-8-sig') as fh:
@@ -265,6 +317,7 @@
    print('项目行合计 = %d' % len(records))
    print('wrote', p_md)
    print('wrote', tsv)
    print('wrote', p_ck)
if __name__ == '__main__':