chore(pipeline): improve pipeline architecture

- Add capability-based dependency resolution with prefix matching
- Add EffectPluginStage with sensor binding support
- Add CameraStage adapter for camera integration
- Add DisplayStage adapter for display integration
- Add Pipeline metrics collection
- Add deprecation notices to legacy modules
- Update app.py with pipeline integration
This commit is contained in:
2026-03-16 13:56:22 -07:00
parent 1a42fca507
commit ce9d888cf5
11 changed files with 328 additions and 32 deletions

View File

@@ -83,12 +83,60 @@ class Pipeline:
def build(self) -> "Pipeline":
"""Build execution order based on dependencies."""
self._capability_map = self._build_capability_map()
self._execution_order = self._resolve_dependencies()
self._validate_dependencies()
self._initialized = True
return self
def _build_capability_map(self) -> dict[str, list[str]]:
"""Build a map of capabilities to stage names.
Returns:
Dict mapping capability -> list of stage names that provide it
"""
capability_map: dict[str, list[str]] = {}
for name, stage in self._stages.items():
for cap in stage.capabilities:
if cap not in capability_map:
capability_map[cap] = []
capability_map[cap].append(name)
return capability_map
def _find_stage_with_capability(self, capability: str) -> str | None:
"""Find a stage that provides the given capability.
Supports wildcard matching:
- "source" matches "source.headlines" (prefix match)
- "source.*" matches "source.headlines"
- "source.headlines" matches exactly
Args:
capability: The capability to find
Returns:
Stage name that provides the capability, or None if not found
"""
# Exact match
if capability in self._capability_map:
return self._capability_map[capability][0]
# Prefix match (e.g., "source" -> "source.headlines")
for cap, stages in self._capability_map.items():
if cap.startswith(capability + "."):
return stages[0]
# Wildcard match (e.g., "source.*" -> "source.headlines")
if ".*" in capability:
prefix = capability[:-2] # Remove ".*"
for cap in self._capability_map:
if cap.startswith(prefix + "."):
return self._capability_map[cap][0]
return None
def _resolve_dependencies(self) -> list[str]:
"""Resolve stage execution order using topological sort."""
"""Resolve stage execution order using topological sort with capability matching."""
ordered = []
visited = set()
temp_mark = set()
@@ -103,9 +151,10 @@ class Pipeline:
stage = self._stages.get(name)
if stage:
for dep in stage.dependencies:
dep_stage = self._stages.get(dep)
if dep_stage:
visit(dep)
# Find a stage that provides this capability
dep_stage_name = self._find_stage_with_capability(dep)
if dep_stage_name:
visit(dep_stage_name)
temp_mark.remove(name)
visited.add(name)
@@ -117,6 +166,25 @@ class Pipeline:
return ordered
def _validate_dependencies(self) -> None:
"""Validate that all dependencies can be satisfied.
Raises StageError if any dependency cannot be resolved.
"""
missing: list[tuple[str, str]] = [] # (stage_name, capability)
for name, stage in self._stages.items():
for dep in stage.dependencies:
if not self._find_stage_with_capability(dep):
missing.append((name, dep))
if missing:
msgs = [f" - {stage} needs {cap}" for stage, cap in missing]
raise StageError(
"validation",
"Missing capabilities:\n" + "\n".join(msgs),
)
def initialize(self) -> bool:
"""Initialize all stages in execution order."""
for name in self._execution_order: