feat: unify CAD workflows on DesignIR 3.0

This commit is contained in:
Jerry
2026-07-28 14:41:26 +08:00
parent 6e33bc0e11
commit b5738e9109
36 changed files with 9690 additions and 1305 deletions
@@ -2,6 +2,7 @@ from __future__ import annotations
import importlib.util
import json
import tempfile
import unittest
from pathlib import Path
@@ -106,6 +107,22 @@ def make_case(index: int) -> dict:
}
def positive_validation(*experience_ids: str) -> dict:
return {
"validation_kind": "heldout_experience_ab",
"method_results": [
{
"experience_id": experience_id,
"independent_case_count": 3,
"edit_contract_pass_rate": 1.0,
"baseline_score_delta": 0.5,
"passed": True,
}
for experience_id in experience_ids
],
}
class CadExperienceTest(unittest.TestCase):
def test_default_data_paths_use_shallow_local_layout(self):
pipeline_root = Path(__file__).parents[1].resolve()
@@ -179,6 +196,47 @@ class CadExperienceTest(unittest.TestCase):
"canonical_reconstruction_plan_not_recovered_history",
reconstruction["interpretation"],
)
parameters = STEP_MODULE._semantic_parameter_candidates(
surfaces,
[0.0, 0.0, 0.0],
[40.0, 20.0, 40.0],
features,
summary,
)
names = {item["name"] for item in parameters}
self.assertIn("repeated_feature_count", names)
self.assertIn("repeated_feature_diameter", names)
self.assertTrue(
all(
item["epistemic_status"] == "inferred_from_final_brep"
for item in parameters
)
)
def test_non_solid_teacher_is_rejected_from_induction(self):
payload = make_case(1)
payload["geometry_evidence"] = {
"valid": False,
"reconstruction_eligible": False,
"solid_count": 0,
"volume": 0.0,
"rejection_reasons": ["no_closed_solid", "zero_volume"],
}
normalized = MODULE.normalize_case(payload)
self.assertFalse(normalized["reconstruction_eligible"])
self.assertIn("no_closed_solid", normalized["rejection_reasons"])
def test_sha256_identity_deduplicates_renamed_sources(self):
digest = "0" * 64
with tempfile.TemporaryDirectory() as temporary:
root = Path(temporary)
for index in range(2):
payload = make_case(index)
payload["provenance"]["source_sha256"] = digest
(root / f"renamed-{index}.json").write_text(json.dumps(payload))
cases, stats = MODULE.load_cases(root)
self.assertEqual(1, len(cases))
self.assertEqual(1, stats["duplicate_case_count"])
def test_conditional_feature_context_does_not_dilute_valid_method(self):
proposal = self.reviewed_proposal()
@@ -202,6 +260,7 @@ class CadExperienceTest(unittest.TestCase):
20,
0.8,
{"duplicate_case_count": 0, "rejected_case_count": 0},
positive_validation(proposal["id"]),
)
self.assertEqual(1, len(library["experiences"]))
@@ -249,6 +308,29 @@ class CadExperienceTest(unittest.TestCase):
self.assertIn("constraint.flanged_hub_adapter.coaxial_stack", ids)
self.assertIn("distribution.sleeve_od_to_base_od", ids)
def test_audit_allows_aggregate_validation_counts_only(self):
library = {
"schema_version": "2.0",
"library_kind": "generalized_cad_experience",
"experiences": [
{
"id": "edit_strategy.test",
"support": 20,
"validation": {
"heldout_pair_count": 20,
"treatment_pass_count": 20,
"regressed_pair_count": 0,
"pass_rate_delta": 0.25,
},
}
],
"candidate_experiences": [],
}
self.assertEqual([], MODULE.audit_library(library))
library["experiences"][0]["validation"]["radius"] = 3.0
errors = MODULE.audit_library(library)
self.assertTrue(any("forbidden instance key" in error for error in errors))
def test_family_support_is_not_diluted_by_unrelated_families(self):
target = [MODULE.normalize_case(make_case(index)) for index in range(20)]
unrelated = []
@@ -299,7 +381,7 @@ class CadExperienceTest(unittest.TestCase):
)
self.assertEqual([], candidate_library["experiences"])
self.assertEqual(1, len(candidate_library["candidate_experiences"]))
self.assertTrue(candidate_library["policy"]["router_consumable"])
self.assertFalse(candidate_library["policy"]["router_consumable"])
cases = [MODULE.normalize_case(make_case(index)) for index in range(20)]
promoted_library = MODULE.build_reviewed_library(
@@ -308,10 +390,71 @@ class CadExperienceTest(unittest.TestCase):
20,
0.8,
{"duplicate_case_count": 0, "rejected_case_count": 0},
positive_validation(proposal["id"]),
)
self.assertEqual(1, len(promoted_library["experiences"]))
self.assertEqual([], promoted_library["candidate_experiences"])
def test_parameter_naming_requires_positive_heldout_validation(self):
cases = []
for index in range(20):
payload = make_case(index)
payload["design_ir"]["semantic_parameter_candidates"] = [
{
"name": "bolt_circle_diameter",
"unit": "mm",
"confidence": 0.9,
"semantic_aliases": ["hole_spacing"],
"affects_feature_roles": ["mounting_pattern"],
"epistemic_status": "inferred_from_final_brep",
"edit_contract": {
"expected_change": "pattern_radius_changes"
},
}
]
cases.append(MODULE.normalize_case(payload))
raw = {
"id": "parameter.flange.bolt_circle_diameter",
"kind": "parameter_naming",
"scope": ["flanged_hub_adapter"],
"evidence_query": {
"required_features": ["base_flange"],
"required_relations": [],
"required_parameters": ["bolt_circle_diameter"],
},
"guidance": "Expose the circular mounting pattern diameter as an editable semantic parameter.",
"semantic_rationale": "The named role preserves edit intent across dimensional variants.",
"canonical_parameter": "bolt_circle_diameter",
"semantic_aliases": ["hole_spacing"],
}
proposal = MODULE.normalize_proposals(
{
"draft_kind": "llm_generalized_experience_proposals",
"proposals": [raw],
}
)[0]
unvalidated = MODULE.build_reviewed_library(
cases,
[proposal],
20,
0.8,
{"duplicate_case_count": 0, "rejected_case_count": 0},
)
self.assertEqual([], unvalidated["experiences"])
self.assertEqual(
"awaiting_positive_heldout_ab",
unvalidated["candidate_experiences"][0]["validation_state"],
)
validated = MODULE.build_reviewed_library(
cases,
[proposal],
20,
0.8,
{"duplicate_case_count": 0, "rejected_case_count": 0},
positive_validation(raw["id"]),
)
self.assertEqual("parameter_naming", validated["experiences"][0]["kind"])
def test_numeric_instance_answer_is_rejected_from_llm_draft(self):
proposal = self.reviewed_proposal()
proposal["guidance"] = "Always use a diameter of 91 millimeters."
@@ -375,6 +518,7 @@ class CadExperienceTest(unittest.TestCase):
20,
0.8,
{"duplicate_case_count": 0, "rejected_case_count": 0},
positive_validation(proposal["id"]),
)
self.assertEqual([], MODULE.audit_library(library))
self.assertEqual("reconstruction_grammar", library["experiences"][0]["kind"])