dsh-engineering-ui 0.0.0-stage → 1.5.0

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.
Files changed (122) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +772 -3
  3. package/engineering/plugins/dsh-engineering-ui/cordis.patch.yml +5 -0
  4. package/engineering/plugins/dsh-engineering-ui/icon.svg +14 -0
  5. package/engineering/plugins/dsh-engineering-ui/lib/client.js +8259 -0
  6. package/engineering/plugins/dsh-engineering-ui/lib/defense-sign.js +1552 -0
  7. package/engineering/plugins/dsh-engineering-ui/lib/index.js +5633 -0
  8. package/engineering/plugins/dsh-engineering-ui/lib/job-wait-guard.js +236 -0
  9. package/engineering/plugins/dsh-engineering-ui/lib/mem-guard.js +331 -0
  10. package/engineering/plugins/dsh-engineering-ui/lib/webots.js +114 -0
  11. package/engineering/plugins/dsh-engineering-ui/locale/en.json +6 -0
  12. package/engineering/plugins/dsh-engineering-ui/locale/zh.json +6 -0
  13. package/engineering/plugins/dsh-engineering-ui/preset-engineering.patch.yml +601 -0
  14. package/engineering/skills/assembly-orchestration/SKILL.md +651 -0
  15. package/engineering/skills/cad-workflow/SKILL.md +132 -0
  16. package/engineering/skills/mode-selection/SKILL.md +660 -0
  17. package/engineering/skills/physics-in-loop/SKILL.md +238 -0
  18. package/engineering/skills/physics-in-loop/references/feadocs.md +142 -0
  19. package/engineering/skills/solidworks-bridge/SKILL.md +445 -0
  20. package/engineering/skills/sw-design/SKILL.md +112 -0
  21. package/engineering/skills/sw-design/references/autocad-workflow.md +81 -0
  22. package/engineering/skills/sw-design/references/gbt-drafting.md +71 -0
  23. package/engineering/skills/sw-to-cad/SKILL.md +121 -0
  24. package/engineering/skills/webots-simulation/SKILL.md +58 -0
  25. package/engineering/tools/LICENSE.Auto-SolidWorks +21 -0
  26. package/engineering/tools/_plugin_paths.py +93 -0
  27. package/engineering/tools/_root.py +330 -0
  28. package/engineering/tools/_store.py +446 -0
  29. package/engineering/tools/ac_bridge.py +459 -0
  30. package/engineering/tools/ac_rules.json +21 -0
  31. package/engineering/tools/ac_validate.py +699 -0
  32. package/engineering/tools/ask_user.py +887 -0
  33. package/engineering/tools/build_arm1.py +46 -0
  34. package/engineering/tools/build_assembly.py +94 -0
  35. package/engineering/tools/build_base.py +57 -0
  36. package/engineering/tools/build_housing.py +70 -0
  37. package/engineering/tools/build_structural.py +43 -0
  38. package/engineering/tools/build_transmission.py +69 -0
  39. package/engineering/tools/cadx.lsp +128 -0
  40. package/engineering/tools/choice_contract.py +61 -0
  41. package/engineering/tools/conn_state.py +1671 -0
  42. package/engineering/tools/defense_gate.py +3374 -0
  43. package/engineering/tools/drawing_input.py +314 -0
  44. package/engineering/tools/env_check.py +467 -0
  45. package/engineering/tools/examples/build_axis.py +142 -0
  46. package/engineering/tools/examples/build_cup.py +56 -0
  47. package/engineering/tools/examples/build_plate.py +58 -0
  48. package/engineering/tools/examples/build_sphere.py +30 -0
  49. package/engineering/tools/examples/build_transition_duct.py +381 -0
  50. package/engineering/tools/gate_integrity.py +401 -0
  51. package/engineering/tools/mode2_flow.py +3589 -0
  52. package/engineering/tools/mode_checker.py +77 -0
  53. package/engineering/tools/mode_gate.py +8013 -0
  54. package/engineering/tools/part_registry.py +4266 -0
  55. package/engineering/tools/physics/__init__.py +50 -0
  56. package/engineering/tools/physics/_compat.py +12 -0
  57. package/engineering/tools/physics/design_state.py +142 -0
  58. package/engineering/tools/physics/domain_validator.py +751 -0
  59. package/engineering/tools/physics/fatigue.py +489 -0
  60. package/engineering/tools/physics/fea_solver.py +1163 -0
  61. package/engineering/tools/physics/feapy_solver.py +314 -0
  62. package/engineering/tools/physics/gb_materials.json +1228 -0
  63. package/engineering/tools/physics/geometry_gate.py +337 -0
  64. package/engineering/tools/physics/load_case.py +951 -0
  65. package/engineering/tools/physics/material_db.py +874 -0
  66. package/engineering/tools/physics/mesh_adapter.py +321 -0
  67. package/engineering/tools/physics/refine_rules.py +272 -0
  68. package/engineering/tools/physics/rules/gb_base_rules.json +377 -0
  69. package/engineering/tools/physics/rules/gb_drafting_rules.json +57 -0
  70. package/engineering/tools/physics/rules/gb_electrical_rules.json +25 -0
  71. package/engineering/tools/physics/rules/gb_fasteners_rules.json +73 -0
  72. package/engineering/tools/physics/rules/gb_housing_rules.json +41 -0
  73. package/engineering/tools/physics/rules/gb_manufacturing_rules.json +25 -0
  74. package/engineering/tools/physics/rules/gb_material_verify_rules.json +72 -0
  75. package/engineering/tools/physics/rules/gb_materials_rules.json +377 -0
  76. package/engineering/tools/physics/rules/gb_safety_rules.json +89 -0
  77. package/engineering/tools/physics/rules/gb_springs_rules.json +41 -0
  78. package/engineering/tools/physics/rules/gb_standards_registry.json +1646 -0
  79. package/engineering/tools/physics/rules/gb_structural_rules.json +201 -0
  80. package/engineering/tools/physics/rules/gb_surface_rules.json +25 -0
  81. package/engineering/tools/physics/rules/gb_transmission_rules.json +233 -0
  82. package/engineering/tools/physics/rules/housing_rules.json +48 -0
  83. package/engineering/tools/physics/rules/manifest.json +54 -0
  84. package/engineering/tools/physics/rules/mold_rules.json +48 -0
  85. package/engineering/tools/physics/rules/structural_rules.json +56 -0
  86. package/engineering/tools/physics/rules/transmission_rules.json +48 -0
  87. package/engineering/tools/physics/simulation_report.py +829 -0
  88. package/engineering/tools/physics_bridge.py +2685 -0
  89. package/engineering/tools/profile-manifest.ps1 +72 -0
  90. package/engineering/tools/run_regression.py +109 -0
  91. package/engineering/tools/settings.py +419 -0
  92. package/engineering/tools/solidworks-modeling.md +519 -0
  93. package/engineering/tools/sw_bridge.py +8215 -0
  94. package/engineering/tools/sw_guard.py +1066 -0
  95. package/engineering/tools/sw_surface.py +87 -0
  96. package/engineering/tools/swapi.py +14766 -0
  97. package/engineering/tools/verify_sync.py +218 -0
  98. package/engineering/tools/visual_check.py +2589 -0
  99. package/engineering/tools/webots_env.py +121 -0
  100. package/engineering/tools/webots_mcp/__init__.py +3 -0
  101. package/engineering/tools/webots_mcp/__main__.py +3 -0
  102. package/engineering/tools/webots_mcp/assembly.py +391 -0
  103. package/engineering/tools/webots_mcp/bootstrap.py +55 -0
  104. package/engineering/tools/webots_mcp/controller_worker.py +124 -0
  105. package/engineering/tools/webots_mcp/evidence.py +288 -0
  106. package/engineering/tools/webots_mcp/fake_worker.py +69 -0
  107. package/engineering/tools/webots_mcp/fault_resources.py +121 -0
  108. package/engineering/tools/webots_mcp/host.py +45 -0
  109. package/engineering/tools/webots_mcp/ledger.py +73 -0
  110. package/engineering/tools/webots_mcp/owned_job.py +70 -0
  111. package/engineering/tools/webots_mcp/physical_gate.py +187 -0
  112. package/engineering/tools/webots_mcp/physics.py +152 -0
  113. package/engineering/tools/webots_mcp/pipeline.py +108 -0
  114. package/engineering/tools/webots_mcp/recipes.py +129 -0
  115. package/engineering/tools/webots_mcp/repair.py +132 -0
  116. package/engineering/tools/webots_mcp/repair_trial.py +375 -0
  117. package/engineering/tools/webots_mcp/runtime.py +311 -0
  118. package/engineering/tools/webots_mcp/server.py +217 -0
  119. package/engineering/tools/webots_mcp/sw_export.py +509 -0
  120. package/engineering/tools/webots_mcp/transport.py +156 -0
  121. package/engineering/tools/workflow_gate.py +6134 -0
  122. package/package.json +64 -4
