feat: strengthen CAD experience distillation and routing
This commit is contained in:
@@ -111,11 +111,29 @@ def resolve_number(spec: dict[str, Any], value: Any, field: str) -> float:
|
||||
raise ModelSpecError(f"{field} must be numeric")
|
||||
if isinstance(value, (int, float)):
|
||||
result = float(value)
|
||||
elif isinstance(value, dict) and set(value) == {"parameter"}:
|
||||
result = _parameter_value(spec, str(value["parameter"]))
|
||||
elif isinstance(value, dict) and "parameter" in value:
|
||||
unexpected = set(value) - {"parameter", "scale", "offset"}
|
||||
if unexpected:
|
||||
raise ModelSpecError(
|
||||
f"{field} parameter expression has unsupported keys: "
|
||||
+ ", ".join(sorted(unexpected))
|
||||
)
|
||||
scale = value.get("scale", 1.0)
|
||||
offset = value.get("offset", 0.0)
|
||||
if (
|
||||
isinstance(scale, bool)
|
||||
or not isinstance(scale, (int, float))
|
||||
or isinstance(offset, bool)
|
||||
or not isinstance(offset, (int, float))
|
||||
):
|
||||
raise ModelSpecError(f"{field} scale and offset must be numeric")
|
||||
result = (
|
||||
_parameter_value(spec, str(value["parameter"])) * float(scale)
|
||||
+ float(offset)
|
||||
)
|
||||
else:
|
||||
raise ModelSpecError(
|
||||
f"{field} must be a number or {{\"parameter\": \"name\"}}"
|
||||
f"{field} must be a number or a parameter expression"
|
||||
)
|
||||
if not math.isfinite(result):
|
||||
raise ModelSpecError(f"{field} must be finite")
|
||||
|
||||
@@ -76,6 +76,11 @@ EXPERIENCE_FAMILY_RULES: tuple[tuple[str, tuple[str, ...]], ...] = (
|
||||
("shaft", ("shaft", "轴类", "传动轴")),
|
||||
("flange", ("flange", "法兰")),
|
||||
("bushing", ("bushing", "sleeve", "轴套", "衬套")),
|
||||
("plate", ("plate", "mounting plate", "安装板", "平板")),
|
||||
("bracket", ("bracket", "mounting bracket", "支架", "托架")),
|
||||
("block", ("block", "rectangular body", "方块", "块体")),
|
||||
("manifold", ("manifold", "valve body", "housing", "歧管", "阀体", "壳体")),
|
||||
("rotational_part", ("roller", "pulley", "cylindrical body", "滚轮", "带轮")),
|
||||
)
|
||||
|
||||
EXPERIENCE_FEATURE_RULES: tuple[tuple[str, tuple[str, ...]], ...] = (
|
||||
@@ -90,8 +95,90 @@ EXPERIENCE_FEATURE_RULES: tuple[tuple[str, tuple[str, ...]], ...] = (
|
||||
("conical_transition", ("cone", "taper", "锥", "斜面过渡")),
|
||||
("gear_teeth", ("gear", "teeth", "齿轮", "齿")),
|
||||
("coaxial_cylindrical_stack", ("stepped shaft", "阶梯轴", "同轴", "轴肩")),
|
||||
(
|
||||
"repeated_axial_hole_pattern",
|
||||
(
|
||||
"mounting holes",
|
||||
"corner holes",
|
||||
"four holes",
|
||||
"multiple holes",
|
||||
"bolt holes",
|
||||
"安装孔",
|
||||
"四个孔",
|
||||
"多个孔",
|
||||
),
|
||||
),
|
||||
("planar_dominant_body", ("plate", "bracket", "flat body", "板", "支架")),
|
||||
(
|
||||
"multi_level_planar_profile",
|
||||
("pocket", "recess", "stepped plate", "台阶面", "凹槽", "沉台"),
|
||||
),
|
||||
(
|
||||
"rotational_body",
|
||||
("cylindrical body", "roller", "pulley", "revolved", "圆柱体", "滚轮", "回转体"),
|
||||
),
|
||||
(
|
||||
"stepped_rotational_profile",
|
||||
("stepped shaft", "multiple diameters", "shoulder", "阶梯轴", "多段直径", "轴肩"),
|
||||
),
|
||||
("shaft_like_body", ("shaft", "axle", "rod", "轴", "长杆")),
|
||||
(
|
||||
"flange_like_rotational_body",
|
||||
("flange", "flanged", "法兰", "带法兰"),
|
||||
),
|
||||
(
|
||||
"cylindrical_feature_network",
|
||||
("bore", "passage", "drilled holes", "孔系", "流道", "钻孔"),
|
||||
),
|
||||
(
|
||||
"multi_axis_passage",
|
||||
("cross hole", "intersecting passage", "multi-axis", "交叉孔", "多轴孔"),
|
||||
),
|
||||
)
|
||||
|
||||
# The router vocabulary is request-facing (plate, bracket, flange), while the
|
||||
# distilled library vocabulary is geometry-facing (patterned_plate,
|
||||
# plate_or_bracket, flanged_rotational_part). Keep the query semantic by
|
||||
# expanding a request family into compatible generalized scopes; this never
|
||||
# uses held-out case geometry or instance parameters.
|
||||
EXPERIENCE_FAMILY_SCOPE_ALIASES: dict[str, set[str]] = {
|
||||
"flanged_hub_adapter": {
|
||||
"flanged_hub_adapter",
|
||||
"flanged_rotational_part",
|
||||
"hollow_or_stepped_shaft",
|
||||
"rotational_part",
|
||||
},
|
||||
"connecting_rod": {
|
||||
"connecting_rod",
|
||||
"patterned_prismatic_part",
|
||||
"plate_or_bracket",
|
||||
},
|
||||
"gear": {"gear", "rotational_part"},
|
||||
"shaft": {"shaft", "hollow_or_stepped_shaft", "rotational_part"},
|
||||
"flange": {"flange", "flanged_rotational_part", "rotational_part"},
|
||||
"bushing": {"bushing", "hollow_or_stepped_shaft", "rotational_part"},
|
||||
"plate": {"plate", "patterned_plate", "plate_or_bracket"},
|
||||
"bracket": {
|
||||
"bracket",
|
||||
"plate_or_bracket",
|
||||
"patterned_plate",
|
||||
"patterned_prismatic_part",
|
||||
},
|
||||
"block": {"block", "prismatic_block", "patterned_prismatic_part"},
|
||||
"manifold": {
|
||||
"manifold",
|
||||
"multi_axis_manifold",
|
||||
"prismatic_block",
|
||||
"patterned_prismatic_part",
|
||||
},
|
||||
"rotational_part": {
|
||||
"rotational_part",
|
||||
"shaft",
|
||||
"hollow_or_stepped_shaft",
|
||||
"flanged_rotational_part",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def contains_any(text: str, terms: tuple[str, ...]) -> bool:
|
||||
return any(term in text for term in terms)
|
||||
@@ -358,13 +445,20 @@ def load_experience_library(
|
||||
if item.strip()
|
||||
}
|
||||
normalized_family = family.strip().lower().replace(" ", "_") if family else None
|
||||
compatible_scopes = (
|
||||
EXPERIENCE_FAMILY_SCOPE_ALIASES.get(
|
||||
normalized_family, {normalized_family}
|
||||
)
|
||||
if normalized_family
|
||||
else set()
|
||||
)
|
||||
selected: list[object] = []
|
||||
for item in payload.get("experiences", []):
|
||||
if not isinstance(item, dict):
|
||||
continue
|
||||
scope = set(item.get("scope", []))
|
||||
if "global" not in scope:
|
||||
if not normalized_family or normalized_family not in scope:
|
||||
if not compatible_scopes or scope.isdisjoint(compatible_scopes):
|
||||
continue
|
||||
required = set(item.get("when", {}).get("features", []))
|
||||
if required and not required.issubset(requested_features):
|
||||
@@ -374,6 +468,7 @@ def load_experience_library(
|
||||
"schema_version": "2.0",
|
||||
"context_kind": "generalized_cad_experience_query",
|
||||
"family": normalized_family,
|
||||
"compatible_scopes": sorted(compatible_scopes),
|
||||
"requested_features": sorted(requested_features),
|
||||
"experiences": selected,
|
||||
"policy": {
|
||||
@@ -526,6 +621,7 @@ def route(args: argparse.Namespace) -> dict[str, object]:
|
||||
"check",
|
||||
"repair",
|
||||
"confidence",
|
||||
"reconstruction_grammar",
|
||||
)
|
||||
if key in item
|
||||
}
|
||||
@@ -536,6 +632,17 @@ def route(args: argparse.Namespace) -> dict[str, object]:
|
||||
)
|
||||
if isinstance(item, dict)
|
||||
],
|
||||
"reconstruction_grammars": [
|
||||
item["reconstruction_grammar"]
|
||||
for item in (
|
||||
experience_context.get("experiences", [])
|
||||
if experience_context
|
||||
else []
|
||||
)
|
||||
if isinstance(item, dict)
|
||||
and item.get("kind") == "reconstruction_grammar"
|
||||
and isinstance(item.get("reconstruction_grammar"), dict)
|
||||
],
|
||||
"experience_status": (
|
||||
"pending_source_classification"
|
||||
if needs_source_classification
|
||||
@@ -575,9 +682,10 @@ def route(args: argparse.Namespace) -> dict[str, object]:
|
||||
if edit_context
|
||||
else [
|
||||
"Resolve explicit user dimensions and preserve them as authoritative parameters.",
|
||||
"Instantiate matched reconstruction grammars as symbolic model-spec.json parameters, datums, and canonical feature stages; never copy teacher dimensions.",
|
||||
"Translate requested feature roles and matched generalized methods into backend operations.",
|
||||
"Generate the editable source of truth and STEP-first artifact when requested.",
|
||||
"Validate topology, dimensions, feature relationships, and export integrity.",
|
||||
"Validate topology, dimensions, feature relationships, surface mix, and export integrity against the grammar validation roles.",
|
||||
]
|
||||
),
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user