from __future__ import annotations import copy import sys import unittest from pathlib import Path ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT / "backend")) def workplane(z: float = 0.0) -> dict[str, list[float]]: return { "origin_mm": [0.0, 0.0, z], "x_dir": [1.0, 0.0, 0.0], "y_dir": [0.0, 1.0, 0.0], "normal": [0.0, 0.0, 1.0], } def mounting_plate_intent() -> dict: return { "schema": "cad.cdsl.design-intent.v1", "schema_version": "1.0", "mode": "create", "request": "Create a mounting plate with four holes and a center slot", "base_revision_id": "", "structures": [ { "id": "base_plate", "cdsl_feature_id": "base_add", "role": "base", "purpose": "Rectangular mounting plate", "depends_on": [], "cdsl_strategy": { "atomic_id": "extrude_add_blind", "profile_type": "rectangle", "parameter_roles": ["width_mm", "height_mm", "thickness_mm"], "selector_roles": [], }, }, { "id": "mounting_holes", "cdsl_feature_id": "hole_cut", "role": "subtractive", "purpose": "Four mounting holes", "depends_on": ["base_plate"], "cdsl_strategy": { "atomic_id": "extrude_cut_blind", "profile_type": "circle_grid", "parameter_roles": ["diameter_mm", "count_x", "count_y"], "selector_roles": [], }, }, { "id": "adjustment_slot", "cdsl_feature_id": "slot_cut", "role": "subtractive", "purpose": "Center adjustment slot", "depends_on": ["mounting_holes"], "cdsl_strategy": { "atomic_id": "extrude_cut_blind", "profile_type": "obround", "parameter_roles": ["length_mm", "width_mm", "depth_mm"], "selector_roles": [], }, }, ], "feature_order": ["base_plate", "mounting_holes", "adjustment_slot"], "assumptions": [], "open_questions": [], "capability_gaps": [], "verification_expectations": [ {"type": "feature_count", "feature_id": "mounting_holes", "expected": 4}, {"type": "symmetry", "axis": "x"}, {"type": "bbox", "expected_mm": {"x": 100, "y": 60, "z": 10}}, ], "status": "ready", } def mounting_plate_cdsl() -> dict: return { "schema": "cad.cdsl.llm.v1", "schema_version": "1.0", "kind": "part", "part_id": "mounting-plate", "geometry": { "sketches": [ {"id": "base_sketch", "workplane": workplane(), "profile": {"type": "rectangle", "center": [0, 0], "width_mm": 100, "height_mm": 60}}, {"id": "holes_sketch", "workplane": workplane(10), "profile": {"type": "circle_grid", "radius_mm": 3, "count_x": 2, "count_y": 2, "spacing_x_mm": 80, "spacing_y_mm": 40, "center_mm": [0, 0]}}, {"id": "slot_sketch", "workplane": workplane(10), "profile": {"type": "obround", "center": [0, 0], "length_mm": 32, "width_mm": 10}}, ], }, "features": [ {"id": "base_add", "atomic_id": "extrude_add_blind", "depends_on": [], "sketch_id": "base_sketch", "params": {"distance_mm": 10}}, {"id": "hole_cut", "atomic_id": "extrude_cut_blind", "depends_on": ["base_add"], "sketch_id": "holes_sketch", "params": {"distance_mm": 10, "reverse": True}}, {"id": "slot_cut", "atomic_id": "extrude_cut_blind", "depends_on": ["hole_cut"], "sketch_id": "slot_sketch", "params": {"distance_mm": 10, "reverse": True}}, ], } class DesignIntentValidationTests(unittest.TestCase): @classmethod def setUpClass(cls) -> None: sys.path.insert(0, str(ROOT / "backend" / "engine")) import cdsl_engine cls.engine = cdsl_engine def assert_plan_error(self, intent: dict, code: str = "INVALID_DESIGN_INTENT") -> None: with self.assertRaises(self.engine.DesignIntentError) as context: self.engine.validate_design_intent(intent, self.engine) self.assertEqual(context.exception.code, code) def test_valid_mounting_plate_plan_and_cdsl_match(self) -> None: intent = mounting_plate_intent() validated = self.engine.validate_design_intent(intent, self.engine) self.assertEqual(validated["feature_order"], ["base_plate", "mounting_holes", "adjustment_slot"]) self.engine.validate_intent_cdsl(validated, mounting_plate_cdsl(), self.engine) def test_revise_requires_current_base_revision(self) -> None: intent = mounting_plate_intent() intent["mode"] = "revise" intent["base_revision_id"] = "" self.assert_plan_error(intent) intent["base_revision_id"] = "rev_001" self.engine.validate_design_intent(intent, self.engine, current_revision_id="rev_001") with self.assertRaises(self.engine.DesignIntentError) as context: self.engine.validate_design_intent(intent, self.engine, current_revision_id="rev_002") self.assertEqual(context.exception.code, "INVALID_DESIGN_INTENT") def test_rejects_duplicate_ids_bad_order_and_unregistered_capabilities(self) -> None: duplicate = mounting_plate_intent() duplicate["structures"][1]["id"] = "base_plate" self.assert_plan_error(duplicate) cyclic = mounting_plate_intent() cyclic["structures"][0]["depends_on"] = ["mounting_holes"] self.assert_plan_error(cyclic) unsupported = mounting_plate_intent() unsupported["structures"][0]["cdsl_strategy"]["atomic_id"] = "thread_add" self.assert_plan_error(unsupported) incomplete = mounting_plate_intent() incomplete["feature_order"].pop() self.assert_plan_error(incomplete) no_profile = mounting_plate_intent() no_profile["structures"][0]["cdsl_strategy"].pop("profile_type") self.assert_plan_error(no_profile) unknown_expectation = mounting_plate_intent() unknown_expectation["verification_expectations"][0]["feature_id"] = "missing" self.assert_plan_error(unknown_expectation) def test_blockers_and_nonblocking_approximations_are_explicit(self) -> None: clarification = mounting_plate_intent() clarification["open_questions"] = [{"id": "thickness", "question": "What thickness is required?", "blocking": True}] clarification["status"] = "needs_clarification" self.engine.validate_design_intent(clarification, self.engine) with self.assertRaises(self.engine.DesignIntentError) as context: self.engine.validate_intent_cdsl(clarification, mounting_plate_cdsl(), self.engine) self.assertEqual(context.exception.code, "DESIGN_INTENT_BLOCKED") approximation = mounting_plate_intent() approximation["capability_gaps"] = [{ "code": "unsupported_thread_geometry", "structure_id": "mounting_holes", "message": "Only a cylindrical bore is available.", "blocking": False, }] self.assert_plan_error(approximation) approximation["assumptions"] = ["Thread geometry is approximated by cylindrical bores."] self.engine.validate_design_intent(approximation, self.engine) def test_rejects_cdsl_feature_atomic_profile_and_selector_mismatches(self) -> None: missing_feature = mounting_plate_cdsl() missing_feature["features"].pop() with self.assertRaises(self.engine.DesignIntentError) as context: self.engine.validate_intent_cdsl(mounting_plate_intent(), missing_feature, self.engine) self.assertEqual(context.exception.code, "INTENT_CDSL_MISMATCH") atomic = mounting_plate_cdsl() atomic["features"][1]["atomic_id"] = "extrude_add_blind" with self.assertRaises(self.engine.DesignIntentError): self.engine.validate_intent_cdsl(mounting_plate_intent(), atomic, self.engine) profile = mounting_plate_cdsl() profile["geometry"]["sketches"][1]["profile"]["type"] = "circles" with self.assertRaises(self.engine.DesignIntentError): self.engine.validate_intent_cdsl(mounting_plate_intent(), profile, self.engine) selector_plan = copy.deepcopy(mounting_plate_intent()) selector_plan["structures"][2]["cdsl_strategy"]["selector_roles"] = ["host_face"] with self.assertRaises(self.engine.DesignIntentError): self.engine.validate_intent_cdsl(selector_plan, mounting_plate_cdsl(), self.engine)