v2.2: 桌面版同步 DESTACO 风格选型 + 网页预览后台启动
桌面版 (仅网页版 v2.1 已做的部分, 本次补齐桌面端, 两端语义一致) - 新增 OptionTile 控件类: 三态选项块 (可选/已选/不可选), 替代 ComboBox 下拉 不可选取值置灰+删除线但保留占位 (让用户看到选项存在, 而非从列表消失); 悬停青色高亮, 再点一次取消选择 - 新增零件号实时条: 复用与网页版同一套「候选型号最长公共前缀」算法, 已确定片段深青实心 + 待定位数虚化 + 进度条, 选满唯一命中时整块转青色实心 - 维度标题后回显当前已选值 - range 参数 (连续域) 保留可编辑下拉, 无法枚举成卡片 - 参数联动仍复用 Configurator.AllowedOptions, 规则引擎未改动 网页预览后台启动 (无 PowerShell 窗口) - 新增 tools/serve-web-bg.ps1: Start-Process -WindowStyle Hidden 启动, 带 -Stop / -Status; 用命令行匹配定位进程 (比只看 PID 文件可靠) - _start_web.bat / _start_all.bat 改用之, 不再弹窗口 工具 - ui-diagnose.ps1 增强: 原只检查 _boxes (下拉), 现同时检查选项块数量、 每维度是否有可选值、零件号条输出 —— 可自动发现"某维度全被置灰无从下手" - 新增 assess-combo-risk.py: 用量化口径评估生成候选的风险 (以同缸径真实数据值域为官方值域的代理) - 新增 make-pending-list.py: 重写待补清单, 增加 nearest_real / diff_from_nearest 列, 每个候选与其最近的真实型号并列, 便于逐个核实 文档 - 新增 工作总结-2026-09.md: 两阶段工作回顾、发现的问题、踩坑经验 验证 - 编译首次通过; UI 诊断 40/40 系列 0 问题 - 零件号条实测: KC0032-10 / ULP32020AEK0 / UAGP075OK120WS0 / LAE140NN31 均完整; UCBP 正确提示"该组合对应 2 个型号" (暴露已知数据问题) 踩坑记录 - PowerShell 变量名大小写不敏感: $root 与 $Root 是同一变量, 互相覆盖导致 启动脚本指向错误目录 (改用 $projRoot) - .ps1 必须带 UTF-8 BOM, 否则 PS 5.1 按 ANSI 读, 中文注释破坏解析 Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because it is too large
Load Diff
76
onebot-data/tools/assess-combo-risk.py
Normal file
76
onebot-data/tools/assess-combo-risk.py
Normal file
@@ -0,0 +1,76 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
用【同缸径的真实数据值域】作为官方值域的代理, 评估生成候选的保守程度。
|
||||
|
||||
为什么用代理: 官方图册是双栏排版, pdftotext 后左右栏交错, 正则解析多次尝试均不可靠
|
||||
(把真实数据都判成越界 = 解析错误)。而【真实型号】是最可靠的一手证据:
|
||||
某缸径的真实型号里从未出现过的取值, 生成它就是推测。
|
||||
|
||||
判定口径 (保守):
|
||||
某候选的每个参数值, 是否都在【同缸径的真实型号】中出现过?
|
||||
- 全部出现过 → 属"组合缺失", 补全风险低
|
||||
- 有值从未出现 → 属"引入新值", 补全风险高 (需人工/AI 核对官方图册)
|
||||
"""
|
||||
import csv, io, os, collections
|
||||
|
||||
CAT = r'D:/开发/OnebotCatalog/onebot-data/catalog'
|
||||
DIMS = ['stroke', 'rod', 'orient', 'sensor', 'mount', 'style', 'pos', 'type']
|
||||
|
||||
|
||||
def main():
|
||||
rows = list(csv.DictReader(io.open(os.path.join(CAT, 'csv', 'ULP.csv'), encoding='utf-8-sig')))
|
||||
real = [r for r in rows if r['step_file'] != 'PLACEHOLDER.step']
|
||||
gen = [r for r in rows if r['step_file'] == 'PLACEHOLDER.step']
|
||||
dims = [d for d in DIMS if d in rows[0]]
|
||||
|
||||
# 每个缸径的真实取值集合
|
||||
domain = collections.defaultdict(lambda: collections.defaultdict(set))
|
||||
for r in real:
|
||||
for d in dims:
|
||||
if r[d] != '':
|
||||
domain[r['size']][d].add(r[d])
|
||||
|
||||
print('=' * 74)
|
||||
print('真实数据里各缸径的取值域 (作为官方值域的代理)')
|
||||
print('=' * 74)
|
||||
for b in sorted(domain, key=int):
|
||||
parts = [f'{d}={sorted(domain[b][d])}' for d in dims if domain[b][d]]
|
||||
n = sum(1 for r in real if r['size'] == b)
|
||||
print(f' ULP{b:>3s} ({n:3d} 个真实型号)')
|
||||
for p in parts:
|
||||
print(f' {p}')
|
||||
|
||||
# 评估生成候选
|
||||
safe, risky = 0, 0
|
||||
risky_detail = collections.Counter()
|
||||
risky_models = []
|
||||
for r in gen:
|
||||
b = r['size']
|
||||
novel = [f'{d}={r[d]}' for d in dims
|
||||
if r[d] != '' and r[d] not in domain.get(b, {}).get(d, set())]
|
||||
if novel:
|
||||
risky += 1
|
||||
for x in novel:
|
||||
risky_detail[f'ULP{b} {x}'] += 1
|
||||
if len(risky_models) < 5:
|
||||
risky_models.append((r['model_code'], novel))
|
||||
else:
|
||||
safe += 1
|
||||
|
||||
print()
|
||||
print('=' * 74)
|
||||
print(f'生成候选 {len(gen)} 个的保守度评估')
|
||||
print('=' * 74)
|
||||
print(f' 低风险 (全部取值在同缸径真实数据中出现过): {safe} ({safe*100//len(gen)}%)')
|
||||
print(f' 高风险 (含该缸径从未出现的取值) : {risky} ({risky*100//len(gen)}%)')
|
||||
if risky_detail:
|
||||
print('\n 高风险取值 TOP15 (该缸径真实数据里从未出现):')
|
||||
for k, v in risky_detail.most_common(15):
|
||||
print(f' {k:28s} {v:5d} 个候选')
|
||||
print('\n 高风险样本:')
|
||||
for mc, novel in risky_models:
|
||||
print(f' {mc:18s} 新值 {novel}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
109
onebot-data/tools/make-pending-list.py
Normal file
109
onebot-data/tools/make-pending-list.py
Normal file
@@ -0,0 +1,109 @@
|
||||
# -*- 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()
|
||||
Reference in New Issue
Block a user