{
  "openapi": "3.0.3",
  "info": {
    "title": "Swim ProHub 360 API",
    "description": "REST API for swimming video analysis, stroke detection, and AI coaching.\n\n## Overview\nThis API allows you to:\n- Upload swimming videos for AI-powered stroke analysis\n- Get detailed metrics including stroke rate, SWOLF score, DPS, and more\n- Compare two videos to track improvement\n- Chat with an AI coach about your swimming technique\n\n## Authentication\nCurrently, the API does not require authentication. Rate limiting is applied to prevent abuse.\n\n## Rate Limits\n- Video upload: 10 requests per hour\n- Chat: 30 requests per hour\n- Comparison: 5 requests per hour\n\n## Flutter Integration\nSee the `x-flutter-examples` extension in each endpoint for Dart code samples.",
    "version": "1.1.0",
    "contact": {
      "name": "Swim ProHub 360 Support",
      "email": "support@swimprohub360.com"
    },
    "license": {
      "name": "MIT",
      "url": "https://opensource.org/licenses/MIT"
    }
  },
  "servers": [
    {
      "url": "http://localhost:5001",
      "description": "Local development server"
    },
    {
      "url": "https://api.swimprohub360.com",
      "description": "Production server"
    }
  ],
  "tags": [
    {
      "name": "Health",
      "description": "Service health and status endpoints"
    },
    {
      "name": "Video Analysis",
      "description": "Upload and analyze swimming videos"
    },
    {
      "name": "Jobs",
      "description": "Manage analysis jobs"
    },
    {
      "name": "Comparison",
      "description": "Compare two swimming videos"
    },
    {
      "name": "AI Coach",
      "description": "Chat with AI swimming coach"
    }
  ],
  "paths": {
    "/api/health": {
      "get": {
        "tags": ["Health"],
        "summary": "Health check",
        "description": "Check the health status of the API and its dependencies (database, LLM service, storage).",
        "operationId": "healthCheck",
        "responses": {
          "200": {
            "description": "Service is healthy",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthResponse"
                },
                "example": {
                  "status": "healthy",
                  "service": "Swimming Video Analysis API",
                  "version": "1.1.0",
                  "database": "connected",
                  "llm": {
                    "provider": "gemini",
                    "status": "configured"
                  },
                  "upload_folder": "writable",
                  "results_folder": "writable"
                }
              }
            }
          },
          "503": {
            "description": "Service is unhealthy",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthResponse"
                }
              }
            }
          }
        },
        "x-flutter-example": "// Dart/Flutter Example\nfinal response = await http.get(\n  Uri.parse('$baseUrl/api/health'),\n);\nif (response.statusCode == 200) {\n  final health = jsonDecode(response.body);\n  print('API Status: ${health['status']}');\n}"
      }
    },
    "/api/info": {
      "get": {
        "tags": ["Health"],
        "summary": "API information",
        "description": "Get API version and available endpoints.",
        "operationId": "getApiInfo",
        "responses": {
          "200": {
            "description": "API information",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ApiInfo"
                }
              }
            }
          }
        }
      }
    },
    "/api/analyze": {
      "post": {
        "tags": ["Video Analysis"],
        "summary": "Upload and analyze a swimming video",
        "description": "Upload a swimming video file for AI-powered stroke analysis. The analysis runs asynchronously - use the returned job_id to poll for status and results.\n\n**Supported formats:** MP4, MOV, AVI\n**Max file size:** 500MB",
        "operationId": "analyzeVideo",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": ["video"],
                "properties": {
                  "video": {
                    "type": "string",
                    "format": "binary",
                    "description": "Video file to analyze (MP4, MOV, or AVI)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Video upload accepted, analysis started",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AnalyzeResponse"
                },
                "example": {
                  "job_id": "e8e89a24-2f99-4fd3-b0e6-64594e1e271a",
                  "status": "processing",
                  "message": "Video uploaded successfully. Analysis started.",
                  "status_url": "/api/jobs/e8e89a24-2f99-4fd3-b0e6-64594e1e271a/status",
                  "result_url": "/api/jobs/e8e89a24-2f99-4fd3-b0e6-64594e1e271a/result"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request (missing file, invalid format, etc.)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "413": {
            "description": "File too large",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-flutter-example": "// Dart/Flutter Example - Upload Video\nimport 'package:http/http.dart' as http;\nimport 'package:http_parser/http_parser.dart';\n\nFuture<String> uploadVideo(File videoFile) async {\n  var request = http.MultipartRequest(\n    'POST',\n    Uri.parse('$baseUrl/api/analyze'),\n  );\n  \n  request.files.add(await http.MultipartFile.fromPath(\n    'video',\n    videoFile.path,\n    contentType: MediaType('video', 'mp4'),\n  ));\n  \n  final response = await request.send();\n  final body = await response.stream.bytesToString();\n  \n  if (response.statusCode == 202) {\n    final data = jsonDecode(body);\n    return data['job_id'];\n  } else {\n    throw Exception('Upload failed: $body');\n  }\n}"
      }
    },
    "/api/jobs": {
      "get": {
        "tags": ["Jobs"],
        "summary": "List all analysis jobs",
        "description": "Get a paginated list of all video analysis jobs with their status and metrics.",
        "operationId": "listJobs",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "description": "Page number (1-indexed)",
            "schema": {
              "type": "integer",
              "default": 1,
              "minimum": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "description": "Items per page",
            "schema": {
              "type": "integer",
              "default": 20,
              "minimum": 1,
              "maximum": 100
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Filter by job status",
            "schema": {
              "type": "string",
              "enum": ["pending", "processing", "complete", "error"]
            }
          },
          {
            "name": "include_messages",
            "in": "query",
            "description": "Include chat messages in response",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of jobs",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobListResponse"
                }
              }
            }
          }
        },
        "x-flutter-example": "// Dart/Flutter Example - List Jobs\nFuture<List<Job>> getJobs({int page = 1, String? status}) async {\n  final uri = Uri.parse('$baseUrl/api/jobs').replace(\n    queryParameters: {\n      'page': page.toString(),\n      if (status != null) 'status': status,\n    },\n  );\n  \n  final response = await http.get(uri);\n  final data = jsonDecode(response.body);\n  \n  return (data['jobs'] as List)\n    .map((j) => Job.fromJson(j))\n    .toList();\n}"
      }
    },
    "/api/jobs/{job_id}/status": {
      "get": {
        "tags": ["Jobs"],
        "summary": "Get job status",
        "description": "Get the current status and progress of an analysis job. Use this to poll for completion.",
        "operationId": "getJobStatus",
        "parameters": [
          {
            "name": "job_id",
            "in": "path",
            "required": true,
            "description": "Job UUID",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Job status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobStatus"
                },
                "example": {
                  "job_id": "e8e89a24-2f99-4fd3-b0e6-64594e1e271a",
                  "status": "processing",
                  "progress": 45,
                  "original_filename": "swim_video.mp4",
                  "created_at": "2026-01-06T11:59:07",
                  "logs": [
                    "[11:59:06] Upload received: swim_video.mp4",
                    "[11:59:07] Processing started",
                    "[11:59:10] Detecting stroke style..."
                  ]
                }
              }
            }
          },
          "404": {
            "description": "Job not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-flutter-example": "// Dart/Flutter Example - Poll Job Status\nFuture<JobStatus> pollJobStatus(String jobId) async {\n  final response = await http.get(\n    Uri.parse('$baseUrl/api/jobs/$jobId/status'),\n  );\n  \n  if (response.statusCode == 200) {\n    return JobStatus.fromJson(jsonDecode(response.body));\n  }\n  throw Exception('Failed to get status');\n}\n\n// Poll with exponential backoff\nFuture<JobResult> waitForCompletion(String jobId) async {\n  int delay = 1000; // Start with 1 second\n  const maxDelay = 10000; // Max 10 seconds\n  \n  while (true) {\n    final status = await pollJobStatus(jobId);\n    \n    if (status.status == 'complete') {\n      return await getJobResult(jobId);\n    } else if (status.status == 'error') {\n      throw Exception(status.error);\n    }\n    \n    await Future.delayed(Duration(milliseconds: delay));\n    delay = (delay * 1.5).clamp(1000, maxDelay).toInt();\n  }\n}"
      }
    },
    "/api/jobs/{job_id}/result": {
      "get": {
        "tags": ["Jobs"],
        "summary": "Get analysis results",
        "description": "Get the complete analysis results including all metrics, stroke detection, and lap analysis.",
        "operationId": "getJobResult",
        "parameters": [
          {
            "name": "job_id",
            "in": "path",
            "required": true,
            "description": "Job UUID",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Analysis results",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobResult"
                }
              }
            }
          },
          "202": {
            "description": "Job still processing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobStatus"
                }
              }
            }
          },
          "404": {
            "description": "Job not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Job failed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-flutter-example": "// Dart/Flutter Example - Get Results\nFuture<AnalysisResult> getJobResult(String jobId) async {\n  final response = await http.get(\n    Uri.parse('$baseUrl/api/jobs/$jobId/result'),\n  );\n  \n  if (response.statusCode == 200) {\n    final data = jsonDecode(response.body);\n    return AnalysisResult.fromJson(data);\n  } else if (response.statusCode == 202) {\n    throw StillProcessingException();\n  }\n  throw Exception('Failed to get result');\n}"
      }
    },
    "/api/jobs/{job_id}": {
      "delete": {
        "tags": ["Jobs"],
        "summary": "Delete a job",
        "description": "Delete an analysis job and its associated video files.",
        "operationId": "deleteJob",
        "parameters": [
          {
            "name": "job_id",
            "in": "path",
            "required": true,
            "description": "Job UUID",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Job deleted successfully",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string",
                      "example": "Job deleted successfully"
                    },
                    "job_id": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "Job not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-flutter-example": "// Dart/Flutter Example - Delete Job\nFuture<void> deleteJob(String jobId) async {\n  final response = await http.delete(\n    Uri.parse('$baseUrl/api/jobs/$jobId'),\n  );\n  \n  if (response.statusCode != 200) {\n    throw Exception('Failed to delete job');\n  }\n}"
      }
    },
    "/api/jobs/{job_id}/chat": {
      "post": {
        "tags": ["AI Coach"],
        "summary": "Chat with AI coach",
        "description": "Ask the AI swimming coach questions about your analysis results. The coach has access to your metrics and can provide personalized advice.",
        "operationId": "chatWithCoach",
        "parameters": [
          {
            "name": "job_id",
            "in": "path",
            "required": true,
            "description": "Job UUID (must be a completed analysis)",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": ["message"],
                "properties": {
                  "message": {
                    "type": "string",
                    "description": "Your question or message to the AI coach",
                    "minLength": 1,
                    "maxLength": 1000,
                    "example": "How can I improve my stroke rate?"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "AI coach response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ChatResponse"
                },
                "example": {
                  "message": "How can I improve my stroke rate?",
                  "response": "Based on your analysis, your stroke rate is 45 strokes per minute, which is within the ideal range for butterfly (40-60 spm). However, I notice your DPS (distance per stroke) is 0.58m, which is below the ideal 1.6-2.2m range.\n\n**Key recommendations:**\n1. Focus on a longer glide phase rather than increasing tempo\n2. Work on your catch phase to maximize pull efficiency\n3. Practice underwater dolphin kicks to improve breakout distance\n\nWould you like specific drills for any of these areas?",
                  "chat_history": [
                    {"role": "user", "content": "How can I improve my stroke rate?"},
                    {"role": "assistant", "content": "Based on your analysis..."}
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Invalid request or job not complete",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "Job not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-flutter-example": "// Dart/Flutter Example - Chat with AI Coach\nFuture<ChatResponse> askCoach(String jobId, String message) async {\n  final response = await http.post(\n    Uri.parse('$baseUrl/api/jobs/$jobId/chat'),\n    headers: {'Content-Type': 'application/json'},\n    body: jsonEncode({'message': message}),\n  );\n  \n  if (response.statusCode == 200) {\n    return ChatResponse.fromJson(jsonDecode(response.body));\n  }\n  throw Exception('Chat failed');\n}\n\n// Usage in a chat UI\nclass ChatScreen extends StatefulWidget {\n  final String jobId;\n  // ...\n}\n\nclass _ChatScreenState extends State<ChatScreen> {\n  final List<ChatMessage> messages = [];\n  final TextEditingController _controller = TextEditingController();\n  \n  Future<void> _sendMessage() async {\n    final text = _controller.text.trim();\n    if (text.isEmpty) return;\n    \n    setState(() {\n      messages.add(ChatMessage(role: 'user', content: text));\n    });\n    _controller.clear();\n    \n    try {\n      final response = await askCoach(widget.jobId, text);\n      setState(() {\n        messages.add(ChatMessage(\n          role: 'assistant',\n          content: response.response,\n        ));\n      });\n    } catch (e) {\n      // Handle error\n    }\n  }\n}"
      }
    },
    "/api/compare": {
      "post": {
        "tags": ["Comparison"],
        "summary": "Compare two swimming videos",
        "description": "Upload two swimming videos to compare technique and track improvement. Video 1 is the baseline, Video 2 is the comparison.",
        "operationId": "compareVideos",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": ["video1", "video2"],
                "properties": {
                  "video1": {
                    "type": "string",
                    "format": "binary",
                    "description": "First video (baseline)"
                  },
                  "video2": {
                    "type": "string",
                    "format": "binary",
                    "description": "Second video (comparison)"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Comparison started",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CompareResponse"
                },
                "example": {
                  "comparison_id": "abc123-def456",
                  "job_id_1": "video1-uuid",
                  "job_id_2": "video2-uuid",
                  "message": "Comparison started",
                  "status": "processing",
                  "status_url": "/api/compare/abc123-def456/status",
                  "result_url": "/api/compare/abc123-def456/result"
                }
              }
            }
          },
          "400": {
            "description": "Invalid request",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-flutter-example": "// Dart/Flutter Example - Compare Two Videos\nFuture<CompareResponse> compareVideos(File video1, File video2) async {\n  var request = http.MultipartRequest(\n    'POST',\n    Uri.parse('$baseUrl/api/compare'),\n  );\n  \n  request.files.add(await http.MultipartFile.fromPath(\n    'video1',\n    video1.path,\n    contentType: MediaType('video', 'mp4'),\n  ));\n  \n  request.files.add(await http.MultipartFile.fromPath(\n    'video2',\n    video2.path,\n    contentType: MediaType('video', 'mp4'),\n  ));\n  \n  final response = await request.send();\n  final body = await response.stream.bytesToString();\n  \n  if (response.statusCode == 202) {\n    return CompareResponse.fromJson(jsonDecode(body));\n  }\n  throw Exception('Comparison failed');\n}"
      }
    },
    "/api/compare/{comparison_id}/status": {
      "get": {
        "tags": ["Comparison"],
        "summary": "Get comparison status",
        "description": "Get the current status and progress of a comparison job.",
        "operationId": "getComparisonStatus",
        "parameters": [
          {
            "name": "comparison_id",
            "in": "path",
            "required": true,
            "description": "Comparison UUID",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Comparison status",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ComparisonStatus"
                }
              }
            }
          },
          "404": {
            "description": "Comparison not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/api/compare/{comparison_id}/result": {
      "get": {
        "tags": ["Comparison"],
        "summary": "Get comparison results",
        "description": "Get the complete comparison results including metric differences and improvement analysis.",
        "operationId": "getComparisonResult",
        "parameters": [
          {
            "name": "comparison_id",
            "in": "path",
            "required": true,
            "description": "Comparison UUID",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Comparison results",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ComparisonResult"
                }
              }
            }
          },
          "202": {
            "description": "Comparison still processing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ComparisonStatus"
                }
              }
            }
          },
          "404": {
            "description": "Comparison not found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "x-flutter-example": "// Dart/Flutter Example - Get Comparison Result\nFuture<ComparisonResult> getComparisonResult(String comparisonId) async {\n  final response = await http.get(\n    Uri.parse('$baseUrl/api/compare/$comparisonId/result'),\n  );\n  \n  if (response.statusCode == 200) {\n    return ComparisonResult.fromJson(jsonDecode(response.body));\n  } else if (response.statusCode == 202) {\n    throw StillProcessingException();\n  }\n  throw Exception('Failed to get comparison');\n}"
      }
    },
    "/api/comparisons": {
      "get": {
        "tags": ["Comparison"],
        "summary": "List all comparisons",
        "description": "Get a paginated list of all video comparison jobs.",
        "operationId": "listComparisons",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1
            }
          },
          {
            "name": "per_page",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "List of comparisons",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ComparisonListResponse"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "HealthResponse": {
        "type": "object",
        "properties": {
          "status": {
            "type": "string",
            "enum": ["healthy", "unhealthy"]
          },
          "service": {
            "type": "string"
          },
          "version": {
            "type": "string"
          },
          "database": {
            "type": "string",
            "enum": ["connected", "disconnected"]
          },
          "llm": {
            "type": "object",
            "properties": {
              "provider": {
                "type": "string"
              },
              "status": {
                "type": "string"
              }
            }
          },
          "upload_folder": {
            "type": "string"
          },
          "results_folder": {
            "type": "string"
          }
        }
      },
      "ApiInfo": {
        "type": "object",
        "properties": {
          "service": {
            "type": "string"
          },
          "version": {
            "type": "string"
          },
          "endpoints": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            }
          }
        }
      },
      "Error": {
        "type": "object",
        "required": ["error", "message"],
        "properties": {
          "error": {
            "type": "string",
            "description": "Error type"
          },
          "message": {
            "type": "string",
            "description": "Human-readable error message"
          }
        }
      },
      "AnalyzeResponse": {
        "type": "object",
        "properties": {
          "job_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "status_url": {
            "type": "string"
          },
          "result_url": {
            "type": "string"
          }
        }
      },
      "JobStatus": {
        "type": "object",
        "properties": {
          "job_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": ["pending", "processing", "complete", "error"]
          },
          "progress": {
            "type": "integer",
            "minimum": 0,
            "maximum": 100
          },
          "original_filename": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "started_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "completed_at": {
            "type": "string",
            "format": "date-time",
            "nullable": true
          },
          "logs": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "error": {
            "type": "string",
            "nullable": true
          }
        }
      },
      "JobResult": {
        "type": "object",
        "properties": {
          "job_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string"
          },
          "original_filename": {
            "type": "string"
          },
          "metrics": {
            "$ref": "#/components/schemas/SwimmingMetrics"
          },
          "result": {
            "type": "object",
            "properties": {
              "metrics": {
                "$ref": "#/components/schemas/SwimmingMetrics"
              },
              "original_video": {
                "type": "string"
              },
              "processed_video": {
                "type": "string"
              },
              "video_url": {
                "type": "string"
              },
              "processed_video_url": {
                "type": "string"
              }
            }
          },
          "processing_time_seconds": {
            "type": "number"
          }
        }
      },
      "SwimmingMetrics": {
        "type": "object",
        "description": "Swimming analysis metrics",
        "properties": {
          "stroke_rate": {
            "type": "number",
            "description": "Strokes per minute"
          },
          "stroke_count": {
            "type": "integer",
            "description": "Total number of strokes"
          },
          "dps": {
            "type": "number",
            "description": "Distance per stroke in meters"
          },
          "speed": {
            "type": "number",
            "description": "Average speed in meters per minute"
          },
          "swolf": {
            "type": "number",
            "description": "SWOLF score (strokes + time)"
          },
          "duration_sec": {
            "type": "number",
            "description": "Video duration in seconds"
          },
          "avg_body_angle": {
            "type": "number",
            "description": "Average body angle deviation in degrees"
          },
          "body_rotation": {
            "type": "number",
            "description": "Body rotation in degrees"
          },
          "crossover_score": {
            "type": "number",
            "description": "Hand crossover score"
          },
          "kick_rhythm": {
            "type": "number",
            "description": "Kick rhythm in kicks per minute"
          },
          "stroke_detection": {
            "type": "object",
            "properties": {
              "detected_stroke": {
                "type": "string",
                "enum": ["freestyle", "backstroke", "breaststroke", "butterfly", "unknown"]
              },
              "confidence": {
                "type": "number"
              },
              "all_scores": {
                "type": "object",
                "additionalProperties": {
                  "type": "number"
                }
              }
            }
          },
          "lap_analysis": {
            "type": "object",
            "properties": {
              "lap_count": {
                "type": "integer"
              },
              "total_turns": {
                "type": "integer"
              },
              "laps": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "lap_number": {
                      "type": "integer"
                    },
                    "duration_sec": {
                      "type": "number"
                    },
                    "stroke_count": {
                      "type": "integer"
                    },
                    "avg_speed_m_min": {
                      "type": "number"
                    }
                  }
                }
              }
            }
          },
          "benchmarks": {
            "type": "object",
            "properties": {
              "ideal_stroke_rate": {
                "type": "array",
                "items": {
                  "type": "number"
                }
              },
              "ideal_dps": {
                "type": "array",
                "items": {
                  "type": "number"
                }
              },
              "common_faults": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "key_focus_areas": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "JobListResponse": {
        "type": "object",
        "properties": {
          "jobs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/JobResult"
            }
          },
          "total": {
            "type": "integer"
          },
          "pages": {
            "type": "integer"
          },
          "current_page": {
            "type": "integer"
          },
          "per_page": {
            "type": "integer"
          }
        }
      },
      "ChatResponse": {
        "type": "object",
        "properties": {
          "message": {
            "type": "string",
            "description": "The user's message"
          },
          "response": {
            "type": "string",
            "description": "AI coach's response"
          },
          "chat_history": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "role": {
                  "type": "string",
                  "enum": ["user", "assistant"]
                },
                "content": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "CompareResponse": {
        "type": "object",
        "properties": {
          "comparison_id": {
            "type": "string",
            "format": "uuid"
          },
          "job_id_1": {
            "type": "string",
            "format": "uuid"
          },
          "job_id_2": {
            "type": "string",
            "format": "uuid"
          },
          "message": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "status_url": {
            "type": "string"
          },
          "result_url": {
            "type": "string"
          }
        }
      },
      "ComparisonStatus": {
        "type": "object",
        "properties": {
          "comparison_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": ["pending", "processing", "complete", "error"]
          },
          "progress": {
            "type": "integer"
          },
          "video1": {
            "type": "object",
            "properties": {
              "job_id": {
                "type": "string"
              },
              "filename": {
                "type": "string"
              }
            }
          },
          "video2": {
            "type": "object",
            "properties": {
              "job_id": {
                "type": "string"
              },
              "filename": {
                "type": "string"
              }
            }
          },
          "logs": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "ComparisonResult": {
        "type": "object",
        "properties": {
          "comparison_id": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "overall_score": {
            "type": "number",
            "description": "Overall comparison score (0-100)"
          },
          "improvement_score": {
            "type": "number",
            "description": "Improvement percentage (negative means regression)"
          },
          "comparison": {
            "type": "object",
            "properties": {
              "differences": {
                "type": "object",
                "additionalProperties": {
                  "type": "object",
                  "properties": {
                    "video1": {
                      "type": "number"
                    },
                    "video2": {
                      "type": "number"
                    },
                    "absolute_diff": {
                      "type": "number"
                    },
                    "percent_change": {
                      "type": "number"
                    },
                    "direction": {
                      "type": "string",
                      "enum": ["higher", "lower", "optimal"]
                    },
                    "unit": {
                      "type": "string"
                    }
                  }
                }
              },
              "improvements": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "regressions": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "summary": {
                "type": "string"
              }
            }
          },
          "video1": {
            "type": "object"
          },
          "video2": {
            "type": "object"
          }
        }
      },
      "ComparisonListResponse": {
        "type": "object",
        "properties": {
          "comparisons": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ComparisonStatus"
            }
          },
          "total": {
            "type": "integer"
          },
          "pages": {
            "type": "integer"
          },
          "current_page": {
            "type": "integer"
          }
        }
      }
    }
  }
}
