# -*- coding: utf-8 -*- """ 重写待补数模清单, 增加「核对」相关列, 便于 AI 逐个核实后补真实数模。 背景: v2.1 按编码模板外推生成了 3157 个组合, 但它们是【候选代号】而非已确认的产品 —— 规律在已有型号上 100% 成立, 外推区域是否有真实产品未经核实。 本清单把每个候选与其"最近的真实型号"并列, 便于逐个比对核实。 输出列: series 系列码 model_code 候选零件号 (已进目录, 选型可选) params 完整参数 nearest_real 同系列中最接近的【真实数模】型号 (按参数相同个数最多, 平手取编码最近) diff 与 nearest_real 不同的参数 (供人工/AI 判断该差异是否真实存在) step_file 当前指向 PLACEHOLDER.step; 补完真实数模后改为 <候选型号>.step status 固定为 pending (待核对+待补数模) 用法: PYTHONUTF8=1 python make-pending-list.py """ import csv, io, os, json, glob, collections CAT = r'D:/开发/OnebotCatalog/onebot-data/catalog' OUT = os.path.join(CAT, '_pending_models.csv') PLACEHOLDER = 'PLACEHOLDER.step' def load_real_models(): """返回 {系列: [(model_code, {参数})]} —— 只含指向真实数模的型号。""" real = {} for f in glob.glob(os.path.join(CAT, 'csv', '*.csv')): code = os.path.basename(f)[:-4] rows = [] for r in csv.DictReader(io.open(f, encoding='utf-8-sig')): if r.get('step_file') == PLACEHOLDER: continue # 跳过占位行 prm = {k: v for k, v in r.items() if k not in ('model_code', 'step_file')} rows.append((r['model_code'], prm)) if rows: real[code] = rows return real def dist(params_a, params_b): """参数相同个数 (越大越接近)。""" keys = set(params_a) | set(params_b) return sum(1 for k in keys if str(params_a.get(k, '')) == str(params_b.get(k, ''))) def main(): real = load_real_models() total_real = sum(len(v) for v in real.values()) print(f'真实数模型号: {total_real} 个, 覆盖 {len(real)} 个系列') # 收集候选项 pending = [] for f in sorted(glob.glob(os.path.join(CAT, 'csv', '*.csv'))): code = os.path.basename(f)[:-4] for r in csv.DictReader(io.open(f, encoding='utf-8-sig')): if r.get('step_file') != PLACEHOLDER: continue prm = {k: v for k, v in r.items() if k not in ('model_code', 'step_file')} pending.append((code, r['model_code'], prm)) print(f'待补候选: {len(pending)} 个') rows = [] for code, mc, prm in pending: cands = real.get(code) or [] near, ndiff = '', '' if cands: # 最接近的真实型号: 参数相同个数最多; 平手取编码字典序 (稳定) best = max(cands, key=lambda x: (dist(prm, x[1]), -len(x[0]))) near = best[0] diffs = [f'{k}={prm.get(k, "")}≠{best[1].get(k, "")}' for k in sorted(set(prm) | set(best[1])) if str(prm.get(k, '')) != str(best[1].get(k, ''))] ndiff = '; '.join(diffs) rows.append([code, mc, ' '.join(f'{k}={v}' for k, v in prm.items()), near, ndiff, PLACEHOLDER, 'pending']) with io.open(OUT, 'w', encoding='utf-8-sig', newline='') as fp: w = csv.writer(fp) w.writerow(['series', 'model_code', 'params', 'nearest_real', 'diff_from_nearest', 'step_file', 'status']) w.writerows(rows) print(f'\n写入 {OUT} ({len(rows)} 行)') # 覆盖度: 多少候选能找到同系列真实型号作参照 withref = sum(1 for r in rows if r[3]) print(f' 有同系列真实型号可参照: {withref}/{len(rows)}') nop = [r[0] for r in rows if not r[3]] if nop: print(f' 无参照的系列: {sorted(set(nop))}') # 按系列 + 缺失维度统计 print('\n各系列待补 (按"与最近真实型号差异的维度数"分布):') bycat = collections.defaultdict(collections.Counter) for r in rows: n = len([x for x in r[4].split('; ') if x]) if r[4] else -1 bycat[r[0]][n] += 1 for code in sorted(bycat): dists = bycat[code] parts = ', '.join(f'{k}维差:{v}' for k, v in sorted(dists.items()) if k >= 0) print(f' {code:6s} 共{sum(dists.values()):5d} {parts}') if __name__ == '__main__': main()