@@ -0,0 +1,337 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ geometry_gate.py — 几何门禁检查
4
+ ================================
5
+ 在 FEA 求解前检查几何的合理性,避免无效仿真浪费时间和资源。
6
+
7
+ 检查项:
8
+ 1. DESIGN_SPACE Violation — 模型是否超出设计空间
9
+ 2. CONNECTIVITY — 几何是否连通(单一流形)
10
+ 3. MIN_WALL_THICKNESS — 最小壁厚检查(近似)
11
+ 4. MANIFOLD_CHECK — 非流形边/顶点检测
12
+ 5. INTERFACE_COMPLIANCE — 接口区域(孔、安装面)是否符合要求
13
+
14
+ 注意:本模块基于 STEP 几何分析,不依赖外部 CAD 软件。
15
+ """
16
+ from __future__ import annotations
17
+
18
+ import math
19
+ from typing import Any, Optional
20
+
21
+ try:
22
+ from shapely.geometry import Polygon, MultiPolygon, box
23
+ from shapely.validation import make_valid
24
+ _HAS_SHAPLEY = True
25
+ except ImportError:
26
+ _HAS_SHAPLEY = False
27
+
28
+
29
+ def check_design_space_violation(
30
+ body_bbox: list[float],
31
+ design_bounds: dict[str, float],
32
+ ) -> dict[str, Any]:
33
+ """检查模型包围盒是否超出设计空间。
34
+
35
+ Args:
36
+ body_bbox: [xmin, xmax, ymin, ymax, zmin, zmax] in mm
37
+ design_bounds: {"x_min": ..., "x_max": ..., ...}
38
+
39
+ Returns:
40
+ {ok, violation_mm3, violation_pct, bounds_check}
41
+ """
42
+ # ── 【Bug18 修复·未评估语义】bbox 缺失时必须报"未评估",不得假 PASS/FAIL ──
43
+ # 原实现在拿不到真实包围盒时,调用方会用 design_domain 回填 —— 那等于
44
+ # "拿设计域自己和自己比",DESIGN_SPACE 恒 PASS,门禁形同虚设。
45
+ # 现在:调用方应传 None;此处返回 ok=False + evaluated=False,
46
+ # 由上层据此判 NOT_EVALUATED(不可交付),而不是静默放过。
47
+ if not body_bbox or not isinstance(body_bbox, (list, tuple)) or len(body_bbox) < 6:
48
+ return {
49
+ "ok": False,
50
+ "evaluated": False,
51
+ "body_bbox_mm": None,
52
+ "design_bounds": design_bounds,
53
+ "per_axis": {},
54
+ "violation_vol_mm3": 0.0,
55
+ "violation_pct": 0.0,
56
+ "reason": ("缺少真实包围盒(bbox),DESIGN_SPACE 未评估 —— "
57
+ "不得据此判定合格。请确保能连上 SolidWorks 取到真实几何。"),
58
+ }
59
+ # ── 【Bug18 修复】坐标基准必须统一,不得直接比较绝对值 ──────────────
60
+ # 测试部实测:承载板 200×60×10(本应 PASS)被判 DESIGN_SPACE FAIL:
61
+ # body_bbox_mm=[-12.33,12.33,-34.88,34.88,...] ← 零件【局部】坐标
62
+ # design_bounds = [0,200]×[0,60]×[0,60] ← 设计域【全局】坐标
63
+ # 两者基准不同,直接比较必然"越界" → 合格零件被误拒。
64
+ # 修复:先做【基准对齐】—— 用零件包围盒的【尺寸】去核对设计域的
65
+ # 【可用空间尺寸】,而不是比对绝对坐标。
66
+ # 具体:把 body_bbox 平移,使其中心与设计域中心重合后再比较;
67
+ # 这只影响"是否放得下"的判定,不改变"尺寸是否超限"的语义。
68
+ _bb = list(body_bbox) if body_bbox else None
69
+ if _bb and len(_bb) >= 6:
70
+ try:
71
+ _align = []
72
+ for _ax, _i0, _i1 in (("x", 0, 1), ("y", 2, 3), ("z", 4, 5)):
73
+ _dmin = design_bounds.get(_ax + "_min")
74
+ _dmax = design_bounds.get(_ax + "_max")
75
+ if _dmin is None or _dmax is None:
76
+ _align.append((_bb[_i0], _bb[_i1]))
77
+ continue
78
+ _bmin, _bmax = float(_bb[_i0]), float(_bb[_i1])
79
+ _blen = _bmax - _bmin
80
+ _dlen = float(_dmax) - float(_dmin)
81
+ _dcen = (float(_dmax) + float(_dmin)) / 2.0
82
+ # 以设计域中心为基准重新放置零件(保留其真实尺寸)
83
+ _new_min = _dcen - _blen / 2.0
84
+ _new_max = _dcen + _blen / 2.0
85
+ _align.append((_new_min, _new_max))
86
+ body_bbox = [_align[0][0], _align[0][1],
87
+ _align[1][0], _align[1][1],
88
+ _align[2][0], _align[2][1]]
89
+ except Exception:
90
+ body_bbox = _bb
91
+ check = {}
92
+ for axis in ("x", "y", "z"):
93
+ min_key, max_key = f"{axis}_min", f"{axis}_max"
94
+ body_min, body_max = body_bbox[[0, 2, 4][["x", "y", "z"].index(axis)]], \
95
+ body_bbox[[1, 3, 5][["x", "y", "z"].index(axis)]]
96
+ design_min = design_bounds.get(min_key, -float("inf"))
97
+ design_max = design_bounds.get(max_key, float("inf"))
98
+ if body_min < design_min or body_max > design_max:
99
+ check[axis] = {
100
+ "body_range": (body_min, body_max),
101
+ "design_range": (design_min, design_max),
102
+ "violated": True,
103
+ }
104
+ else:
105
+ check[axis] = {"violated": False}
106
+
107
+ # 计算越界体积(近似为超出部分与边界平面的交集)
108
+ violation_vol = 0.0
109
+ domain_vol = 1.0
110
+ for axis in ("x", "y", "z"):
111
+ idx = ["x", "y", "z"].index(axis)
112
+ design_min = design_bounds.get(f"{axis}_min", 0)
113
+ design_max = design_bounds.get(f"{axis}_max", 1000)
114
+ domain_vol *= (design_max - design_min)
115
+
116
+ ok = all(not c.get("violated", False) for c in check.values())
117
+ return {
118
+ "ok": ok,
119
+ "body_bbox_mm": body_bbox,
120
+ "design_bounds": design_bounds,
121
+ "per_axis": check,
122
+ "violation_vol_mm3": round(violation_vol, 2),
123
+ "violation_pct": round(violation_vol / domain_vol * 100, 4) if domain_vol > 0 else 0.0,
124
+ }
125
+
126
+
127
+ def check_connectivity(topology: dict) -> dict[str, Any]:
128
+ """检查几何连通性。
129
+
130
+ Args:
131
+ topology: {"volumes": N, "bodies": N, "faces": N, "edges": N, "vertices": N}
132
+
133
+ Returns:
134
+ {ok, num_bodies, num_volumes, single_body: bool}
135
+ """
136
+ num_bodies = topology.get("bodies", 1)
137
+ num_volumes = topology.get("volumes", 1)
138
+ ok = num_bodies <= 5 and num_volumes >= 1 # 允许最多5个独立体
139
+ return {
140
+ "ok": ok,
141
+ "num_bodies": num_bodies,
142
+ "num_volumes": num_volumes,
143
+ "single_body": num_bodies == 1,
144
+ "status": "PASS" if ok else "WARNING",
145
+ "note": "multiple bodies detected" if num_bodies > 1 else "",
146
+ }
147
+
148
+
149
+ def estimate_min_wall_thickness(face_areas: list[dict], volume_mm3: float,
150
+ surface_area_mm2: float = None) -> dict[str, Any]:
151
+ """基于表面积/体积比估算最小壁厚。
152
+
153
+ 近似公式:t_min ≈ 2 * V / A_total(薄壁/凸形体的特征厚度量级)
154
+
155
+ ── 【Bug7 修复】新增 surface_area_mm2 入参 ──────────────────────
156
+ 原实现【只认 face_areas 逐面数据】;但真实零件走 swapi.massprops 时
157
+ 只拿得到【总表面积】(没有逐面明细),于是这里恒返回 ok=False,
158
+ 壁厚门禁变成"永远无法评估" → 实测 0.5mm 极薄壁照样 PASS。
159
+ 现在接受总表面积作为等价输入,使真实零件也能被壁厚门禁覆盖。
160
+ """
161
+ total_area = 0.0
162
+ if face_areas:
163
+ try:
164
+ total_area = sum(float(f.get("area_mm2", 0) or 0) for f in face_areas)
165
+ except Exception:
166
+ total_area = 0.0
167
+ if total_area <= 0 and surface_area_mm2:
168
+ try:
169
+ total_area = float(surface_area_mm2)
170
+ except Exception:
171
+ total_area = 0.0
172
+ if total_area <= 0:
173
+ return {"ok": False, "error": "no face data available(无逐面数据且无总表面积)"}
174
+ if not volume_mm3 or float(volume_mm3) <= 0:
175
+ return {"ok": False, "error": "invalid volume_mm3(体积缺失或为 0)"}
176
+ # 薄壁特征厚度:t ≈ 2V/A(V/A 为半厚度量级)
177
+ t_est_mm = 2.0 * float(volume_mm3) / total_area
178
+ return {
179
+ "ok": True,
180
+ "estimated_min_thickness_mm": round(t_est_mm, 4),
181
+ "volume_mm3": round(float(volume_mm3), 3),
182
+ "total_area_mm2": round(total_area, 3),
183
+ "method": "2V/A_ratio_approximation",
184
+ "note": "近似估计(2V/A);精确壁厚需 STEP 网格分析。用于识别明显过薄件。",
185
+ }
186
+
187
+
188
+ def _polygon_area_shapely(coords: list) -> float:
189
+ """安全地计算多边形面积,shapely 不可用时回退到鞋带公式。"""
190
+ if _HAS_SHAPLEY and len(coords) >= 3:
191
+ try:
192
+ return Polygon(coords).area
193
+ except Exception:
194
+ pass
195
+ # 鞋带公式(平面多边形,z=0)
196
+ if len(coords) < 3:
197
+ return 0.0
198
+ area = 0.0
199
+ n = len(coords)
200
+ for i in range(n):
201
+ j = (i + 1) % n
202
+ area += coords[i][0] * coords[j][1]
203
+ area -= coords[j][0] * coords[i][1]
204
+ return abs(area) / 2.0
205
+
206
+
207
+ def check_interface_compliance(
208
+ interfaces: list[dict],
209
+ design_params: dict,
210
+ ) -> dict[str, Any]:
211
+ """检查接口区域(安装孔、配合面)是否符合设计要求。
212
+
213
+ Args:
214
+ interfaces: [{"id": ..., "type": "hole"|"face", "nominal_dim": ..., "tolerance": ...}]
215
+ design_params: {"locked_params": [...], "min_wall_thickness": ...}
216
+
217
+ Returns:
218
+ {ok, violations, locked_param_changes}
219
+ """
220
+ violations = []
221
+ locked_changes = []
222
+ for iface in interfaces:
223
+ itype = iface.get("type", "")
224
+ if itype == "hole":
225
+ nominal = iface.get("nominal_dim", 0)
226
+ tol = iface.get("tolerance", 0)
227
+ if nominal <= 0:
228
+ violations.append(f"invalid hole diameter: {nominal}")
229
+ elif itype == "face":
230
+ pass # face interfaces are generally OK
231
+ return {
232
+ "ok": len(violations) == 0,
233
+ "violations": violations,
234
+ "locked_param_changes": locked_changes,
235
+ }
236
+
237
+
238
+ def geometry_gate_report(
239
+ bbox: list[float],
240
+ topology: dict,
241
+ face_areas: list[dict],
242
+ volume_mm3: float,
243
+ design_bounds: dict,
244
+ interfaces: list[dict],
245
+ design_params: dict,
246
+ surface_area_mm2: Optional[float] = None,
247
+ min_wall_req_mm: Optional[float] = None,
248
+ ) -> dict[str, Any]:
249
+ """综合几何门禁报告。
250
+
251
+ Returns:
252
+ {status: PASS/WARNING/FAIL, checks: {...}, gates: [...]}
253
+ """
254
+ space_check = check_design_space_violation(bbox, design_bounds)
255
+ conn_check = check_connectivity(topology)
256
+ wall_check = estimate_min_wall_thickness(face_areas, volume_mm3,
257
+ surface_area_mm2=surface_area_mm2)
258
+
259
+ gates = [
260
+ # ── 【Bug18 修复】bbox 缺失时判 NOT_EVALUATED,不得默认 PASS ──────
261
+ # 原实现是二元 "PASS if ok else FAIL":拿不到 bbox 时调用方用
262
+ # design_domain 回填 → 恒 PASS;不填 → 恒 FAIL(误拒合格件)。
263
+ # 现在三态:有真实 bbox 才判 PASS/FAIL,否则 NOT_EVALUATED。
264
+ {"id": "DESIGN_SPACE", "status": (
265
+ "NOT_EVALUATED" if not space_check.get("evaluated", True)
266
+ else ("PASS" if space_check["ok"] else "FAIL"))},
267
+ {"id": "CONNECTIVITY", "status": conn_check["status"]},
268
+ # ── 【Bug7 修复】WALL_THICKNESS 不得默认 PASS ──────────────────
269
+ # 原实现写死 PASS,且 min_wall_req_mm 默认 None → 壁厚检查
270
+ # 永远不可能 FAIL。实测:0.5mm 极薄壁件(FDM 最小 2mm)照样通过。
271
+ # 现在:拿不到真实壁厚估计 → NOT_EVALUATED(不可交付);
272
+ # 拿到估计值 → 与要求比对,低于要求即 FAIL。
273
+ {"id": "WALL_THICKNESS", "status": (
274
+ "NOT_EVALUATED" if not wall_check.get("ok") else "PASS")},
275
+ ]
276
+ if not space_check.get("evaluated", True):
277
+ gates[0]["reason"] = space_check.get("reason")
278
+
279
+ _wall_req = min_wall_req_mm
280
+ if _wall_req is None:
281
+ try:
282
+ _dp = design_params or {}
283
+ _v = _dp.get("min_wall_thickness")
284
+ if isinstance(_v, dict):
285
+ _v = _v.get("value")
286
+ if _v is not None:
287
+ _wall_req = float(_v)
288
+ except Exception:
289
+ _wall_req = None
290
+ if wall_check.get("ok") and _wall_req:
291
+ est = wall_check.get("estimated_min_thickness_mm")
292
+ if est is not None:
293
+ _est = float(est)
294
+ _req = float(_wall_req)
295
+ # ── 【Bug13 修复】临界区间不得直接 FAIL ──────────────────────
296
+ # 2V/A 是【近似】算法(note 已注明),对平板会系统性偏低:
297
+ # 实测 100x100x2.0mm 板算出 1.9231(偏低 3.8%)→ 被误判 FAIL,
298
+ # 而该件壁厚恰好达标。这是"合格设计被误拒"。
299
+ # 处理:
300
+ # · 低于阈值且超出宽容度(15%) → FAIL(明显过薄,必须拦)
301
+ # · 落在阈值 ±15% 内 → WARNING(近似误差区间,提示人工复核)
302
+ # · 其余 → PASS
303
+ _tol = 0.15
304
+ if _est < _req * (1.0 - _tol):
305
+ gates[-1] = {"id": "WALL_THICKNESS", "status": "FAIL",
306
+ "estimated_mm": _est,
307
+ "required_mm": _req,
308
+ "reason": ("估计壁厚 %.3fmm 明显低于要求 %.3fmm"
309
+ "(超出 %.0f%% 近似宽容度)"
310
+ % (_est, _req, _tol * 100))}
311
+ elif _est < _req:
312
+ gates[-1] = {"id": "WALL_THICKNESS", "status": "WARNING",
313
+ "estimated_mm": _est,
314
+ "required_mm": _req,
315
+ "method": "2V/A_ratio_approximation",
316
+ "reason": ("估计壁厚 %.3fmm 略低于要求 %.3fmm,但落在"
317
+ "近似算法 ±%.0f%% 误差带内 —— 建议人工复核,"
318
+ "不据此判定不合格(Bug13)"
319
+ % (_est, _req, _tol * 100))}
320
+ else:
321
+ gates[-1] = {"id": "WALL_THICKNESS", "status": "PASS",
322
+ "estimated_mm": _est, "required_mm": _req}
323
+ elif not wall_check.get("ok"):
324
+ gates[-1]["reason"] = "无法估计壁厚,该门禁未评估,不得据此判定合格"
325
+
326
+ overall = "PASS" if all(g["status"] == "PASS" for g in gates) else "WARNING"
327
+ if any(g["status"] == "FAIL" for g in gates):
328
+ overall = "FAIL"
329
+
330
+ return {
331
+ "status": overall,
332
+ "design_space": space_check,
333
+ "connectivity": conn_check,
334
+ "wall_thickness_estimate": wall_check,
335
+ "interface_compliance": check_interface_compliance(interfaces, design_params),
336
+ "gates": gates,
337
+ }