From 7b6bc7c126657ae943f3d37e5f19133d8bb187c5 Mon Sep 17 00:00:00 2001
From: zhizhijie <zhizhijie@users.noreply.gitee.com>
Date: 星期四, 17 九月 2026 14:54:44 +0800
Subject: [PATCH] fix(投资): 全部改为「按表头定位」——目标明细表不再写死列号 + 同名列按数据消歧

---
 docs/投资/工具/匹配与校验.py |   62 ++++++++++++++++++++++++++----
 1 files changed, 53 insertions(+), 9 deletions(-)

diff --git "a/docs/\346\212\225\350\265\204/\345\267\245\345\205\267/\345\214\271\351\205\215\344\270\216\346\240\241\351\252\214.py" "b/docs/\346\212\225\350\265\204/\345\267\245\345\205\267/\345\214\271\351\205\215\344\270\216\346\240\241\351\252\214.py"
index b57f5e6..80f980c 100644
--- "a/docs/\346\212\225\350\265\204/\345\267\245\345\205\267/\345\214\271\351\205\215\344\270\216\346\240\241\351\252\214.py"
+++ "b/docs/\346\212\225\350\265\204/\345\267\245\345\205\267/\345\214\271\351\205\215\344\270\216\346\240\241\351\252\214.py"
@@ -94,24 +94,55 @@
 
 
 def read_target(spec):
-    """璇荤洰鏍囨槑缁嗚〃 鈫� (琛屽垪琛�, 琛ㄥご琛屽彿, 璇存槑)"""
+    """璇荤洰鏍囨槑缁嗚〃 鈫� (琛屽垪琛�, 鍒椾綅鏄犲皠, 鍒椾綅鍛婅)銆�
+
+    鍒椾綅**鎸夎〃澶村畾浣�**锛堟瘝鐗堥粯璁ゅ垪浣嶅彧浣滃厹搴曪級锛涗笌姣嶇増榛樿涓嶄竴鑷存椂杩斿洖鍛婅锛岀敱璋冪敤鏂瑰脊绐椼��
+    """
     from openpyxl import load_workbook
     import xlrd
-    rows = []
+    rows, notes, infos, merges = [], [], [], []
+    KEYF = ('椤圭洰鍚嶇О', '鎬绘姇璧�', '鑷紑濮嬪缓璁剧疮璁�', '鏈勾璁″垝鎶曡祫', '鑷勾鍒濈疮璁�', '鏈湀瀹屾垚')
     if spec['kind'] == 'xlsx':
         wb = load_workbook(spec['path'], data_only=True)
         ws = wb[spec['sheet']]
         def cells(r, c):
             return ws.cell(r, c).value
         nrows, ncols = ws.max_row, ws.max_column
+        merges = [(r.min_row - 1, r.max_row, r.min_col - 1, r.max_col)
+                  for r in ws.merged_cells.ranges]
     else:
-        bk = xlrd.open_workbook(spec['path'])
+        try:
+            bk = xlrd.open_workbook(spec['path'], formatting_info=True)
+        except Exception:
+            bk = xlrd.open_workbook(spec['path'])
         sh = bk.sheet_by_name(spec['sheet'])
         def cells(r, c):
             return sh.cell_value(r - 1, c - 1) if (r - 1 < sh.nrows and c - 1 < sh.ncols) else ''
         nrows, ncols = sh.nrows, sh.ncols
-    namec = F.colindex(spec['name_col']) + 1
-    anchors = [F.colindex(spec['cols'][k]) + 1 for k in ('鑷紑濮嬪缓璁剧疮璁�', '鑷勾鍒濈疮璁�', '鎬绘姇璧�')]
+        merges = list(getattr(sh, 'merged_cells', []) or [])
+
+    # 1) 鎸夎〃澶村畾浣嶅垪锛堜袱绾ц〃澶� / 鍚屽悕鍒楅兘鍦� pick_columns 閲屾秷姝э級
+    cols = {}
+    try:
+        _c, _h = F.pick_columns(lambda r, c: cells(r + 1, c + 1), nrows, ncols, merges)
+        for k in spec['cols']:
+            if k in _c:
+                cols[k] = F.colname(_c[k])
+    except Exception as e:
+        notes.append('琛ㄥご瀹氫綅寮傚父(%s: %s)锛屽叏閮ㄩ��鍥炴瘝鐗堥粯璁ゅ垪浣�' % (type(e).__name__, str(e)[:40]))
+    # 2) 鏈壘鍒扮殑閫�鍥炴瘝鐗堥粯璁ゅ垪浣嶏紱涓庨粯璁や笉涓�鑷寸殑璁板綍鍛婅锛堝叧閿垪锛�/鎻愮ず锛堝叾浣欏垪锛�
+    for k, exp in spec['cols'].items():
+        got = cols.get(k)
+        if got is None:
+            msg = '%s锛氳〃澶存湭鎵惧埌锛屾寜姣嶇増榛樿 %s 鍒�' % (k, exp)
+            (notes if k in KEYF else infos).append(msg)
+            cols[k] = exp
+        elif got != exp:
+            msg = '%s锛氳〃澶村畾浣� %s 鍒楋紝姣嶇増榛樿 %s 鍒�' % (k, got, exp)
+            (notes if k in KEYF else infos).append(msg)
+
+    namec = F.colindex(cols['椤圭洰鍚嶇О']) + 1
+    anchors = [F.colindex(cols[k]) + 1 for k in ('鑷紑濮嬪缓璁剧疮璁�', '鑷勾鍒濈疮璁�', '鎬绘姇璧�') if k in cols]
     for r in range(1, nrows + 1):
         name = str(cells(r, namec) or '').strip()
         a = str(cells(r, 1) or '').strip()
