# -*- coding: utf-8 -*- """ 编码模板反推 v2 —— 支持零填充与空值占位符。 v1 的 explain() 贪心匹配"非零填充", 把 ULP50040AEK0 的 "040" 拆成 字面量"0" + 值"40", 产生大量假阴性。真实规则是: - 数值参数常带前导零 (stroke=40 -> "040") - 枚举参数为空时, 编码里用 '_' 占位 (UAGP075_K045WS0) 本脚本对每个参数尝试 {原值, 各前导零位数, '_'占位} 的所有组合, 取能对齐的; 若某系列的"字面量序列"完全一致 → 可模板化。 用法: PYTHONUTF8=1 python infer-sku-template.py """ import json, io, collections, sys CAT = r'D:/开发/OnebotCatalog/release/web_2026.08/catalog.json' d = json.load(io.open(CAT, encoding='utf-8-sig')) MAXPAD = 4 def align_all(code, params, values): """返回全部可能的字面量序列 (元组)。每个参数尝试: 原值 / 前导零 / '_'占位。""" results = [] def dfs(i, pos, lits): if i == len(params): results.append(tuple(lits + [code[pos:]])) return pc = params[i] v = str(values.get(pc, '')) cands = [] if v == '': cands = ['_', ''] # 空值: 占位符或什么都不写 else: cands = [v] + ['0' * z + v for z in range(1, MAXPAD + 1)] seen = set() for pat in cands: if pat in seen: continue seen.add(pat) if pat == '': dfs(i + 1, pos, lits + ['']) continue idx = code.find(pat, pos) if idx >= 0: dfs(i + 1, idx + len(pat), lits + [code[pos:idx]]) dfs(0, 0, []) return results def render_template(params, lits): """字面量序列 -> 模板串。空值参数渲染为 '_'。""" out = '' for i, pc in enumerate(params): out += lits[i] + '{' + pc + '}' out += lits[len(params)] return out good, bad = [], [] for s in d['series']: vs = s.get('variants') or [] if not vs: continue params = [p['code'] for p in s['parameters']] lit_sets = [] unaligned = 0 for v in vs: alts = align_all(v['modelCode'], params, v['params']) if not alts: unaligned += 1 continue lit_sets.append(alts) if not lit_sets: bad.append((s['code'], len(vs), '完全无法对齐', [], 0)) continue # 统计"出现在最多变体里"的字面量序列 (每个变体取任一可对齐解) cnt = collections.Counter() for alts in lit_sets: for a in set(alts): cnt[a] += 1 top, hit = cnt.most_common(1)[0] rate = hit / len(vs) * 100 if rate >= 99.5 and unaligned == 0: good.append((s['code'], len(vs), rate, render_template(params, top), len(cnt))) else: bad.append((s['code'], len(vs), f'{rate:.1f}% ({len(cnt)} 种)', cnt.most_common(3), unaligned)) print('=' * 96) print(f'可模板化的系列 ({len(good)} 个)') print('=' * 96) for code, n, rate, tpl, k in sorted(good): print(f'{code:7s} {n:6d} 变体 {rate:6.1f}% {tpl}') print() print('=' * 96) print(f'不可模板化的系列 ({len(bad)} 个)') print('=' * 96) for code, n, why, tops, un in sorted(bad, key=lambda x: -x[1]): print(f'{code:7s} {n:6d} 变体 {why}' + (f' 未对齐 {un}' if un else '')) for lits, c in tops[:3]: print(f' {c:5d}× {lits}') print(f'\n合计: 可模板化 {len(good)} / 共 {len(good) + len(bad)} 个系列')