foodforthought-cli 0.2.1__py3-none-any.whl → 0.2.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. ate/__init__.py +1 -1
  2. ate/bridge_server.py +622 -0
  3. ate/cli.py +2625 -242
  4. ate/compatibility.py +580 -0
  5. ate/generators/__init__.py +19 -0
  6. ate/generators/docker_generator.py +461 -0
  7. ate/generators/hardware_config.py +469 -0
  8. ate/generators/ros2_generator.py +617 -0
  9. ate/generators/skill_generator.py +783 -0
  10. ate/marketplace.py +524 -0
  11. ate/mcp_server.py +1341 -107
  12. ate/primitives.py +1016 -0
  13. ate/robot_setup.py +2222 -0
  14. ate/skill_schema.py +537 -0
  15. ate/telemetry/__init__.py +33 -0
  16. ate/telemetry/cli.py +455 -0
  17. ate/telemetry/collector.py +444 -0
  18. ate/telemetry/context.py +318 -0
  19. ate/telemetry/fleet_agent.py +419 -0
  20. ate/telemetry/formats/__init__.py +18 -0
  21. ate/telemetry/formats/hdf5_serializer.py +503 -0
  22. ate/telemetry/formats/mcap_serializer.py +457 -0
  23. ate/telemetry/types.py +334 -0
  24. foodforthought_cli-0.2.4.dist-info/METADATA +300 -0
  25. foodforthought_cli-0.2.4.dist-info/RECORD +44 -0
  26. foodforthought_cli-0.2.4.dist-info/top_level.txt +6 -0
  27. mechdog_labeled/__init__.py +3 -0
  28. mechdog_labeled/primitives.py +113 -0
  29. mechdog_labeled/servo_map.py +209 -0
  30. mechdog_output/__init__.py +3 -0
  31. mechdog_output/primitives.py +59 -0
  32. mechdog_output/servo_map.py +203 -0
  33. test_autodetect/__init__.py +3 -0
  34. test_autodetect/primitives.py +113 -0
  35. test_autodetect/servo_map.py +209 -0
  36. test_full_auto/__init__.py +3 -0
  37. test_full_auto/primitives.py +113 -0
  38. test_full_auto/servo_map.py +209 -0
  39. test_smart_detect/__init__.py +3 -0
  40. test_smart_detect/primitives.py +113 -0
  41. test_smart_detect/servo_map.py +209 -0
  42. foodforthought_cli-0.2.1.dist-info/METADATA +0 -151
  43. foodforthought_cli-0.2.1.dist-info/RECORD +0 -9
  44. foodforthought_cli-0.2.1.dist-info/top_level.txt +0 -1
  45. {foodforthought_cli-0.2.1.dist-info → foodforthought_cli-0.2.4.dist-info}/WHEEL +0 -0
  46. {foodforthought_cli-0.2.1.dist-info → foodforthought_cli-0.2.4.dist-info}/entry_points.txt +0 -0
