Skip to content

added the the flask server code and the updated flutter code#84

Merged
MasterAffan merged 3 commits intoMasterAffan:mainfrom
sandy4242:main
Oct 31, 2025
Merged

added the the flask server code and the updated flutter code#84
MasterAffan merged 3 commits intoMasterAffan:mainfrom
sandy4242:main

Conversation

@sandy4242
Copy link
Contributor

@sandy4242 sandy4242 commented Oct 24, 2025

User description

Description

This pr adds a functionality on pushup detection within the flutter app

Related Issue

Closes #15

Type of Change

  • New feature

How Has This Been Tested?

Explain the tests you ran and how reviewers can verify the changes.

Screenshots (if applicable)

Checklist

  • My code follows the project’s style guidelines
  • I have performed a self-review of my code
  • I have commented my code where necessary
  • I have added/updated tests (if applicable)
  • Documentation has been updated (if needed)

PR Type

Enhancement


Description

  • Added push-up detection support alongside existing squat analysis

  • Improved Flask backend with better error handling and fallback mechanisms

  • Enhanced Flutter UI with exercise type selection dialog and dynamic prompts

  • Refactored API endpoints to support multiple exercise types with unified interface


Diagram Walkthrough

flowchart LR
  A["Flutter App"] -->|"Select Exercise Type"| B["Exercise Selection Dialog"]
  B -->|"Upload Video"| C["Flask Backend"]
  C -->|"Route by Type"| D["Squat Processor"]
  C -->|"Route by Type"| E["Push-up Processor"]
  D -->|"Analysis Results"| F["Format Results"]
  E -->|"Analysis Results"| F
  F -->|"Display Feedback"| A
Loading

File Walkthrough

Relevant files
Configuration changes
api_constants.dart
Unified API endpoints for exercise detection servers         

optifit app/lib/config/api_constants.dart

  • Unified exercise server endpoints to use local IP address instead of
    ngrok forwarding
  • Added dedicated push-up server endpoints alongside squat endpoints
  • Simplified Gemini API headers formatting
  • Added comments clarifying working local server configuration
+10/-10 
Enhancement
ai_chat_screen.dart
Add exercise type selection and push-up support to chat screen

optifit app/lib/screens/ai_chat_screen.dart

  • Added _selectedExerciseType state variable to track user's exercise
    choice
  • Implemented _showExerciseTypeDialog() for users to select between
    squat and push-up analysis
  • Updated AI chat prompt to support both squat and push-up analysis
    recommendations
  • Refactored video upload flow to require exercise type selection before
    upload
  • Created _formatPushupResults() and _formatSquatResults() helper
    methods for exercise-specific result formatting
  • Extracted _explainWithAI() method to handle AI feedback for both
    exercise types
  • Updated upload dialog to display selected exercise type and
    type-specific icons
  • Added push-up specific video instructions for camera positioning
+235/-93
app.py
Enhanced backend with multi-exercise support and resilience

optifit backend/app.py

  • Improved import handling with try-catch blocks and availability flags
    for squat, push-up, and validation modules
  • Added fallback mock data functions for both squat and push-up analysis
    when processors unavailable
  • Enhanced /upload endpoint to accept and validate exercise_type
    parameter from request
  • Modified video processing to route to appropriate processor based on
    exercise type
  • Added /health endpoint for detailed service status and processor
    availability
  • Added /jobs endpoint to list all processing jobs with their status
  • Implemented file cleanup thread to remove old videos after 2 hours
  • Improved error handling with better logging and standardized error
    responses
  • Added exercise type tracking in job metadata and response messages
+284/-43
pushup_counter.py
New push-up detection processor using pose estimation       

optifit backend/pushup_counter.py

  • New file implementing push-up detection using MediaPipe pose
    estimation
  • Detects push-ups by analyzing elbow angles instead of knee angles like
    squat counter
  • Calculates body alignment score to assess form quality during push-ups
  • Validates hand position relative to shoulder width for proper form
  • Tracks good vs poor form reps and identifies specific form issues
  • Generates annotated video output with real-time angle and alignment
    visualization
  • Outputs push-up specific metrics including count, form quality, and
    tempo statistics
  • Follows same structure and conventions as existing squat_counter.py
    for consistency
+353/-0 

Summary by CodeRabbit

Release Notes

  • New Features
    • Added push-up video analysis capability alongside existing squat analysis
    • Users select exercise type before video upload with updated interface and guidance
    • Enhanced processing with exercise-specific feedback and form quality metrics
    • Added system health monitoring and job tracking endpoints
    • Improved resilience with fallback analysis when services are temporarily unavailable

@coderabbitai
Copy link

coderabbitai bot commented Oct 24, 2025

Important

Review skipped

Review was skipped due to path filters

⛔ Files ignored due to path filters (2)
  • optifit backend/uploads/pushup_video.mp4 is excluded by !**/*.mp4
  • optifit backend/uploads/squat_video.mp4 is excluded by !**/*.mp4

CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including **/dist/** will override the default block on the dist directory, by removing the pattern from both the lists.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

The pull request extends the application to support push-up exercise detection alongside existing squat detection. It introduces exercise type tracking in the chat interface, adds new API endpoints for exercise-specific processing, implements a push-up detection algorithm using MediaPipe, and restructures the backend to route video processing by exercise type with fallback mechanisms.

Changes

Cohort / File(s) Change Summary
API Configuration
app/lib/config/api_constants.dart
Added local exercise server endpoints (exerciseServerUpload, exerciseServerResult) and exercise-specific aliases (pushup/squat variants); consolidated geminiHeaders to single-line format.
Chat Screen UI & Logic
app/lib/screens/ai_chat_screen.dart
Added exercise type tracking and selection dialog; modified upload flow to include exercise_type; added exercise-specific result formatting (_formatSquatResults, _formatPushupResults); updated UI prompts, icons, and guidance for both exercise types; enhanced _UploadDialog to accept and display exerciseType parameter; integrated exercise type into multipart requests and polling endpoints.
Backend Core
backend/app.py
Added robust import fallbacks with availability flags; introduced validation helpers and mock analysis data; enhanced process_video_async to route by exercise_type with fallback to mocks; expanded /upload and /result endpoints to validate and return exercise_type; added /health and /jobs endpoints; introduced background file cleanup; enhanced logging and error handling; updated home and ping endpoints to reflect supported exercises.
Push-up Detection
backend/pushup_counter.py
New module implementing push-up video processing with MediaPipe pose estimation; includes angle/alignment calculation utilities, state machine for rep counting, per-frame metrics logging via CSV, and form quality assessment; returns aggregated results with rep counts, form issues, and tempo statistics.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Chat as AI Chat Screen
    participant Upload as Upload Dialog
    participant Backend as Backend API
    participant Processor as Exercise Processor
    
    User->>Chat: Open video analysis flow
    User->>Chat: Tap upload button
    Chat->>Chat: Show exercise type selection
    User->>Chat: Select exercise (squat or pushup)
    Chat->>Chat: Store _selectedExerciseType
    Chat->>Upload: Open with exerciseType param
    Upload->>Upload: Display selected exercise type
    User->>Upload: Select video file
    User->>Upload: Confirm upload
    
    Upload->>Backend: POST /upload<br/>(video_file, exercise_type)
    Backend->>Backend: Validate exercise_type
    Backend->>Backend: Generate filename with exercise_type
    Backend->>Backend: Save video & create job record
    Backend-->>Upload: Return job_id, exercise_type, video_url
    
    Upload->>Chat: Return with job metadata
    Chat->>Chat: Poll result endpoint<br/>using exercise_type
    
    Backend->>Processor: Route by exercise_type<br/>(squat_counter or pushup_counter)
    Processor->>Processor: Analyze video<br/>(detect reps, form quality)
    Processor-->>Backend: Return analysis results
    
    Backend->>Backend: Attach video_url & exercise_type to results
    
    Chat->>Backend: GET /result/{job_id}
    Backend-->>Chat: Return analysis + exercise_type
    Chat->>Chat: Format results with<br/>exercise-specific handler
    Chat->>User: Display feedback<br/>(form issues, tempo, reps)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

The PR spans four distinct areas with heterogeneous changes: API restructuring, multi-layer UI/logic modifications with conditional branching, backend architectural updates with routing logic, and a new processing module with state machines and pose estimation. While individual changes follow consistent patterns, each file demands separate reasoning due to the new exercise type abstraction, fallback mechanisms, and integration points.

Poem

🐰 Hops of joy for push-ups now!
With squats and presses, I take a bow,
MediaPipe tracks each bend and flex,
Exercise types—what's coming next?
Form feedback flows through every rep! 💪

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 70.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title Check ❓ Inconclusive The title "added the the flask server code and the updated flutter code" is vague and generic, describing what files were changed rather than what functionality was added or improved. The primary objective of this PR is to add push-up detection support alongside existing squat analysis, but the title fails to convey this key change. Instead, it uses non-descriptive terminology ("added the the flask server code and the updated flutter code") that could describe many different types of changes, making it difficult for developers scanning the history to understand the actual purpose of the PR.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues Check ✅ Passed The PR successfully addresses the main coding-related requirements from issue #15. The pushup_counter.py module implements the push-up detection algorithm using MediaPipe with angle calculations and body alignment assessment. The app.py backend has been enhanced to route video processing by exercise type, and ai_chat_screen.dart includes exercise type selection, push-up-specific result formatting, and feedback messages. API endpoints in app.py now support the exercise_type parameter. All core coding tasks for implementing push-up detection are present and aligned with the issue objectives.
Out of Scope Changes Check ✅ Passed All changes in this PR are directly related to implementing push-up detection support as specified in issue #15. The modifications to api_constants.dart add exercise-specific endpoints, ai_chat_screen.dart implements the UI for exercise selection and push-up feedback, app.py extends the backend to route and process multiple exercise types, and the new pushup_counter.py module provides the detection algorithm. No unrelated refactoring, dependency updates, or tangential improvements appear to be included.
Description Check ✅ Passed The PR description contains most required sections from the template: a clear description explaining that the PR "adds a functionality on pushup detection," a properly linked related issue (#15), and a type of change classification. However, the "How Has This Been Tested?" section is present but completely empty—it only restates the template instruction without providing actual testing information. This is a required section that should document how reviewers can verify the changes, which would be particularly important for validating the accuracy claims in the acceptance criteria (95%+ rep counting accuracy).

Comment @coderabbitai help to get the list of available commands and usage tips.

@qodo-code-review
Copy link

qodo-code-review bot commented Oct 24, 2025

PR Compliance Guide 🔍

(Compliance updated until commit 15f8c68)

Below is a summary of compliance checks for this PR:

Security Compliance
🔴
TLS verification disabled

Description: Global override of SSL default context to an unverified context disables certificate
verification for all outbound HTTPS in the process, enabling MITM attacks.
app.py [1-8]

Referred Code
import ssl
try:
    _create_unverified_https_context = ssl._create_unverified_context
except AttributeError:
    pass
else:
    ssl._create_default_https_context = _create_unverified_https_context
Insecure endpoint config

Description: Hardcoded backend URLs use 127.0.0.0 which is an invalid/unsafe placeholder and lacks TLS,
risking failed routing or MITM if copied to production; use proper localhost (127.0.0.1)
or environment-configured HTTPS endpoints.
api_constants.dart [13-20]

Referred Code
// WORKING: Your exercise detection server (local ip v4 address used)
static const String exerciseServerUpload = 'http://127.0.0.0:5000/upload';
static const String exerciseServerResult = 'http://127.0.0.0:5000/result';

static const String pushupServerUpload = exerciseServerUpload;
static const String pushupServerResult = exerciseServerResult;
static const String squatServerUpload = exerciseServerUpload;
static const String squatServerResult = exerciseServerResult;
Debug mode enabled

Description: Flask app runs with debug=True which exposes the interactive debugger and sensitive
internals if accessible in non-dev environments.
app.py [371-385]

Referred Code
print("💪 Supported exercises: squat, pushup")
print("📊 Module status:")
print(f"   Squat processor: {'✅ Available' if SQUAT_AVAILABLE else '❌ Mock data'}")
print(f"   Push-up processor: {'✅ Available' if PUSHUP_AVAILABLE else '❌ Mock data'}")
print(f"   Validation: {'✅ Available' if VALIDATION_AVAILABLE else '❌ Basic validation'}")
print("📊 Endpoints:")
print("   GET  / - Server info")
print("   GET  /ping - Health check")
print("   GET  /health - Detailed health status")
print("   POST /upload - Upload exercise video")
print("   GET  /result/<job_id> - Get analysis results")
print("   GET  /jobs - List all jobs")

# Start Flask app
app.run(host="0.0.0.0", port=5000, debug=True)
Unsafe subprocess usage

Description: Invocation of ffmpeg via subprocess without explicit executable path or sandboxing can be
exploited if PATH is manipulated; also lacks timeout and error handling, risking command
injection vectors from environment and DoS.
pushup_counter.py [314-324]

Referred Code
print("🎞 Converting push-up video to H.264 using ffmpeg...")
ffmpeg_cmd = [
    'ffmpeg', '-y',
    '-i', raw_path,
    '-vcodec', 'libx264',
    '-preset', 'fast',
    '-crf', '23',
    '-acodec', 'aac',
    output_path
]
subprocess.run(ffmpeg_cmd, check=True)
Ticket Compliance
🟡
🎫 #15
🟢 Implement push-up detection algorithm using MediaPipe
Add push-up specific metrics including rep count, form quality, and depth
Create push-up specific thresholds and validation rules
Update video processing/backend to handle push-up analysis
Add push-up specific feedback messages
Update API endpoints to support push-up detection
Provides meaningful form feedback
🔴 Accurate rep counting with at least 95% accuracy
Updated API documentation
Comprehensive testing with sample videos
Research push-up form requirements and key angles
Successfully detects push-up exercises in videos
Handles different push-up variations
Test with various push-up styles and user types
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

🔴
Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status:
Internal details: User-facing responses include internal availability details and stack-related messages via
stringified exceptions in error responses potentially exposing system internals.

Referred Code
        jobs[job_id]["result"] = base_info

    except Exception as e:
        jobs[job_id]["status"] = "error"
        jobs[job_id]["error"] = str(e)
        logger.error(f"Error in {exercise_type} processing for job {job_id}: {e}")

# Route to home
@app.route('/', methods=['GET'])
def home():
    available_exercises = []
    if SQUAT_AVAILABLE:
        available_exercises.append("squat")
    if PUSHUP_AVAILABLE:
        available_exercises.append("pushup")

    # Always show both for testing, even if using mock data
    display_exercises = ["squat", "pushup"]

    base_info = {
        "info": "Welcome to the Exercise Detection AI Server!",


 ... (clipped 21 lines)
Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Limited auditing: New endpoints perform processing and state changes without structured audit logs tying
actions to a user identity or including full action context.

Referred Code
@app.route('/ping', methods=['GET'])
def ping():
    return jsonify({
        "message": "Server is live!",
        "supported_exercises": ["squat", "pushup"]
    }), 200

# Route to upload the video
@app.route('/upload', methods=['POST'])
def upload_video():
    """
    IMPROVED: Better error handling
    """
    try:
        # Use custom validation if validation.py not available
        if VALIDATION_AVAILABLE:
            video = validate_upload_request(request)
            validate_video_file(video)
        else:
            video = validate_upload_request(request)
            validate_video_file(video)


 ... (clipped 47 lines)
Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Generic errors: Broad exception handling returns generic 500s and may not differentiate client validation
errors vs server failures, and polling lacks backoff/timeouts beyond loop count.

Referred Code
    except Exception as e:
        logger.error(f"Upload error: {str(e)}")
        return error_response(str(e), 500)

# Route to get the result of the job
@app.route('/result/<job_id>', methods=['GET'])
def get_result(job_id):
    """
    Get processing results
    """
    try:
        if VALIDATION_AVAILABLE:
            validate_job_request(job_id, jobs)
        else:
            validate_job_request(job_id, jobs)

        job = jobs.get(job_id)
        exercise_type = job.get("exercise_type", "unknown")

        if job["status"] == "processing":
            return jsonify({


 ... (clipped 21 lines)
Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status:
Verbose logging: Added print statements log full server endpoints and raw responses which may include
sensitive or operational details and are unstructured.

Referred Code
print(
  'Posting video to: $uploadEndpoint for exercise: $_selectedExerciseType',
);

final request = http.MultipartRequest('POST', Uri.parse(uploadEndpoint));
request.files.add(
  await http.MultipartFile.fromPath('video', uploadedFile.path),
);

// Add exercise type to request
request.fields['exercise_type'] = _selectedExerciseType ?? 'squat';

final streamedResponse = await request.send();
final responseString = await streamedResponse.stream.bytesToString();
print('Upload response:');
print(responseString);
final uploadResponse = jsonDecode(responseString);
Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status:
Basic validation: Upload validation checks extension only and accepts any filename while enabling debug
mode, lacking size limits, auth, rate limiting, and stronger sanitization.

Referred Code
# IMPROVED: Basic validation functions if validation.py doesn't exist
def validate_upload_request(request):
    """Basic request validation"""
    if 'video' not in request.files:
        raise Exception("No video file provided")
    video = request.files['video']
    if video.filename == '':
        raise Exception("No file selected")
    return video

def validate_video_file(video):
    """Basic video file validation"""
    allowed_extensions = ['mp4', 'mov', 'avi', 'mkv', 'webm', '3gp']
    if video.filename:
        ext = video.filename.rsplit('.', 1)[1].lower()
        if ext not in allowed_extensions:
            raise Exception(f"File type '{ext}' not supported. Allowed: {', '.join(allowed_extensions)}")
    return True

def validate_job_request(job_id, jobs):
    """Basic job validation"""


 ... (clipped 4 lines)
Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

Previous compliance checks

Compliance check up to commit 6c7d8d3
Security Compliance
🔴
Disabled SSL verify

Description: SSL verification is globally disabled by overriding default HTTPS context, which can
enable MITM when backend accesses external resources.
app.py [1-8]

Referred Code
import ssl
try:
    _create_unverified_https_context = ssl._create_unverified_context
except AttributeError:
    pass
else:
    ssl._create_default_https_context = _create_unverified_https_context
Debug server exposed

Description: Flask app runs with debug=True and binds 0.0.0.0 on port 5000, exposing debugging server
publicly which can leak sensitive info and be exploited.
app.py [364-385]

Referred Code
if __name__ == '__main__':
    # Start cleanup thread
    cleanup_thread = threading.Thread(target=cleanup_old_files)
    cleanup_thread.daemon = True
    cleanup_thread.start()

    print("🚀 Exercise Detection Server Starting...")
    print("💪 Supported exercises: squat, pushup")
    print("📊 Module status:")
    print(f"   Squat processor: {'✅ Available' if SQUAT_AVAILABLE else '❌ Mock data'}")
    print(f"   Push-up processor: {'✅ Available' if PUSHUP_AVAILABLE else '❌ Mock data'}")
    print(f"   Validation: {'✅ Available' if VALIDATION_AVAILABLE else '❌ Basic validation'}")
    print("📊 Endpoints:")
    print("   GET  / - Server info")
    print("   GET  /ping - Health check")
    print("   GET  /health - Detailed health status")
    print("   POST /upload - Upload exercise video")
    print("   GET  /result/<job_id> - Get analysis results")
    print("   GET  /jobs - List all jobs")

    # Start Flask app


 ... (clipped 1 lines)
Insecure transport

Description: The client hardcodes server endpoints to http://127.0.0.0:5000 which is an invalid
loopback address and may cause users to modify to public IPs without TLS, risking MITM and
exposing video uploads over plaintext.
api_constants.dart [14-20]

Referred Code
static const String exerciseServerUpload = 'http://127.0.0.0:5000/upload';
static const String exerciseServerResult = 'http://127.0.0.0:5000/result';

static const String pushupServerUpload = exerciseServerUpload;
static const String pushupServerResult = exerciseServerResult;
static const String squatServerUpload = exerciseServerUpload;
static const String squatServerResult = exerciseServerResult;
Command execution risk

Description: Uses ffmpeg via subprocess without full path or sandboxing, which can be abused if PATH is
manipulated on the server host.
pushup_counter.py [315-324]

Referred Code
ffmpeg_cmd = [
    'ffmpeg', '-y',
    '-i', raw_path,
    '-vcodec', 'libx264',
    '-preset', 'fast',
    '-crf', '23',
    '-acodec', 'aac',
    output_path
]
subprocess.run(ffmpeg_cmd, check=True)
Unauthenticated file serving

Description: Processed video files are served directly from a user-influenced filename path; although
secure_filename is used on upload, direct send_file of arbitrary filename parameter may
still enable path probing without access control.
app.py [330-341]

Referred Code
@app.route('/processed/<filename>')
def get_processed_video(filename):
    """Serve processed video files"""
    try:
        file_path = os.path.join(PROCESSED_FOLDER, filename)
        if os.path.exists(file_path):
            return send_file(file_path, as_attachment=True, mimetype='video/mp4')
        else:
            return error_response("Video file not found", 404)
    except Exception as e:
        logger.error(f"Error serving video {filename}: {str(e)}")
        return error_response("Error serving video file", 500)
Ticket Compliance
🟡
🎫 #15
🟢 Implement push-up detection algorithm using MediaPipe
Add push-up specific metrics such as rep count, form quality, and depth
Create push-up specific thresholds and validation rules
Update video processing/backend to handle push-up analysis alongside squats
Add push-up specific feedback messages
Update API endpoints to support push-up detection
Provides meaningful form feedback
Successfully detects push-up exercises in videos
🔴 Updated API documentation
Comprehensive testing with sample videos
Research push-up form requirements and key angles
Test with various push-up styles and user types
Accurate rep counting with 95%+ accuracy
Handles different push-up variations
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
No custom compliance provided

Follow the guide to enable custom compliance check.

@sandy4242
Copy link
Contributor Author

@MasterAffan , i have used my local ip address
// WORKING: Your exercise detection server (local ip v4 address used) static const String exerciseServerUpload = 'http://127.0.0.0:5000/upload'; static const String exerciseServerResult = 'http://127.0.0.0:5000/result';
so change it accordingly...

@qodo-code-review
Copy link

qodo-code-review bot commented Oct 24, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Use a persistent job store

Replace the volatile in-memory dictionary used for tracking jobs in the backend
with a persistent storage solution like a database or key-value store. This will
prevent data loss on server restarts and enable a more robust, scalable
architecture.

Examples:

optifit backend/app.py [110]
jobs = {}

Solution Walkthrough:

Before:

# optifit backend/app.py

# In-memory job store is volatile
jobs = {}

@app.route('/upload', methods=['POST'])
def upload_video():
    job_id = str(uuid.uuid4())
    # Job is stored in the local dictionary
    jobs[job_id] = {"status": "processing"}
    # ...
    return jsonify({"job_id": job_id})

@app.route('/result/<job_id>', methods=['GET'])
def get_result(job_id):
    # Job is retrieved from the local dictionary
    job = jobs.get(job_id)
    # ...
    return jsonify(job)

After:

# optifit backend/app.py
# from db import JobStore

# Persistent job store (e.g., Redis, SQLite)
job_store = JobStore()

@app.route('/upload', methods=['POST'])
def upload_video():
    job_id = str(uuid.uuid4())
    # Job is saved to persistent storage
    job_store.create_job(job_id, {"status": "processing"})
    # ...
    return jsonify({"job_id": job_id})

@app.route('/result/<job_id>', methods=['GET'])
def get_result(job_id):
    # Job is retrieved from persistent storage
    job = job_store.get_job(job_id)
    # ...
    return jsonify(job)
Suggestion importance[1-10]: 9

__

Why: This suggestion addresses a critical reliability flaw in the backend; using an in-memory dictionary for job tracking makes the system fragile and not scalable, and a persistent store is essential for robustness.

High
Replace polling with a modern alternative

Replace the inefficient client-side polling for analysis results with a
push-based system like WebSockets or Server-Sent Events (SSE). This would allow
the server to notify the client directly when results are ready, improving
efficiency and user experience.

Examples:

optifit app/lib/screens/ai_chat_screen.dart [539-558]
      while (!isDone && pollCount < 60) {
        // Poll up to 60 times (5 minutes if 5s interval)
        await Future.delayed(const Duration(seconds: 5));
        pollCount++;

        // Use different result endpoints based on exercise type
        String resultEndpoint = _selectedExerciseType == 'pushup'
            ? '${ApiConstants.pushupServerResult}/$jobId' // added this to ApiConstants
            : '${ApiConstants.exerciseServerResult}/$jobId';


 ... (clipped 10 lines)

Solution Walkthrough:

Before:

// optifit app/lib/screens/ai_chat_screen.dart
void _onUploadVideo() async {
  // ... upload video and get a job_id
  final jobId = uploadResponse['job_id'];

  bool isDone = false;
  while (!isDone && pollCount < 60) {
    await Future.delayed(const Duration(seconds: 5));
    final statusResp = await http.get(Uri.parse('/result/$jobId'));
    final statusData = jsonDecode(statusResp.body);
    if (statusData['status'] == 'done') {
      isDone = true;
      // process results...
    }
  }
}

After:

// optifit app/lib/screens/ai_chat_screen.dart (with a WebSocket service)
void _onUploadVideo() async {
  // ... upload video and get a job_id
  final jobId = uploadResponse['job_id'];

  // Subscribe to results for this job_id
  webSocketService.listenForJob(jobId, (result) {
    // This callback is triggered by the server when the job is done
    setState(() {
      // update UI with results...
    });
  });
}

// optifit backend/app.py
def process_video_async(job_id, ...):
  # ... process video
  # Push result to client via WebSocket
  websocket.send(job_id, result)
Suggestion importance[1-10]: 8

__

Why: This is a significant architectural suggestion that correctly identifies an inefficient polling mechanism, which impacts performance and user experience, and proposes a superior push-based alternative.

Medium
Possible issue
Handle non-existent job ID gracefully

In the get_result function, add a check to handle cases where an invalid job_id
is provided. If jobs.get(job_id) returns None, return a 404 error to prevent a
server crash.

optifit backend/app.py [254-269]

 # Route to get the result of the job
 @app.route('/result/<job_id>', methods=['GET'])
 def get_result(job_id):
     """
     Get processing results
     """
     try:
         if VALIDATION_AVAILABLE:
             validate_job_request(job_id, jobs)
         else:
             validate_job_request(job_id, jobs)
 
         job = jobs.get(job_id)
+        if not job:
+            return error_response("Job not found", 404)
+
         exercise_type = job.get("exercise_type", "unknown")
         
         if job["status"] == "processing":
 ...

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a critical bug where providing an invalid job_id would cause a 500 Internal Server Error due to an AttributeError. The proposed fix is essential for robust error handling.

High
Prevent file cleanup race condition

To prevent a race condition in the cleanup_old_files function, check if a file
is in use before attempting to delete it. This can be done by trying to rename
the file first.

optifit backend/app.py [343-362]

 # Cleanup function
 def cleanup_old_files():
     """Clean up old video files periodically"""
     while True:
         try:
             current_time = time.time()
             for folder in [UPLOAD_FOLDER, PROCESSED_FOLDER]:
                 if os.path.exists(folder):
                     for filename in os.listdir(folder):
                         file_path = os.path.join(folder, filename)
                         try:
                             file_time = os.path.getctime(file_path)
                             if current_time - file_time > 7200:  # 2 hours
-                                os.remove(file_path)
-                                logger.info(f"Cleaned up old file: {filename}")
-                        except:
+                                try:
+                                    # Check if file is in use by trying to rename
+                                    os.rename(file_path, file_path)
+                                    os.remove(file_path)
+                                    logger.info(f"Cleaned up old file: {filename}")
+                                except OSError:
+                                    logger.warning(f"Could not clean up file, it may be in use: {filename}")
+                        except FileNotFoundError:
+                            # File might have been deleted by another process
                             pass
         except Exception as e:
             logger.error(f"Cleanup error: {e}")
         time.sleep(3600)  # Run every hour
  • Apply / Chat
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a potential race condition in the new file cleanup thread and proposes a more robust way to handle file deletion, preventing potential errors or corrupted files.

Medium
Use standard loopback IP address

Replace the non-standard loopback IP address 127.0.0.0 with the standard
127.0.0.1 in ApiConstants to ensure cross-platform compatibility.

optifit app/lib/config/api_constants.dart [13-15]

 // WORKING: Your exercise detection server (local ip v4 address used)
-static const String exerciseServerUpload = 'http://127.0.0.0:5000/upload';
-static const String exerciseServerResult = 'http://127.0.0.0:5000/result';
+static const String exerciseServerUpload = 'http://127.0.0.1:5000/upload';
+static const String exerciseServerResult = 'http://127.0.0.1:5000/result';
  • Apply / Chat
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies the use of a non-standard loopback IP address (127.0.0.0) and recommends the standard 127.0.0.1 for better portability and correctness.

Low
  • Update

@sandy4242
Copy link
Contributor Author

@MasterAffan also renamed the squat video and added a push_up video...

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🧹 Nitpick comments (13)
optifit app/lib/screens/ai_chat_screen.dart (5)

616-624: UI expects avg_elbow_angle/body_alignment_score not produced by real backend.

Push-up processor doesn’t return these fields; UI will show “-”. Either compute and return them backend-side or remove from UI.

Want a patch to compute average elbow angle and average alignment in pushup_counter.py and include in result?


115-121: Add HTTP timeouts to Gemini calls.

Prevent hangs on slow networks.

- final response = await http.post(
+ final response = await http
+     .post(
        url,
        headers: headers,
        body: jsonEncode(body),
-      );
+      )
+     .timeout(const Duration(seconds: 30));

Repeat in _explainWithAI as shown.

Also applies to: 669-675


41-42: Remove unused serverUrl.

It’s not read; keep code clean.

- String serverUrl = ApiConstants.exerciseServerUpload;

443-449: Broaden suggestion matching for “pushup/push up/push-ups”.

Covers more phrasing.

- } else if (suggestion.toLowerCase().contains('push-up')) {
+ } else if (RegExp(r'\bpush[\s-]?ups?\b').hasMatch(suggestion.toLowerCase())) {

1-14: Minor: remove unused imports.

path_provider isn’t used.

- import 'package:path_provider/path_provider.dart';
optifit backend/pushup_counter.py (3)

131-135: Bind lm in nested function to avoid late-binding lint.

Prevents B023 warning.

-                def elbow_angle(side_prefix):
+                def elbow_angle(side_prefix, lm_local=lm):
-                    shoulder = lm[getattr(mp_pose.PoseLandmark, f"{side_prefix}_SHOULDER").value]
-                    elbow = lm[getattr(mp_pose.PoseLandmark, f"{side_prefix}_ELBOW").value]
-                    wrist = lm[getattr(mp_pose.PoseLandmark, f"{side_prefix}_WRIST").value]
+                    shoulder = lm_local[getattr(mp_pose.PoseLandmark, f"{side_prefix}_SHOULDER").value]
+                    elbow = lm_local[getattr(mp_pose.PoseLandmark, f"{side_prefix}_ELBOW").value]
+                    wrist = lm_local[getattr(mp_pose.PoseLandmark, f"{side_prefix}_WRIST").value]

52-52: Function too complex for CI (C901).

Extract helpers (e.g., compute_angles_and_alignment, update_state_machine, draw_overlays). Keeps logic testable and satisfies complexity threshold.


331-352: Return schema: consider adding averages to match UI expectations.

Add avg_elbow_angle and body_alignment_score to result.

-    result = {
+    # Optional: include averages for UI
+    avg_angle = float(np.mean(angle_history)) if angle_history else None
+    avg_align = float(np.mean(alignment_history)) if alignment_history else None
+    result = {
         "pushup_count": rep_count,
         "good_form_reps": good_form_reps,
         "poor_form_reps": poor_form_reps,
         "form_issues": form_issues,
         "tempo_stats": tempo_stats,
         "debug_csv": csv_path if log_csv else None
     }
+    if avg_angle is not None:
+        result["avg_elbow_angle"] = round(avg_angle, 1)
+    if avg_align is not None:
+        result["body_alignment_score"] = int(round(avg_align))
optifit backend/app.py (5)

234-237: Thread safety and daemon threads.

Protect shared jobs dict and don’t block shutdown.

-jobs = {}
+jobs = {}
+jobs_lock = threading.Lock()
@@
-        threading.Thread(
+        threading.Thread(
             target=process_video_async, 
             args=(job_id, input_path, output_path, video_url, exercise_type)
-        ).start()
+        , daemon=True).start()
@@
-        jobs[job_id]["status"] = "done"
-        jobs[job_id]["result"] = base_info
+        with jobs_lock:
+            jobs[job_id]["status"] = "done"
+            jobs[job_id]["result"] = base_info

Apply similar locking where jobs is written (error path, creation).

Also applies to: 110-111, 149-151


329-341: Serve videos inline, not as attachments.

Enables playback in clients.

-            return send_file(file_path, as_attachment=True, mimetype='video/mp4')
+            return send_file(file_path, as_attachment=False, mimetype='video/mp4')

358-362: Don’t use bare except in cleanup.

Catch Exception and log.

-                        except:
-                            pass
+                        except Exception as e:
+                            logger.warning("Failed to check/remove %s: %s", file_path, e)

1-7: Avoid globally disabling SSL verification.

Insecure by default; remove or guard with an env flag for local-only debugging.

-import ssl
-try:
-    _create_unverified_https_context = ssl._create_unverified_context
-except AttributeError:
-    pass
-else:
-    ssl._create_default_https_context = _create_unverified_https_context
+# Consider removing this block or guard it with an env flag (ALLOW_INSECURE_SSL)

169-180: Tighten displayed processors list.

You show both squat/pushup even when real processors unavailable. Consider reflecting availability to avoid user confusion.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 52cd30c and 6c7d8d3.

📒 Files selected for processing (4)
  • optifit app/lib/config/api_constants.dart (1 hunks)
  • optifit app/lib/screens/ai_chat_screen.dart (17 hunks)
  • optifit backend/app.py (2 hunks)
  • optifit backend/pushup_counter.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
optifit backend/app.py (3)
optifit backend/squat_counter.py (1)
  • process_squat_video (22-280)
optifit backend/pushup_counter.py (1)
  • process_pushup_video (52-353)
optifit backend/validation.py (3)
  • validate_upload_request (40-60)
  • validate_video_file (64-90)
  • validate_job_request (94-106)
🪛 ast-grep (0.39.6)
optifit backend/app.py

[warning] 384-384: Detected Flask app with debug=True. Do not deploy to production with this flag enabled as it will leak sensitive information. Instead, consider using Flask configuration variables or setting 'debug' using system environment variables.
Context: app.run(host="0.0.0.0", port=5000, debug=True)
Note: [CWE-489] Active Debug Code. [REFERENCES]
- https://labs.detectify.com/2015/10/02/how-patreon-got-hacked-publicly-exposed-werkzeug-debugger/

(debug-enabled-python)


[warning] 384-384: Running flask app with host 0.0.0.0 could expose the server publicly.
Context: app.run(host="0.0.0.0", port=5000, debug=True)
Note: [CWE-668]: Exposure of Resource to Wrong Sphere [OWASP A01:2021]: Broken Access Control [REFERENCES]
https://owasp.org/Top10/A01_2021-Broken_Access_Control

(avoid_app_run_with_bad_host-python)

🪛 GitHub Actions: Python CI
optifit backend/pushup_counter.py

[error] 13-13: E302 expected 2 blank lines, found 1


[error] 20-20: E302 expected 2 blank lines, found 1


[error] 24-24: E302 expected 2 blank lines, found 1


[error] 34-34: W293 blank line contains whitespace


[error] 39-39: W293 blank line contains whitespace


[error] 43-43: W293 blank line contains whitespace


[error] 47-47: W293 blank line contains whitespace


[error] 49-49: E722 do not use bare 'except'


[error] 52-52: C901 'process_pushup_video' is too complex (30)


[error] 52-52: E302 expected 2 blank lines, found 1


[error] 89-89: F841 local variable 'HAND_POSITION_MARGIN' is assigned to but never used


[error] 89-89: E261 at least two spaces before inline comment


[error] 144-144: E501 line too long (133 > 127 characters)


[error] 173-173: W293 blank line contains whitespace


[error] 176-176: W293 blank line contains whitespace


[error] 205-205: W293 blank line contains whitespace


[error] 212-212: W293 blank line contains whitespace


[error] 216-216: W293 blank line contains whitespace


[error] 220-220: W293 blank line contains whitespace


[error] 224-224: W293 blank line contains whitespace


[error] 228-228: W293 blank line contains whitespace


[error] 231-231: W293 blank line contains whitespace


[error] 233-233: E501 line too long (128 > 127 characters)


[error] 235-235: W293 blank line contains whitespace


[error] 257-257: E128 continuation line under-indented for visual indent


[error] 258-258: E128 continuation line under-indented for visual indent


[error] 263-263: E128 continuation line under-indented for visual indent


[error] 267-267: E128 continuation line under-indented for visual indent


[error] 270-270: E128 continuation line under-indented for visual indent


[error] 273-273: E128 continuation line under-indented for visual indent


[error] 275-275: E128 continuation line under-indented for visual indent


[error] 277-277: E128 continuation line under-indented for visual indent


[error] 280-280: E128 continuation line under-indented for visual indent


[error] 289-289: E128 continuation line under-indented for visual indent


[error] 327-327: E722 do not use bare 'except'


[error] 337-337: W293 blank line contains whitespace


[error] 339-339: W293 blank line contains whitespace

optifit backend/app.py

[error] 34-34: F403 'from validation import *' used; unable to detect undefined names


[error] 34-34: F401 'validation.*' imported but unused


[error] 53-53: W291 trailing whitespace


[error] 54-54: E302 expected 2 blank lines, found 1


[error] 64-64: E302 expected 2 blank lines, found 1


[error] 73-73: E302 expected 2 blank lines, found 1


[error] 82-82: E302 expected 2 blank lines, found 1


[error] 89-89: E302 expected 2 blank lines, found 1


[error] 98-98: E302 expected 2 blank lines, found 1


[error] 110-110: E305 expected 2 blank lines after class or function definition, found 1


[error] 112-112: E302 expected 2 blank lines, found 1


[error] 118-118: W293 blank line contains whitespace


[error] 142-142: W293 blank line contains whitespace


[error] 147-147: W293 blank line contains whitespace


[error] 151-151: W293 blank line contains whitespace


[error] 158-158: E302 expected 2 blank lines, found 1


[error] 165-165: W293 blank line contains whitespace


[error] 168-168: W293 blank line contains whitespace


[error] 179-179: W291 trailing whitespace


[error] 182-182: E302 expected 2 blank lines, found 1


[error] 190-190: E302 expected 2 blank lines, found 1


[error] 203-203: W293 blank line contains whitespace


[error] 206-206: W293 blank line contains whitespace


[error] 210-210: W293 blank line contains whitespace


[error] 215-215: W293 blank line contains whitespace


[error] 218-218: W293 blank line contains whitespace


[error] 221-221: W293 blank line contains whitespace


[error] 224-224: W293 blank line contains whitespace


[error] 232-232: W293 blank line contains whitespace


[error] 235-235: W291 trailing whitespace


[error] 238-238: W293 blank line contains whitespace


[error] 246-246: W293 blank line contains whitespace


[error] 249-249: W293 blank line contains whitespace


[error] 255-255: E302 expected 2 blank lines, found 1


[error] 268-268: W293 blank line contains whitespace


[error] 271-271: W291 trailing whitespace


[error] 283-283: W291 trailing whitespace


[error] 287-287: W293 blank line contains whitespace


[error] 293-293: E302 expected 2 blank lines, found 1


[error] 311-311: E302 expected 2 blank lines, found 1


[error] 321-321: W293 blank line contains whitespace


[error] 330-330: E302 expected 2 blank lines, found 1


[error] 344-344: E302 expected 2 blank lines, found 1


[error] 358-358: E722 do not use bare 'except'


[error] 364-364: E305 expected 2 blank lines after class or function definition, found 1


[error] 369-369: W293 blank line contains whitespace


[error] 383-383: W293 blank line contains whitespace

🪛 Ruff (0.14.1)
optifit backend/pushup_counter.py

48-48: Consider moving this statement to an else block

(TRY300)


49-49: Do not use bare except

(E722)


89-89: Local variable HAND_POSITION_MARGIN is assigned to but never used

Remove assignment to unused variable HAND_POSITION_MARGIN

(F841)


131-131: Function definition does not bind loop variable lm

(B023)


132-132: Function definition does not bind loop variable lm

(B023)


133-133: Function definition does not bind loop variable lm

(B023)


324-324: subprocess call: check for execution of untrusted input

(S603)


327-327: Do not use bare except

(E722)


327-328: try-except-pass detected, consider logging the exception

(S110)

optifit backend/app.py

34-34: from validation import * used; unable to detect undefined names

(F403)


67-67: Create your own exception

(TRY002)


67-67: Avoid specifying long messages outside the exception class

(TRY003)


70-70: Create your own exception

(TRY002)


70-70: Avoid specifying long messages outside the exception class

(TRY003)


79-79: Create your own exception

(TRY002)


79-79: Avoid specifying long messages outside the exception class

(TRY003)


85-85: Create your own exception

(TRY002)


85-85: Avoid specifying long messages outside the exception class

(TRY003)


125-125: Do not catch blind exception: Exception

(BLE001)


126-126: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


136-136: Do not catch blind exception: Exception

(BLE001)


137-137: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


152-152: Do not catch blind exception: Exception

(BLE001)


155-155: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


250-250: Do not catch blind exception: Exception

(BLE001)


251-251: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


251-251: Use explicit conversion flag

Replace with conversion flag

(RUF010)


288-288: Do not catch blind exception: Exception

(BLE001)


289-289: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


289-289: Use explicit conversion flag

Replace with conversion flag

(RUF010)


339-339: Do not catch blind exception: Exception

(BLE001)


340-340: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


340-340: Use explicit conversion flag

Replace with conversion flag

(RUF010)


358-358: Do not use bare except

(E722)


358-359: try-except-pass detected, consider logging the exception

(S110)


360-360: Do not catch blind exception: Exception

(BLE001)


361-361: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


385-385: Possible binding to all interfaces

(S104)


385-385: Use of debug=True in Flask app detected

(S201)

🔇 Additional comments (2)
optifit app/lib/screens/ai_chat_screen.dart (1)

905-907: Color.withValues may be unsupported on your Flutter channel.

If using older stable, use withOpacity or withAlpha.

Would you confirm your Flutter version? If needed:

- ? AppTheme.primary.withValues(alpha: 0.1)
+ ? AppTheme.primary.withOpacity(0.1)
optifit backend/pushup_counter.py (1)

91-101: The review comment references non-existent PEP8 violations.

Ruff analysis of the file shows no E302/E128/E501/W293 errors at lines 91-101, 257-281, 266-277, or 269-274. The file content at these locations is properly formatted with correct indentation and structure. The actual errors detected in the file are different (E722, F841, B023, S603, S110), none of which appear in the specified line ranges.

Likely an incorrect or invalid review comment.

Comment on lines 10 to 12
static const String chatApiEndpoint =
'https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent';

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Unify endpoint and harden API key access.

  • Avoid duplicate URL constants: point chatApiEndpoint to geminiApiEndpoint.
  • Don’t crash on missing GEMINI_API_KEY; fail fast with a clear error.
- static const String chatApiEndpoint =
-     'https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent';
+ static const String chatApiEndpoint = geminiApiEndpoint;
@@
- static final String geminiApiKey = dotenv.env['GEMINI_API_KEY']!;
+ static String get geminiApiKey {
+   final key = dotenv.env['GEMINI_API_KEY'];
+   if (key == null || key.isEmpty) {
+     throw StateError('GEMINI_API_KEY is not set');
+   }
+   return key;
+ }
@@
- static Map<String, String> get geminiHeaders => {'Content-Type': 'application/json', 'x-goog-api-key': geminiApiKey};
+ static Map<String, String> get geminiHeaders => {
+   'Content-Type': 'application/json',
+   'x-goog-api-key': geminiApiKey,
+ };

Also applies to: 22-24

🤖 Prompt for AI Agents
In optifit app/lib/config/api_constants.dart around lines 10-12 and 22-24, the
chatApiEndpoint is duplicated and API key access can crash silently; refactor so
chatApiEndpoint references the single geminiApiEndpoint constant (remove the
duplicate literal), and harden GEMINI_API_KEY retrieval by checking for
null/empty and throwing a clear, early exception (or returning a typed failure)
with a descriptive message instead of allowing a runtime null error—ensure
callers expect/handle that error.

Comment on lines +14 to +20
static const String exerciseServerUpload = 'http://127.0.0.0:5000/upload';
static const String exerciseServerResult = 'http://127.0.0.0:5000/result';

static const String pushupServerUpload = exerciseServerUpload;
static const String pushupServerResult = exerciseServerResult;
static const String squatServerUpload = exerciseServerUpload;
static const String squatServerResult = exerciseServerResult;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Blocking: invalid loopback IP (127.0.0.0) will fail requests.

Use 127.0.0.1 (or env-provided base URL). Current constants will never reach the local Flask server.

Apply:

- static const String exerciseServerUpload = 'http://127.0.0.0:5000/upload';
- static const String exerciseServerResult = 'http://127.0.0.0:5000/result';
+ static const String exerciseServerUpload = 'http://127.0.0.1:5000/upload';
+ static const String exerciseServerResult = 'http://127.0.0.1:5000/result';

Recommended: read a base URL from .env (EXERCISE_SERVER_BASE_URL) and derive all four endpoints.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
static const String exerciseServerUpload = 'http://127.0.0.0:5000/upload';
static const String exerciseServerResult = 'http://127.0.0.0:5000/result';
static const String pushupServerUpload = exerciseServerUpload;
static const String pushupServerResult = exerciseServerResult;
static const String squatServerUpload = exerciseServerUpload;
static const String squatServerResult = exerciseServerResult;
static const String exerciseServerUpload = 'http://127.0.0.1:5000/upload';
static const String exerciseServerResult = 'http://127.0.0.1:5000/result';
static const String pushupServerUpload = exerciseServerUpload;
static const String pushupServerResult = exerciseServerResult;
static const String squatServerUpload = exerciseServerUpload;
static const String squatServerResult = exerciseServerResult;
🤖 Prompt for AI Agents
In optifit app/lib/config/api_constants.dart around lines 14 to 20, the
endpoints use the invalid loopback IP 127.0.0.0 which will never reach a local
Flask server; change the base host to 127.0.0.1 or, better, read a base URL from
an environment variable (EXERCISE_SERVER_BASE_URL) and derive the four endpoints
from that base (e.g. `${base}/upload` and `${base}/result`), falling back to
'http://127.0.0.1:5000' if the env var is missing; update the four constants to
use the computed base and ensure any consumers import the updated constants.

Comment on lines +544 to +551
// Use different result endpoints based on exercise type
String resultEndpoint = _selectedExerciseType == 'pushup'
? '${ApiConstants.pushupServerResult}/$jobId' // added this to ApiConstants
: '${ApiConstants.exerciseServerResult}/$jobId';

print(
'Polling $_selectedExerciseType analysis result endpoint: $resultEndpoint',
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Handle non-200 and “error” responses during polling.

Avoid decoding invalid/error bodies and waiting full timeout.

- final statusResp = await http.get(Uri.parse(resultEndpoint));
- final statusData = jsonDecode(statusResp.body);
+ final statusResp = await http.get(Uri.parse(resultEndpoint));
+ if (statusResp.statusCode >= 400) {
+   throw Exception('Analysis error (HTTP ${statusResp.statusCode})');
+ }
+ final statusData = jsonDecode(statusResp.body);
  if (statusData['status'] == 'done') {
    isDone = true;
    analysisData = statusData['result'];
+ } else if (statusData['status'] == 'error') {
+   throw Exception(statusData['error'] ?? 'Analysis failed');
  }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In optifit app/lib/screens/ai_chat_screen.dart around lines 544 to 551, the
polling logic assumes every HTTP response is 200 and decodable; update it to
check the response.statusCode first and treat non-200 as an error (log and break
or retry sooner), avoid calling json.decode on empty/invalid bodies by first
verifying response.body is not empty and/or response.headers['content-type']
contains 'application/json', and detect explicit error payloads by checking for
an "error" field before using the result; when an error/non-200 is detected, do
not wait the full timeout — return or schedule a quick retry and surface/log the
error with context (endpoint, statusCode, body) so the caller can handle failure
gracefully.

Comment on lines +626 to +633
// Format squat analysis results
String _formatSquatResults(Map<String, dynamic> data) {
return 'Squat count: ${data['squat_count'] ?? '-'}\n'
'Reps below parallel: ${data['reps_below_parallel'] ?? '-'}\n'
'Bad reps: ${data['bad_reps'] ?? '-'}\n'
'Form issues: ${(data['form_issues'] as List?)?.join(', ') ?? '-'}\n'
'Squat speed (sec): avg ${data['tempo_stats']?['average'] ?? '-'}, fastest ${data['tempo_stats']?['fastest'] ?? '-'}, slowest ${data['tempo_stats']?['slowest'] ?? '-'}';
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix result schema mismatch (squat uses rep_time, not tempo_stats).

UI shows “-” for squat tempo fields with current backend. Support both keys or normalize backend.

- String _formatSquatResults(Map<String, dynamic> data) {
-   return 'Squat count: ${data['squat_count'] ?? '-'}\n'
-       'Reps below parallel: ${data['reps_below_parallel'] ?? '-'}\n'
-       'Bad reps: ${data['bad_reps'] ?? '-'}\n'
-       'Form issues: ${(data['form_issues'] as List?)?.join(', ') ?? '-'}\n'
-       'Squat speed (sec): avg ${data['tempo_stats']?['average'] ?? '-'}, fastest ${data['tempo_stats']?['fastest'] ?? '-'}, slowest ${data['tempo_stats']?['slowest'] ?? '-'}';
- }
+ String _formatSquatResults(Map<String, dynamic> data) {
+   final stats = data['tempo_stats'] ?? data['rep_time'];
+   return 'Squat count: ${data['squat_count'] ?? '-'}\n'
+       'Reps below parallel: ${data['reps_below_parallel'] ?? '-'}\n'
+       'Bad reps: ${data['bad_reps'] ?? '-'}\n'
+       'Form issues: ${(data['form_issues'] as List?)?.join(', ') ?? '-'}\n'
+       'Squat speed (sec): avg ${stats?['average'] ?? '-'}, fastest ${stats?['fastest'] ?? '-'}, slowest ${stats?['slowest'] ?? '-'}';
+ }
@@
- String summary = _selectedExerciseType == 'pushup'
+ String summary = _selectedExerciseType == 'pushup'
     ? _formatPushupResults(data)
     : _formatSquatResults(data);

Alternatively, normalize in backend to always return tempo_stats. See app.py comment.

Also applies to: 563-566

🤖 Prompt for AI Agents
In optifit app/lib/screens/ai_chat_screen.dart around lines 626 to 633 (and also
applies to 563-566), the UI expects tempo_stats but the backend returns
rep_time, causing tempo fields to show "-" ; update the formatting to handle
both shapes by checking for tempo_stats first, then falling back to rep_time
(map rep_time keys to average/fastest/slowest) or normalize the incoming data
before formatting so tempo fields are populated; ensure null-safe access and
join/list handling for form_issues as before.

Comment on lines +33 to 41
try:
from validation import *
print("✅ validation imported successfully")
VALIDATION_AVAILABLE = True
except ImportError as e:
print(f"❌ validation not available: {e}")
VALIDATION_AVAILABLE = False

import logging
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: fallback validators override real ones.

The locally-defined validate_* functions shadow imported ones, even when validation.py exists. This bypasses stricter validation and fails CI (F403).

-try:
-    from validation import *
+try:
+    import validation as v
@@
-# IMPROVED: Basic validation functions if validation.py doesn't exist
-def validate_upload_request(request):
-    """Basic request validation"""
-    if 'video' not in request.files:
-        raise Exception("No video file provided")
-    video = request.files['video']
-    if video.filename == '':
-        raise Exception("No file selected")
-    return video
-
-def validate_video_file(video):
-    """Basic video file validation"""
-    allowed_extensions = ['mp4', 'mov', 'avi', 'mkv', 'webm', '3gp']
-    if video.filename:
-        ext = video.filename.rsplit('.', 1)[1].lower()
-        if ext not in allowed_extensions:
-            raise Exception(f"File type '{ext}' not supported. Allowed: {', '.join(allowed_extensions)}")
-    return True
-
-def validate_job_request(job_id, jobs):
-    """Basic job validation"""
-    if job_id not in jobs:
-        raise Exception("Job not found")
-    return True
+if not VALIDATION_AVAILABLE:
+    # Minimal fallbacks only if validation module missing
+    def validate_upload_request(request):
+        if 'video' not in request.files:
+            raise Exception("No video file provided")
+        video = request.files['video']
+        if not video.filename:
+            raise Exception("No file selected")
+        return video
+    def validate_video_file(video):
+        allowed = {'mp4','mov','avi','mkv','webm','3gp'}
+        if '.' in video.filename and video.filename.rsplit('.',1)[1].lower() not in allowed:
+            raise Exception("Unsupported file type")
+    def validate_job_request(job_id, jobs):
+        if job_id not in jobs:
+            raise Exception("Job not found")
@@
-        if VALIDATION_AVAILABLE:
-            video = validate_upload_request(request)
-            validate_video_file(video)
+        if VALIDATION_AVAILABLE:
+            video = v.validate_upload_request(request)
+            v.validate_video_file(video)
         else:
             video = validate_upload_request(request)
             validate_video_file(video)
@@
-        if VALIDATION_AVAILABLE:
-            validate_job_request(job_id, jobs)
+        if VALIDATION_AVAILABLE:
+            v.validate_job_request(job_id, jobs)
         else:
             validate_job_request(job_id, jobs)

Also applies to: 63-87, 197-203, 261-265

🧰 Tools
🪛 GitHub Actions: Python CI

[error] 34-34: F403 'from validation import *' used; unable to detect undefined names


[error] 34-34: F401 'validation.*' imported but unused

🪛 Ruff (0.14.1)

34-34: from validation import * used; unable to detect undefined names

(F403)

Comment on lines +250 to 253
except Exception as e:
logger.error(f"Upload error: {str(e)}")
return error_response(str(e), 500)

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Use logger.exception for tracebacks; avoid blind/bare catches.

Improves diagnostics and satisfies lint.

-    except Exception as e:
-        logger.error(f"Upload error: {str(e)}")
+    except Exception:
+        logger.exception("Upload error")
         return error_response(str(e), 500)
@@
-    except Exception as e:
-        logger.error(f"Error getting result: {str(e)}")
+    except Exception:
+        logger.exception("Error getting result")
         return error_response(str(e), 404 if "not found" in str(e).lower() else 500)
@@
-    except Exception as e:
-        logger.error(f"Error serving video {filename}: {str(e)}")
+    except Exception:
+        logger.exception("Error serving video %s", filename)
         return error_response("Error serving video file", 500)

Also applies to: 288-291, 339-341

🧰 Tools
🪛 Ruff (0.14.1)

250-250: Do not catch blind exception: Exception

(BLE001)


251-251: Use logging.exception instead of logging.error

Replace with exception

(TRY400)


251-251: Use explicit conversion flag

Replace with conversion flag

(RUF010)

🤖 Prompt for AI Agents
In optifit backend/app.py around lines 250-253 (and similarly at 288-291 and
339-341), the except blocks catch Exception and call logger.error without
emitting the traceback; replace these blind catches with logger.exception to log
the full traceback and keep the same error_response behavior. Update each except
Exception e: block to call logger.exception("Upload error" or an equivalent
contextual message) and then return the error_response(str(e), 500); consider
narrowing the exception type if possible to avoid a bare catch.


# Start Flask app
app.run(host="0.0.0.0", port=5000)
app.run(host="0.0.0.0", port=5000, debug=True)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Disable debug and public bind by default.

Security risk in production; make it configurable.

-    app.run(host="0.0.0.0", port=5000, debug=True)
+    host = os.getenv("FLASK_HOST", "127.0.0.1")
+    port = int(os.getenv("PORT", "5000"))
+    debug = os.getenv("FLASK_DEBUG", "false").lower() == "true"
+    app.run(host=host, port=port, debug=debug)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
app.run(host="0.0.0.0", port=5000, debug=True)
host = os.getenv("FLASK_HOST", "127.0.0.1")
port = int(os.getenv("PORT", "5000"))
debug = os.getenv("FLASK_DEBUG", "false").lower() == "true"
app.run(host=host, port=port, debug=debug)
🧰 Tools
🪛 Ruff (0.14.1)

385-385: Possible binding to all interfaces

(S104)


385-385: Use of debug=True in Flask app detected

(S201)

🤖 Prompt for AI Agents
In optifit backend/app.py around line 385, the app.run call currently binds to
0.0.0.0 with debug=True which is insecure for production; change it to use
secure defaults and make it configurable via environment/config: default to
host="127.0.0.1" and debug=False, read host and debug values from environment
variables or the app config (e.g., FLASK_RUN_HOST, FLASK_DEBUG or a custom
config key), and only enable public binding or debug mode when those variables
explicitly request it; also ensure app.run is invoked inside an if __name__ ==
"__main__" guard so it isn’t executed on import.

Comment on lines +24 to +51
def calculate_body_alignment(landmarks):
"""Calculate body alignment score for push-up form"""
try:
# Get key body points
left_shoulder = landmarks[mp_pose.PoseLandmark.LEFT_SHOULDER.value]
right_shoulder = landmarks[mp_pose.PoseLandmark.RIGHT_SHOULDER.value]
left_hip = landmarks[mp_pose.PoseLandmark.LEFT_HIP.value]
right_hip = landmarks[mp_pose.PoseLandmark.RIGHT_HIP.value]
left_ankle = landmarks[mp_pose.PoseLandmark.LEFT_ANKLE.value]
right_ankle = landmarks[mp_pose.PoseLandmark.RIGHT_ANKLE.value]

# Calculate center points
shoulder_center = [(left_shoulder.x + right_shoulder.x) / 2, (left_shoulder.y + right_shoulder.y) / 2]
hip_center = [(left_hip.x + right_hip.x) / 2, (left_hip.y + right_hip.y) / 2]
ankle_center = [(left_ankle.x + right_ankle.x) / 2, (left_ankle.y + right_ankle.y) / 2]

# Calculate alignment angles
shoulder_hip_angle = np.arctan2(hip_center[1] - shoulder_center[1], hip_center[0] - shoulder_center[0])
hip_ankle_angle = np.arctan2(ankle_center[1] - hip_center[1], ankle_center[0] - hip_center[0])

# Calculate alignment deviation (perfect alignment = small deviation)
angle_diff = abs(shoulder_hip_angle - hip_ankle_angle)
alignment_score = max(0, 100 - (angle_diff * 180 / np.pi) * 10)

return alignment_score
except:
return 50

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Avoid bare except; log and bound score.

Catch Exception and log; keeps score predictable.

-def calculate_body_alignment(landmarks):
+import logging
+logger = logging.getLogger(__name__)
+
+def calculate_body_alignment(landmarks):
@@
-    except:
-        return 50
+    except Exception as e:
+        logger.exception("Body alignment calculation failed: %s", e)
+        return 50
🧰 Tools
🪛 GitHub Actions: Python CI

[error] 24-24: E302 expected 2 blank lines, found 1


[error] 34-34: W293 blank line contains whitespace


[error] 39-39: W293 blank line contains whitespace


[error] 43-43: W293 blank line contains whitespace


[error] 47-47: W293 blank line contains whitespace


[error] 49-49: E722 do not use bare 'except'

🪛 Ruff (0.14.1)

48-48: Consider moving this statement to an else block

(TRY300)


49-49: Do not use bare except

(E722)

🤖 Prompt for AI Agents
In optifit backend/pushup_counter.py around lines 24 to 51, replace the bare
except with a specific Exception handler that logs the error and returns a
predictable, bounded score: import and use the module logger (e.g.,
logging.getLogger(__name__)) or an existing logger, change "except:" to "except
Exception as e:", call logger.exception("Failed to calculate body alignment") or
logger.error with the exception, and then return a bounded default such as
int(min(max(50, 0), 100)) (or simply 50) so the function always returns a
numeric score within 0–100.

Comment on lines +84 to +91
# Push-up specific thresholds
UP_THRESHOLD = 150 # angle considered up position
START_DOWN_THRESHOLD = 130 # start of descent detection
DOWN_THRESHOLD = 90 # angle considered down position (good depth)
ALIGNMENT_THRESHOLD = 70 # minimum body alignment score
HAND_POSITION_MARGIN = 0.05 # margin for hand position relative to shoulders

# Prepare CSV logging
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove unused HAND_POSITION_MARGIN or use it.

Currently unused; fails CI.

-    HAND_POSITION_MARGIN = 0.05 # margin for hand position relative to shoulders

(Or apply it to 0.8/1.5 thresholds.)

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Push-up specific thresholds
UP_THRESHOLD = 150 # angle considered up position
START_DOWN_THRESHOLD = 130 # start of descent detection
DOWN_THRESHOLD = 90 # angle considered down position (good depth)
ALIGNMENT_THRESHOLD = 70 # minimum body alignment score
HAND_POSITION_MARGIN = 0.05 # margin for hand position relative to shoulders
# Prepare CSV logging
# Push-up specific thresholds
UP_THRESHOLD = 150 # angle considered up position
START_DOWN_THRESHOLD = 130 # start of descent detection
DOWN_THRESHOLD = 90 # angle considered down position (good depth)
ALIGNMENT_THRESHOLD = 70 # minimum body alignment score
# Prepare CSV logging
🧰 Tools
🪛 GitHub Actions: Python CI

[error] 89-89: F841 local variable 'HAND_POSITION_MARGIN' is assigned to but never used


[error] 89-89: E261 at least two spaces before inline comment

🪛 Ruff (0.14.1)

89-89: Local variable HAND_POSITION_MARGIN is assigned to but never used

Remove assignment to unused variable HAND_POSITION_MARGIN

(F841)

🤖 Prompt for AI Agents
In optifit backend/pushup_counter.py around lines 84 to 91, the constant
HAND_POSITION_MARGIN is declared but never used which fails CI; either remove
the constant or use it to parameterize the hand-position checks instead of
hardcoding multipliers (e.g. incorporate HAND_POSITION_MARGIN into the 0.8/1.5
thresholds so the lower/upper hand-position bounds are computed from those base
values plus/minus HAND_POSITION_MARGIN), update any comparisons that currently
use literal 0.8 or 1.5 to use the computed bounds, and run tests to ensure no
remaining unused-variable warnings.

Comment on lines +314 to +330
print("🎞 Converting push-up video to H.264 using ffmpeg...")
ffmpeg_cmd = [
'ffmpeg', '-y',
'-i', raw_path,
'-vcodec', 'libx264',
'-preset', 'fast',
'-crf', '23',
'-acodec', 'aac',
output_path
]
subprocess.run(ffmpeg_cmd, check=True)
try:
os.remove(raw_path)
except:
pass
print("✅ H.264 conversion complete")

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Use logging, not prints; avoid bare except on cleanup.

Also keeps CI happy.

-    print("🎞 Converting push-up video to H.264 using ffmpeg...")
+    logger.info("Converting push-up video to H.264 using ffmpeg...")
@@
-    subprocess.run(ffmpeg_cmd, check=True)
-    try:
+    subprocess.run(ffmpeg_cmd, check=True)
+    try:
         os.remove(raw_path)
-    except:
-        pass
-    print("✅ H.264 conversion complete")
+    except Exception as e:
+        logger.warning("Failed to remove temp file %s: %s", raw_path, e)
+    logger.info("H.264 conversion complete")

Also applies to: 324-328

🧰 Tools
🪛 GitHub Actions: Python CI

[error] 327-327: E722 do not use bare 'except'

🪛 Ruff (0.14.1)

324-324: subprocess call: check for execution of untrusted input

(S603)


327-327: Do not use bare except

(E722)


327-328: try-except-pass detected, consider logging the exception

(S110)

🤖 Prompt for AI Agents
In optifit backend/pushup_counter.py around lines 314-330 (and similarly
324-328), replace the two print statements with appropriate logger calls (e.g.,
logger.info or logger.debug) and remove the bare except on os.remove; instead
catch specific exceptions like OSError (or FileNotFoundError) and log a warning
or debug with the exception details so cleanup failures are recorded without
swallowing errors, ensuring CI-friendly behavior.

@MasterAffan
Copy link
Owner

@sandy4242 this looks really good. can u please share a screen rec or some screenshots that confirm that its working without any issues. once you share it i will merge them

@sandy4242
Copy link
Contributor Author

@MasterAffan i was facing an issue related to connection of the flask server... now i don't know if its only with my flask server or my network because my gemini api was also not providing any response it gave an 404 status error....
Video Upload feature:-
image
gemini response:-
image

@MasterAffan
Copy link
Owner

@sandy4242 alright no problem thanks for being honest. can you just share a video of the pushup detection model working. if its working fine then ill merge it.

@MasterAffan MasterAffan merged commit 34afa55 into MasterAffan:main Oct 31, 2025
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement Push-up Detection

2 participants