"""Deterministic phase-pool selection for CDSL runtime baselines. Pool membership is intentionally input-based. It does not claim a part is truth-verified; that remains the responsibility of ``batch_rebuild --build``. """ from __future__ import annotations import json from pathlib import Path from typing import Any from .capabilities import sketch_ids_required_by_contract from cdsl_importer.legacy_profile_adapter import lower_legacy_profiles from .semantic_validation import validate_semantic_cdsl from .sketch_solver import resolve_required_sketches P3_ATOMIC_IDS = frozenset({ "reference_plane", "reference_axis", "extrude_add_blind", "extrude_add_two_sided", "extrude_cut_blind", "extrude_cut_two_sided", "revolve_add", "revolve_cut", }) P4_ATOMIC_IDS = P3_ATOMIC_IDS | frozenset({"hole_wizard"}) P6_ATOMIC_IDS = P4_ATOMIC_IDS | frozenset({"pattern_linear", "pattern_mirror"}) P3_PROFILE_TYPES = frozenset({"analytic_contours", "circle", "polygon"}) # Static pool membership asks whether the exported history is an extrude/ # revolve history. Whether a first cut has a preceding active body remains a # runtime preflight question, not a reason to erase it from the input pool. _P3_PRIMARY_ATOMICS = frozenset({ "extrude_add_blind", "extrude_add_two_sided", "extrude_cut_blind", "extrude_cut_two_sided", "revolve_add", "revolve_cut", }) def _p3_profile_ready(cdsl: dict[str, Any]) -> bool: required_ids = sketch_ids_required_by_contract(cdsl) errors: dict[str, str] = {} try: resolved = resolve_required_sketches(cdsl, required_ids, errors=errors) except ValueError: return False if errors: return False sketches = {str(sketch.get("id")): sketch for sketch in (resolved.get("geometry") or {}).get("sketches") or ()} for sketch_id in required_ids: sketch = sketches.get(sketch_id) or {} profile = sketch.get("profile") or {} if profile.get("type") not in P3_PROFILE_TYPES: return False if not ( any(len(region.get("outer") or []) >= 1 for region in sketch.get("contour_regions_mm") or () if isinstance(region, dict)) or len(sketch.get("contour_edges_mm") or ()) >= 1 or any( entity.get("type") == "circle" and not entity.get("construction") and float(entity.get("radius_mm") or 0.0) > 0 for entity in sketch.get("entities") or () if isinstance(entity, dict) ) ): return False return True _PHASE_ATOMIC_IDS = {"p3": P3_ATOMIC_IDS, "p4": P4_ATOMIC_IDS, "p6": P6_ATOMIC_IDS} def is_static_phase_ready(cdsl: dict[str, Any], phase: str) -> bool: """Return whether CDSL belongs to a documented static phase input pool.""" allowed = _PHASE_ATOMIC_IDS.get(phase) if allowed is None: raise ValueError(f"Unknown CDSL runtime phase {phase!r}") # Corpus pools retain historical profiles on disk but evaluate them after # the importer compatibility lowering used by the batch path. cdsl = lower_legacy_profiles(cdsl) semantic = validate_semantic_cdsl(cdsl) if semantic["unresolved"]: return False atoms = {str(feature.get("atomic_id") or "") for feature in cdsl.get("features") or ()} if not atoms <= allowed: return False if not any(atom in _P3_PRIMARY_ATOMICS for atom in atoms): return False return _p3_profile_ready(cdsl) def select_static_phase_pool(cdsl_dir: Path, phase: str) -> list[str]: """Return sorted CDSL file-stem selectors for one static phase pool. Batch rebuild selection is keyed by the immutable filename rather than the document's display ``part_id``. Imports retain the latter as source provenance and it may differ only by case or normalisation, which makes it unsuitable as a unique filesystem selector. """ selectors: list[str] = [] for path in sorted(cdsl_dir.glob("*.cdsl.json")): document: dict[str, Any] = json.loads(path.read_text(encoding="utf-8")) if is_static_phase_ready(document, phase): selectors.append(path.name.removesuffix(".cdsl.json")) return selectors def is_p3_static_ready(cdsl: dict[str, Any]) -> bool: return is_static_phase_ready(cdsl, "p3") def select_p3_static_pool(cdsl_dir: Path) -> list[str]: return select_static_phase_pool(cdsl_dir, "p3")