added the the flask server code and the updated flutter code#84
added the the flask server code and the updated flutter code#84MasterAffan merged 3 commits intoMasterAffan:mainfrom
Conversation
|
Important Review skippedReview was skipped due to path filters ⛔ Files ignored due to path filters (2)
CodeRabbit blocks several paths by default. You can override this behavior by explicitly including those paths in the path filters. For example, including You can disable this status message by setting the WalkthroughThe 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
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)
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
Pre-merge checks and finishing touches❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
Comment |
PR Compliance Guide 🔍(Compliance updated until commit 15f8c68)Below is a summary of compliance checks for this PR:
Compliance status legend🟢 - Fully Compliant🟡 - Partial Compliant 🔴 - Not Compliant ⚪ - Requires Further Human Verification 🏷️ - Compliance label Previous compliance checksCompliance check up to commit 6c7d8d3
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
@MasterAffan , i have used my local ip address |
PR Code Suggestions ✨Explore these optional code suggestions:
|
||||||||||||||||||
|
@MasterAffan also renamed the squat video and added a push_up video... |
There was a problem hiding this comment.
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_infoApply 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
📒 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.
| static const String chatApiEndpoint = | ||
| 'https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent'; | ||
|
|
There was a problem hiding this comment.
🛠️ 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.
| 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; |
There was a problem hiding this comment.
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.
| 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.
| // 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', | ||
| ); |
There was a problem hiding this comment.
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.
| // 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'] ?? '-'}'; | ||
| } |
There was a problem hiding this comment.
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.
| 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 |
There was a problem hiding this comment.
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)
| except Exception as e: | ||
| logger.error(f"Upload error: {str(e)}") | ||
| return error_response(str(e), 500) | ||
|
|
There was a problem hiding this comment.
🛠️ 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) |
There was a problem hiding this comment.
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.
| 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.
| 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 | ||
|
|
There was a problem hiding this comment.
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.
| # 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 |
There was a problem hiding this comment.
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.
| # 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.
| 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") | ||
|
|
There was a problem hiding this comment.
🛠️ 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.
|
@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 |
|
@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.... |
|
@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. |


User description
Description
This pr adds a functionality on pushup detection within the flutter app
Related Issue
Closes #15
Type of Change
How Has This Been Tested?
Explain the tests you ran and how reviewers can verify the changes.
Screenshots (if applicable)
Checklist
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
File Walkthrough
api_constants.dart
Unified API endpoints for exercise detection serversoptifit app/lib/config/api_constants.dart
ngrok forwarding
ai_chat_screen.dart
Add exercise type selection and push-up support to chat screenoptifit app/lib/screens/ai_chat_screen.dart
_selectedExerciseTypestate variable to track user's exercisechoice
_showExerciseTypeDialog()for users to select betweensquat and push-up analysis
recommendations
upload
_formatPushupResults()and_formatSquatResults()helpermethods for exercise-specific result formatting
_explainWithAI()method to handle AI feedback for bothexercise types
type-specific icons
app.py
Enhanced backend with multi-exercise support and resilienceoptifit backend/app.py
for squat, push-up, and validation modules
when processors unavailable
/uploadendpoint to accept and validateexercise_typeparameter from request
exercise type
/healthendpoint for detailed service status and processoravailability
/jobsendpoint to list all processing jobs with their statusresponses
pushup_counter.py
New push-up detection processor using pose estimationoptifit backend/pushup_counter.py
estimation
squat counter
visualization
tempo statistics
for consistency
Summary by CodeRabbit
Release Notes