chenemii commited on
Commit
af7ef4f
·
1 Parent(s): 2d71c32

comparison

Browse files
Files changed (1) hide show
  1. app/models/llm_analyzer.py +785 -305
app/models/llm_analyzer.py CHANGED
@@ -7,6 +7,8 @@ import httpx
7
  from openai import OpenAI
8
  import streamlit as st
9
  import re
 
 
10
 
11
 
12
  def check_llm_services():
@@ -210,282 +212,425 @@ def call_openai_service(prompt, config):
210
  return None
211
 
212
 
213
- def prepare_data_for_llm(pose_data, swing_phases, trajectory_data):
214
  """
215
  Prepare swing data for LLM analysis
216
 
217
  Args:
218
  pose_data (dict): Dictionary mapping frame indices to pose keypoints
219
  swing_phases (dict): Dictionary mapping phase names to lists of frame indices
220
- trajectory_data (dict): Dictionary mapping frame indices to trajectory data
221
 
222
  Returns:
223
- dict: Processed data for LLM analysis
224
  """
225
- analysis_data = {"swing_phases": {}, "joint_angles": {}, "trajectory": {}}
226
-
227
- # Process swing phases
228
- for phase, frames in swing_phases.items():
229
- if frames:
230
- # Get a representative frame for each phase
231
- mid_frame = frames[len(frames) // 2]
232
-
233
- # Get joint angles for the representative frame
234
- if mid_frame in pose_data:
235
- keypoints = pose_data[mid_frame]
236
-
237
- # Calculate key metrics for each phase
238
- analysis_data["swing_phases"][phase] = {
239
- "frame_index": mid_frame,
240
- "duration_frames": len(frames)
241
- }
242
-
243
- # Process trajectory data
244
- impact_frames = swing_phases.get("impact", [])
245
- if impact_frames:
246
- impact_frame = impact_frames[len(impact_frames) // 2]
247
- if impact_frame in trajectory_data:
248
- impact_data = trajectory_data[impact_frame]
249
- if "club_speed" in impact_data and impact_data["club_speed"]:
250
- analysis_data["trajectory"]["club_speed_mph"] = impact_data[
251
- "club_speed"]
252
-
253
- # Calculate backswing and downswing durations if available
254
- backswing_frames = swing_phases.get("backswing", [])
255
  downswing_frames = swing_phases.get("downswing", [])
256
-
257
- backswing_duration = None
258
- downswing_duration = None
259
-
260
- if backswing_frames:
261
- # Assuming 30 fps video
262
- backswing_duration = len(backswing_frames) / 30.0
263
-
264
- if downswing_frames:
265
- # Assuming 30 fps video
266
- downswing_duration = len(downswing_frames) / 30.0
267
-
268
- # Calculate tempo ratio if both durations are available
269
- tempo_ratio = None
270
- if backswing_duration and downswing_duration and downswing_duration > 0:
271
- tempo_ratio = backswing_duration / downswing_duration
272
-
273
- # Add comprehensive metrics with default values or calculated values
274
- # These values would normally be calculated from pose and trajectory data
275
- analysis_data["metrics"] = {
276
- # Core body mechanics
277
- "tempo_ratio": tempo_ratio or 3.0, # Backswing to downswing time ratio
278
- "swing_plane_consistency": 0.85, # 0-1 scale
279
- "weight_shift": 0.7, # 0-1 scale
280
- "hip_rotation": 45, # degrees
281
- "shoulder_rotation": 90, # degrees
282
- "posture_score": 0.8, # 0-1 scale
283
-
284
- # Upper body mechanics
285
- "arm_extension": 0.8, # 0-1 scale
286
- "wrist_hinge": 80, # degrees
287
- "chest_rotation_efficiency": 0.75, # 0-1 scale
288
- "head_movement_lateral": 2.5, # inches
289
- "head_movement_vertical": 1.8, # inches
290
-
291
- # Lower body mechanics
292
- "knee_flexion_address": 25, # degrees
293
- "knee_flexion_impact": 30, # degrees
294
- "hip_thrust": 0.6, # 0-1 scale
295
- "ground_force_efficiency": 0.7, # 0-1 scale
296
-
297
- # Club path and face metrics
298
- "swing_path":
299
- 2.5, # degrees (positive = out-to-in, negative = in-to-out)
300
- "clubface_angle": 2.1, # degrees (positive = open, negative = closed)
301
- "attack_angle":
302
- -4.2, # degrees (negative = descending, positive = ascending)
303
- "club_path_consistency": 0.78, # 0-1 scale
304
-
305
- # Tempo and timing metrics
306
- "transition_smoothness": 0.75, # 0-1 scale
307
- "backswing_duration": backswing_duration or 0.9, # seconds
308
- "downswing_duration": downswing_duration or 0.3, # seconds
309
- "kinematic_sequence": 0.82, # 0-1 scale
310
-
311
- # Efficiency and power metrics
312
- "energy_transfer": 0.78, # 0-1 scale
313
- "potential_distance": 240, # yards
314
- "power_accumulation": 0.75, # 0-1 scale
315
- "speed_generation": "Arms-dominant" # String description
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
316
  }
317
-
318
- return analysis_data
319
 
320
 
321
  def create_llm_prompt(analysis_data):
322
  """
323
- Create a prompt for the LLM based on swing analysis data
324
 
325
  Args:
326
- analysis_data (dict): Processed swing analysis data
327
 
328
  Returns:
329
- str: Prompt for LLM
330
  """
331
- prompt = """
332
- You are analyzing a golf swing. First, here are examples of professional golfer swing analyses that represent benchmark performance levels:
333
-
334
- ## PROFESSIONAL BENCHMARKS
335
-
336
- ### Nelly Korda (Example 1) - LPGA Tour Professional
337
- **Swing Phases:**
338
- - Setup: 122 frames, Backswing: 2 frames, Downswing: 5 frames, Impact: 1 frame, Follow-through: 42 frames
339
-
340
- **Key Metrics:**
341
- - Tempo Ratio: 0.4, Hip Rotation: 45°, Shoulder Rotation: 90°, Posture Score: 80%
342
- - Arm Extension: 80%, Wrist Hinge: 80°, Shoulder Plane Consistency: 85%
343
- - Weight Shift: 70%, Transition Smoothness: 75%, Sequential Kinematic Sequence: 82%
344
- - Energy Transfer Efficiency: 78%, Backswing Duration: 0.067s, Downswing Duration: 0.167s
345
-
346
- ### Nelly Korda (Example 2) - Different Swing Tempo Style
347
- **Key Metrics:**
348
- - Tempo Ratio: 3.0, Backswing Duration: 0.9s, Downswing Duration: 0.9s
349
- - All other core metrics remain consistent: Hip Rotation: 45°, Shoulder Rotation: 90°, etc.
350
-
351
- ### Nelly Korda (Example 3) - Fast Tempo Style
352
- **Key Metrics:**
353
- - Tempo Ratio: 0.3, Backswing Duration: 0.067s, Downswing Duration: 0.2s
354
- - Consistent professional metrics maintained across all mechanical aspects
355
-
356
- ### Lydia Ko - LPGA Tour Professional
357
- **Key Metrics:**
358
- - Tempo Ratio: 14.0, Backswing Duration: 0.467s, Downswing Duration: 0.033s
359
- - Demonstrates that professional tempo can vary dramatically while maintaining consistency in:
360
- - Hip/Shoulder Rotation, Posture, Arm Extension, Weight Shift, and Sequential Timing
361
-
362
- ### Atthaya Thitikul - LPGA Tour Professional
363
- **Key Metrics:**
364
- - Tempo Ratio: 2.8, Backswing Duration: 0.567s, Downswing Duration: 0.2s
365
- - Consistent with professional standards across all biomechanical markers
366
-
367
- ## PROFESSIONAL STANDARDS SUMMARY
368
- Based on these examples, professional golfers consistently achieve:
369
- - **Core Body Mechanics**: Hip Rotation: 45°, Shoulder Rotation: 90°, Posture Score: 80%
370
- - **Upper Body**: Arm Extension: 80%, Wrist Hinge: 80°, Shoulder Plane Consistency: 85%
371
- - **Lower Body**: Weight Shift: 70%, Ground Force Efficiency: 70%
372
- - **Timing**: Transition Smoothness: 75%, Sequential Kinematic Sequence: 82%
373
- - **Efficiency**: Energy Transfer: 78%, Power Accumulation: 75%
374
- - **Head Movement**: Lateral: 2.5in, Vertical: 1.8in (minimal movement is professional standard)
375
- - **Tempo**: Highly variable (0.3 to 14.0 ratio) - personal style, not performance indicator
376
-
377
- ---
378
-
379
- ## CURRENT PLAYER ANALYSIS
380
-
381
- I've analyzed a golf swing and extracted the following data:
382
-
383
- ## Swing Phases
384
- """
385
-
386
- # Add swing phases information
387
- for phase, data in analysis_data["swing_phases"].items():
388
- prompt += f"- {phase.capitalize()}: Frame {data['frame_index']}, Duration: {data['duration_frames']} frames\n"
389
-
390
- # Add detailed biomechanical metrics
391
- prompt += "\n## Swing Mechanics\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
392
 
393
  # Core body mechanics
394
- prompt += "\n### Body Mechanics\n"
395
- prompt += "- Tempo Ratio (Backswing:Downswing): {:.1f}\n".format(
396
- analysis_data["metrics"].get("tempo_ratio", 0))
397
- prompt += "- Hip Rotation (degrees): {}\n".format(
398
- analysis_data["metrics"].get("hip_rotation", 0))
399
- prompt += "- Shoulder Rotation (degrees): {}\n".format(
400
- analysis_data["metrics"].get("shoulder_rotation", 0))
401
- prompt += "- Posture Score: {}%\n".format(
402
- int(analysis_data["metrics"].get("posture_score", 0) * 100))
403
 
404
  # Upper body mechanics
405
  prompt += "\n### Upper Body Mechanics\n"
406
- prompt += "- Arm Extension (impact): {}%\n".format(
407
- int(analysis_data["metrics"].get("arm_extension", 0.8) * 100))
408
- prompt += "- Wrist Hinge (degrees): {}\n".format(
409
- analysis_data["metrics"].get("wrist_hinge", 0))
410
- prompt += "- Shoulder Plane Consistency: {}%\n".format(
411
- int(analysis_data["metrics"].get("swing_plane_consistency", 0) * 100))
412
- prompt += "- Chest Rotation Efficiency: {}%\n".format(
413
- int(analysis_data["metrics"].get("chest_rotation_efficiency", 0.75) *
414
- 100))
415
- prompt += "- Head Movement (lateral): {}in\n".format(
416
- analysis_data["metrics"].get("head_movement_lateral", 2.5))
417
- prompt += "- Head Movement (vertical): {}in\n".format(
418
- analysis_data["metrics"].get("head_movement_vertical", 1.8))
419
 
420
  # Lower body mechanics
421
  prompt += "\n### Lower Body Mechanics\n"
422
- prompt += "- Weight Shift (lead foot at impact): {}%\n".format(
423
- int(analysis_data["metrics"].get("weight_shift", 0) * 100))
424
- prompt += "- Knee Flexion (address): {}°\n".format(
425
- analysis_data["metrics"].get("knee_flexion_address", 25))
426
- prompt += "- Knee Flexion (impact): {}°\n".format(
427
- analysis_data["metrics"].get("knee_flexion_impact", 30))
428
- prompt += "- Hip Thrust (impact): {}%\n".format(
429
- int(analysis_data["metrics"].get("hip_thrust", 0.6) * 100))
430
- prompt += "- Ground Force Efficiency: {}%\n".format(
431
- int(analysis_data["metrics"].get("ground_force_efficiency", 0.7) *
432
- 100))
433
-
434
- # Tempo and timing metrics
435
- prompt += "\n### Tempo & Timing\n"
436
- prompt += "- Transition Smoothness: {}%\n".format(
437
- int(analysis_data["metrics"].get("transition_smoothness", 0.75) * 100))
438
- prompt += "- Backswing Duration: {} seconds\n".format(
439
- analysis_data["metrics"].get("backswing_duration", 0.9))
440
- prompt += "- Downswing Duration: {} seconds\n".format(
441
- analysis_data["metrics"].get("downswing_duration", 0.3))
442
- prompt += "- Sequential Kinematic Sequence: {}%\n".format(
443
- int(analysis_data["metrics"].get("kinematic_sequence", 0.82) * 100))
444
 
445
  # Efficiency and power metrics
446
  prompt += "\n### Efficiency & Power Metrics\n"
447
- prompt += "- Energy Transfer Efficiency: {}%\n".format(
448
- int(analysis_data["metrics"].get("energy_transfer", 0.78) * 100))
449
- prompt += "- Potential Distance: {} yards\n".format(
450
- analysis_data["metrics"].get("potential_distance", 240))
451
- prompt += "- Power Accumulation: {}%\n".format(
452
- int(analysis_data["metrics"].get("power_accumulation", 0.75) * 100))
453
- prompt += "- Speed Generation Method: {}\n".format(
454
- analysis_data["metrics"].get("speed_generation", "Arms-dominant"))
455
 
456
  prompt += """
457
 
458
  ## ANALYSIS INSTRUCTIONS
459
 
460
- Using the professional benchmarks above as your calibration reference, provide your analysis in the following EXACT structured format:
461
 
462
- **PERFORMANCE_CLASSIFICATION:** [Professional/Advanced/Intermediate/Beginner]
463
 
464
  **STRENGTHS:**
465
- • [Specific strength with metric comparison to professional standard]
466
- • [Another strength with professional benchmark reference]
467
- • [Third strength if applicable]
468
 
469
  **WEAKNESSES:**
470
- • [Specific weakness with gap from professional standard]
471
- • [Another weakness with professional benchmark comparison]
472
- • [Third weakness if applicable]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
473
 
474
  **PRIORITY_IMPROVEMENTS:**
475
- 1. [Most Critical] Topic Name - Detailed description of current issue and what should be improved to reach professional standard
476
- 2. [Important] Topic Name - Detailed description of current issue and desired improvement outcome
477
- 3. [Focus Area] Topic Name - Detailed description of current issue and target improvement goal
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
478
 
479
  IMPORTANT FORMATTING RULES:
480
  - Use the exact headers shown above (PERFORMANCE_CLASSIFICATION, STRENGTHS, WEAKNESSES, PRIORITY_IMPROVEMENTS)
 
481
  - For strengths and weaknesses, use bullet points (•)
482
  - For priority improvements, use numbered format (1., 2., 3.) with priority level in brackets
483
- - Each priority improvement must have: [Priority Level] Topic Name - Full description
484
- - Provide complete sentences and descriptions - no incomplete thoughts
485
- - Compare all metrics to the professional benchmarks provided above
486
- - Be specific about what needs improvement and what the target should be
487
-
488
- Remember: Professional golfers consistently achieve the benchmark metrics shown above. Use these as the gold standard for what constitutes excellent golf swing mechanics.
 
 
489
  """
490
 
491
  return prompt
@@ -503,29 +648,32 @@ def parse_and_format_analysis(raw_analysis):
503
  """
504
  # Default structure
505
  formatted_analysis = {
506
- 'classification': 'Intermediate', # Default classification
507
  'strengths': [],
508
  'weaknesses': [],
509
  'priority_improvements': []
510
  }
511
 
512
- # Extract classification using the new structured format
513
- classification_match = re.search(r'\*\*PERFORMANCE_CLASSIFICATION:\*\*\s*([A-Za-z]+)', raw_analysis, re.IGNORECASE)
514
  if classification_match:
515
- formatted_analysis['classification'] = classification_match.group(1).title()
 
 
516
  else:
517
- # Fallback to original patterns
518
- classification_patterns = [
519
- r'(?:Performance Classification|Classification|Level).*?:\s*(Professional|Advanced|Intermediate|Beginner)',
520
- r'(Professional|Advanced|Intermediate|Beginner)\s+(?:Level|Amateur)',
521
- r'classified as\s+(Professional|Advanced|Intermediate|Beginner)',
522
- r'(?:at|as)\s+(?:an?\s+)?(Professional|Advanced|Intermediate|Beginner)\s+level'
523
  ]
524
 
525
- for pattern in classification_patterns:
526
  match = re.search(pattern, raw_analysis, re.IGNORECASE)
527
  if match:
528
- formatted_analysis['classification'] = match.group(1).title()
 
529
  break
530
 
531
  # Extract strengths using the new structured format
@@ -632,29 +780,30 @@ def parse_and_format_analysis(raw_analysis):
632
  formatted_analysis['weaknesses'] = ['Areas for improvement identified']
633
 
634
  if not formatted_analysis['priority_improvements']:
635
- if formatted_analysis['classification'] == 'Beginner':
 
636
  formatted_analysis['priority_improvements'] = [
637
- {'rank': 1, 'description': '[Most Critical] Fundamental Posture and Setup - Focus on establishing proper spine angle and athletic stance throughout the swing for better consistency and power transfer.'},
638
- {'rank': 2, 'description': '[Important] Tempo and Timing Development - Develop consistent swing rhythm and timing to improve sequence and control.'},
639
- {'rank': 3, 'description': '[Focus Area] Weight Shift and Balance - Improve weight transfer from back foot to front foot during swing for better power and stability.'}
640
  ]
641
- elif formatted_analysis['classification'] == 'Intermediate':
642
  formatted_analysis['priority_improvements'] = [
643
  {'rank': 1, 'description': '[Most Critical] Kinematic Sequence Enhancement - Improve body rotation coordination to generate more power and consistency.'},
644
  {'rank': 2, 'description': '[Important] Clubface Control - Enhance swing path consistency for better ball striking accuracy.'},
645
  {'rank': 3, 'description': '[Focus Area] Energy Transfer Efficiency - Optimize power transfer throughout the swing to maximize distance.'}
646
  ]
647
- elif formatted_analysis['classification'] == 'Advanced':
648
  formatted_analysis['priority_improvements'] = [
649
- {'rank': 1, 'description': '[Most Critical] Transition Smoothness - Fine-tune timing and tempo to achieve professional-level consistency.'},
650
- {'rank': 2, 'description': '[Important] Power Accumulation - Optimize energy storage and release for maximum clubhead speed.'},
651
- {'rank': 3, 'description': '[Focus Area] Pressure Performance - Enhance consistency under competitive conditions.'}
652
  ]
653
- else: # Professional
654
  formatted_analysis['priority_improvements'] = [
655
- {'rank': 1, 'description': '[Most Critical] Technical Refinement - Maintain excellence with minor adjustments to specific mechanics.'},
656
- {'rank': 2, 'description': '[Important] Strategic Optimization - Focus on course management and scoring opportunities.'},
657
- {'rank': 3, 'description': '[Focus Area] Physical Conditioning - Continue fitness work for career longevity and peak performance.'}
658
  ]
659
 
660
  return formatted_analysis
@@ -667,70 +816,89 @@ def display_formatted_analysis(analysis_data):
667
  Args:
668
  analysis_data (dict): Structured analysis data from parse_and_format_analysis
669
  """
670
- # 1. Performance Classification with colored rounded rectangles
671
- user_classification = analysis_data['classification']
672
 
673
  # Display classification in black bolded header
674
  st.markdown(f"""
675
  <h2 style='color: black; font-weight: bold; text-align: center; margin-bottom: 20px;'>
676
- 🎯 Performance Classification: {user_classification}
677
  </h2>
678
  """, unsafe_allow_html=True)
679
 
680
- # Create columns for the classification rectangles
681
- col1, col2, col3, col4 = st.columns(4)
 
 
 
 
 
 
682
 
683
- # Define colors and styling - all rectangles should have colors
684
- colors = {
685
- 'Beginner': {'bg': '#ff4444', 'text': 'white'},
686
- 'Intermediate': {'bg': '#ff8800', 'text': 'white'},
687
- 'Advanced': {'bg': '#ffdd00', 'text': 'black'},
688
- 'Professional': {'bg': '#44aa44', 'text': 'white'}
689
- }
690
-
691
- with col1:
692
- bg_color = colors['Beginner']['bg']
693
- text_color = colors['Beginner']['text']
694
- border_style = '3px solid #333' if user_classification == 'Beginner' else '2px solid #ddd'
695
- st.markdown(f"""
696
- <div style='text-align: center; padding: 15px; background-color: {bg_color};
697
- border-radius: 15px; margin: 5px; border: {border_style};'>
698
- <div style='font-size: 14px; font-weight: bold; color: {text_color};'>Beginner</div>
699
  </div>
700
- """, unsafe_allow_html=True)
701
-
702
- with col2:
703
- bg_color = colors['Intermediate']['bg']
704
- text_color = colors['Intermediate']['text']
705
- border_style = '3px solid #333' if user_classification == 'Intermediate' else '2px solid #ddd'
706
- st.markdown(f"""
707
- <div style='text-align: center; padding: 15px; background-color: {bg_color};
708
- border-radius: 15px; margin: 5px; border: {border_style};'>
709
- <div style='font-size: 14px; font-weight: bold; color: {text_color};'>Intermediate</div>
710
  </div>
711
- """, unsafe_allow_html=True)
712
-
713
- with col3:
714
- bg_color = colors['Advanced']['bg']
715
- text_color = colors['Advanced']['text']
716
- border_style = '3px solid #333' if user_classification == 'Advanced' else '2px solid #ddd'
717
- st.markdown(f"""
718
- <div style='text-align: center; padding: 15px; background-color: {bg_color};
719
- border-radius: 15px; margin: 5px; border: {border_style};'>
720
- <div style='font-size: 14px; font-weight: bold; color: {text_color};'>Advanced</div>
 
721
  </div>
722
- """, unsafe_allow_html=True)
 
723
 
724
- with col4:
725
- bg_color = colors['Professional']['bg']
726
- text_color = colors['Professional']['text']
727
- border_style = '3px solid #333' if user_classification == 'Professional' else '2px solid #ddd'
728
- st.markdown(f"""
729
- <div style='text-align: center; padding: 15px; background-color: {bg_color};
730
- border-radius: 15px; margin: 5px; border: {border_style};'>
731
- <div style='font-size: 14px; font-weight: bold; color: {text_color};'>Professional</div>
732
- </div>
733
- """, unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
734
 
735
  st.markdown("---")
736
 
@@ -844,3 +1012,315 @@ def display_formatted_analysis(analysis_data):
844
  st.write(desc)
845
 
846
  st.write("") # Add spacing between items
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  from openai import OpenAI
8
  import streamlit as st
9
  import re
10
+ import numpy as np
11
+ from app.models.pose_estimator import calculate_joint_angles
12
 
13
 
14
  def check_llm_services():
 
212
  return None
213
 
214
 
215
+ def prepare_data_for_llm(pose_data, swing_phases, trajectory_data=None):
216
  """
217
  Prepare swing data for LLM analysis
218
 
219
  Args:
220
  pose_data (dict): Dictionary mapping frame indices to pose keypoints
221
  swing_phases (dict): Dictionary mapping phase names to lists of frame indices
222
+ trajectory_data (dict, optional): Ball trajectory data
223
 
224
  Returns:
225
+ dict: Formatted swing data for LLM
226
  """
227
+
228
+ # Calculate actual biomechanical metrics from pose data
229
+ bio_metrics = calculate_biomechanical_metrics(pose_data, swing_phases)
230
+
231
+ # Calculate phase durations and timing metrics
232
+ setup_frames = swing_phases.get("setup", [])
233
+ backswing_frames = swing_phases.get("backswing", [])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
234
  downswing_frames = swing_phases.get("downswing", [])
235
+ impact_frames = swing_phases.get("impact", [])
236
+ follow_through_frames = swing_phases.get("follow_through", [])
237
+
238
+ # Calculate tempo ratio (downswing:backswing)
239
+ backswing_duration = len(backswing_frames) if backswing_frames else 1
240
+ downswing_duration = len(downswing_frames) if downswing_frames else 1
241
+ tempo_ratio = downswing_duration / backswing_duration if backswing_duration > 0 else 1.0
242
+
243
+ # Calculate total swing duration and club speed estimates
244
+ total_frames = len(setup_frames) + len(backswing_frames) + len(downswing_frames) + len(impact_frames) + len(follow_through_frames)
245
+
246
+ # Estimate club speed based on downswing duration (faster downswing = higher speed)
247
+ # Professional downswings are typically 10-15 frames at 30fps
248
+ if downswing_duration > 0:
249
+ speed_factor = max(0.5, min(2.0, 12.0 / downswing_duration)) # Normalize around 12 frames
250
+ estimated_club_speed = 70 + (speed_factor * 40) # Base 70 mph, up to 110 mph
251
+ else:
252
+ estimated_club_speed = 85
253
+
254
+ # Process joint angles if available
255
+ joint_angles = {}
256
+ if pose_data:
257
+ # Get a representative frame for joint analysis
258
+ rep_frame = None
259
+ if impact_frames:
260
+ rep_frame = impact_frames[0]
261
+ elif downswing_frames:
262
+ rep_frame = downswing_frames[len(downswing_frames) // 2]
263
+ elif backswing_frames:
264
+ rep_frame = backswing_frames[-1]
265
+
266
+ if rep_frame and rep_frame in pose_data:
267
+ try:
268
+ from app.models.pose_estimator import calculate_joint_angles
269
+ joint_angles = calculate_joint_angles(pose_data[rep_frame])
270
+ except Exception as e:
271
+ print(f"Error calculating joint angles: {e}")
272
+ joint_angles = {}
273
+
274
+ # Prepare the structured data
275
+ swing_data = {
276
+ "swing_phases": {
277
+ "setup": {
278
+ "frame_count": len(setup_frames),
279
+ "duration_ms": len(setup_frames) * 33.33 # Assuming 30fps
280
+ },
281
+ "backswing": {
282
+ "frame_count": len(backswing_frames),
283
+ "duration_ms": len(backswing_frames) * 33.33
284
+ },
285
+ "downswing": {
286
+ "frame_count": len(downswing_frames),
287
+ "duration_ms": len(downswing_frames) * 33.33
288
+ },
289
+ "impact": {
290
+ "frame_count": len(impact_frames),
291
+ "duration_ms": len(impact_frames) * 33.33
292
+ },
293
+ "follow_through": {
294
+ "frame_count": len(follow_through_frames),
295
+ "duration_ms": len(follow_through_frames) * 33.33
296
+ }
297
+ },
298
+
299
+ "timing_metrics": {
300
+ "tempo_ratio": round(tempo_ratio, 2),
301
+ "total_swing_frames": total_frames,
302
+ "total_swing_time_ms": total_frames * 33.33,
303
+ "estimated_club_speed_mph": round(estimated_club_speed, 1)
304
+ },
305
+
306
+ "biomechanical_metrics": {
307
+ # Core rotation metrics
308
+ "hip_rotation_degrees": round(bio_metrics.get("hip_rotation", 25), 1),
309
+ "shoulder_rotation_degrees": round(bio_metrics.get("shoulder_rotation", 60), 1),
310
+ "chest_rotation_efficiency_percent": round(bio_metrics.get("chest_rotation_efficiency", 0.6) * 100, 1),
311
+
312
+ # Weight transfer and stability
313
+ "weight_shift_percent": round(bio_metrics.get("weight_shift", 0.5) * 100, 1),
314
+ "ground_force_efficiency_percent": round(bio_metrics.get("ground_force_efficiency", 0.6) * 100, 1),
315
+ "hip_thrust_percent": round(bio_metrics.get("hip_thrust", 0.5) * 100, 1),
316
+
317
+ # Arm and club mechanics
318
+ "arm_extension_percent": round(bio_metrics.get("arm_extension", 0.6) * 100, 1),
319
+ "wrist_hinge_degrees": round(bio_metrics.get("wrist_hinge", 60), 1),
320
+ "swing_plane_consistency_percent": round(bio_metrics.get("swing_plane_consistency", 0.6) * 100, 1),
321
+
322
+ # Posture and stability
323
+ "posture_score_percent": round(bio_metrics.get("posture_score", 0.6) * 100, 1),
324
+ "head_movement_lateral_inches": round(bio_metrics.get("head_movement_lateral", 3.0), 1),
325
+ "head_movement_vertical_inches": round(bio_metrics.get("head_movement_vertical", 2.0), 1),
326
+
327
+ # Leg mechanics
328
+ "knee_flexion_address_degrees": round(bio_metrics.get("knee_flexion_address", 25), 1),
329
+ "knee_flexion_impact_degrees": round(bio_metrics.get("knee_flexion_impact", 30), 1),
330
+
331
+ # Advanced coordination metrics
332
+ "transition_smoothness_percent": round(bio_metrics.get("transition_smoothness", 0.6) * 100, 1),
333
+ "kinematic_sequence_percent": round(bio_metrics.get("kinematic_sequence", 0.6) * 100, 1),
334
+ "energy_transfer_efficiency_percent": round(bio_metrics.get("energy_transfer", 0.6) * 100, 1),
335
+ "power_accumulation_percent": round(bio_metrics.get("power_accumulation", 0.6) * 100, 1),
336
+
337
+ # Performance estimates
338
+ "potential_distance_yards": round(bio_metrics.get("potential_distance", 200), 0),
339
+ "speed_generation_method": bio_metrics.get("speed_generation", "Mixed")
340
+ },
341
+
342
+ "joint_angles": joint_angles,
343
+
344
+ "trajectory_analysis": trajectory_data if trajectory_data else {
345
+ "estimated_carry_distance": round(bio_metrics.get("potential_distance", 200) * 0.85, 0),
346
+ "estimated_ball_speed": round(estimated_club_speed * 1.4, 1), # Rough conversion
347
+ "trajectory_type": "Mid" if bio_metrics.get("arm_extension", 0.6) > 0.7 else "Low"
348
+ }
349
  }
350
+
351
+ return swing_data
352
 
353
 
354
  def create_llm_prompt(analysis_data):
355
  """
356
+ Create a comprehensive prompt for LLM analysis with professional benchmarks
357
 
358
  Args:
359
+ analysis_data (dict): Processed swing analysis data with biomechanical metrics
360
 
361
  Returns:
362
+ str: Formatted prompt for LLM analysis
363
  """
364
+
365
+ # Extract metrics from the new data structure
366
+ bio_metrics = analysis_data.get("biomechanical_metrics", {})
367
+ timing_metrics = analysis_data.get("timing_metrics", {})
368
+ swing_phases = analysis_data.get("swing_phases", {})
369
+
370
+ prompt = """# Golf Swing Analysis
371
+
372
+ ## PROFESSIONAL BENCHMARKS FOR CALIBRATION
373
+ Use these professional standards as your 100% reference for scoring. These represent elite-level golf swing mechanics based on actual LPGA Tour professional analysis:
374
+
375
+ ### Professional Golfer Analysis Summary (100% Reference Standards):
376
+
377
+ **Atthaya Thitikul (LPGA Tour - Elite Level):**
378
+ - Hip Rotation: 63.4°, Shoulder Rotation: 120°, Posture Score: 98.2%
379
+ - Weight Shift: 88.4%, Arm Extension: 99.8%, Wrist Hinge: 120°
380
+ - Energy Transfer: 96.1%, Power Accumulation: 100%, Potential Distance: 295 yards
381
+ - Sequential Kinematic Sequence: 100%, Swing Plane Consistency: 85%
382
+
383
+ **Nelly Korda (LPGA Tour - Elite Level):**
384
+ - Hip Rotation: 90°, Shoulder Rotation: 120°, Posture Score: 97.4%
385
+ - Weight Shift: 73.5%, Arm Extension: 96.7%, Wrist Hinge: 114.8°
386
+ - Energy Transfer: 91.2%, Power Accumulation: 100%, Potential Distance: 289 yards
387
+ - Sequential Kinematic Sequence: 100%, Swing Plane Consistency: 85%
388
+
389
+ **Demi Runas (Professional Level):**
390
+ - Hip Rotation: 63.4°, Shoulder Rotation: 120°, Posture Score: 95.9%
391
+ - Weight Shift: 63.9%, Arm Extension: 96.6%, Wrist Hinge: 93.
392
+ - Energy Transfer: 88.0%, Power Accumulation: 100%, Potential Distance: 286 yards
393
+ - Sequential Kinematic Sequence: 100%, Swing Plane Consistency: 85%
394
+
395
+ ### **PROFESSIONAL STANDARDS CALIBRATION (100% Level):**
396
+ **Core Biomechanical Metrics:**
397
+ - **Hip Rotation**: 60-90° (Exceptional body turn and flexibility)
398
+ - **Shoulder Rotation**: 120° (Full shoulder coil for maximum power)
399
+ - **Posture Score**: 95-98% (Exceptional spine angle consistency)
400
+ - **Weight Shift**: 70-88% (Excellent weight transfer to lead side)
401
+
402
+ **Upper Body Excellence:**
403
+ - **Arm Extension**: 96-100% (Near-perfect extension at impact)
404
+ - **Wrist Hinge**: 95-120° (Optimal lag and release timing)
405
+ - **Swing Plane Consistency**: 85% (Tour-level repeatability)
406
+ - **Chest Rotation Efficiency**: 100% (Perfect coordination)
407
+
408
+ **Power & Efficiency Markers:**
409
+ - **Energy Transfer Efficiency**: 88-96% (Elite power transfer)
410
+ - **Power Accumulation**: 100% (Maximum power generation)
411
+ - **Sequential Kinematic Sequence**: 100% (Perfect body sequencing)
412
+ - **Potential Distance**: 285-295 yards (Tour-level power)
413
+
414
+ **Movement Quality Standards:**
415
+ - **Head Movement**: 2-8 inches (Controlled, minimal excessive movement)
416
+ - **Ground Force Efficiency**: 70-88% (Excellent ground interaction)
417
+ - **Hip Thrust**: 40-100% (Strong lower body drive)
418
+
419
+ ### **AMATEUR REFERENCE EXAMPLES FOR CALIBRATION:**
420
+
421
+ **70% Level Skilled Amateur (Female):**
422
+ - Hip Rotation: 23.0°, Shoulder Rotation: 120° (Excellent shoulder turn, limited hip mobility)
423
+ - Posture Score: 89.5%, Weight Shift: 90.0% (Strong fundamentals)
424
+ - Arm Extension: 99.8%, Wrist Hinge: 49.4° (Great extension, needs more lag)
425
+ - Energy Transfer: 94.5%, Power Accumulation: 82.1% (Very good coordination)
426
+ - Potential Distance: 273 yards, Sequential Kinematic: 93.6%
427
+ - Head Movement: 8.0in lateral, 6.0in vertical (Excessive movement)
428
+ - Speed Generation: Mixed
429
+
430
+ **50-60% Level Amateur (Male #1 - Body-Dominant):**
431
+ - Hip Rotation: 90°, Shoulder Rotation: 84.8° (Great hip turn, limited shoulder)
432
+ - Posture Score: 90.7%, Weight Shift: 90.0% (Solid fundamentals)
433
+ - Arm Extension: 100.0%, Wrist Hinge: 66.8° (Good extension and lag)
434
+ - Energy Transfer: 91.8%, Power Accumulation: 100.0% (Strong power generation)
435
+ - Potential Distance: 290 yards, Sequential Kinematic: 100.0%
436
+ - Hip Thrust: 100.0%, Ground Force: 90.0% (Excellent lower body)
437
+ - Speed Generation: Body-dominant
438
+
439
+ **50-60% Level Amateur (Male #2 - Body-Dominant):**
440
+ - Hip Rotation: 90°, Shoulder Rotation: 120° (Excellent rotation both)
441
+ - Posture Score: 89.3%, Weight Shift: 90.0% (Good fundamentals)
442
+ - Arm Extension: 99.6%, Wrist Hinge: 52.6° (Great extension, limited lag)
443
+ - Energy Transfer: 96.7%, Power Accumulation: 100.0% (Excellent coordination)
444
+ - Potential Distance: 296 yards, Sequential Kinematic: 100.0%
445
+ - Tempo Issues: Very fast downswing (2.86 ratio vs ideal ~0.3)
446
+ - Speed Generation: Body-dominant
447
+
448
+ **50-60% Level Amateur (Female - Arms-Dominant):**
449
+ - Hip Rotation: 25°, Shoulder Rotation: 60° (Limited body rotation)
450
+ - Posture Score: 80.6%, Weight Shift: 50.0% (Needs improvement)
451
+ - Arm Extension: 94.8%, Wrist Hinge: 116.6° (Good extension, excellent lag)
452
+ - Energy Transfer: 56.8%, Power Accumulation: 89.3% (Mixed efficiency)
453
+ - Potential Distance: 241 yards, Sequential Kinematic: 66.8%
454
+ - Head Movement: 3.0in lateral, 2.0in vertical (Good head control)
455
+ - Ground Force: 50.0%, Hip Thrust: 30.0% (Weak lower body)
456
+ - Speed Generation: Arms-dominant
457
+
458
+ **CRITICAL INSIGHTS FROM AMATEUR ANALYSIS:**
459
+ 1. **Hip Rotation Varies Significantly**: From 23-90° in amateurs vs 60-90° in professionals
460
+ 2. **Shoulder Rotation Range**: 60-120° in amateurs, professionals consistently at 120°
461
+ 3. **Wrist Hinge Compensation**: Some amateurs (116.6°) exceed professional standards to compensate for limited body rotation
462
+ 4. **Power Generation Methods**: Body-dominant amateurs can achieve near-professional distances despite technical limitations
463
+ 5. **Head Movement Control**: Varies dramatically (3-8 inches) - major differentiator
464
+ 6. **Energy Transfer Efficiency**: Ranges from 56.8-96.7% in amateurs vs 88-96% in professionals
465
+ 7. **Weight Transfer Issues**: Some amateurs struggle with weight shift (50% vs professional 70-88%)
466
+
467
+ ## CURRENT SWING ANALYSIS
468
+
469
+ ### Swing Phase Breakdown
470
+ """.format(
471
+ swing_phases.get("setup", {}).get("frame_count", 44),
472
+ swing_phases.get("backswing", {}).get("frame_count", 7),
473
+ swing_phases.get("downswing", {}).get("frame_count", 12),
474
+ swing_phases.get("impact", {}).get("frame_count", 1),
475
+ swing_phases.get("follow_through", {}).get("frame_count", 37),
476
+ timing_metrics.get("tempo_ratio", 0.6)
477
+ )
478
+
479
+ # Add swing phase details
480
+ for phase_name, phase_data in swing_phases.items():
481
+ prompt += f"- {phase_name.title()}: {phase_data.get('frame_count', 0)} frames ({phase_data.get('duration_ms', 0):.0f}ms)\n"
482
+
483
+ prompt += f"- Total Swing: {timing_metrics.get('total_swing_frames', 0)} frames ({timing_metrics.get('total_swing_time_ms', 0):.0f}ms)\n"
484
+ prompt += f"- Tempo Ratio (down:back): {timing_metrics.get('tempo_ratio', 1.0)}\n"
485
+ prompt += f"- Estimated Club Speed: {timing_metrics.get('estimated_club_speed_mph', 85)} mph\n"
486
 
487
  # Core body mechanics
488
+ prompt += "\n### Core Body Mechanics\n"
489
+ prompt += f"- Hip Rotation: {bio_metrics.get('hip_rotation_degrees', 25)}°\n"
490
+ prompt += f"- Shoulder Rotation: {bio_metrics.get('shoulder_rotation_degrees', 60)}°\n"
491
+ prompt += f"- Posture Score: {bio_metrics.get('posture_score_percent', 60)}%\n"
492
+ prompt += f"- Weight Shift (lead foot at impact): {bio_metrics.get('weight_shift_percent', 50)}%\n"
 
 
 
 
493
 
494
  # Upper body mechanics
495
  prompt += "\n### Upper Body Mechanics\n"
496
+ prompt += f"- Arm Extension: {bio_metrics.get('arm_extension_percent', 60)}%\n"
497
+ prompt += f"- Wrist Hinge: {bio_metrics.get('wrist_hinge_degrees', 60)}°\n"
498
+ prompt += f"- Shoulder Plane Consistency: {bio_metrics.get('swing_plane_consistency_percent', 60)}%\n"
499
+ prompt += f"- Chest Rotation Efficiency: {bio_metrics.get('chest_rotation_efficiency_percent', 60)}%\n"
500
+ prompt += f"- Head Movement (lateral): {bio_metrics.get('head_movement_lateral_inches', 3.0)}in\n"
501
+ prompt += f"- Head Movement (vertical): {bio_metrics.get('head_movement_vertical_inches', 2.0)}in\n"
 
 
 
 
 
 
 
502
 
503
  # Lower body mechanics
504
  prompt += "\n### Lower Body Mechanics\n"
505
+ prompt += f"- Knee Flexion (address): {bio_metrics.get('knee_flexion_address_degrees', 25)}°\n"
506
+ prompt += f"- Knee Flexion (impact): {bio_metrics.get('knee_flexion_impact_degrees', 30)}°\n"
507
+ prompt += f"- Hip Thrust (impact): {bio_metrics.get('hip_thrust_percent', 50)}%\n"
508
+ prompt += f"- Ground Force Efficiency: {bio_metrics.get('ground_force_efficiency_percent', 60)}%\n"
509
+
510
+ # Advanced coordination metrics
511
+ prompt += "\n### Movement Quality & Timing\n"
512
+ prompt += f"- Transition Smoothness: {bio_metrics.get('transition_smoothness_percent', 60)}%\n"
513
+ prompt += f"- Sequential Kinematic Sequence: {bio_metrics.get('kinematic_sequence_percent', 60)}%\n"
 
 
 
 
 
 
 
 
 
 
 
 
 
514
 
515
  # Efficiency and power metrics
516
  prompt += "\n### Efficiency & Power Metrics\n"
517
+ prompt += f"- Energy Transfer Efficiency: {bio_metrics.get('energy_transfer_efficiency_percent', 60)}%\n"
518
+ prompt += f"- Power Accumulation: {bio_metrics.get('power_accumulation_percent', 60)}%\n"
519
+ prompt += f"- Potential Distance: {bio_metrics.get('potential_distance_yards', 200)} yards\n"
520
+ prompt += f"- Speed Generation Method: {bio_metrics.get('speed_generation_method', 'Mixed')}\n"
 
 
 
 
521
 
522
  prompt += """
523
 
524
  ## ANALYSIS INSTRUCTIONS
525
 
526
+ Using the professional benchmarks and amateur examples above as your calibration reference, provide your analysis in the following EXACT structured format:
527
 
528
+ **PERFORMANCE_CLASSIFICATION:** [XX%] (where XX is a percentage from 10% to 100%)
529
 
530
  **STRENGTHS:**
531
+ • [Specific strength with direct comparison to professional/amateur benchmarks - e.g. "Hip rotation of 45° approaches professional range (60-90°) and exceeds most amateur examples (23-90°)"]
532
+ • [Another strength with benchmark comparison - e.g. "Energy transfer efficiency of 88% meets professional standards (88-96%) and surpasses amateur range (56.8-96.7%)"]
533
+ • [Third strength with specific metric comparison to benchmarks]
534
 
535
  **WEAKNESSES:**
536
+ • [Specific weakness with gap from professional standard - e.g. "Wrist hinge of 35° falls significantly below professional range (95-120°) and amateur compensation patterns (116.6°)"]
537
+ • [Another weakness with professional/amateur comparison - e.g. "Head movement of 12 inches exceeds both professional (2-8in) and amateur examples (3-8in)"]
538
+ • [Third weakness with benchmark gap analysis]
539
+
540
+ **PRIORITY_IMPROVEMENTS:**
541
+ 1. [Most Critical] Topic Name - Current metric vs professional benchmark vs amateur examples, specific target improvement to reach next level
542
+ 2. [Important] Topic Name - Current performance vs benchmarks, actionable steps to improve toward professional standards
543
+ 3. [Focus Area] Topic Name - Current state vs benchmark ranges, realistic improvement goals based on amateur progression examples
544
+
545
+ **MANDATORY REQUIREMENTS FOR EACH SECTION:**
546
+
547
+ **For STRENGTHS** - Must include:
548
+ - Specific metric values from current analysis
549
+ - Direct comparison to professional benchmarks (60-90° hip rotation, 95-120° wrist hinge, 88-96% energy transfer, etc.)
550
+ - Comparison to amateur examples where relevant
551
+ - Recognition when metrics meet or exceed professional standards
552
+ - Acknowledgment when metrics surpass typical amateur performance
553
+
554
+ **For WEAKNESSES** - Must include:
555
+ - Specific metric gaps from professional standards
556
+ - Comparison to amateur examples to show relative standing
557
+ - Quantified differences (e.g., "15° below professional minimum," "20% gap from professional range")
558
+ - Impact on overall performance potential
559
+
560
+ **For PRIORITY_IMPROVEMENTS** - Must include:
561
+ - Current metric value vs professional benchmark range
562
+ - Reference to amateur examples showing improvement potential
563
+ - Specific target values based on professional standards
564
+ - Realistic progression steps based on amateur improvement patterns
565
+ - Clear explanation of why this improvement would impact overall performance
566
+
567
+ **EXAMPLE ANALYSIS STRUCTURE:**
568
+
569
+ **STRENGTHS:**
570
+ • Shoulder rotation of 118° nearly matches professional standard (120°) and exceeds many amateur examples (60-120° range)
571
+ • Weight shift of 85% falls within professional range (70-88%) and surpasses amateur struggles (50-90% range)
572
+
573
+ **WEAKNESSES:**
574
+ • Hip rotation of 28° falls significantly below professional minimum (60°) and amateur body-dominant examples (90°)
575
+ • Energy transfer of 62% below professional range (88-96%) and some amateur achievements (96.7%)
576
 
577
  **PRIORITY_IMPROVEMENTS:**
578
+ 1. [Most Critical] Hip Mobility Development - Current 28° vs professional 60-90° and amateur body-dominant 90°. Target 45° as next milestone toward professional range.
579
+ 2. [Important] Kinematic Sequence Optimization - Current 70% vs professional 100% and amateur range 66.8-100%. Improve to 85% through better hip-shoulder coordination.
580
+
581
+ PERFORMANCE CLASSIFICATION SCALE:
582
+ - **90-100%**: Professional/Tour level - Consistently meets or exceeds professional benchmarks across all metrics
583
+ - **80-89%**: Advanced amateur - Meets most professional standards with minor gaps in 1-2 areas
584
+ - **70-79%**: Skilled amateur - Solid fundamentals with some gaps from professional standards
585
+ - **60-69%**: Intermediate - Good basic mechanics but several areas need improvement to reach professional level
586
+ - **50-59%**: Developing intermediate - Basic swing structure present but multiple areas below professional standards
587
+ - **40-49%**: Advanced beginner - Some fundamentals in place but significant gaps in most areas
588
+ - **30-39%**: Beginner - Basic swing motion present but major improvements needed across most metrics
589
+ - **20-29%**: Novice - Limited swing fundamentals, extensive work needed on basic mechanics
590
+ - **10-19%**: Complete beginner - Minimal swing structure, needs comprehensive fundamental development
591
+
592
+ IMPORTANT ANALYSIS PRIORITIES (Based on Real Professional Data):
593
+ 1. **PRIMARY FOCUS - Critical Biomechanical Differentiators**:
594
+ - Hip Rotation (Professional: 60-90°, Amateur Range: 23-90°) - MOST IMPORTANT
595
+ - Shoulder Rotation (Professional: 120°, Amateur Range: 60-120°) - MOST IMPORTANT
596
+ - Sequential Kinematic Sequence (Professional: 100%, Amateur Range: 66.8-100%)
597
+ - Energy Transfer Efficiency (Professional: 88-96%, Amateur Range: 56.8-96.7%)
598
+
599
+ 2. **SECONDARY FOCUS - Power Generation Mechanics**:
600
+ - Power Accumulation (Professional: 100%, Amateur Range: 82.1-100%)
601
+ - Chest Rotation Efficiency (Professional: 100%, Amateur Range: 53.7-100%)
602
+ - Wrist Hinge (Professional: 95-120°, Amateur Range: 49.4-116.6°)
603
+ - Swing Plane Consistency (Professional: 85%, Amateur: 70-85%)
604
+
605
+ 3. **TERTIARY FOCUS - Refinement Metrics**:
606
+ - Posture Score (Professional: 95-98%, Amateur Range: 80.6-90.7%)
607
+ - Arm Extension (Professional: 96-100%, Amateur Range: 94.8-100%)
608
+ - Weight Shift (Professional: 70-88%, Amateur Range: 50-90%)
609
+ - Ground Force Efficiency (Professional: 70-88%, Amateur Range: 50-90%)
610
+
611
+ 4. **DE-EMPHASIZE - Timing Variables**: Frame counts, tempo ratios, and duration metrics vary significantly based on video capture rates and personal style preferences
612
+
613
+ **SCORING CALIBRATION GUIDELINES:**
614
+ - **Hip/Shoulder Rotation Analysis**: Compare to professional minimums (60° hip, 120° shoulder) and amateur ranges
615
+ - **Energy Transfer <70%**: Score below 60%, compare to amateur range (56.8-96.7%)
616
+ - **Sequential Kinematic <80%**: Score below 70%, reference amateur examples (66.8-100%)
617
+ - **Power Accumulation <90%**: Score below 80%, compare to amateur achievements (82.1-100%)
618
+ - **Head Movement >10 inches**: Major limitation, compare to professional (2-8in) and amateur (3-8in) ranges
619
+ - **Weight Shift <60%**: Significant weakness, reference amateur struggles (50%) vs successes (90%)
620
 
621
  IMPORTANT FORMATTING RULES:
622
  - Use the exact headers shown above (PERFORMANCE_CLASSIFICATION, STRENGTHS, WEAKNESSES, PRIORITY_IMPROVEMENTS)
623
+ - For performance classification, use format: [XX%] where XX is the percentage (10-100)
624
  - For strengths and weaknesses, use bullet points (•)
625
  - For priority improvements, use numbered format (1., 2., 3.) with priority level in brackets
626
+ - Each priority improvement must have: [Priority Level] Topic Name - Full description with benchmark comparisons
627
+ - **MANDATORY**: Include specific metric values and benchmark comparisons in every strength, weakness, and improvement
628
+ - **MANDATORY**: Reference professional standards and amateur examples in analysis content
629
+ - Provide complete sentences with quantified comparisons - no generic statements
630
+ - Focus analysis on biomechanical consistency rather than timing variations
631
+ - **CRITICAL**: Every analysis point must tie back to the professional benchmarks and amateur examples provided
632
+
633
+ Remember: Use the professional benchmarks (Atthaya Thitikul: 63.4° hip, 120° shoulder, 96.1% energy transfer, etc.) and amateur examples (23-90° hip rotation range, 56.8-96.7% energy transfer range, etc.) as the foundation for ALL analysis content, not just the percentage classification. Every strength, weakness, and improvement recommendation must include specific metric comparisons to these established benchmarks.
634
  """
635
 
636
  return prompt
 
648
  """
649
  # Default structure
650
  formatted_analysis = {
651
+ 'classification': 50, # Default to 50%
652
  'strengths': [],
653
  'weaknesses': [],
654
  'priority_improvements': []
655
  }
656
 
657
+ # Extract percentage classification using the new structured format
658
+ classification_match = re.search(r'\*\*PERFORMANCE_CLASSIFICATION:\*\*\s*\[?(\d+)%?\]?', raw_analysis, re.IGNORECASE)
659
  if classification_match:
660
+ percentage = int(classification_match.group(1))
661
+ # Ensure percentage is within valid range
662
+ formatted_analysis['classification'] = max(10, min(100, percentage))
663
  else:
664
+ # Fallback to look for standalone percentages
665
+ percentage_patterns = [
666
+ r'(?:Performance|Classification|Level|Score).*?(\d+)%',
667
+ r'(\d+)%.*?(?:level|performance|classification)',
668
+ r'classified.*?(\d+)%',
669
+ r'(?:at|as)\s+(\d+)%'
670
  ]
671
 
672
+ for pattern in percentage_patterns:
673
  match = re.search(pattern, raw_analysis, re.IGNORECASE)
674
  if match:
675
+ percentage = int(match.group(1))
676
+ formatted_analysis['classification'] = max(10, min(100, percentage))
677
  break
678
 
679
  # Extract strengths using the new structured format
 
780
  formatted_analysis['weaknesses'] = ['Areas for improvement identified']
781
 
782
  if not formatted_analysis['priority_improvements']:
783
+ percentage = formatted_analysis['classification']
784
+ if percentage >= 80:
785
  formatted_analysis['priority_improvements'] = [
786
+ {'rank': 1, 'description': '[Most Critical] Technical Refinement - Fine-tune specific mechanics to achieve consistency at the highest level.'},
787
+ {'rank': 2, 'description': '[Important] Performance Optimization - Focus on maximizing efficiency and power transfer.'},
788
+ {'rank': 3, 'description': '[Focus Area] Competitive Preparation - Enhance mental game and course management skills.'}
789
  ]
790
+ elif percentage >= 60:
791
  formatted_analysis['priority_improvements'] = [
792
  {'rank': 1, 'description': '[Most Critical] Kinematic Sequence Enhancement - Improve body rotation coordination to generate more power and consistency.'},
793
  {'rank': 2, 'description': '[Important] Clubface Control - Enhance swing path consistency for better ball striking accuracy.'},
794
  {'rank': 3, 'description': '[Focus Area] Energy Transfer Efficiency - Optimize power transfer throughout the swing to maximize distance.'}
795
  ]
796
+ elif percentage >= 40:
797
  formatted_analysis['priority_improvements'] = [
798
+ {'rank': 1, 'description': '[Most Critical] Fundamental Mechanics - Establish consistent posture, grip, and setup positions.'},
799
+ {'rank': 2, 'description': '[Important] Body Rotation Development - Improve hip and shoulder turn coordination.'},
800
+ {'rank': 3, 'description': '[Focus Area] Weight Transfer - Develop proper weight shift from back foot to front foot during swing.'}
801
  ]
802
+ else: # Below 40%
803
  formatted_analysis['priority_improvements'] = [
804
+ {'rank': 1, 'description': '[Most Critical] Basic Setup and Posture - Focus on establishing proper spine angle and athletic stance.'},
805
+ {'rank': 2, 'description': '[Important] Fundamental Swing Motion - Develop basic backswing and downswing mechanics.'},
806
+ {'rank': 3, 'description': '[Focus Area] Balance and Stability - Improve overall balance throughout the swing motion.'}
807
  ]
808
 
809
  return formatted_analysis
 
816
  Args:
817
  analysis_data (dict): Structured analysis data from parse_and_format_analysis
818
  """
819
+ # 1. Performance Classification with percentage-based progress bar
820
+ user_percentage = analysis_data['classification']
821
 
822
  # Display classification in black bolded header
823
  st.markdown(f"""
824
  <h2 style='color: black; font-weight: bold; text-align: center; margin-bottom: 20px;'>
825
+ 🎯 Performance Score: {user_percentage}%
826
  </h2>
827
  """, unsafe_allow_html=True)
828
 
829
+ # Create a visual progress bar
830
+ progress_color = "#ff4444" # Red for low scores
831
+ if user_percentage >= 80:
832
+ progress_color = "#44aa44" # Green for high scores
833
+ elif user_percentage >= 60:
834
+ progress_color = "#ffdd00" # Yellow for good scores
835
+ elif user_percentage >= 40:
836
+ progress_color = "#ff8800" # Orange for medium scores
837
 
838
+ # Progress bar with percentage labels
839
+ st.markdown(f"""
840
+ <div style='margin: 20px 0;'>
841
+ <div style='display: flex; justify-content: space-between; font-size: 12px; color: #666; margin-bottom: 5px;'>
842
+ <span>10% - Complete Beginner</span>
843
+ <span>50% - Intermediate</span>
844
+ <span>100% - Professional</span>
 
 
 
 
 
 
 
 
 
845
  </div>
846
+ <div style='width: 100%; background-color: #f0f0f0; border-radius: 25px; height: 30px; position: relative;'>
847
+ <div style='width: {user_percentage}%; background-color: {progress_color}; height: 30px; border-radius: 25px;
848
+ display: flex; align-items: center; justify-content: center; color: white; font-weight: bold;'>
849
+ {user_percentage}%
850
+ </div>
 
 
 
 
 
851
  </div>
852
+ <div style='display: flex; justify-content: space-between; font-size: 10px; color: #888; margin-top: 5px;'>
853
+ <span>10%</span>
854
+ <span>20%</span>
855
+ <span>30%</span>
856
+ <span>40%</span>
857
+ <span>50%</span>
858
+ <span>60%</span>
859
+ <span>70%</span>
860
+ <span>80%</span>
861
+ <span>90%</span>
862
+ <span>100%</span>
863
  </div>
864
+ </div>
865
+ """, unsafe_allow_html=True)
866
 
867
+ # Performance level description based on percentage
868
+ if user_percentage >= 90:
869
+ level_desc = "🏆 **Professional/Tour Level** - Consistently meets or exceeds professional benchmarks"
870
+ level_color = "#44aa44"
871
+ elif user_percentage >= 80:
872
+ level_desc = "🥇 **Advanced Amateur** - Meets most professional standards with minor gaps"
873
+ level_color = "#66bb44"
874
+ elif user_percentage >= 70:
875
+ level_desc = "🥈 **Skilled Amateur** - Solid fundamentals with some gaps from professional standards"
876
+ level_color = "#88cc44"
877
+ elif user_percentage >= 60:
878
+ level_desc = "🥉 **Intermediate** - Good basic mechanics but several areas need improvement"
879
+ level_color = "#ffdd00"
880
+ elif user_percentage >= 50:
881
+ level_desc = "📈 **Developing Intermediate** - Basic swing structure present"
882
+ level_color = "#ffcc00"
883
+ elif user_percentage >= 40:
884
+ level_desc = "📚 **Advanced Beginner** - Some fundamentals in place"
885
+ level_color = "#ff8800"
886
+ elif user_percentage >= 30:
887
+ level_desc = "🎯 **Beginner** - Basic swing motion present but major improvements needed"
888
+ level_color = "#ff6600"
889
+ elif user_percentage >= 20:
890
+ level_desc = "🌱 **Novice** - Limited swing fundamentals, extensive work needed"
891
+ level_color = "#ff4444"
892
+ else:
893
+ level_desc = "🚀 **Complete Beginner** - Minimal swing structure, needs comprehensive fundamental development"
894
+ level_color = "#ff2222"
895
+
896
+ st.markdown(f"""
897
+ <div style='text-align: center; padding: 15px; background-color: {level_color}20;
898
+ border-radius: 10px; margin: 20px 0; border: 2px solid {level_color};'>
899
+ <div style='color: {level_color}; font-size: 16px; font-weight: bold;'>{level_desc}</div>
900
+ </div>
901
+ """, unsafe_allow_html=True)
902
 
903
  st.markdown("---")
904
 
 
1012
  st.write(desc)
1013
 
1014
  st.write("") # Add spacing between items
1015
+
1016
+
1017
+ def calculate_biomechanical_metrics(pose_data, swing_phases):
1018
+ """
1019
+ Calculate biomechanical metrics from pose keypoints data
1020
+
1021
+ Args:
1022
+ pose_data (dict): Dictionary mapping frame indices to pose keypoints
1023
+ swing_phases (dict): Dictionary mapping phase names to lists of frame indices
1024
+
1025
+ Returns:
1026
+ dict: Calculated biomechanical metrics
1027
+ """
1028
+ metrics = {}
1029
+
1030
+ # Get key frames for analysis
1031
+ setup_frames = swing_phases.get("setup", [])
1032
+ backswing_frames = swing_phases.get("backswing", [])
1033
+ downswing_frames = swing_phases.get("downswing", [])
1034
+ impact_frames = swing_phases.get("impact", [])
1035
+
1036
+ # Get representative frames
1037
+ setup_frame = setup_frames[len(setup_frames) // 2] if setup_frames else None
1038
+ top_backswing_frame = backswing_frames[-1] if backswing_frames else None
1039
+ impact_frame = impact_frames[0] if impact_frames else None
1040
+
1041
+ # MediaPipe Pose landmark indices
1042
+ # Shoulders: left(11), right(12)
1043
+ # Hips: left(23), right(24)
1044
+ # Knees: left(25), right(26)
1045
+ # Ankles: left(27), right(28)
1046
+ # Elbows: left(13), right(14)
1047
+ # Wrists: left(15), right(16)
1048
+
1049
+ try:
1050
+ # Calculate Hip Rotation
1051
+ if setup_frame and top_backswing_frame and setup_frame in pose_data and top_backswing_frame in pose_data:
1052
+ setup_keypoints = pose_data[setup_frame]
1053
+ backswing_keypoints = pose_data[top_backswing_frame]
1054
+
1055
+ if len(setup_keypoints) >= 33 and len(backswing_keypoints) >= 33:
1056
+ # Hip rotation calculation using hip landmarks
1057
+ setup_left_hip = np.array(setup_keypoints[23][:2])
1058
+ setup_right_hip = np.array(setup_keypoints[24][:2])
1059
+ backswing_left_hip = np.array(backswing_keypoints[23][:2])
1060
+ backswing_right_hip = np.array(backswing_keypoints[24][:2])
1061
+
1062
+ # Calculate hip line angles
1063
+ setup_hip_vector = setup_right_hip - setup_left_hip
1064
+ backswing_hip_vector = backswing_right_hip - backswing_left_hip
1065
+
1066
+ setup_hip_angle = np.degrees(np.arctan2(setup_hip_vector[1], setup_hip_vector[0]))
1067
+ backswing_hip_angle = np.degrees(np.arctan2(backswing_hip_vector[1], backswing_hip_vector[0]))
1068
+
1069
+ hip_rotation = abs(backswing_hip_angle - setup_hip_angle)
1070
+ # Normalize to reasonable range (professionals typically achieve 45+ degrees)
1071
+ metrics["hip_rotation"] = min(hip_rotation, 90)
1072
+ else:
1073
+ metrics["hip_rotation"] = 25 # Lower default for incomplete data
1074
+ else:
1075
+ metrics["hip_rotation"] = 25
1076
+
1077
+ # Calculate Shoulder Rotation
1078
+ if setup_frame and top_backswing_frame and setup_frame in pose_data and top_backswing_frame in pose_data:
1079
+ setup_keypoints = pose_data[setup_frame]
1080
+ backswing_keypoints = pose_data[top_backswing_frame]
1081
+
1082
+ if len(setup_keypoints) >= 33 and len(backswing_keypoints) >= 33:
1083
+ # Shoulder rotation calculation
1084
+ setup_left_shoulder = np.array(setup_keypoints[11][:2])
1085
+ setup_right_shoulder = np.array(setup_keypoints[12][:2])
1086
+ backswing_left_shoulder = np.array(backswing_keypoints[11][:2])
1087
+ backswing_right_shoulder = np.array(backswing_keypoints[12][:2])
1088
+
1089
+ setup_shoulder_vector = setup_right_shoulder - setup_left_shoulder
1090
+ backswing_shoulder_vector = backswing_right_shoulder - backswing_left_shoulder
1091
+
1092
+ setup_shoulder_angle = np.degrees(np.arctan2(setup_shoulder_vector[1], setup_shoulder_vector[0]))
1093
+ backswing_shoulder_angle = np.degrees(np.arctan2(backswing_shoulder_vector[1], backswing_shoulder_vector[0]))
1094
+
1095
+ shoulder_rotation = abs(backswing_shoulder_angle - setup_shoulder_angle)
1096
+ metrics["shoulder_rotation"] = min(shoulder_rotation, 120)
1097
+ else:
1098
+ metrics["shoulder_rotation"] = 60 # Lower default
1099
+ else:
1100
+ metrics["shoulder_rotation"] = 60
1101
+
1102
+ # Calculate Weight Shift (using hip and ankle positions)
1103
+ if setup_frame and impact_frame and setup_frame in pose_data and impact_frame in pose_data:
1104
+ setup_keypoints = pose_data[setup_frame]
1105
+ impact_keypoints = pose_data[impact_frame]
1106
+
1107
+ if len(setup_keypoints) >= 33 and len(impact_keypoints) >= 33:
1108
+ # Use center of mass approximation
1109
+ setup_left_ankle = np.array(setup_keypoints[27][:2])
1110
+ setup_right_ankle = np.array(setup_keypoints[28][:2])
1111
+ impact_left_ankle = np.array(impact_keypoints[27][:2])
1112
+ impact_right_ankle = np.array(impact_keypoints[28][:2])
1113
+
1114
+ # Calculate weight distribution based on foot positioning
1115
+ setup_center = (setup_left_ankle + setup_right_ankle) / 2
1116
+ impact_center = (impact_left_ankle + impact_right_ankle) / 2
1117
+
1118
+ # Weight shift calculation (simplified)
1119
+ foot_width = np.linalg.norm(setup_right_ankle - setup_left_ankle)
1120
+ if foot_width > 0:
1121
+ weight_shift_amount = np.linalg.norm(impact_center - setup_center) / foot_width
1122
+ # Convert to percentage (professionals typically achieve 70%+ to front foot)
1123
+ weight_shift = min(0.5 + weight_shift_amount * 0.5, 0.9)
1124
+ else:
1125
+ weight_shift = 0.5
1126
+ metrics["weight_shift"] = weight_shift
1127
+ else:
1128
+ metrics["weight_shift"] = 0.5 # Neutral default
1129
+ else:
1130
+ metrics["weight_shift"] = 0.5
1131
+
1132
+ # Calculate Posture Score (spine angle consistency)
1133
+ posture_scores = []
1134
+ for frame_list in [setup_frames, backswing_frames, impact_frames]:
1135
+ if frame_list:
1136
+ frame = frame_list[len(frame_list) // 2]
1137
+ if frame in pose_data and len(pose_data[frame]) >= 33:
1138
+ keypoints = pose_data[frame]
1139
+ # Use shoulder and hip landmarks to estimate spine angle
1140
+ left_shoulder = np.array(keypoints[11][:2])
1141
+ right_shoulder = np.array(keypoints[12][:2])
1142
+ left_hip = np.array(keypoints[23][:2])
1143
+ right_hip = np.array(keypoints[24][:2])
1144
+
1145
+ shoulder_center = (left_shoulder + right_shoulder) / 2
1146
+ hip_center = (left_hip + right_hip) / 2
1147
+
1148
+ spine_vector = shoulder_center - hip_center
1149
+ spine_angle = np.degrees(np.arctan2(spine_vector[1], spine_vector[0]))
1150
+ posture_scores.append(abs(spine_angle))
1151
+
1152
+ if posture_scores:
1153
+ # Good posture = consistent spine angle across phases
1154
+ posture_consistency = 1.0 - (np.std(posture_scores) / 90.0) # Normalize by 90 degrees
1155
+ metrics["posture_score"] = max(0.3, min(posture_consistency, 1.0))
1156
+ else:
1157
+ metrics["posture_score"] = 0.6
1158
+
1159
+ # Calculate Arm Extension at Impact
1160
+ if impact_frame and impact_frame in pose_data and len(pose_data[impact_frame]) >= 33:
1161
+ keypoints = pose_data[impact_frame]
1162
+ right_shoulder = np.array(keypoints[12][:2])
1163
+ right_elbow = np.array(keypoints[14][:2])
1164
+ right_wrist = np.array(keypoints[16][:2])
1165
+
1166
+ # Calculate arm extension
1167
+ upper_arm = np.linalg.norm(right_elbow - right_shoulder)
1168
+ forearm = np.linalg.norm(right_wrist - right_elbow)
1169
+ total_arm_length = upper_arm + forearm
1170
+
1171
+ # Calculate actual distance from shoulder to wrist
1172
+ actual_distance = np.linalg.norm(right_wrist - right_shoulder)
1173
+
1174
+ if total_arm_length > 0:
1175
+ extension_ratio = actual_distance / total_arm_length
1176
+ metrics["arm_extension"] = min(extension_ratio, 1.0)
1177
+ else:
1178
+ metrics["arm_extension"] = 0.6
1179
+ else:
1180
+ metrics["arm_extension"] = 0.6
1181
+
1182
+ # Calculate Wrist Hinge using joint angles
1183
+ wrist_angles = []
1184
+ for frame_list in [backswing_frames, impact_frames]:
1185
+ if frame_list:
1186
+ frame = frame_list[len(frame_list) // 2]
1187
+ if frame in pose_data:
1188
+ angles = calculate_joint_angles(pose_data[frame])
1189
+ if "right_wrist" in angles:
1190
+ wrist_angles.append(angles["right_wrist"])
1191
+
1192
+ if wrist_angles:
1193
+ avg_wrist_angle = np.mean(wrist_angles)
1194
+ # Good wrist hinge is typically 80+ degrees
1195
+ metrics["wrist_hinge"] = min(avg_wrist_angle, 120)
1196
+ else:
1197
+ metrics["wrist_hinge"] = 60
1198
+
1199
+ # Calculate Head Movement (lateral and vertical)
1200
+ if setup_frame and impact_frame and setup_frame in pose_data and impact_frame in pose_data:
1201
+ setup_keypoints = pose_data[setup_frame]
1202
+ impact_keypoints = pose_data[impact_frame]
1203
+
1204
+ if len(setup_keypoints) >= 33 and len(impact_keypoints) >= 33:
1205
+ # Use nose landmark (index 0) for head position
1206
+ setup_head = np.array(setup_keypoints[0][:2])
1207
+ impact_head = np.array(impact_keypoints[0][:2])
1208
+
1209
+ head_movement = np.abs(impact_head - setup_head)
1210
+ # Convert pixel movement to approximate inches (rough estimation)
1211
+ # Assume average person's head is about 9 inches, use that as scale
1212
+ if len(setup_keypoints) > 10: # Have enough landmarks
1213
+ head_height_pixels = abs(setup_keypoints[0][1] - setup_keypoints[10][1]) # Nose to mouth
1214
+ if head_height_pixels > 0:
1215
+ pixel_to_inch = 4.0 / head_height_pixels # Approximate nose-to-mouth is 4 inches
1216
+ lateral_movement = head_movement[0] * pixel_to_inch
1217
+ vertical_movement = head_movement[1] * pixel_to_inch
1218
+ else:
1219
+ lateral_movement = 3.0
1220
+ vertical_movement = 2.0
1221
+ else:
1222
+ lateral_movement = 3.0
1223
+ vertical_movement = 2.0
1224
+
1225
+ metrics["head_movement_lateral"] = min(lateral_movement, 8.0)
1226
+ metrics["head_movement_vertical"] = min(vertical_movement, 6.0)
1227
+ else:
1228
+ metrics["head_movement_lateral"] = 3.0
1229
+ metrics["head_movement_vertical"] = 2.0
1230
+ else:
1231
+ metrics["head_movement_lateral"] = 3.0
1232
+ metrics["head_movement_vertical"] = 2.0
1233
+
1234
+ # Calculate Knee Flexion
1235
+ knee_flexions = {}
1236
+ for phase_name, frame_list in [("address", setup_frames), ("impact", impact_frames)]:
1237
+ if frame_list:
1238
+ frame = frame_list[len(frame_list) // 2]
1239
+ if frame in pose_data and len(pose_data[frame]) >= 33:
1240
+ keypoints = pose_data[frame]
1241
+ # Right knee angle using hip, knee, ankle
1242
+ right_hip = np.array(keypoints[24][:2])
1243
+ right_knee = np.array(keypoints[26][:2])
1244
+ right_ankle = np.array(keypoints[28][:2])
1245
+
1246
+ # Calculate knee angle
1247
+ thigh_vector = right_hip - right_knee
1248
+ shin_vector = right_ankle - right_knee
1249
+
1250
+ if np.linalg.norm(thigh_vector) > 0 and np.linalg.norm(shin_vector) > 0:
1251
+ cos_angle = np.dot(thigh_vector, shin_vector) / (np.linalg.norm(thigh_vector) * np.linalg.norm(shin_vector))
1252
+ cos_angle = np.clip(cos_angle, -1, 1)
1253
+ knee_angle = np.degrees(np.arccos(cos_angle))
1254
+ knee_flexions[phase_name] = min(knee_angle, 60)
1255
+ else:
1256
+ knee_flexions[phase_name] = 25
1257
+ else:
1258
+ knee_flexions[phase_name] = 25
1259
+
1260
+ metrics["knee_flexion_address"] = knee_flexions.get("address", 25)
1261
+ metrics["knee_flexion_impact"] = knee_flexions.get("impact", 30)
1262
+
1263
+ # Calculate derived metrics based on quality of basic metrics
1264
+ # These are more complex and would require additional analysis
1265
+
1266
+ # Swing Plane Consistency (based on arm and club positions across frames)
1267
+ if metrics["shoulder_rotation"] >= 80 and metrics["arm_extension"] >= 0.75:
1268
+ metrics["swing_plane_consistency"] = 0.85
1269
+ elif metrics["shoulder_rotation"] >= 60 and metrics["arm_extension"] >= 0.6:
1270
+ metrics["swing_plane_consistency"] = 0.70
1271
+ else:
1272
+ metrics["swing_plane_consistency"] = 0.55
1273
+
1274
+ # Chest Rotation Efficiency (derived from shoulder rotation and posture)
1275
+ chest_efficiency = (metrics["shoulder_rotation"] / 90.0) * metrics["posture_score"]
1276
+ metrics["chest_rotation_efficiency"] = min(chest_efficiency, 1.0)
1277
+
1278
+ # Hip Thrust (derived from weight shift and hip rotation)
1279
+ hip_thrust = (metrics["weight_shift"] - 0.5) * 2 * (metrics["hip_rotation"] / 45.0)
1280
+ metrics["hip_thrust"] = max(0.3, min(hip_thrust, 1.0))
1281
+
1282
+ # Ground Force Efficiency (derived from weight shift and knee flexion consistency)
1283
+ knee_consistency = 1.0 - abs(metrics["knee_flexion_impact"] - metrics["knee_flexion_address"]) / 30.0
1284
+ ground_force = metrics["weight_shift"] * knee_consistency
1285
+ metrics["ground_force_efficiency"] = max(0.4, min(ground_force, 1.0))
1286
+
1287
+ # Transition Smoothness (based on posture consistency and movement quality)
1288
+ head_movement_penalty = (metrics["head_movement_lateral"] + metrics["head_movement_vertical"]) / 10.0
1289
+ transition_smoothness = metrics["posture_score"] * (1.0 - head_movement_penalty)
1290
+ metrics["transition_smoothness"] = max(0.4, min(transition_smoothness, 1.0))
1291
+
1292
+ # Sequential Kinematic Sequence (based on overall coordination)
1293
+ coordination_score = (metrics["hip_rotation"] / 45.0 + metrics["shoulder_rotation"] / 90.0 +
1294
+ metrics["weight_shift"] + metrics["arm_extension"]) / 4.0
1295
+ metrics["kinematic_sequence"] = max(0.5, min(coordination_score, 1.0))
1296
+
1297
+ # Energy Transfer Efficiency (based on multiple factors)
1298
+ energy_transfer = (metrics["kinematic_sequence"] + metrics["ground_force_efficiency"] +
1299
+ metrics["chest_rotation_efficiency"]) / 3.0
1300
+ metrics["energy_transfer"] = max(0.4, min(energy_transfer, 1.0))
1301
+
1302
+ # Power Accumulation (based on body mechanics)
1303
+ power_accumulation = (metrics["hip_rotation"] / 45.0 + metrics["shoulder_rotation"] / 90.0 +
1304
+ metrics["wrist_hinge"] / 80.0) / 3.0
1305
+ metrics["power_accumulation"] = max(0.4, min(power_accumulation, 1.0))
1306
+
1307
+ # Potential Distance (based on power metrics and efficiency)
1308
+ base_distance = 180 # Base amateur distance
1309
+ power_multiplier = metrics["power_accumulation"] * metrics["energy_transfer"]
1310
+ potential_distance = base_distance + (power_multiplier * 120) # Up to 300 yards for perfect mechanics
1311
+ metrics["potential_distance"] = min(potential_distance, 320)
1312
+
1313
+ # Speed Generation Method (based on power sources)
1314
+ if metrics["hip_rotation"] >= 40 and metrics["shoulder_rotation"] >= 80:
1315
+ metrics["speed_generation"] = "Body-dominant"
1316
+ elif metrics["arm_extension"] >= 0.8 and metrics["wrist_hinge"] >= 75:
1317
+ metrics["speed_generation"] = "Arms-dominant"
1318
+ else:
1319
+ metrics["speed_generation"] = "Mixed"
1320
+
1321
+ except Exception as e:
1322
+ print(f"Error calculating biomechanical metrics: {str(e)}")
1323
+ # Fail here
1324
+ return None
1325
+
1326
+ return metrics