Files
cadSet/cad-experience-plugin/tests/test_cad_experience.py
T
2026-07-23 14:08:17 +08:00

257 lines
9.0 KiB
Python

from __future__ import annotations
import importlib.util
import json
import unittest
from pathlib import Path
SCRIPT = (
Path(__file__).parents[1]
/ "skills"
/ "cad-experience-builder"
/ "scripts"
/ "cad_experience.py"
)
SPEC = importlib.util.spec_from_file_location("cad_experience", SCRIPT)
MODULE = importlib.util.module_from_spec(SPEC)
assert SPEC.loader
SPEC.loader.exec_module(MODULE)
STEP_SCRIPT = (
Path(__file__).parents[1]
/ "skills"
/ "cad-experience-builder"
/ "scripts"
/ "step_to_case.py"
)
STEP_SPEC = importlib.util.spec_from_file_location("step_to_case", STEP_SCRIPT)
STEP_MODULE = importlib.util.module_from_spec(STEP_SPEC)
assert STEP_SPEC.loader
STEP_SPEC.loader.exec_module(STEP_MODULE)
def make_case(index: int) -> dict:
return {
"provenance": {"source_sha256": f"case_{index}"},
"design_ir": {
"part_family": "flanged_hub_adapter",
"features": [
{"type": "base_flange", "center": [91.0, 52.0]},
{"type": "hollow_sleeve", "surface_ids": ["face_1"]},
{"type": "counterbore"},
],
"constraints": [{"id": "coaxial_stack", "type": "coaxial"}],
"parameters": {
"base_outer_diameter": 91.0,
"counterbore_spacing": 52.0,
},
"normalized_observations": [
{
"name": "sleeve_od_to_base_od",
"value": 0.3 + index * 0.001,
"numerator_role": "sleeve_outer_diameter",
"denominator_role": "base_outer_diameter",
}
],
},
"experience": {
"rules": [
{
"id": "bound_counterbore_to_host",
"scope": ["flanged_hub_adapter"],
"statement": "Terminate a counterbore at its host boundary.",
"repair": "Bind the cutter extent to the host feature.",
}
],
"validation_targets": [
{
"id": "host_boundary",
"check": "A counterbore does not cross an unrelated adjacent feature.",
}
],
},
}
class CadExperienceTest(unittest.TestCase):
def reviewed_proposal(self) -> dict:
return {
"id": "constraint.flanged_hub_adapter.shared_axis",
"kind": "constraint",
"scope": ["flanged_hub_adapter"],
"evidence_query": {
"required_features": ["base_flange", "hollow_sleeve"],
"required_relations": ["coaxial_stack"],
},
"guidance": "Preserve a shared axis across participating feature roles.",
"semantic_rationale": "The relationship transfers across dimensional variants.",
}
def test_semantic_summary_derives_roles_without_copying_dimensions(self):
surfaces = [
{
"type": "cylinder",
"axis": [0.0, 1.0, 0.0],
"location": [0.0, 0.0, 0.0],
"radius": radius,
"area": 100.0,
}
for radius in (5.0, 10.0, 20.0)
]
surfaces.extend(
{
"type": "cylinder",
"axis": [0.0, 1.0, 0.0],
"location": [offset, 0.0, 0.0],
"radius": 2.0,
"area": 20.0,
}
for offset in (-15.0, 15.0, 16.0)
)
surfaces.append(
{
"type": "cone",
"axis": [0.0, 1.0, 0.0],
"location": [0.0, 0.0, 0.0],
"reference_radius": 10.0,
"semi_angle_radians": 0.5,
"area": 50.0,
}
)
family, features, constraints, summary = STEP_MODULE._semantic_summary(
surfaces, [0.0, 0.0, 0.0], [40.0, 20.0, 40.0]
)
self.assertEqual("flanged_hub_adapter_candidate", family)
self.assertIn(
"coaxial_cylindrical_stack", {item["type"] for item in features}
)
self.assertIn("coaxial_stack", {item["id"] for item in constraints})
self.assertEqual("y", summary["dominant_axis"])
def test_single_case_never_promotes(self):
case = MODULE.normalize_case(make_case(1))
library = MODULE.induce(
[case],
2,
0.8,
{"duplicate_case_count": 0, "rejected_case_count": 0},
)
self.assertEqual(library["status"], "collecting_evidence")
self.assertEqual(library["experiences"], [])
self.assertTrue(library["candidate_experiences"])
self.assertTrue(
all(
item["promotion_state"] == "candidate"
for item in library["candidate_experiences"]
)
)
self.assertTrue(
all(
item["consumer_policy"]
== "visible_for_review_but_not_available_to_cad_router"
for item in library["candidate_experiences"]
)
)
def test_batch_promotes_methods_without_instance_values(self):
cases = [MODULE.normalize_case(make_case(index)) for index in range(20)]
library = MODULE.induce(
cases,
20,
0.8,
{"duplicate_case_count": 0, "rejected_case_count": 0},
)
self.assertEqual(MODULE.audit_library(library), [])
rendered = json.dumps(library)
self.assertNotIn('"parameters"', rendered)
self.assertNotIn("91.0", rendered)
self.assertNotIn("52.0", rendered)
self.assertNotIn('"center"', rendered)
ids = {item["id"] for item in library["experiences"]}
self.assertIn("rule.bound_counterbore_to_host", ids)
self.assertIn("constraint.flanged_hub_adapter.coaxial_stack", ids)
self.assertIn("distribution.sleeve_od_to_base_od", ids)
def test_family_support_is_not_diluted_by_unrelated_families(self):
target = [MODULE.normalize_case(make_case(index)) for index in range(20)]
unrelated = []
for index in range(100):
payload = make_case(index + 1000)
payload["design_ir"]["part_family"] = "gear"
payload["design_ir"]["features"] = [
{"type": "gear_blank"},
{"type": "gear_teeth"},
]
unrelated.append(MODULE.normalize_case(payload))
library = MODULE.induce(
target + unrelated,
20,
0.8,
{"duplicate_case_count": 0, "rejected_case_count": 0},
)
ids = {item["id"] for item in library["experiences"]}
self.assertIn(
"motif.flanged_hub_adapter.base_flange+hollow_sleeve", ids
)
def test_query_returns_backend_neutral_context(self):
cases = [MODULE.normalize_case(make_case(index)) for index in range(20)]
library = MODULE.induce(
cases,
20,
0.8,
{"duplicate_case_count": 0, "rejected_case_count": 0},
)
result = MODULE.query_library(
library,
"flanged_hub_adapter",
{"base_flange", "hollow_sleeve", "counterbore"},
)
self.assertTrue(result["experiences"])
self.assertFalse(result["policy"]["contains_instance_parameters"])
def test_llm_proposal_is_candidate_until_evidence_threshold(self):
proposal = self.reviewed_proposal()
one_case = MODULE.normalize_case(make_case(1))
candidate_library = MODULE.build_reviewed_library(
[one_case],
[proposal],
20,
0.8,
{"duplicate_case_count": 0, "rejected_case_count": 0},
)
self.assertEqual([], candidate_library["experiences"])
self.assertEqual(1, len(candidate_library["candidate_experiences"]))
self.assertTrue(candidate_library["policy"]["router_consumable"])
cases = [MODULE.normalize_case(make_case(index)) for index in range(20)]
promoted_library = MODULE.build_reviewed_library(
cases,
[proposal],
20,
0.8,
{"duplicate_case_count": 0, "rejected_case_count": 0},
)
self.assertEqual(1, len(promoted_library["experiences"]))
self.assertEqual([], promoted_library["candidate_experiences"])
def test_numeric_instance_answer_is_rejected_from_llm_draft(self):
proposal = self.reviewed_proposal()
proposal["guidance"] = "Always use a diameter of 91 millimeters."
with self.assertRaisesRegex(ValueError, "instance numbers"):
MODULE.normalize_proposals(
{
"draft_kind": "llm_generalized_experience_proposals",
"proposals": [proposal],
}
)
def test_direct_distill_without_model_draft_is_blocked(self):
with self.assertRaisesRegex(SystemExit, "statistical distillation is disabled"):
MODULE.main(["distill"])
if __name__ == "__main__":
unittest.main()