chenemii commited on
Commit
e8f3c26
·
1 Parent(s): ed08beb
Files changed (2) hide show
  1. app/models/llm_analyzer.py +398 -12
  2. app/streamlit_app.py +8 -24
app/models/llm_analyzer.py CHANGED
@@ -6,6 +6,7 @@ import json
6
  import httpx
7
  from openai import OpenAI
8
  import streamlit as st
 
9
 
10
 
11
  def check_llm_services():
@@ -328,6 +329,55 @@ def create_llm_prompt(analysis_data):
328
  str: Prompt for LLM
329
  """
330
  prompt = """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
331
  I've analyzed a golf swing and extracted the following data:
332
 
333
  ## Swing Phases
@@ -405,22 +455,358 @@ I've analyzed a golf swing and extracted the following data:
405
 
406
  prompt += """
407
 
408
- Based on this detailed biomechanical data, please provide:
 
 
409
 
410
- 1. A comprehensive analysis of the golf swing including:
411
- - Detailed breakdown of each swing phase
412
- - Analysis of body mechanics and kinematic sequence
413
- - Assessment of power generation and efficiency
414
- - Evaluation of clubface control and swing path
415
 
416
- 2. Key strengths and weaknesses in the swing, including:
417
- - Specific biomechanical inefficiencies
418
- - Compensatory movements
419
- - Physical limitations
420
- - Technical flaws
 
 
 
421
 
 
 
 
 
422
 
423
- Please be specific, detailed, and actionable in your feedback, providing the kind of analysis a professional golf coach would give after a thorough assessment.
 
 
424
  """
425
 
426
  return prompt
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  import httpx
7
  from openai import OpenAI
8
  import streamlit as st
9
+ import re
10
 
11
 
12
  def check_llm_services():
 
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
 
455
 
456
  prompt += """
457
 
458
+ ## ANALYSIS INSTRUCTIONS
459
+
460
+ Using the professional benchmarks above as your calibration reference, provide:
461
 
462
+ 1. **Performance Classification**: Start with "Performance Classification: [Professional/Advanced/Intermediate/Beginner]" based on how the player's metrics compare to professional standards.
 
 
 
 
463
 
464
+ 2. **Comparative Analysis**:
465
+ - **Strengths** (metrics that meet/exceed professional benchmarks):
466
+ List specific strong points (use bullet points)
467
+ Reference professional benchmark values
468
+
469
+ - **Areas for Improvement** (metrics significantly below professional standards):
470
+ • List specific weaknesses (use bullet points)
471
+ • Note the gap from professional standards
472
 
473
+ 3. **Priority Improvement Areas**: List exactly 3 areas in order of importance:
474
+ 1. [Most Critical] - Describe what's wrong and what it should be like
475
+ 2. [Important] - Describe what's wrong and what it should be like
476
+ 3. [Focus Area] - Describe what's wrong and what it should be like
477
 
478
+ Remember: Professional golfers consistently achieve the benchmark metrics shown above. Use these as the gold standard for what constitutes excellent golf swing mechanics, while being realistic about the progression needed to reach those levels.
479
+
480
+ Provide your analysis in the structured format above for optimal coaching feedback.
481
  """
482
 
483
  return prompt