ate/compatibility.py ADDED
@@ -0,0 +1,580 @@
1
+ """
2
+ Compatibility Checker - Verify skill compatibility with robots.
3
+
4
+ This module checks whether a skill can run on a given robot by:
5
+ - Comparing hardware requirements vs robot capabilities
6
+ - Identifying potential issues and their severity
7
+ - Suggesting adaptations to make incompatible skills work
8
+ """
9
+
10
+ from dataclasses import dataclass, field
11
+ from enum import Enum
12
+ from pathlib import Path
13
+ from typing import Any, Dict, List, Optional, Tuple
14
+
15
+ from .skill_schema import SkillSpecification, HardwareRequirement
16
+ from .generators.hardware_config import URDFInfo, parse_urdf, ATEConfig
17
+
18
+
19
+ class IssueSeverity(str, Enum):
20
+ """Severity levels for compatibility issues."""
21
+ INFO = "info" # Informational, no action needed
22
+ WARNING = "warning" # May work but with limitations
23
+ ERROR = "error" # Will not work without changes
24
+ CRITICAL = "critical" # Fundamentally incompatible
25
+
26
+
27
+ class AdaptationType(str, Enum):
28
+ """Types of adaptations to make skills compatible."""
29
+ IK_CONSTRAINT = "ik_constraint" # Lock or limit certain joints
30
+ SPEED_LIMIT = "speed_limit" # Reduce maximum speed
31
+ WORKSPACE_LIMIT = "workspace_limit" # Limit operational workspace
32
+ FORCE_LIMIT = "force_limit" # Reduce force limits
33
+ TRAJECTORY_REMAP = "trajectory_remap" # Remap joint trajectories
34
+ GRIPPER_ADAPT = "gripper_adapt" # Adapt gripper behavior
35
+ SENSOR_SUBSTITUTE = "sensor_substitute" # Use alternative sensor
36
+
37
+
38
+ @dataclass
39
+ class CompatibilityIssue:
40
+ """A single compatibility issue between skill and robot."""
41
+ severity: IssueSeverity
42
+ category: str # e.g., "hardware", "kinematics", "safety"
43
+ message: str
44
+ details: Optional[str] = None
45
+ mitigation: Optional[str] = None
46
+
47
+ def to_dict(self) -> Dict[str, Any]:
48
+ return {
49
+ "severity": self.severity.value,
50
+ "category": self.category,
51
+ "message": self.message,
52
+ "details": self.details,
53
+ "mitigation": self.mitigation,
54
+ }
55
+
56
+
57
+ @dataclass
58
+ class Adaptation:
59
+ """An adaptation to make a skill compatible with a robot."""
60
+ type: AdaptationType
61
+ description: str
62
+ config: Dict[str, Any] = field(default_factory=dict)
63
+ automated: bool = False # Can this be automatically applied?
64
+
65
+ def to_dict(self) -> Dict[str, Any]:
66
+ return {
67
+ "type": self.type.value,
68
+ "description": self.description,
69
+ "config": self.config,
70
+ "automated": self.automated,
71
+ }
72
+
73
+
74
+ @dataclass
75
+ class CompatibilityReport:
76
+ """Complete compatibility report between a skill and robot."""
77
+ skill_name: str
78
+ robot_name: str
79
+ compatible: bool
80
+ score: float # 0.0 to 1.0 compatibility score
81
+ issues: List[CompatibilityIssue] = field(default_factory=list)
82
+ adaptations: List[Adaptation] = field(default_factory=list)
83
+
84
+ def to_dict(self) -> Dict[str, Any]:
85
+ return {
86
+ "skill_name": self.skill_name,
87
+ "robot_name": self.robot_name,
88
+ "compatible": self.compatible,
89
+ "score": self.score,
90
+ "issues": [i.to_dict() for i in self.issues],
91
+ "adaptations": [a.to_dict() for a in self.adaptations],
92
+ }
93
+
94
+ def __str__(self) -> str:
95
+ status = "COMPATIBLE" if self.compatible else "INCOMPATIBLE"
96
+ lines = [
97
+ f"Compatibility Report: {self.skill_name} -> {self.robot_name}",
98
+ f"Status: {status} (Score: {self.score:.1%})",
99
+ "",
100
+ ]
101
+
102
+ if self.issues:
103
+ lines.append("Issues:")
104
+ for issue in self.issues:
105
+ severity_icon = {
106
+ IssueSeverity.INFO: "ℹ️",
107
+ IssueSeverity.WARNING: "⚠️",
108
+ IssueSeverity.ERROR: "❌",
109
+ IssueSeverity.CRITICAL: "🚫",
110
+ }.get(issue.severity, "•")
111
+ lines.append(f" {severity_icon} [{issue.category}] {issue.message}")
112
+ if issue.mitigation:
113
+ lines.append(f" Mitigation: {issue.mitigation}")
114
+
115
+ if self.adaptations:
116
+ lines.append("")
117
+ lines.append("Suggested Adaptations:")
118
+ for adapt in self.adaptations:
119
+ auto = " (automatic)" if adapt.automated else ""
120
+ lines.append(f" • {adapt.description}{auto}")
121
+
122
+ return "\n".join(lines)
123
+
124
+
125
+ @dataclass
126
+ class RobotProfile:
127
+ """Profile describing a robot's capabilities."""
128
+ name: str
129
+ urdf_info: Optional[URDFInfo] = None
130
+ ate_config: Optional[ATEConfig] = None
131
+
132
+ # Hardware capabilities
133
+ arm_dof: int = 0
134
+ has_gripper: bool = False
135
+ gripper_type: Optional[str] = None
136
+ has_force_sensor: bool = False
137
+ has_camera: bool = False
138
+ has_mobile_base: bool = False
139
+
140
+ # Kinematic properties
141
+ max_reach: Optional[float] = None # meters
142
+ payload: Optional[float] = None # kg
143
+ workspace_bounds: Optional[Dict[str, Tuple[float, float]]] = None
144
+
145
+ # Performance limits
146
+ max_joint_velocity: Optional[float] = None # rad/s
147
+ max_cartesian_velocity: Optional[float] = None # m/s
148
+ max_force: Optional[float] = None # N
149
+
150
+ @classmethod
151
+ def from_urdf(cls, urdf_path: str, name: Optional[str] = None) -> "RobotProfile":
152
+ """Create a robot profile from URDF file."""
153
+ urdf_info = parse_urdf(urdf_path)
154
+
155
+ profile = cls(
156
+ name=name or urdf_info.name,
157
+ urdf_info=urdf_info,
158
+ arm_dof=urdf_info.dof,
159
+ )
160
+
161
+ # Detect gripper from joint names
162
+ gripper_joints = [
163
+ j for j in urdf_info.movable_joints
164
+ if any(kw in j.name.lower() for kw in ["gripper", "finger", "hand"])
165
+ ]
166
+ if gripper_joints:
167
+ profile.has_gripper = True
168
+ profile.gripper_type = "parallel" # Default assumption
169
+
170
+ # Extract velocity limits
171
+ velocities = [
172
+ j.velocity_limit for j in urdf_info.movable_joints
173
+ if j.velocity_limit is not None
174
+ ]
175
+ if velocities:
176
+ profile.max_joint_velocity = min(velocities)
177
+
178
+ return profile
179
+
180
+ @classmethod
181
+ def from_ate_dir(cls, ate_dir: str, name: Optional[str] = None) -> "RobotProfile":
182
+ """Create a robot profile from ATE configuration directory."""
183
+ ate_config = ATEConfig.from_directory(ate_dir)
184
+
185
+ profile = cls(
186
+ name=name or Path(ate_dir).name,
187
+ ate_config=ate_config,
188
+ arm_dof=len(ate_config.servo_map),
189
+ )
190
+
191
+ # Detect gripper (typically last servo or specific IDs)
192
+ if ate_config.servo_map:
193
+ max_id = max(ate_config.servo_map.keys())
194
+ # Heuristic: if there's a servo with different characteristics, it might be gripper
195
+ profile.has_gripper = True
196
+
197
+ return profile
198
+
199
+
200
+ class CompatibilityChecker:
201
+ """
202
+ Check compatibility between a skill and a robot.
203
+
204
+ Performs comprehensive checks including:
205
+ - Hardware requirements
206
+ - Kinematic constraints
207
+ - Safety limits
208
+ - Sensor requirements
209
+ """
210
+
211
+ def __init__(self, spec: SkillSpecification, robot: RobotProfile):
212
+ """
213
+ Initialize the checker.
214
+
215
+ Args:
216
+ spec: Skill specification to check
217
+ robot: Robot profile to check against
218
+ """
219
+ self.spec = spec
220
+ self.robot = robot
221
+ self.issues: List[CompatibilityIssue] = []
222
+ self.adaptations: List[Adaptation] = []
223
+
224
+ def check(self) -> CompatibilityReport:
225
+ """
226
+ Perform all compatibility checks.
227
+
228
+ Returns:
229
+ CompatibilityReport with results
230
+ """
231
+ self.issues = []
232
+ self.adaptations = []
233
+
234
+ # Run all checks
235
+ self._check_hardware_requirements()
236
+ self._check_kinematic_requirements()
237
+ self._check_safety_requirements()
238
+ self._check_primitive_support()
239
+
240
+ # Calculate compatibility
241
+ compatible, score = self._calculate_compatibility()
242
+
243
+ return CompatibilityReport(
244
+ skill_name=self.spec.name,
245
+ robot_name=self.robot.name,
246
+ compatible=compatible,
247
+ score=score,
248
+ issues=self.issues,
249
+ adaptations=self.adaptations,
250
+ )
251
+
252
+ def _check_hardware_requirements(self) -> None:
253
+ """Check hardware requirements against robot capabilities."""
254
+ for req in self.spec.hardware_requirements:
255
+ if req.component_type == "arm":
256
+ self._check_arm_requirement(req)
257
+ elif req.component_type == "gripper":
258
+ self._check_gripper_requirement(req)
259
+ elif req.component_type == "force_sensor":
260
+ self._check_force_sensor_requirement(req)
261
+ elif req.component_type == "camera":
262
+ self._check_camera_requirement(req)
263
+ elif req.component_type == "mobile_base":
264
+ self._check_mobile_base_requirement(req)
265
+
266
+ def _check_arm_requirement(self, req: HardwareRequirement) -> None:
267
+ """Check arm hardware requirements."""
268
+ # Check DOF
269
+ required_dof = req.constraints.get("dof")
270
+ if required_dof:
271
+ # Parse requirement like ">=6" or "6"
272
+ if isinstance(required_dof, str):
273
+ if required_dof.startswith(">="):
274
+ min_dof = int(required_dof[2:])
275
+ if self.robot.arm_dof < min_dof:
276
+ self.issues.append(CompatibilityIssue(
277
+ severity=IssueSeverity.ERROR,
278
+ category="hardware",
279
+ message=f"Robot has {self.robot.arm_dof} DOF, skill requires >= {min_dof}",
280
+ mitigation="Consider using a different skill or robot"
281
+ ))
282
+ elif self.robot.arm_dof > min_dof:
283
+ # More DOF than needed - just informational
284
+ self.issues.append(CompatibilityIssue(
285
+ severity=IssueSeverity.INFO,
286
+ category="hardware",
287
+ message=f"Robot has {self.robot.arm_dof} DOF, skill designed for {min_dof}+",
288
+ details="Extra DOF may provide redundancy"
289
+ ))
290
+ elif isinstance(required_dof, int):
291
+ if self.robot.arm_dof < required_dof:
292
+ diff = required_dof - self.robot.arm_dof
293
+ severity = IssueSeverity.ERROR if diff > 1 else IssueSeverity.WARNING
294
+ self.issues.append(CompatibilityIssue(
295
+ severity=severity,
296
+ category="hardware",
297
+ message=f"Robot has {self.robot.arm_dof} DOF, skill designed for {required_dof}",
298
+ mitigation="Reduced workspace, may not reach all poses" if diff == 1 else None
299
+ ))
300
+ if diff == 1:
301
+ self.adaptations.append(Adaptation(
302
+ type=AdaptationType.IK_CONSTRAINT,
303
+ description="Lock one joint to compensate for missing DOF",
304
+ config={"fixed_joints": [], "note": "Auto-detect best joint to lock"},
305
+ automated=True
306
+ ))
307
+
308
+ # Check payload
309
+ required_payload = req.constraints.get("payload")
310
+ if required_payload and self.robot.payload:
311
+ # Parse ">=1kg" format
312
+ if isinstance(required_payload, str):
313
+ required_payload = required_payload.replace("kg", "").replace(">=", "")
314
+ try:
315
+ required_payload = float(required_payload)
316
+ except ValueError:
317
+ pass
318
+
319
+ if isinstance(required_payload, (int, float)):
320
+ if self.robot.payload < required_payload:
321
+ self.issues.append(CompatibilityIssue(
322
+ severity=IssueSeverity.WARNING,
323
+ category="hardware",
324
+ message=f"Robot payload ({self.robot.payload}kg) may be insufficient ({required_payload}kg required)",
325
+ mitigation="Reduce object weight or use slower movements"
326
+ ))
327
+ self.adaptations.append(Adaptation(
328
+ type=AdaptationType.SPEED_LIMIT,
329
+ description="Reduce speed to compensate for payload limit",
330
+ config={"max_speed_factor": self.robot.payload / required_payload},
331
+ automated=True
332
+ ))
333
+
334
+ def _check_gripper_requirement(self, req: HardwareRequirement) -> None:
335
+ """Check gripper hardware requirements."""
336
+ if not self.robot.has_gripper:
337
+ self.issues.append(CompatibilityIssue(
338
+ severity=IssueSeverity.ERROR,
339
+ category="hardware",
340
+ message="Skill requires gripper but robot does not have one",
341
+ mitigation="Add a gripper to the robot"
342
+ ))
343
+ return
344
+
345
+ # Check gripper type
346
+ required_type = req.constraints.get("type")
347
+ if required_type and self.robot.gripper_type:
348
+ if required_type != self.robot.gripper_type:
349
+ self.issues.append(CompatibilityIssue(
350
+ severity=IssueSeverity.WARNING,
351
+ category="hardware",
352
+ message=f"Skill expects {required_type} gripper, robot has {self.robot.gripper_type}",
353
+ mitigation="Gripper behavior may need adaptation"
354
+ ))
355
+ self.adaptations.append(Adaptation(
356
+ type=AdaptationType.GRIPPER_ADAPT,
357
+ description=f"Adapt gripper commands from {required_type} to {self.robot.gripper_type}",
358
+ config={"source_type": required_type, "target_type": self.robot.gripper_type},
359
+ automated=True
360
+ ))
361
+
362
+ def _check_force_sensor_requirement(self, req: HardwareRequirement) -> None:
363
+ """Check force sensor requirements."""
364
+ if not self.robot.has_force_sensor:
365
+ if req.optional:
366
+ self.issues.append(CompatibilityIssue(
367
+ severity=IssueSeverity.WARNING,
368
+ category="hardware",
369
+ message="Skill can use force sensor but robot does not have one",
370
+ details="Force-based features will be disabled"
371
+ ))
372
+ else:
373
+ self.issues.append(CompatibilityIssue(
374
+ severity=IssueSeverity.ERROR,
375
+ category="hardware",
376
+ message="Skill requires force sensor but robot does not have one",
377
+ mitigation="Consider using position-based control instead"
378
+ ))
379
+ self.adaptations.append(Adaptation(
380
+ type=AdaptationType.SENSOR_SUBSTITUTE,
381
+ description="Use current-based force estimation instead of F/T sensor",
382
+ config={"method": "current_estimation"},
383
+ automated=False
384
+ ))
385
+
386
+ def _check_camera_requirement(self, req: HardwareRequirement) -> None:
387
+ """Check camera requirements."""
388
+ if not self.robot.has_camera:
389
+ self.issues.append(CompatibilityIssue(
390
+ severity=IssueSeverity.ERROR if not req.optional else IssueSeverity.WARNING,
391
+ category="hardware",
392
+ message="Skill requires camera but robot does not have one",
393
+ mitigation="Add a camera to the robot or provide pre-computed poses"
394
+ ))
395
+
396
+ def _check_mobile_base_requirement(self, req: HardwareRequirement) -> None:
397
+ """Check mobile base requirements."""
398
+ if not self.robot.has_mobile_base:
399
+ self.issues.append(CompatibilityIssue(
400
+ severity=IssueSeverity.ERROR,
401
+ category="hardware",
402
+ message="Skill requires mobile base but robot is stationary",
403
+ mitigation="Skill cannot be executed on stationary robot"
404
+ ))
405
+
406
+ def _check_kinematic_requirements(self) -> None:
407
+ """Check kinematic constraints and workspace."""
408
+ # Check workspace bounds
409
+ if self.spec.workspace_bounds and self.robot.workspace_bounds:
410
+ for axis in ["x", "y", "z"]:
411
+ if axis in self.spec.workspace_bounds and axis in self.robot.workspace_bounds:
412
+ skill_min, skill_max = self.spec.workspace_bounds[axis]
413
+ robot_min, robot_max = self.robot.workspace_bounds[axis]
414
+
415
+ if skill_min < robot_min or skill_max > robot_max:
416
+ self.issues.append(CompatibilityIssue(
417
+ severity=IssueSeverity.WARNING,
418
+ category="kinematics",
419
+ message=f"Skill workspace ({axis}: {skill_min} to {skill_max}) "
420
+ f"exceeds robot workspace ({robot_min} to {robot_max})",
421
+ mitigation=f"Limit {axis} axis to robot range"
422
+ ))
423
+ self.adaptations.append(Adaptation(
424
+ type=AdaptationType.WORKSPACE_LIMIT,
425
+ description=f"Limit {axis} workspace to robot capability",
426
+ config={
427
+ "axis": axis,
428
+ "min": max(skill_min, robot_min),
429
+ "max": min(skill_max, robot_max)
430
+ },
431
+ automated=True
432
+ ))
433
+
434
+ def _check_safety_requirements(self) -> None:
435
+ """Check safety-related constraints."""
436
+ # Check velocity limits
437
+ if self.spec.max_velocity and self.robot.max_cartesian_velocity:
438
+ if self.spec.max_velocity > self.robot.max_cartesian_velocity:
439
+ self.issues.append(CompatibilityIssue(
440
+ severity=IssueSeverity.WARNING,
441
+ category="safety",
442
+ message=f"Skill max velocity ({self.spec.max_velocity}m/s) "
443
+ f"exceeds robot limit ({self.robot.max_cartesian_velocity}m/s)",
444
+ mitigation="Skill will run at reduced speed"
445
+ ))
446
+ self.adaptations.append(Adaptation(
447
+ type=AdaptationType.SPEED_LIMIT,
448
+ description="Cap velocity to robot maximum",
449
+ config={"max_velocity": self.robot.max_cartesian_velocity},
450
+ automated=True
451
+ ))
452
+
453
+ # Check force limits
454
+ if self.spec.max_force and self.robot.max_force:
455
+ if self.spec.max_force > self.robot.max_force:
456
+ self.issues.append(CompatibilityIssue(
457
+ severity=IssueSeverity.WARNING,
458
+ category="safety",
459
+ message=f"Skill max force ({self.spec.max_force}N) "
460
+ f"exceeds robot limit ({self.robot.max_force}N)",
461
+ mitigation="Force-limited operations may fail"
462
+ ))
463
+ self.adaptations.append(Adaptation(
464
+ type=AdaptationType.FORCE_LIMIT,
465
+ description="Cap force commands to robot maximum",
466
+ config={"max_force": self.robot.max_force},
467
+ automated=True
468
+ ))
469
+
470
+ def _check_primitive_support(self) -> None:
471
+ """Check if robot can execute required primitives."""
472
+ from .primitives import PRIMITIVE_REGISTRY, get_required_hardware
473
+
474
+ # Get hardware required by all primitives
475
+ required_hardware = get_required_hardware(self.spec.primitives)
476
+
477
+ # Check each required hardware type
478
+ hardware_map = {
479
+ "arm": self.robot.arm_dof > 0,
480
+ "gripper": self.robot.has_gripper,
481
+ "force_sensor": self.robot.has_force_sensor,
482
+ "camera": self.robot.has_camera,
483
+ "mobile_base": self.robot.has_mobile_base,
484
+ }
485
+
486
+ for hw in required_hardware:
487
+ if hw in hardware_map and not hardware_map.get(hw, False):
488
+ # Find which primitives need this hardware
489
+ prims_needing_hw = [
490
+ p for p in self.spec.primitives
491
+ if p in PRIMITIVE_REGISTRY and hw in PRIMITIVE_REGISTRY[p].hardware
492
+ ]
493
+ self.issues.append(CompatibilityIssue(
494
+ severity=IssueSeverity.ERROR,
495
+ category="primitives",
496
+ message=f"Primitives {prims_needing_hw} require {hw} which robot lacks",
497
+ details=f"Missing hardware: {hw}"
498
+ ))
499
+
500
+ def _calculate_compatibility(self) -> Tuple[bool, float]:
501
+ """
502
+ Calculate overall compatibility score.
503
+
504
+ Returns:
505
+ Tuple of (is_compatible, score)
506
+ """
507
+ # Weight by severity
508
+ severity_weights = {
509
+ IssueSeverity.INFO: 0,
510
+ IssueSeverity.WARNING: 0.1,
511
+ IssueSeverity.ERROR: 0.4,
512
+ IssueSeverity.CRITICAL: 1.0,
513
+ }
514
+
515
+ total_penalty = sum(
516
+ severity_weights.get(issue.severity, 0)
517
+ for issue in self.issues
518
+ )
519
+
520
+ # Cap penalty at 1.0
521
+ score = max(0.0, 1.0 - min(1.0, total_penalty))
522
+
523
+ # Not compatible if any critical or error issues without mitigation
524
+ has_blocking = any(
525
+ issue.severity in (IssueSeverity.CRITICAL, IssueSeverity.ERROR)
526
+ and not issue.mitigation
527
+ for issue in self.issues
528
+ )
529
+
530
+ compatible = not has_blocking and score >= 0.5
531
+
532
+ return compatible, score
533
+
534
+
535
+ def check_compatibility(
536
+ spec: SkillSpecification,
537
+ robot: RobotProfile,
538
+ ) -> CompatibilityReport:
539
+ """
540
+ Convenience function to check skill-robot compatibility.
541
+
542
+ Args:
543
+ spec: Skill specification
544
+ robot: Robot profile
545
+
546
+ Returns:
547
+ CompatibilityReport with results
548
+ """
549
+ checker = CompatibilityChecker(spec, robot)
550
+ return checker.check()
551
+
552
+
553
+ def check_compatibility_from_paths(
554
+ skill_yaml: str,
555
+ robot_urdf: Optional[str] = None,
556
+ robot_ate_dir: Optional[str] = None,
557
+ robot_name: Optional[str] = None,
558
+ ) -> CompatibilityReport:
559
+ """
560
+ Check compatibility from file paths.
561
+
562
+ Args:
563
+ skill_yaml: Path to skill.yaml
564
+ robot_urdf: Path to robot URDF (optional)
565
+ robot_ate_dir: Path to ATE config directory (optional)
566
+ robot_name: Name for the robot profile
567
+
568
+ Returns:
569
+ CompatibilityReport with results
570
+ """
571
+ spec = SkillSpecification.from_yaml(skill_yaml)
572
+
573
+ if robot_urdf:
574
+ robot = RobotProfile.from_urdf(robot_urdf, robot_name)
575
+ elif robot_ate_dir:
576
+ robot = RobotProfile.from_ate_dir(robot_ate_dir, robot_name)
577
+ else:
578
+ robot = RobotProfile(name=robot_name or "unknown")
579
+
580
+ return check_compatibility(spec, robot)
@@ -0,0 +1,19 @@
1
+ """
2
+ Skill Compiler Generators.
3
+
4
+ This module provides code generators for transforming skill specifications
5
+ into deployable packages for various target platforms.
6
+ """
7
+
8
+ from .skill_generator import SkillCodeGenerator
9
+ from .ros2_generator import ROS2PackageGenerator
10
+ from .docker_generator import DockerGenerator
11
+ from .hardware_config import HardwareConfigGenerator, generate_hardware_config
12
+
13
+ __all__ = [
14
+ "SkillCodeGenerator",
15
+ "ROS2PackageGenerator",
16
+ "DockerGenerator",
17
+ "HardwareConfigGenerator",
18
+ "generate_hardware_config",
19
+ ]