dsh-motor-ai-l0 0.2.3 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +19 -0
- package/README.md +12 -5
- package/docs/ENGINEERING.md +1 -1
- package/docs/QUALITY-GATE.md +4 -1
- package/examples/02-75kw-1480rpm-660v.md +5 -1
- package/index.mjs +3 -1
- package/lib/design-rules.mjs +26 -0
- package/lib/formula-engine.mjs +14 -0
- package/lib/param-schema.mjs +3 -1
- package/lib/surrogate-engine.mjs +63 -5
- package/models/l0_surrogate_family.json +162678 -238
- package/package.json +1 -1
- package/scripts/verify.mjs +160 -2
- package/skills/motor-l0-estimate/SKILL.md +11 -3
- package/skills/motor-l0-estimate/references/formula-reference.md +14 -2
- package/tools/l0/design-validate.mjs +5 -2
- package/tools/l0/index.mjs +11 -2
- package/tools/l0/l0-estimate.mjs +56 -11
- package/tools/l0/param-matrix.mjs +73 -3
- package/tools/l0/pipeline.mjs +213 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,25 @@
|
|
|
5
5
|
|
|
6
6
|
版本三重一致性:`package.json.version` = `SKILL.md` 的 `metadata.version` = `CHANGELOG.md` 最新条目。
|
|
7
7
|
|
|
8
|
+
## [0.2.4] - 2026-10-07 — 代理模型 v2.3.0-fixseg 部署 + GBR 残差校正启用
|
|
9
|
+
|
|
10
|
+
### 🚀 代理模型升级:v2.2.0 → v2.3.0-fixseg
|
|
11
|
+
- **特征空间扩展**:5 特征(含 `poles`),OD 三分位切段(small/medium/large × induction/pmsm)。
|
|
12
|
+
- **模型锁定**:云端重训流水线锁定 `scikit-learn==1.9.1`(`numpy==2.5.0` / `scipy==1.18.1`),复现 G7 门禁(纯 numpy 回放 vs sklearn 重训 maxdiff < 1e-6)。
|
|
13
|
+
- **校验**:`verify.mjs` 70/70 通过;代理模型版本断言 `2.3.0-fixseg`。
|
|
14
|
+
|
|
15
|
+
### ✨ 新能力:GBR 残差校正(ridge_plus_gbr 第二阶段)
|
|
16
|
+
- `lib/surrogate-engine.mjs` 新增 `predictGBR()`,消费模型每段 `gbr` 字段(400 树 / `lr=0.05`),对 ridge 基线做残差修正:`final = primary + gbr_residual`。
|
|
17
|
+
- 单测新增 4 例(无 gbr 退化为 0、残差量级合理、预测携带 `used_gbr` / `gbr_residual_pp`),单测 18 → 22 全绿。
|
|
18
|
+
- 置信度门控维持 ridge 通道口径(不放大降级),仅提升预测精度。
|
|
19
|
+
|
|
20
|
+
### 🔧 工程
|
|
21
|
+
- `tools/l0/pipeline.mjs` 聚合工具随包发布(一步跑完 `buildParamMatrix → runDesignValidate → runL0Estimate`,规避 DSH 2000 行截断)。
|
|
22
|
+
- 版本三重一致性:package.json / SKILL.md metadata / CHANGELOG 同步至 0.2.4。
|
|
23
|
+
|
|
24
|
+
### ⚠️ 不在本次发布范围
|
|
25
|
+
- V16(极数-转速同步一致性校验)等规则层改动仍在开发中,未并入本版本,避免裹挟未经验证的变更。
|
|
26
|
+
|
|
8
27
|
## [0.2.3] - 2026-09-27 — 默认通道回归修复 + 对外物料(推广就绪)
|
|
9
28
|
|
|
10
29
|
### 🔴 P0 回归修复:`auto` 不再进入代理通道
|
package/README.md
CHANGED
|
@@ -27,6 +27,12 @@ node scripts/verify.mjs
|
|
|
27
27
|
2. `motor_design_validate` —— 批量物理校验,剔除 `failed`
|
|
28
28
|
3. `motor_l0_estimate` —— 对剩余候选排序,输出 TopN 与 L1 交接载荷
|
|
29
29
|
|
|
30
|
+
> ⚠️ **推荐用聚合工具 `motor_l0_pipeline` 一步跑完上述三步链**。DSH 对话上下文有 **2000 行读取上限**:
|
|
31
|
+
> 当 `count=120` 时,单 `motor_param_matrix` 就回显 4000+ 行,模型静默丢行后计数对不上 120 还**无报错**——
|
|
32
|
+
> 这是「让模型亲手编排三步链」的结构性风险(三案例实战全中)。聚合工具在插件进程内跑完三步链,
|
|
33
|
+
> 只回吐紧凑摘要(计数自检 + Top10 + 交接载荷 + 文件交接路径),上下文恒 < 2000 行,从根上绕开截断。
|
|
34
|
+
> 只有在单独调试某一步(查完整校验报告、手调 failed 阈值)时才拆开用三个独立工具。
|
|
35
|
+
|
|
30
36
|
> ⚠️ `motor_l0_estimate` 只接受 `params_list`(每项含 16 个 L1 顶层字段),**不接受** `power_kw` / `speed_rpm` 这类散装命名参数 —— 请先用第 1 步生成矩阵再传入。
|
|
31
37
|
> L0 结果只用于**排序与相对比较**,不用于绝对值交付;输出恒带 `solve_mode='l0'` 标记,最终结论须由 L1/L2 给出。
|
|
32
38
|
>
|
|
@@ -37,7 +43,7 @@ node scripts/verify.mjs
|
|
|
37
43
|
> | **公式通道**(默认) | `l0Mode: 'auto'` / `'formula'` | 效率 + 温升 + 损耗拆分 + 齿/轭磁密 + 功率因数 + 可行判定 —— 字段完整 | 日常排序与相对比较 |
|
|
38
44
|
> | **代理通道**(可选增强) | `l0Mode: 'surrogate'` | **仅效率**(温升/损耗/磁密不输出) | 异步机效率排序的交叉参考 |
|
|
39
45
|
>
|
|
40
|
-
> 代理模型 v2.2.0
|
|
46
|
+
> 代理模型 v2.3.0-fixseg(在 v2.2.0 基础上用全量 6029 条真实 RMxprt 结果重训,特征扩为含 `poles` 的 5 维;`designs.db.l0_residuals`,`fault=0`,效率 ∈ [50,99]),按机座外径分三段:
|
|
41
47
|
>
|
|
42
48
|
> | 机种 | 样本 OD 覆盖 | 样本数 | 5 折 CV R² | MAE |
|
|
43
49
|
> |---|---|---|---|---|
|
|
@@ -60,9 +66,10 @@ node scripts/verify.mjs
|
|
|
60
66
|
|
|
61
67
|
| 工具 | 作用 | 关键入参 |
|
|
62
68
|
|---|---|---|
|
|
69
|
+
| `motor_l0_pipeline` | **推荐入口**:三步链聚合,只回紧凑摘要 + 文件交接,根治 2000 行截断 | `power_kw` `speed_rpm` `count` `top_n` `write_handoff` |
|
|
63
70
|
| `motor_param_matrix` | 生成候选参数矩阵(聚焦扫描,非笛卡尔积) | `power_kw` `speed_rpm` `voltage_v` `poles` `count` |
|
|
64
|
-
| `motor_l0_estimate` | 毫秒级估算 + 排序 + L1
|
|
65
|
-
| `motor_design_validate` |
|
|
71
|
+
| `motor_l0_estimate` | 毫秒级估算 + 排序 + L1 交接载荷(默认 Top10) | `params_list` `top_n` `sort_by` |
|
|
72
|
+
| `motor_design_validate` | 15 条物理一致性校验,返回 `passed`/`warning`/`failed` | `params_list` `escalate` |
|
|
66
73
|
|
|
67
74
|
入参别名:`power`→`power_kw`、`rpm`→`speed_rpm`、`volt`→`voltage_v`。
|
|
68
75
|
`sort_by` 白名单:`efficiency` `torque_density` `temp_rise` `total_loss` `power`(`temp_rise`、`total_loss` 升序,越小越好)。
|
|
@@ -94,9 +101,9 @@ $ motor_l0_estimate sort_by: 'efficiency' → Top5,80 个可行候选
|
|
|
94
101
|
|---|---|---|
|
|
95
102
|
| `level` | `'l0'` | 层级总闸。设为 `l1`/`l2` 立即抛错(快速失败,杜绝静默降级) |
|
|
96
103
|
| `l0Mode` | `'auto'` | `formula` 纯公式(字段完整)/ `surrogate` 代理模型(**仅输出效率**)/ `auto` = 公式通道,段外与低置信度自动降级 |
|
|
97
|
-
| `surrogatePath` | `'models/l0_surrogate_family.json'` | 代理模型路径(v2.
|
|
104
|
+
| `surrogatePath` | `'models/l0_surrogate_family.json'` | 代理模型路径(v2.3.0-fixseg) |
|
|
98
105
|
| `surrogateConfidenceThreshold` | `0.4` | 代理置信度门限,低于此值降级公式 |
|
|
99
|
-
| `efficiencyCap` | `96` | 效率封顶(与 L1
|
|
106
|
+
| `efficiencyCap` | `96` | 效率封顶(与 L1 同口径,避免两层排序跳变)。v0.2.4 起仅钳**展示值** `efficiency`,排序按 `efficiency_raw`(未封顶真值) |
|
|
100
107
|
| `insulationClass` | `'F'` | 绝缘等级,决定温升限值(B=80K / F=105K / H=125K) |
|
|
101
108
|
|
|
102
109
|
其余配置项(本地统计 `usageLog`;L0 运行 `surrogatePath` / `surrogateConfidenceThreshold` / `maxMatrixSize` / `topNPreview` / `tempRiseRange` / `airGapFluxT` / `highSpeedRpm`;脱敏回传 `telemetryEnabled` / `telemetryEndpoint` / `telemetryBatchSize` / `telemetryIntervalSec` / `sessionTelemetry`)见 [`docs/ENGINEERING.md`](docs/ENGINEERING.md#八配置全表) 与本文「脱敏聚合指标回传」节。
|
package/docs/ENGINEERING.md
CHANGED
|
@@ -225,7 +225,7 @@ Python 走 `Knowledge/y_series_physics.json` 查表;L0 要求零外部依赖
|
|
|
225
225
|
| `surrogateConfidenceThreshold` | `0.7` | 置信度低于此值自动降级到公式模式 |
|
|
226
226
|
| `maxMatrixSize` | `2000` | 参数矩阵最大组合数 |
|
|
227
227
|
| `topNPreview` | `20` | L0 结果预览返回的 TopN 数量 |
|
|
228
|
-
| `efficiencyCap` | `96` | 效率封顶(对齐 `motor_tools.py:752`,避免 L0/L1
|
|
228
|
+
| `efficiencyCap` | `96` | 效率封顶(对齐 `motor_tools.py:752`,避免 L0/L1 排序跳变)。⚠ v0.2.4 起它只作用于**展示值** `efficiency`;排序与区分度看 `efficiency_raw` |
|
|
229
229
|
| `tempRiseRange` | `[45, 130]` | `max_temp` 钳位区间(**°C**)。⚠ 命名待议 —— 易被误读为 L0 温升(K),拟改 `maxTempClamp` |
|
|
230
230
|
| `airGapFluxT` | `0.8` | 气隙磁密基准 T(齿 / 轭磁密反算输入),PMSM 典型 0.75~0.90 |
|
|
231
231
|
| `insulationClass` | `'F'` | 绝缘等级,决定温升限值(B=80K / F=105K / H=125K) |
|
package/docs/QUALITY-GATE.md
CHANGED
|
@@ -41,7 +41,9 @@ node scripts/regression.mjs --json # 机器可读输出
|
|
|
41
41
|
2. **铜损**:槽面积由几何推算,缺少「匝数 × 电流」的电路约束;槽满率 0.45 与电密 5.5 A/mm² 均为经验值
|
|
42
42
|
3. **机械损**:`MECH_LOSS_K = 5` 未标定,仅保证量级正确
|
|
43
43
|
4. **温升**:散热面积按**光滑**机座外表面积计,未计散热筋;`COOLING_COEFFICIENT` 为单点标定
|
|
44
|
-
5. **效率封顶**:`efficiencyCap=96` 是为与 `_run_simulated`
|
|
44
|
+
5. **效率封顶**:`efficiencyCap=96` 是为与 `_run_simulated` 同口径的权宜值,对高效高速机会低估。
|
|
45
|
+
v0.2.4 起封顶只影响展示值 `efficiency`;**排序与 TopN 区分度改用未封顶真值 `efficiency_raw`**,
|
|
46
|
+
整批触顶时结果行会带 `efficiency_capped=true`、顶层带 `efficiency_note` 说明
|
|
45
47
|
|
|
46
48
|
---
|
|
47
49
|
|
|
@@ -78,6 +80,7 @@ node scripts/regression.mjs --json # 机器可读输出
|
|
|
78
80
|
|
|
79
81
|
- [ ] 用 RMxprt 批量结果做 native vs L1 回归,拟合 `kh/ke`、`MECH_LOSS_K`、`COOLING_COEFFICIENT`
|
|
80
82
|
- [ ] 回归目标:标定域内 `|Δη| ≤ 2pt`、`|Δ温升| ≤ 10K`
|
|
83
|
+
- [x] 校准前先解耦"封顶"与"排序"(v0.2.4 已完成):cap 保持 96 维持 L1 同口径,排序改用 `efficiency_raw`
|
|
81
84
|
- [ ] 校准完成后重新评估 `efficiencyCap` 是否需要放宽(当前 96 对高速高效机偏保守)
|
|
82
85
|
|
|
83
86
|
### 翻转判据
|
|
@@ -43,7 +43,11 @@
|
|
|
43
43
|
|
|
44
44
|
## 5. 这个算例能说明什么
|
|
45
45
|
|
|
46
|
-
**效率并列时的二次排序。** 前 5
|
|
46
|
+
**效率并列时的二次排序。** 前 5 名的**展示值**全部为 96 —— 这是配置项 `efficiencyCap`(默认 96)的封顶结果,不是巧合。封顶的意义是与 L1 同口径,避免 L0/L1 两层排序跳变。
|
|
47
|
+
|
|
48
|
+
⚠️ **v0.2.4 起这两个数要分开读**:`efficiency` 是钳位后的展示值(触顶时都等于 96),
|
|
49
|
+
`efficiency_raw` 才是未封顶真值,**排序按 `efficiency_raw` 进行**,所以 Top5 的先后顺序是有物理依据的,
|
|
50
|
+
不是并列。要看真实差距请读结果行里的 `efficiency_raw` 字段。若仍只见并列,可换维度看:
|
|
47
51
|
|
|
48
52
|
| 维度 | 结论 |
|
|
49
53
|
|---|---|
|
package/index.mjs
CHANGED
|
@@ -17,7 +17,9 @@
|
|
|
17
17
|
* @property {number} surrogateConfidenceThreshold 置信度低于此值降级到公式
|
|
18
18
|
* @property {number} maxMatrixSize 参数矩阵最大组合数
|
|
19
19
|
* @property {number} topNPreview L0 结果预览 TopN
|
|
20
|
-
* @property {number} efficiencyCap 效率封顶(对齐 _run_simulated L752
|
|
20
|
+
* @property {number} efficiencyCap 效率封顶(对齐 _run_simulated L752)。
|
|
21
|
+
* v0.2.4 起仅钳展示值 efficiency;
|
|
22
|
+
* 排序与区分度看未封顶真值 efficiency_raw
|
|
21
23
|
* @property {[number, number]} tempRiseRange temp 钳位区间 (°C),对齐 L1 max_temp;命名待议(拟改 maxTempClamp)
|
|
22
24
|
* @property {boolean} [l1Enabled] 预留
|
|
23
25
|
* @property {boolean} [l2Enabled] 预留
|
package/lib/design-rules.mjs
CHANGED
|
@@ -43,6 +43,7 @@ export const RULE_CATALOG = [
|
|
|
43
43
|
{ id: 'V12', name: '温升限值', severity: 'warning', needs: 'power_kw 或 torque_nm(+ 冷却方式)' },
|
|
44
44
|
{ id: 'V14', name: '反电势闭环', severity: 'failed', needs: 'voltage/speed/poles/stator_id/core_length/slots_stator/parallel_circuits/turns_per_coil' },
|
|
45
45
|
{ id: 'V15', name: '槽满率可行性', severity: 'failed', needs: 'stator_od/stator_id/slots_stator/poles/peak_current/turns_per_coil/parallel_circuits' },
|
|
46
|
+
{ id: 'V16', name: '极数-转速同步一致性', severity: 'failed', needs: 'speed/poles' },
|
|
46
47
|
]
|
|
47
48
|
|
|
48
49
|
/** 可在 config 中把严重度提升为 failed 的规则(默认 warning) */
|
|
@@ -426,6 +427,31 @@ export function validateDesign(row, opts = {}) {
|
|
|
426
427
|
skipped.push('V15')
|
|
427
428
|
}
|
|
428
429
|
|
|
430
|
+
// ---- V16 极数-转速同步一致性(超同步拦截,P0 门禁)----
|
|
431
|
+
// 异步电动机额定转速必须低于同步转速 n_sync = 120·f_line/p(转差为正才能输出转矩);
|
|
432
|
+
// 若 n_sync ≤ 额定转速 ⇒ 转差 ≤ 0(超同步),异步机无法作为电动机运行(只能作发电机/制动)。
|
|
433
|
+
// 同步机(PMSM/BLDC/IPM)无转差概念,本规则不适用,显式跳过。
|
|
434
|
+
// ⚠ 工频 f_line 按 IEC/中国 50Hz 假设;60Hz 电网需后续在 config 暴露 line_freq_hz。
|
|
435
|
+
if (speed !== null && poles !== null) {
|
|
436
|
+
const isPm = isPmsmType(row?.motor_type)
|
|
437
|
+
if (isPm) {
|
|
438
|
+
skipped.push('V16')
|
|
439
|
+
} else {
|
|
440
|
+
const LINE_FREQ_HZ = 50
|
|
441
|
+
const nSync = (120 * LINE_FREQ_HZ) / poles
|
|
442
|
+
metrics.sync_speed_rpm = round1(nSync)
|
|
443
|
+
if (nSync <= speed) {
|
|
444
|
+
// 推荐极数:满足 n_sync > 转速 的最大极数(取 floor 留 1e-6 余量避免同步边界=speed 误判)
|
|
445
|
+
const recommendedPole = Math.max(2, Math.floor((120 * LINE_FREQ_HZ) / speed - 1e-6) || 2)
|
|
446
|
+
fail('V16', `极数 ${poles} 与转速 ${speed}rpm 不同步:同步转速 ${round1(nSync)}rpm ≤ 额定转速,` +
|
|
447
|
+
`异步机转差≤0(超同步),无法作为电动机运行;该转速档应选 ${recommendedPole} 极` +
|
|
448
|
+
`(n_sync=${(120 * LINE_FREQ_HZ) / recommendedPole}rpm)`)
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
} else {
|
|
452
|
+
skipped.push('V16')
|
|
453
|
+
}
|
|
454
|
+
|
|
429
455
|
// ---- V13 每线圈匝数下限(附在 V06 组,不单独占号)----
|
|
430
456
|
// v0.1.6:闭环反推可能给出 turns_per_coil=1(380V/高频低匝数场景,hairpin 扁线绕组可行),
|
|
431
457
|
// 故下限改为 <1(仅排除真正不可实现的 0/负匝数),不再误报 1 匝方案。
|
package/lib/formula-engine.mjs
CHANGED
|
@@ -302,6 +302,13 @@ export function estimateTempRise({ statorOd, coreLength, cooling }, totalLossW)
|
|
|
302
302
|
* L1 仿真效率去噪克隆 —— motor_tools.py:750-752
|
|
303
303
|
* base_eff = 85 + (od-180)·0.04 + (poles-8)·1.2,Python 侧另有 ±1.5 噪声
|
|
304
304
|
*/
|
|
305
|
+
/**
|
|
306
|
+
* 未封顶效率的物理硬上界(v0.2.4)
|
|
307
|
+
* 仅作数值保险:物理模型不可能算出 >100% 的效率,取 99.9 以防浮点/极端输入溢出。
|
|
308
|
+
* 注意与 efficiencyCap 的区别:cap 是「与 L1 同口径」的口径约定,此值是物理边界。
|
|
309
|
+
*/
|
|
310
|
+
const RAW_EFF_MAX = 99.9
|
|
311
|
+
|
|
305
312
|
export function l1EfficiencyProxy({ statorOd, poles }, clampMax = SIM_EFF.clampMax) {
|
|
306
313
|
const base =
|
|
307
314
|
SIM_EFF.base +
|
|
@@ -384,6 +391,11 @@ export function quickL0Estimate(params, opts = {}) {
|
|
|
384
391
|
const poleFactor = POLE_EFFICIENCY_FACTOR[poles] ?? 0.98
|
|
385
392
|
const rawEff = efficiencyFromLosses(powerKw, totalLossW) * poleFactor
|
|
386
393
|
const efficiency = round2(Math.min(efficiencyCap, Math.max(SIM_EFF.clampMin, rawEff)))
|
|
394
|
+
// v0.2.4:efficiencyCap 存在的意义是「与 L1 同口径」,不是物理真值。
|
|
395
|
+
// 大机座整批触顶时 efficiency 全被压成同一个数 ⇒ TopN 排序失去区分度(450kW 实测 10/10 触顶)。
|
|
396
|
+
// 故额外保留未封顶真值供排序使用,并打触顶标记,让调用方知道 efficiency 是"钳位显示值"。
|
|
397
|
+
const efficiencyRaw = round2(clamp(rawEff, SIM_EFF.clampMin, RAW_EFF_MAX))
|
|
398
|
+
const efficiencyCapped = rawEff > efficiencyCap + 1e-9
|
|
387
399
|
|
|
388
400
|
// ---- 温升(物理通道,含液冷标定修正)----
|
|
389
401
|
const tempRise = estimateTempRise({ statorOd, coreLength, cooling }, totalLossW)
|
|
@@ -409,6 +421,8 @@ export function quickL0Estimate(params, opts = {}) {
|
|
|
409
421
|
|
|
410
422
|
const native = {
|
|
411
423
|
efficiency,
|
|
424
|
+
efficiency_raw: efficiencyRaw,
|
|
425
|
+
efficiency_capped: efficiencyCapped,
|
|
412
426
|
torque: round2(torque),
|
|
413
427
|
temp_rise: tempRise,
|
|
414
428
|
total_loss: totalLossW,
|
package/lib/param-schema.mjs
CHANGED
|
@@ -136,7 +136,9 @@ export const PARAM_SCHEMA = {
|
|
|
136
136
|
|
|
137
137
|
/** L0 原生字段 —— 物理损耗通道的独立产出 + v0.1.6 可追溯诊断字段 */
|
|
138
138
|
export const L0_NATIVE_FIELDS = [
|
|
139
|
-
'efficiency', // %
|
|
139
|
+
'efficiency', // % 物理损耗模型推算(按 efficiencyCap 封顶,与 L1 同口径)
|
|
140
|
+
'efficiency_raw', // % 未封顶真实值(v0.2.4)—— 排序用,避免封顶压平区分度
|
|
141
|
+
'efficiency_capped',// bool 是否触顶被封(v0.2.4)—— 触顶时 efficiency 非真值
|
|
140
142
|
'torque', // Nm 9550·P/n
|
|
141
143
|
'temp_rise', // K 热负荷法温升(注意:与 max_temp 的 °C 口径不同)
|
|
142
144
|
'total_loss', // W 铜损+铁损+机械损
|
package/lib/surrogate-engine.mjs
CHANGED
|
@@ -151,6 +151,51 @@ export function predictLinear(features, coeffs) {
|
|
|
151
151
|
return result
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
+
/**
|
|
155
|
+
* 评估单棵 GBR 决策树(sklearn 数组导出格式)
|
|
156
|
+
* 约定:feature[i] === -2 表示叶子节点,values[i] 为叶子输出;
|
|
157
|
+
* 叶子节点 left/right 均为 -1。以 maxIter 兜底防异常树结构死循环。
|
|
158
|
+
* @param {number[]} features 标准化特征向量
|
|
159
|
+
* @param {object} tree 单棵树 { feature, threshold, left, right, values }
|
|
160
|
+
* @returns {number} 该树对残差的贡献
|
|
161
|
+
*/
|
|
162
|
+
function applyTree(features, tree) {
|
|
163
|
+
let node = 0
|
|
164
|
+
const maxIter = tree.feature.length + 1
|
|
165
|
+
for (let i = 0; i < maxIter; i++) {
|
|
166
|
+
const f = tree.feature[node]
|
|
167
|
+
if (f === -2) {
|
|
168
|
+
return tree.values[node]
|
|
169
|
+
}
|
|
170
|
+
if (features[f] <= tree.threshold[node]) {
|
|
171
|
+
node = tree.left[node]
|
|
172
|
+
} else {
|
|
173
|
+
node = tree.right[node]
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return tree.values[node] ?? 0
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* GBR 残差校正预测(ridge_plus_gbr 第二阶段)
|
|
181
|
+
* 公式(与训练端 M1 一致):residual = init + learning_rate * Σ applyTree(features, tree)
|
|
182
|
+
* 残差与 primary 线性预测同单位(效率百分点),最终效率 = primary + residual。
|
|
183
|
+
* 模型无 gbr 字段时返回 0(退化为纯 ridge 通道,行为不变)。
|
|
184
|
+
* @param {number[]} features 标准化特征向量
|
|
185
|
+
* @param {object|undefined} gbr GBR 模型对象(含 init/learning_rate/trees)
|
|
186
|
+
* @returns {number} 残差修正量(效率百分点)
|
|
187
|
+
*/
|
|
188
|
+
export function predictGBR(features, gbr) {
|
|
189
|
+
if (!gbr || !Array.isArray(gbr.trees) || gbr.trees.length === 0) {
|
|
190
|
+
return 0
|
|
191
|
+
}
|
|
192
|
+
let sum = 0
|
|
193
|
+
for (const tree of gbr.trees) {
|
|
194
|
+
sum += applyTree(features, tree)
|
|
195
|
+
}
|
|
196
|
+
return (gbr.init ?? 0) + (gbr.learning_rate ?? 1) * sum
|
|
197
|
+
}
|
|
198
|
+
|
|
154
199
|
/**
|
|
155
200
|
* 代理模型预测单个样本(分族模型)
|
|
156
201
|
* @param {object} params L0 参数字典
|
|
@@ -166,7 +211,7 @@ export function predictSurrogate(params, model, motorType = null) {
|
|
|
166
211
|
if (model.schema === 'l0_surrogate_family') {
|
|
167
212
|
const segment = selectFamilySegment(statorOd, type, model)
|
|
168
213
|
if (segment) {
|
|
169
|
-
return predictFromSegment(params, segment
|
|
214
|
+
return predictFromSegment(params, segment, model, type)
|
|
170
215
|
}
|
|
171
216
|
// 超出范围,返回 null 让调用方降级
|
|
172
217
|
return null
|
|
@@ -183,7 +228,8 @@ export function predictSurrogate(params, model, motorType = null) {
|
|
|
183
228
|
/**
|
|
184
229
|
* 从分族模型预测
|
|
185
230
|
*/
|
|
186
|
-
function predictFromSegment(params,
|
|
231
|
+
function predictFromSegment(params, segment, model, motorType) {
|
|
232
|
+
const primary = segment.primary
|
|
187
233
|
const norm = {
|
|
188
234
|
// primary.feature_names 是 v1.x 格式;v2.x 把特征顺序存在顶层 model.features
|
|
189
235
|
featureOrder: primary.feature_names ?? model.features,
|
|
@@ -192,11 +238,20 @@ function predictFromSegment(params, primary, model, motorType) {
|
|
|
192
238
|
}
|
|
193
239
|
const features = extractFeatures(params, norm)
|
|
194
240
|
|
|
195
|
-
//
|
|
241
|
+
// 基础预测(加权 Ridge)
|
|
196
242
|
const rawEff = predictLinear(features, primary)
|
|
197
|
-
const efficiency = Math.min(99, Math.max(50, rawEff))
|
|
198
243
|
|
|
199
|
-
//
|
|
244
|
+
// GBR 残差校正(ridge_plus_gbr 第二阶段):final = base + residual
|
|
245
|
+
// 残差与 primary 同单位(效率百分点),模型无 gbr 字段时退化为纯 ridge(行为不变)
|
|
246
|
+
const gbrResidual = predictGBR(features, segment.gbr)
|
|
247
|
+
const correctedEff = rawEff + gbrResidual
|
|
248
|
+
|
|
249
|
+
// 预测效率 (物理合理性钳位: 效率不可能 <50% 或 >99%)
|
|
250
|
+
const efficiency = Math.min(99, Math.max(50, correctedEff))
|
|
251
|
+
|
|
252
|
+
const usedGbr = !!segment.gbr && Array.isArray(segment.gbr.trees) && segment.gbr.trees.length > 0
|
|
253
|
+
|
|
254
|
+
// 置信度:基于 R² 和特征范围(维持与纯 ridge 通道一致,避免改变降级判定)
|
|
200
255
|
const confidence = estimateConfidence(features, norm, primary.cv_r2)
|
|
201
256
|
|
|
202
257
|
return {
|
|
@@ -207,6 +262,8 @@ function predictFromSegment(params, primary, model, motorType) {
|
|
|
207
262
|
prediction_source: 'surrogate',
|
|
208
263
|
family: { motor_type: motorType, size: primary.tag?.split('_')[0] },
|
|
209
264
|
cv_r2: primary.cv_r2,
|
|
265
|
+
used_gbr: usedGbr,
|
|
266
|
+
gbr_residual_pp: usedGbr ? Math.round(gbrResidual * 1000) / 1000 : 0,
|
|
210
267
|
}
|
|
211
268
|
}
|
|
212
269
|
|
|
@@ -387,5 +444,6 @@ export default {
|
|
|
387
444
|
buildSurrogateResult,
|
|
388
445
|
extractFeatures,
|
|
389
446
|
selectFamilySegment,
|
|
447
|
+
predictGBR,
|
|
390
448
|
DEFAULT_CONFIDENCE_THRESHOLD,
|
|
391
449
|
}
|