484
+
485
+
486
+ def parse_and_format_analysis(raw_analysis):
487
+ """
488
+ Parse the raw LLM analysis and format it into structured components
489
+
490
+ Args:
491
+ raw_analysis (str): Raw analysis text from LLM
492
+
493
+ Returns:
494
+ dict: Structured analysis with classification, strengths/weaknesses, and priorities
495
+ """
496
+ # Default structure
497
+ formatted_analysis = {
498
+ 'classification': 'Intermediate', # Default classification
499
+ 'strengths': [],
500
+ 'weaknesses': [],
501
+ 'priority_improvements': []
502
+ }
503
+
504
+ # Try to extract classification from the analysis
505
+ classification_patterns = [
506
+ r'(?:Performance Classification|Classification|Level).*?:\s*(Professional|Advanced|Intermediate|Beginner)',
507
+ r'(Professional|Advanced|Intermediate|Beginner)\s+(?:Level|Amateur)',
508
+ r'classified as\s+(Professional|Advanced|Intermediate|Beginner)',
509
+ r'(?:at|as)\s+(?:an?\s+)?(Professional|Advanced|Intermediate|Beginner)\s+level'
510
+ ]
511
+
512
+ classification_found = False
513
+ for pattern in classification_patterns:
514
+ match = re.search(pattern, raw_analysis, re.IGNORECASE)
515
+ if match:
516
+ formatted_analysis['classification'] = match.group(1).title()
517
+ classification_found = True
518
+ break
519
+
520
+ # If no classification found, try to infer from content
521
+ if not classification_found:
522
+ analysis_lower = raw_analysis.lower()
523
+ if 'professional' in analysis_lower and ('meets' in analysis_lower or 'exceeds' in analysis_lower):
524
+ formatted_analysis['classification'] = 'Professional'
525
+ elif 'advanced' in analysis_lower or ('within 10' in analysis_lower and 'pro' in analysis_lower):
526
+ formatted_analysis['classification'] = 'Advanced'
527
+ elif 'beginner' in analysis_lower or ('30%' in analysis_lower and 'below' in analysis_lower):
528
+ formatted_analysis['classification'] = 'Beginner'
529
+ else:
530
+ formatted_analysis['classification'] = 'Intermediate'
531
+
532
+ # Extract strengths and weaknesses
533
+ strengths_section = ""
534
+ weaknesses_section = ""
535
+
536
+ # Look for strengths/weaknesses sections
537
+ strengths_patterns = [
538
+ r'(?:Strengths|Strong Points|Positives|Meets.*Standards)[\s\S]*?(?=(?:Weak|Priority|Improvement|Areas|$))',
539
+ r'(?:Professional Level|Exceeds.*Standards)[\s\S]*?(?=(?:Below|Weak|Priority|$))'
540
+ ]
541
+
542
+ weaknesses_patterns = [
543
+ r'(?:Weaknesses|Weak|Areas.*Improvement|Priority.*Areas|Below.*Standards)[\s\S]*?(?=(?:Recommendation|Priority|$))',
544
+ r'(?:Critical|Important|Significant.*gaps?)[\s\S]*?(?=(?:Recommendation|$))'
545
+ ]
546
+
547
+ for pattern in strengths_patterns:
548
+ match = re.search(pattern, raw_analysis, re.IGNORECASE)
549
+ if match:
550
+ strengths_section = match.group(0)
551
+ break
552
+
553
+ for pattern in weaknesses_patterns:
554
+ match = re.search(pattern, raw_analysis, re.IGNORECASE)
555
+ if match:
556
+ weaknesses_section = match.group(0)
557
+ break
558
+
559
+ # Parse strengths from the section
560
+ if strengths_section:
561
+ strength_items = re.findall(r'[-•]\s*([^-•\n]+)', strengths_section)
562
+ formatted_analysis['strengths'] = [item.strip() for item in strength_items[:4]] # Limit to 4
563
+
564
+ # If no bullet points found, try to extract from general content
565
+ if not formatted_analysis['strengths']:
566
+ # Look for positive indicators in the full text
567
+ positive_indicators = [
568
+ r'(?:meets|exceeds|matches).*professional.*(?:standard|benchmark)',
569
+ r'(?:excellent|good|strong).*(?:posture|rotation|extension|timing)',
570
+ r'(?:consistent|solid).*(?:mechanics|form|technique)',
571
+ r'(?:efficient|effective).*(?:transfer|generation|sequence)'
572
+ ]
573
+
574
+ for pattern in positive_indicators:
575
+ matches = re.findall(pattern, raw_analysis, re.IGNORECASE)
576
+ for match in matches[:2]: # Limit to avoid overwhelming
577
+ formatted_analysis['strengths'].append(match.strip())
578
+
579
+ # Parse weaknesses from the section
580
+ if weaknesses_section:
581
+ weakness_items = re.findall(r'[-•]\s*([^-•\n]+)', weaknesses_section)
582
+ formatted_analysis['weaknesses'] = [item.strip() for item in weakness_items[:4]] # Limit to 4
583
+
584
+ # If no bullet points found, try to extract from general content
585
+ if not formatted_analysis['weaknesses']:
586
+ # Look for negative indicators in the full text
587
+ negative_indicators = [
588
+ r'(?:below|under).*professional.*(?:standard|benchmark)',
589
+ r'(?:poor|weak|limited).*(?:posture|rotation|extension|timing)',
590
+ r'(?:inconsistent|unstable).*(?:mechanics|form|technique)',
591
+ r'(?:inefficient|ineffective).*(?:transfer|generation|sequence)'
592
+ ]
593
+
594
+ for pattern in negative_indicators:
595
+ matches = re.findall(pattern, raw_analysis, re.IGNORECASE)
596
+ for match in matches[:2]: # Limit to avoid overwhelming
597
+ formatted_analysis['weaknesses'].append(match.strip())
598
+
599
+ # Extract priority improvements
600
+ priority_patterns = [
601
+ r'(?:Priority.*Improvement|Critical.*Areas?)[\s\S]*?(?=(?:Recommendation|$))',
602
+ r'(?:1\..*?2\..*?3\.)', # Numbered list
603
+ r'(?:Critical|Important|Fine-tuning)[\s\S]*?(?=(?:Critical|Important|Fine-tuning|$))'
604
+ ]
605
+
606
+ for pattern in priority_patterns:
607
+ match = re.search(pattern, raw_analysis, re.IGNORECASE | re.DOTALL)
608
+ if match:
609
+ priority_text = match.group(0)
610
+ # Extract numbered items
611
+ numbered_items = re.findall(r'(\d+)\.\s*([^1-9\n]+)', priority_text)
612
+ for num, item in numbered_items[:3]: # Limit to 3
613
+ formatted_analysis['priority_improvements'].append({
614
+ 'rank': int(num),
615
+ 'description': item.strip()
616
+ })
617
+ break
618
+
619
+ # If no numbered priorities found, create generic ones based on classification
620
+ if not formatted_analysis['priority_improvements']:
621
+ if formatted_analysis['classification'] == 'Beginner':
622
+ formatted_analysis['priority_improvements'] = [
623
+ {'rank': 1, 'description': 'Focus on fundamental posture and setup position'},
624
+ {'rank': 2, 'description': 'Develop consistent tempo and timing'},
625
+ {'rank': 3, 'description': 'Improve weight shift and balance throughout swing'}
626
+ ]
627
+ elif formatted_analysis['classification'] == 'Intermediate':
628
+ formatted_analysis['priority_improvements'] = [
629
+ {'rank': 1, 'description': 'Enhance kinematic sequence and body rotation'},
630
+ {'rank': 2, 'description': 'Improve clubface control and swing path consistency'},
631
+ {'rank': 3, 'description': 'Optimize energy transfer efficiency'}
632
+ ]
633
+ elif formatted_analysis['classification'] == 'Advanced':
634
+ formatted_analysis['priority_improvements'] = [
635
+ {'rank': 1, 'description': 'Fine-tune transition smoothness and timing'},
636
+ {'rank': 2, 'description': 'Optimize power accumulation and release'},
637
+ {'rank': 3, 'description': 'Enhance consistency under pressure'}
638
+ ]
639
+ else: # Professional
640
+ formatted_analysis['priority_improvements'] = [
641
+ {'rank': 1, 'description': 'Maintain current excellence with minor adjustments'},
642
+ {'rank': 2, 'description': 'Focus on course management and strategy'},
643
+ {'rank': 3, 'description': 'Continue physical conditioning for longevity'}
644
+ ]
645
+
646
+ # Ensure we have some default content if parsing failed
647
+ if not formatted_analysis['strengths']:
648
+ formatted_analysis['strengths'] = ['Swing analysis completed successfully']
649
+
650
+ if not formatted_analysis['weaknesses']:
651
+ formatted_analysis['weaknesses'] = ['Areas for improvement identified']
652
+
653
+ return formatted_analysis
654
+
655
+
656
+ def display_formatted_analysis(analysis_data):
657
+ """
658
+ Display the formatted analysis with performance classification, strengths/weaknesses table, and priorities
659
+
660
+ Args:
661
+ analysis_data (dict): Structured analysis data from parse_and_format_analysis
662
+ """
663
+ # 1. Performance Classification with colored rounded rectangles
664
+ user_classification = analysis_data['classification']
665
+
666
+ # Display classification in black bolded header
667
+ st.markdown(f"""
668
+ <h2 style='color: black; font-weight: bold; text-align: center; margin-bottom: 20px;'>
669
+ 🎯 Performance Classification: {user_classification}
670
+ </h2>
671
+ """, unsafe_allow_html=True)
672
+
673
+ # Create columns for the classification rectangles
674
+ col1, col2, col3, col4 = st.columns(4)
675
+
676
+ # Define colors and styling - all rectangles should have colors
677
+ colors = {
678
+ 'Beginner': {'bg': '#ff4444', 'text': 'white'},
679
+ 'Intermediate': {'bg': '#ff8800', 'text': 'white'},
680
+ 'Advanced': {'bg': '#ffdd00', 'text': 'black'},
681
+ 'Professional': {'bg': '#44aa44', 'text': 'white'}
682
+ }
683
+
684
+ with col1:
685
+ bg_color = colors['Beginner']['bg']
686
+ text_color = colors['Beginner']['text']
687
+ border_style = '3px solid #333' if user_classification == 'Beginner' else '2px solid #ddd'
688
+ st.markdown(f"""
689
+ <div style='text-align: center; padding: 15px; background-color: {bg_color};
690
+ border-radius: 15px; margin: 5px; border: {border_style};'>
691
+ <div style='font-size: 14px; font-weight: bold; color: {text_color};'>Beginner</div>
692
+ </div>
693
+ """, unsafe_allow_html=True)
694
+
695
+ with col2:
696
+ bg_color = colors['Intermediate']['bg']
697
+ text_color = colors['Intermediate']['text']
698
+ border_style = '3px solid #333' if user_classification == 'Intermediate' else '2px solid #ddd'
699
+ st.markdown(f"""
700
+ <div style='text-align: center; padding: 15px; background-color: {bg_color};
701
+ border-radius: 15px; margin: 5px; border: {border_style};'>
702
+ <div style='font-size: 14px; font-weight: bold; color: {text_color};'>Intermediate</div>
703
+ </div>
704
+ """, unsafe_allow_html=True)
705
+
706
+ with col3:
707
+ bg_color = colors['Advanced']['bg']
708
+ text_color = colors['Advanced']['text']
709
+ border_style = '3px solid #333' if user_classification == 'Advanced' else '2px solid #ddd'
710
+ st.markdown(f"""
711
+ <div style='text-align: center; padding: 15px; background-color: {bg_color};
712
+ border-radius: 15px; margin: 5px; border: {border_style};'>
713
+ <div style='font-size: 14px; font-weight: bold; color: {text_color};'>Advanced</div>
714
+ </div>
715
+ """, unsafe_allow_html=True)
716
+
717
+ with col4:
718
+ bg_color = colors['Professional']['bg']
719
+ text_color = colors['Professional']['text']
720
+ border_style = '3px solid #333' if user_classification == 'Professional' else '2px solid #ddd'
721
+ st.markdown(f"""
722
+ <div style='text-align: center; padding: 15px; background-color: {bg_color};
723
+ border-radius: 15px; margin: 5px; border: {border_style};'>
724
+ <div style='font-size: 14px; font-weight: bold; color: {text_color};'>Professional</div>
725
+ </div>
726
+ """, unsafe_allow_html=True)
727
+
728
+ st.markdown("---")
729
+
730
+ # 2. Strengths and Weaknesses Table
731
+ st.subheader("⚖️ Strengths & Areas for Improvement")
732
+
733
+ # Create two columns for the table with a visual divider
734
+ col_left, col_divider, col_right = st.columns([5, 1, 5])
735
+
736
+ with col_left:
737
+ st.markdown("""
738
+ <div style='background-color: #e8f5e8; padding: 15px; border-radius: 10px; height: 100%;'>
739
+ <h4 style='color: #2d5a2d; margin-top: 0;'>✅ Strengths</h4>
740
+ """, unsafe_allow_html=True)
741
+ for strength in analysis_data['strengths']:
742
+ st.markdown(f"• {strength}")
743
+ st.markdown("</div>", unsafe_allow_html=True)
744
+
745
+ with col_divider:
746
+ st.markdown("""
747
+ <div style='width: 2px; background-color: #ddd; height: 200px; margin: 20px auto;'></div>
748
+ """, unsafe_allow_html=True)
749
+
750
+ with col_right:
751
+ st.markdown("""
752
+ <div style='background-color: #fff5e6; padding: 15px; border-radius: 10px; height: 100%;'>
753
+ <h4 style='color: #cc6600; margin-top: 0;'>⚠️ Areas for Improvement</h4>
754
+ """, unsafe_allow_html=True)
755
+ for weakness in analysis_data['weaknesses']:
756
+ st.markdown(f"• {weakness}")
757
+ st.markdown("</div>", unsafe_allow_html=True)
758
+
759
+ st.markdown("---")
760
+
761
+ # 3. Priority Improvement Areas
762
+ st.subheader("🎯 Priority Improvement Areas")
763
+
764
+ for priority in sorted(analysis_data['priority_improvements'], key=lambda x: x['rank']):
765
+ rank = priority['rank']
766
+ description = priority['description']
767
+
768
+ # Extract improvement area and description if possible
769
+ if ':' in description:
770
+ area, desc = description.split(':', 1)
771
+ area = area.strip()
772
+ desc = desc.strip()
773
+ elif '-' in description:
774
+ parts = description.split('-', 1)
775
+ if len(parts) == 2:
776
+ area = parts[0].strip()
777
+ desc = parts[1].strip()
778
+ else:
779
+ area = description
780
+ desc = ""
781
+ else:
782
+ # Try to extract first sentence as area, rest as description
783
+ sentences = description.split('. ')
784
+ if len(sentences) > 1:
785
+ area = sentences[0]
786
+ desc = '. '.join(sentences[1:])
787
+ else:
788
+ area = description
789
+ desc = ""
790
+
791
+ # Color code by priority with better styling
792
+ if rank == 1:
793
+ st.markdown(f"""
794
+ <div style='background-color: #ffebee; padding: 15px; border-left: 5px solid #f44336; border-radius: 5px; margin: 10px 0; word-wrap: break-word; overflow-wrap: break-word;'>
795
+ <strong style='color: #d32f2f; font-size: 16px; display: block; margin-bottom: 8px;'>{rank}. MOST CRITICAL: {area}</strong>
796
+ {f"<div style='color: #666; font-size: 14px; line-height: 1.4; word-wrap: break-word;'>{desc}</div>" if desc else ""}
797
+ </div>
798
+ """, unsafe_allow_html=True)
799
+ elif rank == 2:
800
+ st.markdown(f"""
801
+ <div style='background-color: #fff8e1; padding: 15px; border-left: 5px solid #ff9800; border-radius: 5px; margin: 10px 0; word-wrap: break-word; overflow-wrap: break-word;'>
802
+ <strong style='color: #f57c00; font-size: 16px; display: block; margin-bottom: 8px;'>{rank}. IMPORTANT: {area}</strong>
803
+ {f"<div style='color: #666; font-size: 14px; line-height: 1.4; word-wrap: break-word;'>{desc}</div>" if desc else ""}
804
+ </div>
805
+ """, unsafe_allow_html=True)
806
+ else:
807
+ st.markdown(f"""
808
+ <div style='background-color: #e3f2fd; padding: 15px; border-left: 5px solid #2196f3; border-radius: 5px; margin: 10px 0; word-wrap: break-word; overflow-wrap: break-word;'>
809
+ <strong style='color: #1976d2; font-size: 16px; display: block; margin-bottom: 8px;'>{rank}. FOCUS AREA: {area}</strong>
810
+ {f"<div style='color: #666; font-size: 14px; line-height: 1.4; word-wrap: break-word;'>{desc}</div>" if desc else ""}
811
+ </div>
812
+ """, unsafe_allow_html=True)
app/streamlit_app.py CHANGED
@@ -23,7 +23,7 @@ from app.utils.video_downloader import download_youtube_video, download_pro_refe
23
  from app.utils.video_processor import process_video
