from __future__ import annotations from datetime import datetime, timezone import os from pathlib import Path from typing import Any, Callable from uuid import uuid4 from pozify.artifacts import write_json from pozify.contracts import UserProfile, Verification, to_dict from pozify.env import env_truthy, load_local_env from pozify.exercises import create_exercise_strategy from pozify.knowledge_cards import retrieve_cards_with_metadata from pozify.steps import ( annotated_renderer, coach_summary, coach_summary_fallback, exercise_classifier, pose_cleaning, pose_landmarker, verifier, video_qc, ) RUNS_DIR = Path("runs") ProgressCallback = Callable[[dict[str, Any]], None] BYPASS_VERIFIER_ENV = "POZIFY_COACH_SUMMARY_BYPASS_VERIFIER" DEFAULT_BYPASS_VERIFIER = True def _bypass_verifier_enabled(requested: bool | None) -> bool: if requested is not None: return requested configured = os.getenv(BYPASS_VERIFIER_ENV) if configured is None: return DEFAULT_BYPASS_VERIFIER return env_truthy(configured) def _disabled_verification() -> Verification: return Verification( passed=True, checks={"verifier_disabled": True}, notes=["Coach summary verifier is disabled for this run."], ) def _env_mock_mode(video_path: str | None) -> bool: configured = os.getenv("POZIFY_MOCK_MODE") if configured is None: return video_path is None value = configured.strip().lower() return value not in {"0", "false", "no", "off"} def run_pipeline( video_path: str | None, profile_input: dict[str, Any], *, mock: bool | None = None, bypass_verifier: bool | None = None, progress: ProgressCallback | None = None, ) -> dict[str, Any]: load_local_env() mock_mode = _env_mock_mode(video_path) if mock is None else mock bypass_verifier_enabled = _bypass_verifier_enabled(bypass_verifier) run_id = f"{datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%SZ')}-{uuid4().hex[:8]}" run_dir = RUNS_DIR / run_id artifact_index: list[dict[str, str]] = [] def write_artifact(filename: str, payload: Any) -> None: path = write_json(run_dir, filename, payload) artifact_index.append( { "name": filename, "path": str(path), "contract": filename, } ) def emit(step: str, status: str, text: str, **payload: Any) -> None: if progress is None: return progress( { "type": "progress", "step": step, "status": status, "text": text, "payload": payload, } ) profile = UserProfile( goal=profile_input["goal"], experience_level=profile_input["experience_level"], intended_exercise=profile_input.get("intended_exercise", "auto"), intended_variation=profile_input.get("intended_variation"), known_limitations=profile_input.get("known_limitations", []), equipment=profile_input.get("equipment", "unknown"), ) write_artifact("user_profile.json", profile) emit( "quality", "active", "First up, I am checking if the video is clear enough to coach from.", ) manifest = video_qc.run(video_path) write_artifact("video_manifest.json", manifest) emit( "quality", "done", ( "Quick note: the video has a few things to watch." if manifest.quality_warnings else "Nice, your video quality looks solid." ), warnings=manifest.quality_warnings, analysis_allowed=manifest.analysis_allowed, ) emit( "pose", "active", "Now I am mapping your posture and tracking the key body landmarks.", ) pose_sequence = pose_landmarker.run(manifest, mock=mock_mode) cleaned_pose_sequence = pose_cleaning.run(pose_sequence) write_artifact("pose_sequence.json", cleaned_pose_sequence) pose_source = ( cleaned_pose_sequence.frames[0].pose_quality.get("source") if cleaned_pose_sequence.frames else "none" ) emit( "pose", "done", "Posture tracking is done. I found the key landmarks I need.", frame_count=len(cleaned_pose_sequence.frames), pose_source=pose_source, pose_valid_ratio=cleaned_pose_sequence.pose_valid_ratio, ) emit("exercise", "active", "Let me figure out which exercise you are doing.") classification = exercise_classifier.run(cleaned_pose_sequence, profile, mock=mock_mode) write_artifact("exercise_classification.json", classification) emit( "exercise", "done", f"Looks like you are doing {classification.exercise.replace('_', ' ')}.", exercise=classification.exercise, confidence=classification.confidence, ) exercise = create_exercise_strategy( classification.exercise, video_manifest=manifest, pose_sequence=cleaned_pose_sequence, profile=profile, ) emit("reps", "active", "Counting your reps now. One clean rep at a time.") reps, rep_debug = exercise.count() write_artifact("reps.json", reps) write_artifact("rep_debug.json", rep_debug) emit( "reps", "done", ( f"I counted {len(reps.reps)} {classification.exercise.replace('_', ' ')} " "reps in this set." ), rep_count=len(reps.reps), exercise=classification.exercise, ) emit( "issues", "active", "Almost there. I am checking the moments that may need a small fix.", ) analysis = exercise.analyze_reps(reps) write_artifact("rep_analysis.json", analysis) variation = exercise.resolve_variation(analysis) write_artifact("variation.json", variation) issues = exercise.mark_issues(reps, analysis, variation) write_artifact("issue_markers.json", issues) emit( "issues", "done", ( f"I found {len(issues.issues)} coaching point" f"{'' if len(issues.issues) == 1 else 's'} worth reviewing." if issues.issues else "Good news, I did not spot any clear form issues in this set." ), issue_count=len(issues.issues), ) emit("render", "active", "I am preparing your annotated video and issue clips.") render_artifacts = annotated_renderer.run( manifest, cleaned_pose_sequence, reps, issues, run_dir, ) emit( "render", "done", ( "Your annotated video is ready." if render_artifacts.annotated_video_path else "I could not render an annotated video, but the report is ready." ), annotated_video_path=render_artifacts.annotated_video_path, issue_clip_count=len(render_artifacts.issue_clip_paths), ) emit( "coach", "active", "I am turning the scan into coaching notes you can use right away.", ) analysis_mode = "mock" if mock_mode else "real" mock_steps = ["coach_summary"] if not bypass_verifier_enabled: mock_steps.append("verifier") if mock_mode: mock_steps.insert(0, "exercise_classifier") knowledge_retrieval = retrieve_cards_with_metadata( profile=profile, classification=classification, variation=variation, issues=issues, ) summary_cards = knowledge_retrieval.cards coach_result = coach_summary.run_with_metadata( profile, classification, reps, analysis, variation, issues, cards=summary_cards, ) summary = coach_result.summary coach_summary_source = coach_result.source coach_summary_provider = coach_result.provider coach_summary_model = coach_result.model coach_summary_verifier_bypassed = bypass_verifier_enabled if bypass_verifier_enabled: verification = _disabled_verification() else: verification = verifier.run( summary, issues, variation, classification=classification, analysis=analysis, reps=reps, ) if not verification.passed: summary = coach_summary_fallback.build_fallback_summary( profile=profile, classification=classification, reps=reps, analysis=analysis, variation=variation, issues=issues, cards=summary_cards, failure_reason="; ".join(verification.notes) or "verification_failed", ) coach_summary_source = "fallback_after_verification" coach_summary_provider = coach_result.provider coach_summary_model = coach_result.model verification = verifier.run( summary, issues, variation, classification=classification, analysis=analysis, reps=reps, ) write_artifact("coach_summary.json", summary) write_artifact("verification.json", verification) emit( "coach", "done", "Coach notes are ready.", verification_passed=verification.passed, ) final_report = { "run_id": run_id, "profile": to_dict(profile), "video_manifest": to_dict(manifest), "exercise": to_dict(classification), "reps": to_dict(reps), "rep_analysis": to_dict(analysis), "variation": to_dict(variation), "issue_markers": to_dict(issues), "coach_summary": to_dict(summary), "verification": to_dict(verification), "artifacts": { "run_dir": str(run_dir), "annotated_video_path": render_artifacts.annotated_video_path, "issue_thumbnail_paths": render_artifacts.issue_thumbnail_paths, "issue_clip_paths": render_artifacts.issue_clip_paths, "rep_debug_path": str(run_dir / "rep_debug.json"), "analysis_mode": analysis_mode, "pose_source": pose_source, "mock_steps": mock_steps, "coach_summary_source": coach_summary_source, "coach_summary_provider": coach_summary_provider, "coach_summary_model": coach_summary_model, "coach_summary_verifier_bypassed": coach_summary_verifier_bypassed, "coach_summary_verifier_bypass_requested": bypass_verifier_enabled, "knowledge_card_pack_paths": list(knowledge_retrieval.loaded_pack_paths), "knowledge_external_cards_loaded": knowledge_retrieval.external_cards_loaded, "knowledge_external_cards_retrieved": knowledge_retrieval.external_cards_retrieved, }, } write_artifact("final_report.json", final_report) run_manifest = { "run_id": run_id, "mock_mode": mock_mode, "artifacts": artifact_index, } write_json(run_dir, "manifest.json", run_manifest) return { "run_id": run_id, "run_dir": str(run_dir), "annotated_video_path": render_artifacts.annotated_video_path, "issue_thumbnail_paths": render_artifacts.issue_thumbnail_paths, "issue_clip_paths": render_artifacts.issue_clip_paths, "manifest_path": str(run_dir / "manifest.json"), "final_report": final_report, }