优化agent
This commit is contained in:
@@ -99,8 +99,8 @@ def validate_design_intent(
|
||||
raise DesignIntentError("INVALID_DESIGN_INTENT", "revise DesignIntent requires base_revision_id")
|
||||
if current_revision_id and base_revision_id != current_revision_id:
|
||||
raise DesignIntentError(
|
||||
"INVALID_DESIGN_INTENT",
|
||||
"revise DesignIntent base_revision_id must be the current successful revision",
|
||||
"DESIGN_INTENT_BASE_REVISION_MISMATCH",
|
||||
f"revise DesignIntent base_revision_id must be {current_revision_id}, the current successful revision",
|
||||
)
|
||||
|
||||
structures = normalized["structures"]
|
||||
|
||||
@@ -0,0 +1,212 @@
|
||||
"""Deterministic GenerationSpec -> CDSL compilers.
|
||||
|
||||
The LLM supplies semantic parameters and feature intent. This module owns
|
||||
the CDSL details so tool calls do not need to contain sketch IDs, selectors, or
|
||||
runtime-specific parameter wrappers.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Protocol
|
||||
|
||||
from .generation_spec import GenerationSpecError, validate_generation_spec
|
||||
|
||||
|
||||
def _workplane(z: float = 0.0, normal: list[float] | None = None) -> dict[str, list[float]]:
|
||||
return {
|
||||
"origin_mm": [0.0, 0.0, float(z)],
|
||||
"x_dir": [1.0, 0.0, 0.0],
|
||||
"y_dir": [0.0, 1.0, 0.0],
|
||||
"normal": list(normal or [0.0, 0.0, 1.0]),
|
||||
}
|
||||
|
||||
|
||||
def _value(spec: dict[str, Any], name: str, default: float | int | str) -> Any:
|
||||
parameter = (spec.get("parameters") or {}).get(name)
|
||||
if isinstance(parameter, dict) and "value" in parameter:
|
||||
return parameter["value"]
|
||||
return default
|
||||
|
||||
|
||||
def _number(spec: dict[str, Any], name: str, default: float) -> float:
|
||||
value = _value(spec, name, default)
|
||||
if isinstance(value, bool) or not isinstance(value, (int, float)):
|
||||
raise GenerationSpecError(f"Parameter {name} must be numeric", f"$.parameters.{name}.value")
|
||||
number = float(value)
|
||||
if not number == number or number in {float("inf"), float("-inf")}:
|
||||
raise GenerationSpecError(f"Parameter {name} must be finite", f"$.parameters.{name}.value")
|
||||
return number
|
||||
|
||||
|
||||
def _integer(spec: dict[str, Any], name: str, default: int) -> int:
|
||||
value = _number(spec, name, default)
|
||||
if int(value) != value:
|
||||
raise GenerationSpecError(f"Parameter {name} must be an integer", f"$.parameters.{name}.value")
|
||||
return int(value)
|
||||
|
||||
|
||||
def _positive(number: float, name: str) -> float:
|
||||
if number <= 0:
|
||||
raise GenerationSpecError(f"Parameter {name} must be greater than zero", f"$.parameters.{name}.value")
|
||||
return number
|
||||
|
||||
|
||||
def _acceptance(spec: dict[str, Any], generated: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
existing = copy.deepcopy(spec.get("acceptance") or [])
|
||||
known = {str(item.get("id")) for item in existing}
|
||||
for item in generated:
|
||||
if item["id"] not in known:
|
||||
existing.append(item)
|
||||
known.add(item["id"])
|
||||
return existing
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class CompiledGeneration:
|
||||
cdsl: dict[str, Any]
|
||||
acceptance: list[dict[str, Any]]
|
||||
provenance: dict[str, Any]
|
||||
assumptions: list[str]
|
||||
approximations: list[dict[str, Any]]
|
||||
|
||||
|
||||
class Compiler(Protocol):
|
||||
family: str
|
||||
|
||||
def compile(self, spec: dict[str, Any], references: list[dict[str, Any]]) -> CompiledGeneration:
|
||||
...
|
||||
|
||||
|
||||
class MountingPlateCompiler:
|
||||
family = "mounting_plate"
|
||||
|
||||
def compile(self, spec: dict[str, Any], references: list[dict[str, Any]]) -> CompiledGeneration:
|
||||
width = _positive(_number(spec, "width", 100.0), "width")
|
||||
height = _positive(_number(spec, "height", 60.0), "height")
|
||||
thickness = _positive(_number(spec, "thickness", 6.0), "thickness")
|
||||
hole_diameter = _positive(_number(spec, "hole_diameter", 4.5), "hole_diameter")
|
||||
hole_offset = _positive(_number(spec, "hole_edge_offset", 10.0), "hole_edge_offset")
|
||||
hole_count = _integer(spec, "hole_count", 4)
|
||||
counterbore_diameter = _positive(_number(spec, "counterbore_diameter", hole_diameter * 2), "counterbore_diameter")
|
||||
counterbore_depth = _positive(_number(spec, "counterbore_depth", min(thickness / 2, 3.0)), "counterbore_depth")
|
||||
slot_length = _positive(_number(spec, "slot_length", 20.0), "slot_length")
|
||||
slot_width = _positive(_number(spec, "slot_width", 12.0), "slot_width")
|
||||
|
||||
if hole_count != 4:
|
||||
raise GenerationSpecError("mounting_plate compiler currently requires four corner holes", "$.parameters.hole_count.value")
|
||||
if counterbore_diameter <= hole_diameter:
|
||||
raise GenerationSpecError("counterbore_diameter must exceed hole_diameter", "$.parameters.counterbore_diameter.value")
|
||||
if counterbore_depth > thickness:
|
||||
raise GenerationSpecError("counterbore_depth must not exceed thickness", "$.parameters.counterbore_depth.value")
|
||||
if hole_offset * 2 >= min(width, height):
|
||||
raise GenerationSpecError("hole_edge_offset leaves no usable plate area", "$.parameters.hole_edge_offset.value")
|
||||
if slot_length < slot_width:
|
||||
raise GenerationSpecError("slot_length must be at least slot_width", "$.parameters.slot_length.value")
|
||||
|
||||
centers = [
|
||||
[-width / 2 + hole_offset, -height / 2 + hole_offset],
|
||||
[width / 2 - hole_offset, -height / 2 + hole_offset],
|
||||
[width / 2 - hole_offset, height / 2 - hole_offset],
|
||||
[-width / 2 + hole_offset, height / 2 - hole_offset],
|
||||
]
|
||||
frame = _workplane(thickness)
|
||||
cdsl = {
|
||||
"schema": "cad.cdsl.llm.v1",
|
||||
"schema_version": "1.1.0",
|
||||
"kind": "part",
|
||||
"part_id": "generation-spec-preflight",
|
||||
"meta": {"unit": "mm", "name": str(spec["part"]["name"])},
|
||||
"geometry": {"sketches": [
|
||||
{"id": "base_plate", "workplane": _workplane(), "profile": {
|
||||
"type": "rectangle", "center": [0.0, 0.0], "width_mm": width, "height_mm": height,
|
||||
}},
|
||||
{"id": "mounting_holes", "workplane": frame, "profile": {
|
||||
"type": "circles", "items": [{"center": center, "radius_mm": hole_diameter / 2} for center in centers],
|
||||
}},
|
||||
{"id": "center_slot", "workplane": frame, "profile": {
|
||||
"type": "obround", "center": [0.0, 0.0], "length_mm": slot_length, "width_mm": slot_width,
|
||||
}},
|
||||
]},
|
||||
"features": [
|
||||
{"id": "base_plate", "atomic_id": "extrude_add_blind", "depends_on": [], "sketch_id": "base_plate", "params": {"distance_mm": thickness}},
|
||||
{"id": "mounting_holes", "atomic_id": "extrude_cut_blind", "depends_on": ["base_plate"], "sketch_id": "mounting_holes", "params": {"distance_mm": thickness, "reverse": True}},
|
||||
{"id": "mounting_counterbores", "atomic_id": "hole_counterbore", "depends_on": ["mounting_holes"], "sketch_id": "mounting_holes", "params": {
|
||||
"diameter_mm": hole_diameter, "depth_mm": thickness, "counterbore_diameter_mm": counterbore_diameter,
|
||||
"counterbore_depth_mm": counterbore_depth, "positions": [{"mm": [x, y, 0.0]} for x, y in centers], "host_face": {"frame": frame},
|
||||
}},
|
||||
{"id": "center_slot", "atomic_id": "extrude_cut_blind", "depends_on": ["mounting_counterbores"], "sketch_id": "center_slot", "params": {"distance_mm": thickness, "reverse": True}},
|
||||
],
|
||||
}
|
||||
assumptions = [str(item) for item in spec.get("assumptions") or []]
|
||||
if "hole_diameter" not in (spec.get("parameters") or {}):
|
||||
assumptions.append("M4 clearance hole defaults to 4.5 mm")
|
||||
if "counterbore_depth" not in (spec.get("parameters") or {}):
|
||||
assumptions.append("Counterbore depth defaults to the lesser of 3 mm and half the plate thickness")
|
||||
acceptance = _acceptance(spec, [
|
||||
{"id": "overall_bbox", "type": "bbox", "expected": [width, height, thickness], "tolerance": 0.1, "severity": "blocking"},
|
||||
{"id": "mounting_hole_count", "type": "feature_count", "feature": "mounting_holes", "expected": 4, "tolerance": 0, "severity": "blocking"},
|
||||
{"id": "mounting_hole_diameter", "type": "hole_diameter", "feature": "mounting_holes", "expected": hole_diameter, "tolerance": 0.05, "severity": "blocking"},
|
||||
{"id": "center_slot_size", "type": "overall_length", "feature": "center_slot", "expected": slot_length, "tolerance": 0.1, "severity": "blocking"},
|
||||
])
|
||||
provenance = {
|
||||
"compiler": self.family,
|
||||
"compiler_version": "1.0",
|
||||
"references": references,
|
||||
"feature_sources": {item["id"]: item["id"] for item in spec.get("features") or []},
|
||||
"parameter_paths": {name: f"parameters.{name}.value" for name in (spec.get("parameters") or {})},
|
||||
}
|
||||
return CompiledGeneration(cdsl, acceptance, provenance, list(dict.fromkeys(assumptions)), list(spec.get("approximations") or []))
|
||||
|
||||
|
||||
class FlangeSleeveCompiler:
|
||||
family = "flange_sleeve"
|
||||
|
||||
def compile(self, spec: dict[str, Any], references: list[dict[str, Any]]) -> CompiledGeneration:
|
||||
from app.services.flange_sleeve_template import build_flange_sleeve_cdsl, TEMPLATE_ID
|
||||
|
||||
aliases = {
|
||||
"width": "flange_width_mm", "height": "flange_height_mm", "thickness": "flange_thickness_mm",
|
||||
"tube_outer_diameter": "tube_outer_diameter_mm", "bore_diameter": "bore_diameter_mm",
|
||||
"hole_diameter": "mount_hole_diameter_mm", "counterbore_diameter": "mount_counterbore_diameter_mm",
|
||||
"counterbore_depth": "mount_counterbore_depth_mm", "hole_offset_x": "mount_hole_u_mm", "hole_offset_y": "mount_hole_v_mm",
|
||||
}
|
||||
plan: dict[str, Any] = {"template": TEMPLATE_ID, "name": spec["part"]["name"]}
|
||||
for source, target in aliases.items():
|
||||
if source in (spec.get("parameters") or {}):
|
||||
plan[target] = _value(spec, source, 0)
|
||||
cdsl, values = build_flange_sleeve_cdsl(plan)
|
||||
acceptance = _acceptance(spec, [
|
||||
{"id": "flange_bbox", "type": "bbox", "expected": [float(values["flange_width_mm"]), float(values["flange_height_mm"]), float(values["flange_thickness_mm"])], "tolerance": 0.1, "severity": "blocking"},
|
||||
])
|
||||
provenance = {"compiler": self.family, "compiler_version": "1.0", "template_id": TEMPLATE_ID, "references": references}
|
||||
assumptions = list(dict.fromkeys([*map(str, spec.get("assumptions") or []), "Flange sleeve dimensions use the verified parameterized template defaults for omitted fields."]))
|
||||
return CompiledGeneration(cdsl, acceptance, provenance, assumptions, list(spec.get("approximations") or []))
|
||||
|
||||
|
||||
COMPILERS: dict[str, Compiler] = {
|
||||
"mounting_plate": MountingPlateCompiler(),
|
||||
"flange_sleeve": FlangeSleeveCompiler(),
|
||||
}
|
||||
|
||||
|
||||
def compiler_for(family: str) -> Compiler:
|
||||
try:
|
||||
return COMPILERS[family]
|
||||
except KeyError as error:
|
||||
raise GenerationSpecError(f"No GenerationSpec compiler registered for part family: {family}", "$.part.family") from error
|
||||
|
||||
|
||||
def compile_generation_spec(spec: dict[str, Any], references: list[dict[str, Any]] | None = None) -> dict[str, Any]:
|
||||
normalized = validate_generation_spec(spec)
|
||||
compiler = compiler_for(str(normalized["part"]["family"]))
|
||||
compiled = compiler.compile(normalized, references or [])
|
||||
return {
|
||||
"spec": normalized,
|
||||
"cdsl": compiled.cdsl,
|
||||
"acceptance": compiled.acceptance,
|
||||
"provenance": compiled.provenance,
|
||||
"assumptions": compiled.assumptions,
|
||||
"approximations": compiled.approximations,
|
||||
}
|
||||
@@ -0,0 +1,177 @@
|
||||
"""Validation and normalization for the semantic GenerationSpec contract."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import json
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from jsonschema import Draft202012Validator
|
||||
|
||||
|
||||
KNOWN_PART_FAMILIES = frozenset({
|
||||
"mounting_plate",
|
||||
"flange",
|
||||
"flange_sleeve",
|
||||
"simple_shaft",
|
||||
"bearing_housing",
|
||||
"mounting_bracket",
|
||||
"hex_nut",
|
||||
"slotted_plate",
|
||||
})
|
||||
|
||||
KNOWN_FEATURE_KINDS = frozenset({
|
||||
"base_extrusion",
|
||||
"base_revolve",
|
||||
"boss",
|
||||
"through_hole",
|
||||
"blind_hole",
|
||||
"counterbored_hole",
|
||||
"countersunk_hole",
|
||||
"hole_pattern",
|
||||
"counterbored_hole_pattern",
|
||||
"obround_cut",
|
||||
"pocket",
|
||||
"revolve_profile",
|
||||
"coaxial_bore",
|
||||
"fillet",
|
||||
"chamfer",
|
||||
})
|
||||
|
||||
|
||||
class GenerationSpecError(ValueError):
|
||||
"""A stable, user-repairable GenerationSpec validation error."""
|
||||
|
||||
def __init__(self, message: str, path: str = "$") -> None:
|
||||
super().__init__(message)
|
||||
self.path = path
|
||||
|
||||
|
||||
@lru_cache(maxsize=1)
|
||||
def generation_spec_schema() -> dict[str, Any]:
|
||||
path = Path(__file__).with_name("generation_spec_schema.json")
|
||||
schema = json.loads(path.read_text(encoding="utf-8"))
|
||||
Draft202012Validator.check_schema(schema)
|
||||
return schema
|
||||
|
||||
|
||||
def _schema_error(spec: dict[str, Any]) -> GenerationSpecError | None:
|
||||
errors = sorted(
|
||||
Draft202012Validator(generation_spec_schema()).iter_errors(spec),
|
||||
key=lambda error: (list(error.absolute_path), error.message),
|
||||
)
|
||||
if not errors:
|
||||
return None
|
||||
error = errors[0]
|
||||
location = "$" + "".join(
|
||||
f"[{item}]" if isinstance(item, int) else f".{item}"
|
||||
for item in error.absolute_path
|
||||
)
|
||||
return GenerationSpecError(error.message, location)
|
||||
|
||||
|
||||
def _parameter_value(spec: dict[str, Any], name: str) -> Any:
|
||||
value = (spec.get("parameters") or {}).get(name)
|
||||
if not isinstance(value, dict):
|
||||
raise GenerationSpecError(f"Unknown parameter: {name}", f"$.parameters.{name}")
|
||||
return value.get("value")
|
||||
|
||||
|
||||
def _numeric(value: Any, path: str) -> float:
|
||||
if isinstance(value, bool) or not isinstance(value, (int, float)):
|
||||
raise GenerationSpecError("Expected a finite numeric value", path)
|
||||
number = float(value)
|
||||
if number != number or number in {float("inf"), float("-inf")}:
|
||||
raise GenerationSpecError("Expected a finite numeric value", path)
|
||||
return number
|
||||
|
||||
|
||||
def _check_constraints(spec: dict[str, Any]) -> None:
|
||||
for index, constraint in enumerate(spec.get("constraints") or []):
|
||||
path = f"$.constraints[{index}]"
|
||||
kind = constraint.get("type")
|
||||
if kind in {"less_than", "less_equal", "greater_than", "greater_equal", "equal"}:
|
||||
left = _numeric(_parameter_value(spec, str(constraint.get("left") or "")), f"{path}.left")
|
||||
right_name = constraint.get("right")
|
||||
right = _numeric(_parameter_value(spec, str(right_name)), f"{path}.right") if right_name else _numeric(constraint.get("value"), f"{path}.value")
|
||||
ok = {
|
||||
"less_than": left < right,
|
||||
"less_equal": left <= right,
|
||||
"greater_than": left > right,
|
||||
"greater_equal": left >= right,
|
||||
"equal": abs(left - right) <= 1e-9,
|
||||
}[kind]
|
||||
if not ok:
|
||||
raise GenerationSpecError(constraint.get("message") or f"Constraint {kind} failed", path)
|
||||
|
||||
|
||||
def _check_graph(spec: dict[str, Any]) -> None:
|
||||
features = spec.get("features") or []
|
||||
ids = [str(item.get("id")) for item in features]
|
||||
if len(ids) != len(set(ids)):
|
||||
raise GenerationSpecError("Feature ids must be unique", "$.features")
|
||||
known: set[str] = set()
|
||||
for index, feature in enumerate(features):
|
||||
feature_id = str(feature.get("id"))
|
||||
kind = str(feature.get("kind"))
|
||||
if kind not in KNOWN_FEATURE_KINDS:
|
||||
raise GenerationSpecError(f"Unsupported feature kind: {kind}", f"$.features[{index}].kind")
|
||||
for dependency in feature.get("depends_on") or []:
|
||||
if dependency not in known:
|
||||
raise GenerationSpecError(
|
||||
f"Feature {feature_id} has a forward or missing dependency: {dependency}",
|
||||
f"$.features[{index}].depends_on",
|
||||
)
|
||||
known.add(feature_id)
|
||||
|
||||
acceptance_ids = {str(item.get("id")) for item in spec.get("acceptance") or []}
|
||||
if len(acceptance_ids) != len(spec.get("acceptance") or []):
|
||||
raise GenerationSpecError("Acceptance ids must be unique", "$.acceptance")
|
||||
for index, item in enumerate(spec.get("acceptance") or []):
|
||||
feature = item.get("feature")
|
||||
if feature and feature not in known:
|
||||
raise GenerationSpecError(f"Acceptance refers to missing feature: {feature}", f"$.acceptance[{index}].feature")
|
||||
|
||||
|
||||
def normalize_generation_spec(spec: dict[str, Any], *, request: str = "") -> dict[str, Any]:
|
||||
if not isinstance(spec, dict):
|
||||
raise GenerationSpecError("GenerationSpec must be a JSON object")
|
||||
normalized = copy.deepcopy(spec)
|
||||
normalized.setdefault("schema", "cad.generation-spec.v1")
|
||||
normalized.setdefault("schema_version", "1.0")
|
||||
normalized.setdefault("mode", "create")
|
||||
normalized.setdefault("base_revision_id", "")
|
||||
normalized.setdefault("parameters", {})
|
||||
normalized.setdefault("features", [])
|
||||
normalized.setdefault("constraints", [])
|
||||
normalized.setdefault("acceptance", [])
|
||||
normalized.setdefault("assumptions", [])
|
||||
normalized.setdefault("approximations", [])
|
||||
normalized.setdefault("references", [])
|
||||
normalized.setdefault("patch_intent", request)
|
||||
return normalized
|
||||
|
||||
|
||||
def validate_generation_spec(
|
||||
spec: dict[str, Any],
|
||||
*,
|
||||
known_families: set[str] | frozenset[str] = KNOWN_PART_FAMILIES,
|
||||
) -> dict[str, Any]:
|
||||
normalized = normalize_generation_spec(spec)
|
||||
error = _schema_error(normalized)
|
||||
if error:
|
||||
raise error
|
||||
family = str(normalized["part"]["family"])
|
||||
if family not in known_families:
|
||||
raise GenerationSpecError(f"Unsupported part family: {family}", "$.part.family")
|
||||
for name, parameter in normalized["parameters"].items():
|
||||
source = parameter["source"]
|
||||
if source == "user" and not parameter["locked"]:
|
||||
raise GenerationSpecError("User parameters must be locked", f"$.parameters.{name}.locked")
|
||||
if source == "image_estimate" and not parameter.get("assumption"):
|
||||
raise GenerationSpecError("Image estimates require an assumption", f"$.parameters.{name}.assumption")
|
||||
_check_graph(normalized)
|
||||
_check_constraints(normalized)
|
||||
return normalized
|
||||
@@ -0,0 +1,100 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://cdsl.local/schema/cad.generation-spec.v1",
|
||||
"title": "CDSL GenerationSpec",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"schema": {"const": "cad.generation-spec.v1"},
|
||||
"schema_version": {"const": "1.0"},
|
||||
"mode": {"enum": ["create", "revise"]},
|
||||
"base_revision_id": {"type": "string"},
|
||||
"patch_intent": {"type": "string"},
|
||||
"part": {"$ref": "#/$defs/part"},
|
||||
"parameters": {"type": "object", "additionalProperties": {"$ref": "#/$defs/parameter"}},
|
||||
"features": {"type": "array", "items": {"$ref": "#/$defs/feature"}},
|
||||
"constraints": {"type": "array", "items": {"$ref": "#/$defs/constraint"}},
|
||||
"acceptance": {"type": "array", "items": {"$ref": "#/$defs/acceptance"}},
|
||||
"assumptions": {"type": "array", "items": {"type": "string", "minLength": 1}},
|
||||
"approximations": {"type": "array", "items": {"$ref": "#/$defs/approximation"}},
|
||||
"references": {"type": "array", "items": {"type": "object"}}
|
||||
},
|
||||
"required": ["schema", "schema_version", "mode", "base_revision_id", "part", "parameters", "features", "constraints", "acceptance", "assumptions", "approximations", "references"],
|
||||
"$defs": {
|
||||
"part": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"family": {"type": "string", "minLength": 1},
|
||||
"name": {"type": "string", "minLength": 1},
|
||||
"units": {"const": "mm"},
|
||||
"coordinate_system": {"type": "string", "minLength": 1}
|
||||
},
|
||||
"required": ["family", "name", "units", "coordinate_system"]
|
||||
},
|
||||
"parameter": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"value": {},
|
||||
"unit": {"enum": ["mm", "deg", "rad", "count", ""]},
|
||||
"source": {"enum": ["user", "technical_drawing", "image_estimate", "template_default", "derived_standard", "assumption"]},
|
||||
"confidence": {"type": "number", "minimum": 0, "maximum": 1},
|
||||
"locked": {"type": "boolean"},
|
||||
"assumption": {"type": "string"}
|
||||
},
|
||||
"required": ["value", "unit", "source", "confidence", "locked", "assumption"]
|
||||
},
|
||||
"feature": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {"type": "string", "pattern": "^[A-Za-z0-9_-]{1,80}$"},
|
||||
"kind": {"type": "string", "minLength": 1},
|
||||
"depends_on": {"type": "array", "items": {"type": "string", "pattern": "^[A-Za-z0-9_-]{1,80}$"}},
|
||||
"parameters": {"type": "object"},
|
||||
"semantic_role": {"type": "string"}
|
||||
},
|
||||
"required": ["id", "kind", "depends_on", "parameters"]
|
||||
},
|
||||
"constraint": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"type": {"enum": ["less_than", "less_equal", "greater_than", "greater_equal", "equal", "inside_boundary", "symmetric", "same_axis", "distance", "count"]},
|
||||
"left": {"type": "string"},
|
||||
"right": {"type": "string"},
|
||||
"value": {},
|
||||
"feature": {"type": "string"},
|
||||
"margin": {"type": "number"},
|
||||
"ratio": {"type": "number"},
|
||||
"message": {"type": "string"}
|
||||
},
|
||||
"required": ["type"]
|
||||
},
|
||||
"acceptance": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {"type": "string", "pattern": "^[A-Za-z0-9_-]{1,80}$"},
|
||||
"type": {"enum": ["bbox", "solid_count", "feature_count", "hole_diameter", "hole_center", "hole_spacing", "wall_thickness", "overall_length", "overall_diameter", "coaxiality", "symmetry", "through_condition", "blind_depth"]},
|
||||
"feature": {"type": "string"},
|
||||
"expected": {},
|
||||
"tolerance": {"type": "number", "minimum": 0},
|
||||
"severity": {"enum": ["blocking", "warning", "informational"]}
|
||||
},
|
||||
"required": ["id", "type", "expected", "tolerance", "severity"]
|
||||
},
|
||||
"approximation": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"request": {"type": "string", "minLength": 1},
|
||||
"status": {"enum": ["approximated", "expanded", "omitted", "blocked"]},
|
||||
"translation": {"type": "string", "minLength": 1},
|
||||
"reason": {"type": "string", "minLength": 1}
|
||||
},
|
||||
"required": ["request", "status", "translation", "reason"]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user