24
  from app.models.pose_estimator import analyze_pose
25
  from app.models.swing_analyzer import segment_swing, analyze_trajectory
26
- from app.models.llm_analyzer import generate_swing_analysis, create_llm_prompt, prepare_data_for_llm, check_llm_services
27
  from app.utils.visualizer import create_annotated_video
28
  from app.utils.comparison import create_key_frame_comparison, extract_key_swing_frames
29
 
@@ -451,29 +451,13 @@ def main():
451
  elif llm_services['openai']['available']:
452
  st.info("🤖 **Analysis generated using OpenAI**")
453
 
454
- st.markdown(analysis)
455
-
456
- # Add some example drills based on the analysis
457
- if "Error:" not in analysis: # Only show drills if analysis was successful
458
- st.subheader("Recommended Drills")
459
- drill1, drill2 = st.columns(2)
460
-
461
- with drill1:
462
- st.markdown("**Posture Drill**")
463
- st.markdown("- Stand with your back against a wall")
464
- st.markdown(
465
- "- Take your golf stance while maintaining contact"
466
- )
467
- st.markdown(
468
- "- Practice maintaining this posture during your swing"
469
- )
470
-
471
- with drill2:
472
- st.markdown("**Tempo Drill**")
473
- st.markdown("- Count '1-2-3' for your backswing")
474
- st.markdown("- Count '1' for your downswing")
475
- st.markdown("- Practice maintaining a 3:1 tempo ratio")
476
-
477
  # Handle key frame analysis (new tab/option)
478
  if keyframe_analysis_clicked:
479
  try:
 
23
  from app.utils.video_processor import process_video
24
  from app.models.pose_estimator import analyze_pose
25
  from app.models.swing_analyzer import segment_swing, analyze_trajectory
26
+ from app.models.llm_analyzer import generate_swing_analysis, create_llm_prompt, prepare_data_for_llm, check_llm_services, parse_and_format_analysis, display_formatted_analysis
27
  from app.utils.visualizer import create_annotated_video
28
  from app.utils.comparison import create_key_frame_comparison, extract_key_swing_frames
29
 
 
451
  elif llm_services['openai']['available']:
452
  st.info("🤖 **Analysis generated using OpenAI**")
453
 
454
+ # Parse and display the formatted analysis instead of raw markdown
455
+ if "Error:" not in analysis:
456
+ formatted_analysis = parse_and_format_analysis(analysis)
457
+ display_formatted_analysis(formatted_analysis)
458
+ else:
459
+ # Show error message if analysis failed
460
+ st.error(analysis)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
461
  # Handle key frame analysis (new tab/option)
462
  if keyframe_analysis_clicked:
463
  try: