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", } ], "reconstruction_evidence": { "parameter_roles": [ "overall_long_span", "overall_middle_span", "overall_short_span", ], "datum_roles": ["part_center", "primary_axis"], "feature_roles": ["base_flange", "hollow_sleeve", "counterbore"], "relation_roles": ["coaxial_stack"], "canonical_stages": [ { "id": "establish_reference_frame", "operation": "define_datums", "feature_roles": [], "reference_roles": ["part_center", "primary_axis"], }, { "id": "construct_primary_envelope", "operation": "revolve_profile", "feature_roles": ["base_flange", "hollow_sleeve"], "reference_roles": ["primary_axis"], }, ], "private_cardinality_evidence": { "surface_type_classes": { "plane": "repeated", "cylinder": "dense", }, "feature_role_count_class": "repeated", }, "validation_roles": ["closed_solid", "coaxial_stack"], }, }, "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_rotational_part", 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"]) self.assertTrue(summary["normalized_observations"]) reconstruction = STEP_MODULE._reconstruction_evidence( surfaces, features, constraints, summary ) self.assertIn("overall_long_span", reconstruction["parameter_roles"]) self.assertTrue(reconstruction["canonical_stages"]) self.assertEqual( "canonical_reconstruction_plan_not_recovered_history", reconstruction["interpretation"], ) def test_conditional_feature_context_does_not_dilute_valid_method(self): proposal = self.reviewed_proposal() proposal["evidence_query"]["context_features"] = [ "base_flange", "hollow_sleeve", ] target = [MODULE.normalize_case(make_case(index)) for index in range(20)] unrelated = [] for index in range(80): payload = make_case(index + 100) payload["design_ir"]["features"] = [{"type": "base_flange"}] payload["design_ir"]["constraints"] = [] unrelated.append(MODULE.normalize_case(payload)) library = MODULE.build_reviewed_library( target + unrelated, [MODULE.normalize_proposals({ "draft_kind": "llm_generalized_experience_proposals", "proposals": [proposal], })[0]], 20, 0.8, {"duplicate_case_count": 0, "rejected_case_count": 0}, ) self.assertEqual(1, len(library["experiences"])) 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_reconstruction_grammar_promotes_without_instance_values(self): proposal = { "id": "reconstruction.flanged_hub_adapter.primary", "kind": "reconstruction_grammar", "scope": ["flanged_hub_adapter"], "evidence_query": { "required_features": ["base_flange", "hollow_sleeve"], "required_relations": ["coaxial_stack"], "context_features": ["base_flange", "hollow_sleeve"], }, "guidance": "Establish shared datums before composing the primary envelope.", "semantic_rationale": "The symbolic stages transfer across dimensional variants.", "reconstruction_grammar": { "parameter_roles": [ "overall_long_span", "overall_middle_span", "overall_short_span", ], "datum_roles": ["part_center", "primary_axis"], "feature_roles": ["base_flange", "hollow_sleeve"], "relation_roles": ["coaxial_stack"], "canonical_stages": [ { "id": "establish_reference_frame", "operation": "define_datums", "feature_roles": [], "reference_roles": ["part_center", "primary_axis"], }, { "id": "construct_primary_envelope", "operation": "revolve_profile", "feature_roles": ["base_flange", "hollow_sleeve"], "reference_roles": ["primary_axis"], }, ], "cardinality_classes": ["plane_repeated", "cylinder_dense"], "validation_roles": ["closed_solid", "coaxial_stack"], }, } normalized = MODULE.normalize_proposals( { "draft_kind": "llm_generalized_experience_proposals", "proposals": [proposal], } ) cases = [MODULE.normalize_case(make_case(index)) for index in range(20)] library = MODULE.build_reviewed_library( cases, normalized, 20, 0.8, {"duplicate_case_count": 0, "rejected_case_count": 0}, ) self.assertEqual([], MODULE.audit_library(library)) self.assertEqual("reconstruction_grammar", library["experiences"][0]["kind"]) rendered = json.dumps(library) self.assertNotIn("91.0", rendered) self.assertNotIn('"parameters"', rendered) 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()