网页版选型改版 (仅网页版, 桌面版待同步)
- 新增零件号实时条: 边选边拼, 已确定片段实心高亮, 未定片段虚化, 进度条随选推进;
浅色配色 (初版深色块与页面不协调, 已改); 选满且唯一命中时整块转青色实心
- 技术方案: 40 系列中仅 KC 有编码模板 → 改用「当前候选型号的最长公共前缀」,
已验证 40/40 系列前缀单调增长(不回退)
- 参数选择由下拉菜单改为可视化选项块: 悬停抬升+青色投影, 选中青色填充+✓,
不可选取值置灰(虚线+划线, 不消失, 让用户看到选项存在但当前组合不适用),
再点一次取消; 有说明的显示代号+说明, 纯数值用紧凑文字块
- 参数联动仍复用 allowedOptions 规则引擎, 语义未变, 只改渲染
参数组合补全
- 原逻辑: 必须有真实数模才能生成型号 → 库里没有的组合选不出来也出不了号
- infer-sku-template.py 从已有型号反推编码模板, 得 23 个系列 100% 可复现
- verify-templates.py 作为门禁: 用模板重渲染每个已有型号必须逐字一致 ——
该检查抓出 ULM/ULP 行程补零位数写错(2位写3位), 否则会生成 3000 个不存在的零件号
- gen-missing-combos.py 补全 8 个系列共 3157 个型号 (全库 54136 → 57293)
- 占位数模: 3157 个型号共用 1 个 PLACEHOLDER.step(4.5KB), 构建器按路径打包只存一份,
数据包仅 2.10GB → 2.14GB (中途用硬链接方案踩坑: NTFS 上限 1024, 超出退化为拷贝白占 500MB)
- _pending_models.csv 输出待补数模清单, 供 AI 补完后替换 step_file
新增脚本: infer-sku-template / verify-templates / quantify-combo-gap /
gen-missing-combos / gen-placeholder-step / check-combo-unique
新增文档: docs/更改说明-v2.1.md
发现未处理 (已记录在更改说明):
- 夹紧缸 9 系列一组参数对应多型号 (310 个型号, 编码含参数外尾缀, 旧码/新码混用)
- UAGP/LAGP 参数顺序与官方编码结构不符 (传感器应在转角之前)
- 17 个系列因模板不干净未补全 (宁可少补, 不生成可能不存在的零件号)
Co-Authored-By: Claude Code <noreply@anthropic.com>
102 lines
3.1 KiB
Python
102 lines
3.1 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
量化"参数组合缺口": 合法组合 (域 + 规则内) vs 库中已有变体, 以及补全的规模。
|
|
|
|
对每个系列:
|
|
- 现有变体数
|
|
- 笛卡尔积规模 (各参数值域相乘)
|
|
- 规则过滤后的合法组合数 (复用 C# Configurator 同语义的规则求值)
|
|
- 缺口 = 合法组合 - 已覆盖
|
|
这决定"补全组合数据"的可行性与数据量。
|
|
"""
|
|
import json, io, itertools
|
|
|
|
CAT = r'D:/开发/OnebotCatalog/release/web_2026.08/catalog.json'
|
|
d = json.load(io.open(CAT, encoding='utf-8-sig'))
|
|
|
|
|
|
def S(v):
|
|
return '' if v is None else str(v)
|
|
|
|
|
|
def eq(a, b):
|
|
return S(a).lower() == S(b).lower()
|
|
|
|
|
|
def cond_match(c, params):
|
|
"""规则条件求值 (与 app.js condMatches / C# CondMatches 同语义)。"""
|
|
if not c:
|
|
return True
|
|
val = params.get(c['Param'])
|
|
if val is None:
|
|
return False
|
|
op = c['Op']
|
|
cv = c['Value']
|
|
if op == 'eq':
|
|
return eq(val, cv)
|
|
if op == 'ne':
|
|
return not eq(val, cv)
|
|
if op == 'in':
|
|
return isinstance(cv, list) and any(eq(val, x) for x in cv)
|
|
try:
|
|
a, b = float(S(val)), float(S(cv))
|
|
except ValueError:
|
|
return False
|
|
return {'gt': a > b, 'ge': a >= b, 'lt': a < b, 'le': a <= b}.get(op, False)
|
|
|
|
|
|
def rule_violated(rules, params):
|
|
for r in rules or []:
|
|
if cond_match(r.get('If'), params) and not cond_match(r.get('Then'), params):
|
|
return True
|
|
return False
|
|
|
|
|
|
print(f'{"系列":7s} {"现有":>6s} {"笛卡尔":>9s} {"规则内":>9s} {"缺口":>9s} {"倍数":>5s}')
|
|
print('-' * 62)
|
|
tot_exist = tot_gap = 0
|
|
rows = []
|
|
for s in d['series']:
|
|
vs = s.get('variants') or []
|
|
if not vs:
|
|
continue
|
|
params = s['parameters']
|
|
doms = []
|
|
for p in params:
|
|
vals = p.get('values') or []
|
|
if not vals:
|
|
doms = None
|
|
break
|
|
doms.append(vals)
|
|
if doms is None:
|
|
rows.append((s['code'], len(vs), None, None, None))
|
|
continue
|
|
cart = 1
|
|
for vals in doms:
|
|
cart *= len(vals)
|
|
# 规则过滤 (笛卡尔过大时抽样估算)
|
|
legal = 0
|
|
if cart <= 400000:
|
|
for combo in itertools.product(*doms):
|
|
prm = {p['code']: v for p, v in zip(params, combo)}
|
|
if not rule_violated(s.get('rules'), prm):
|
|
legal += 1
|
|
else:
|
|
legal = None # 太大, 跳过精确统计
|
|
gap = (legal - len(vs)) if legal is not None else None
|
|
rows.append((s['code'], len(vs), cart, legal, gap))
|
|
if legal is not None:
|
|
tot_exist += len(vs)
|
|
tot_gap += max(0, gap)
|
|
|
|
for code, n, cart, legal, gap in sorted(rows, key=lambda x: -(x[4] or -1)):
|
|
c = f'{cart:,}' if cart else '—'
|
|
l = f'{legal:,}' if legal else '—'
|
|
g = f'{gap:,}' if gap is not None else '—'
|
|
mult = f'{legal/n:.1f}x' if legal and n else '—'
|
|
flag = ' <-- 缺口大' if gap and gap > 50000 else ''
|
|
print(f'{code:7s} {n:6d} {c:>9s} {l:>9s} {g:>9s} {mult:>5s}{flag}')
|
|
|
|
print(f'\n合计: 现有 {tot_exist:,} 变体, 缺口 {tot_gap:,} 组合')
|
|
print(f'补全后约 {tot_exist + tot_gap:,} 变体 (是现在的 {(tot_exist+tot_gap)/tot_exist:.1f} 倍)')
|