@@ -124,11 +155,11 @@
         if not any(to_num(cells(r, c)) is not None for c in anchors):
             continue
         rec = {'鐩爣琛屽彿': r, '甯傚窞鎴栬棣�': a}
-        for k, col in spec['cols'].items():
-            v = cells(r, F.colindex(col) + 1)
+        for k in spec['cols']:
+            v = cells(r, F.colindex(cols[k]) + 1)
             rec[k] = pretty(to_num(v)) if k in NUMF else str(v or '').strip()
         rows.append(rec)
-    return rows
+    return rows, cols, notes, infos
 
 
 def read_system(path):
@@ -308,6 +339,11 @@
             add_alert('闃绘柇' if st.startswith('瑙f瀽澶辫触') else '璀﹀憡', '婧愭枃浠惰В鏋愬紓甯�',
                       ck.get('鍝佺被', ''), ck.get('甯傚窞', ''), '', ck.get('鏂囦欢', ''),
                       st, '浜哄伐鎵撳紑婧愭枃浠剁‘璁よ〃澶�/鏁版嵁锛涘繀瑕佹椂鐧昏鐗逛緥鍚庡啀瀵煎叆')
+        elif ck.get('鍒椾綅') and not all(k in ck['鍒椾綅'] for k in ('鑷紑濮嬪缓璁剧疮璁�=', '鑷勾鍒濈疮璁�=', '鏈湀瀹屾垚=')):
+            _miss = [k for k in ('鑷紑濮嬪缓璁剧疮璁�', '鑷勾鍒濈疮璁�', '鏈湀瀹屾垚') if k + '=' not in ck['鍒椾綅']]
+            add_alert('璀﹀憡', '婧愭枃浠剁己鍏抽敭鍒�', ck.get('鍝佺被', ''), ck.get('甯傚窞', ''), '', ck.get('鏂囦欢', ''),
+                      '鎸夎〃澶存病鎵惧埌鍒楋細%s锛堝疄闄呰瘑鍒埌鐨勫垪浣嶏細%s锛�' % ('銆�'.join(_miss), ck['鍒椾綅']),
+                      '浜哄伐纭婧愭枃浠惰〃澶存槸鍚﹁鏀硅繃锛涘繀瑕佹椂鐧昏涓虹壒渚�')
         elif ck.get('涓�鑷存��', '') == '鈿狅笍 涓嶄竴鑷�':
             add_alert('璀﹀憡', '婧愭枃浠跺唴閮ㄥ涓嶄笂', ck.get('鍝佺被', ''), ck.get('甯傚窞', ''), '', ck.get('鏂囦欢', ''),
                       '椤圭洰琛屾眰鍜� %s/%s/%s 鈮� %s 琛屽悎璁� %s/%s/%s' % (
@@ -324,7 +360,15 @@
             add_alert('闃绘柇', '缂虹洰鏍囨槑缁嗚〃', cat, '', '', spec['path'],
                       '鐩爣鏄庣粏琛ㄤ笉瀛樺湪锛屾湰瀛e害璇ュ搧绫绘棤娉曞啓鍏�', '鎶婃ā鏉胯〃鏀惧埌 docs\\鎶曡祫\\杈撳嚭\\')
             continue
-        tgt = read_target(spec)
+        tgt, tcols, tnotes, tinfos = read_target(spec)
+        if tnotes:
+            add_alert('璀﹀憡', '鐩爣琛ㄥ垪浣嶇疆涓庨鏈熶笉绗�', cat, '', '', os.path.basename(spec['path']),
+                      '锛�'.join(tnotes[:8]),
+                      '纭姣嶇増琛ㄥご鏄惁琚敼鍔紱绋嬪簭宸叉寜琛ㄥご瀹氫綅锛屼絾璇蜂汉宸ユ牳瀵瑰悗鍐嶅啓鍏�')
+        if tinfos:
+            add_alert('鎻愮ず', '鐩爣琛ㄤ釜鍒垪鎸夋瘝鐗堥粯璁�', cat, '', '', os.path.basename(spec['path']),
+                      '锛�'.join(tinfos[:8]),
+                      '鐭ユ倝鍗冲彲锛堣繖浜涘垪鍦ㄦ瘝鐗堥噷鏈潵灏辨病鏈夊瓙琛ㄥご锛屾寜榛樿鍒椾綅鍐欏叆锛�')
         prow = [r for r in parsed if r['鍝佺被'] == cat]
         tmap = {}
         for t in tgt:

--
Gitblit v1.9.1