Spaces:
Paused
Paused
Fix front-facing golf metrics: improve accuracy and reliability
Browse files- Torso side-bend: standardize sign convention, use eyes+hips for de-roll
- Hip sway: add robust baseline with MAD jitter, two-stage denoising, yaw correction
- Wrist hinge: report acute angles (50-70° pro range) instead of obtuse
- Hip-shoulder separation: improve span ratio scaling, clamp to 0.7-1.0 range
- Fix format string errors with None value safety checks
- Enhanced debug logging with normalized sway and trust ratios
- app/models/front_facing_metrics.py +0 -0
- app/models/llm_analyzer.py +22 -19
- app/streamlit_app.py +72 -68
app/models/front_facing_metrics.py
CHANGED
|
The diff for this file is too large to render.
See raw diff
|
|
|
app/models/llm_analyzer.py
CHANGED
|
@@ -24,9 +24,9 @@ try:
|
|
| 24 |
get_knee_flexion_grading,
|
| 25 |
get_head_drop_grading,
|
| 26 |
get_shoulder_tilt_impact_grading,
|
| 27 |
-
get_hip_turn_impact_grading,
|
| 28 |
get_hip_sway_grading,
|
| 29 |
-
get_wrist_hinge_grading
|
|
|
|
| 30 |
)
|
| 31 |
except ImportError:
|
| 32 |
# Fallback if imports fail - define minimal grading functions
|
|
@@ -38,13 +38,14 @@ except ImportError:
|
|
| 38 |
return {'value': value, 'status': 'n/a', 'badge': '⚪'}
|
| 39 |
def get_hip_depth_grading(value, confidence):
|
| 40 |
return {'value': value, 'status': 'n/a', 'badge': '⚪'}
|
| 41 |
-
# Hip turn grading function removed per user request
|
| 42 |
def get_shoulder_tilt_impact_grading(value, confidence):
|
| 43 |
return {'value': value, 'status': 'n/a', 'badge': '⚪'}
|
| 44 |
def get_hip_sway_grading(value, confidence, position):
|
| 45 |
return {'value': value, 'status': 'n/a', 'badge': '⚪'}
|
| 46 |
def get_wrist_hinge_grading(value, confidence, position):
|
| 47 |
return {'value': value, 'status': 'n/a', 'badge': '⚪'}
|
|
|
|
|
|
|
| 48 |
def get_head_drop_grading(value, confidence):
|
| 49 |
"""Grade head drop metric based on percentage of torso length
|
| 50 |
|
|
@@ -352,23 +353,25 @@ def compute_front_facing_core_metrics(pose_data, swing_phases, frames=None, worl
|
|
| 352 |
|
| 353 |
# Process each front-facing metric with new calibration values
|
| 354 |
for metric_name, metric_data in front_metrics.items():
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
else:
|
| 359 |
-
# Handle other metrics with traditional processing
|
| 360 |
-
value = metric_data.get('value') if isinstance(metric_data, dict) else metric_data
|
| 361 |
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 372 |
|
| 373 |
return core_metrics
|
| 374 |
|
|
|
|
| 24 |
get_knee_flexion_grading,
|
| 25 |
get_head_drop_grading,
|
| 26 |
get_shoulder_tilt_impact_grading,
|
|
|
|
| 27 |
get_hip_sway_grading,
|
| 28 |
+
get_wrist_hinge_grading,
|
| 29 |
+
get_hip_shoulder_separation_impact_grading
|
| 30 |
)
|
| 31 |
except ImportError:
|
| 32 |
# Fallback if imports fail - define minimal grading functions
|
|
|
|
| 38 |
return {'value': value, 'status': 'n/a', 'badge': '⚪'}
|
| 39 |
def get_hip_depth_grading(value, confidence):
|
| 40 |
return {'value': value, 'status': 'n/a', 'badge': '⚪'}
|
|
|
|
| 41 |
def get_shoulder_tilt_impact_grading(value, confidence):
|
| 42 |
return {'value': value, 'status': 'n/a', 'badge': '⚪'}
|
| 43 |
def get_hip_sway_grading(value, confidence, position):
|
| 44 |
return {'value': value, 'status': 'n/a', 'badge': '⚪'}
|
| 45 |
def get_wrist_hinge_grading(value, confidence, position):
|
| 46 |
return {'value': value, 'status': 'n/a', 'badge': '⚪'}
|
| 47 |
+
def get_hip_shoulder_separation_impact_grading(value, confidence):
|
| 48 |
+
return {'value': value, 'status': 'n/a', 'badge': '⚪'}
|
| 49 |
def get_head_drop_grading(value, confidence):
|
| 50 |
"""Grade head drop metric based on percentage of torso length
|
| 51 |
|
|
|
|
| 353 |
|
| 354 |
# Process each front-facing metric with new calibration values
|
| 355 |
for metric_name, metric_data in front_metrics.items():
|
| 356 |
+
# Skip hip turn metric entirely
|
| 357 |
+
if 'hip_turn_impact' in metric_name:
|
| 358 |
+
continue
|
|
|
|
|
|
|
|
|
|
| 359 |
|
| 360 |
+
# Handle other metrics with traditional processing
|
| 361 |
+
value = metric_data.get('value') if isinstance(metric_data, dict) else metric_data
|
| 362 |
+
|
| 363 |
+
if 'shoulder_tilt_impact' in metric_name:
|
| 364 |
+
core_metrics[metric_name] = get_shoulder_tilt_impact_grading(value, 0.9)
|
| 365 |
+
elif 'hip_shoulder_separation_impact' in metric_name:
|
| 366 |
+
confidence = metric_data.get('confidence', 0.8) if isinstance(metric_data, dict) else 0.8
|
| 367 |
+
core_metrics[metric_name] = get_hip_shoulder_separation_impact_grading(value, confidence)
|
| 368 |
+
elif 'hip_sway_top' in metric_name:
|
| 369 |
+
core_metrics[metric_name] = get_hip_sway_grading(value, 0.8, "top")
|
| 370 |
+
elif 'wrist_hinge_top' in metric_name:
|
| 371 |
+
core_metrics[metric_name] = get_wrist_hinge_grading(value, 0.8, "top")
|
| 372 |
+
else:
|
| 373 |
+
# Default case
|
| 374 |
+
core_metrics[metric_name] = {'value': value, 'status': 'n/a'}
|
| 375 |
|
| 376 |
return core_metrics
|
| 377 |
|
app/streamlit_app.py
CHANGED
|
@@ -381,21 +381,24 @@ def get_head_drop_grading(value, confidence):
|
|
| 381 |
}
|
| 382 |
|
| 383 |
|
| 384 |
-
def
|
| 385 |
-
"""Grade
|
| 386 |
if value is None:
|
| 387 |
return {'display_value': 'n/a', 'badge': '⚪', 'status': 'No data available'}
|
| 388 |
|
| 389 |
-
#
|
| 390 |
-
|
|
|
|
|
|
|
|
|
|
| 391 |
badge = '🟢'
|
| 392 |
-
label = 'Excellent
|
| 393 |
-
elif
|
| 394 |
badge = '🟠'
|
| 395 |
-
label = 'Good
|
| 396 |
else:
|
| 397 |
badge = '🔴'
|
| 398 |
-
label = 'Needs
|
| 399 |
|
| 400 |
return {
|
| 401 |
'display_value': f'{value:.1f}°',
|
|
@@ -405,18 +408,19 @@ def get_shoulder_tilt_impact_grading(value, confidence):
|
|
| 405 |
}
|
| 406 |
|
| 407 |
|
| 408 |
-
|
| 409 |
-
|
|
|
|
| 410 |
if value is None:
|
| 411 |
return {'display_value': 'n/a', 'badge': '⚪', 'status': 'No data available'}
|
| 412 |
|
| 413 |
-
# Professional
|
| 414 |
-
if
|
| 415 |
badge = '🟢'
|
| 416 |
-
label = 'Excellent
|
| 417 |
-
elif
|
| 418 |
badge = '🟠'
|
| 419 |
-
label = 'Good
|
| 420 |
else:
|
| 421 |
badge = '🔴'
|
| 422 |
label = 'Needs improvement'
|
|
@@ -428,7 +432,6 @@ def get_hip_turn_impact_grading(value, confidence):
|
|
| 428 |
'confidence': confidence
|
| 429 |
}
|
| 430 |
|
| 431 |
-
|
| 432 |
def get_hip_sway_grading(value, confidence, position="top"):
|
| 433 |
"""Grade hip sway - professional = 3.9" towards target, 30 handicap = 2.5" towards target"""
|
| 434 |
if value is None:
|
|
@@ -501,28 +504,6 @@ def get_wrist_hinge_grading(value, confidence, position="top"):
|
|
| 501 |
|
| 502 |
|
| 503 |
|
| 504 |
-
def grade_hip_turn(delta_deg: float, club: str = "iron") -> dict:
|
| 505 |
-
"""Grade hip turn with club-aware bands and actionable tips"""
|
| 506 |
-
c = club.lower()
|
| 507 |
-
bands = {
|
| 508 |
-
"driver": {"excellent": (38,48), "good_low": (32,38), "good_high": (48,52)},
|
| 509 |
-
"iron": {"excellent": (32,40), "good_low": (28,32), "good_high": (40,44)},
|
| 510 |
-
"wedge": {"excellent": (25,35), "good_low": (22,25), "good_high": (35,38)},
|
| 511 |
-
}
|
| 512 |
-
b = bands["driver" if "driver" in c else "wedge" if "wedge" in c else "iron"]
|
| 513 |
-
x = delta_deg
|
| 514 |
-
|
| 515 |
-
if b["excellent"][0] <= x <= b["excellent"][1]:
|
| 516 |
-
label, color, tip = "🟢 Excellent", "green", "Great rotation with control."
|
| 517 |
-
elif b["good_low"][0] <= x < b["good_low"][1]:
|
| 518 |
-
label, color, tip = "🟠 Good (slightly low)", "orange", "Open a touch more through impact."
|
| 519 |
-
elif b["good_high"][0] <= x <= b["good_high"][1]:
|
| 520 |
-
label, color, tip = "🟠 Good (slightly high)", "orange", "Post on lead leg; avoid over-spinning hips."
|
| 521 |
-
else:
|
| 522 |
-
label, color, tip = "🔴 Needs work", "red", (
|
| 523 |
-
"Too little: shift then open. Too much: trail heel down longer; post into lead glute."
|
| 524 |
-
)
|
| 525 |
-
return {"label": label, "color": color, "tip": tip}
|
| 526 |
|
| 527 |
|
| 528 |
def display_new_grading_scheme(core_metrics):
|
|
@@ -530,9 +511,8 @@ def display_new_grading_scheme(core_metrics):
|
|
| 530 |
# Check if we have front-facing metrics
|
| 531 |
# Front-facing metrics have unique keys that DTL doesn't have
|
| 532 |
has_front_facing_metrics = any(key in core_metrics for key in [
|
| 533 |
-
'shoulder_tilt_impact_deg', 'hip_sway_top_inches', 'wrist_hinge_top_deg'
|
| 534 |
])
|
| 535 |
-
# Hip turn check for front-facing metrics
|
| 536 |
|
| 537 |
if has_front_facing_metrics:
|
| 538 |
st.subheader("Swing Analysis")
|
|
@@ -546,7 +526,6 @@ def display_new_grading_scheme(core_metrics):
|
|
| 546 |
knee_flexion_data = core_metrics.get("knee_flexion_deg", {})
|
| 547 |
head_drop_data = core_metrics.get("head_drop_top_pct", {})
|
| 548 |
hip_depth_data = core_metrics.get("hip_depth_early_extension", {})
|
| 549 |
-
# Hip turn data for front-facing metrics
|
| 550 |
|
| 551 |
# Calculate confidence with QC penalties as per feedback
|
| 552 |
def get_confidence(data, metric_type='general'):
|
|
@@ -701,21 +680,30 @@ def display_new_grading_scheme(core_metrics):
|
|
| 701 |
# Front-facing metrics are now always calculated
|
| 702 |
pass
|
| 703 |
|
| 704 |
-
#
|
| 705 |
-
|
| 706 |
-
if
|
| 707 |
confidence = 0.9 # High confidence for front-facing measurements
|
| 708 |
-
grading =
|
| 709 |
if grading:
|
| 710 |
-
metrics_to_display.append(("
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 711 |
|
| 712 |
-
|
| 713 |
-
|
| 714 |
-
|
| 715 |
-
|
| 716 |
-
|
|
|
|
| 717 |
if grading:
|
| 718 |
-
metrics_to_display.append(("Hip
|
| 719 |
|
| 720 |
# Hip Sway at Top
|
| 721 |
hip_sway_top_data = core_metrics.get("hip_sway_top_inches", {})
|
|
@@ -779,8 +767,9 @@ def get_metric_definition(metric_name):
|
|
| 779 |
"Knee Flexion": "Measures knee bend angle at address position.",
|
| 780 |
"Head Movement @ Top": "Measures head movement during backswing as percentage of torso length.",
|
| 781 |
"Hip Depth / Early Extension": "Tracks loss of hip flexion through impact.",
|
| 782 |
-
"
|
| 783 |
-
"Shoulder Tilt @ Impact": "Measures shoulder angle at ball contact.",
|
|
|
|
| 784 |
"Hip Sway @ Top": "Measures lateral hip movement at the top of backswing.",
|
| 785 |
"Wrist Hinge @ Top": "Measures wrist hinge angle at the top of backswing."
|
| 786 |
}
|
|
@@ -833,7 +822,19 @@ def get_metric_evaluation(metric_name, grading):
|
|
| 833 |
return f"Your hip depth of **{value}** indicates early extension issues. This movement pattern reduces power transfer and can cause inconsistent contact. Focus on maintaining spine angle and proper hip position throughout the downswing and impact."
|
| 834 |
|
| 835 |
# FRONT-FACING METRICS (4 current metrics)
|
| 836 |
-
elif metric_name == "
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 837 |
if "🟢" in badge:
|
| 838 |
return f"Your shoulder tilt of **{value}** at impact shows excellent position. This proper shoulder angle indicates ideal impact dynamics and power transfer. Good shoulder tilt at impact promotes solid contact, optimal ball flight, and consistent distance control."
|
| 839 |
elif "🟠" in badge:
|
|
@@ -841,13 +842,13 @@ def get_metric_evaluation(metric_name, grading):
|
|
| 841 |
else:
|
| 842 |
return f"Your shoulder tilt of **{value}** at impact needs attention. Proper shoulder angle at impact is crucial for power transfer and ball flight control. Work on impact position for better contact and consistency."
|
| 843 |
|
| 844 |
-
elif metric_name == "Hip
|
| 845 |
if "🟢" in badge:
|
| 846 |
-
return f"Your hip
|
| 847 |
elif "🟠" in badge:
|
| 848 |
-
return f"Your hip
|
| 849 |
else:
|
| 850 |
-
return f"Your hip
|
| 851 |
|
| 852 |
elif metric_name == "Hip Sway @ Top":
|
| 853 |
if "🟢" in badge:
|
|
@@ -966,14 +967,13 @@ Timing Metrics:
|
|
| 966 |
- Knee Flexion @ Setup: {format_metric_value(core_metrics.get('knee_flexion_deg', {}), '°')}
|
| 967 |
- Head Movement @ Top: {format_metric_value(core_metrics.get('head_drop_top_pct', {}), '%')}
|
| 968 |
- Hip Depth / Early Extension: {format_metric_value(core_metrics.get('hip_depth_early_extension', {}), '%')}
|
| 969 |
-
# Hip Turn @ Impact metric restored for front-facing analysis
|
| 970 |
|
| 971 |
=== DTL-LIMITED METRICS (Approximate) ===
|
| 972 |
- Shoulder Turn Quality: {format_metric_value(core_metrics.get('shoulder_turn_quality', {}))}
|
| 973 |
|
| 974 |
=== FRONT-FACING METRICS ===
|
| 975 |
-
-
|
| 976 |
-
|
| 977 |
- Hip Sway @ Top: {format_metric_value(core_metrics.get('hip_sway_top_inches', {}), '"')}
|
| 978 |
- Wrist Hinge @ Top: {format_metric_value(core_metrics.get('wrist_hinge_top_deg', {}), '°')}
|
| 979 |
"""
|
|
@@ -1820,19 +1820,23 @@ def render_step_4():
|
|
| 1820 |
shoulder_swing_plane_status = core_metrics.get("shoulder_tilt_swing_plane_top_deg", {}).get('status', 'n/a')
|
| 1821 |
st.caption(f"Shoulder Tilt/Swing Plane @ Top: {shoulder_swing_plane_raw}° ({shoulder_swing_plane_status})")
|
| 1822 |
|
| 1823 |
-
#
|
| 1824 |
-
|
| 1825 |
-
|
| 1826 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1827 |
|
| 1828 |
-
# Hip turn data for front-facing metrics
|
| 1829 |
|
| 1830 |
# Hip depth raw value
|
| 1831 |
hip_depth_raw = core_metrics.get("hip_depth_early_extension", {}).get('value')
|
| 1832 |
hip_depth_status = core_metrics.get("hip_depth_early_extension", {}).get('status', 'n/a')
|
| 1833 |
st.caption(f"Hip Depth / Early Extension: {hip_depth_raw} ({hip_depth_status})")
|
| 1834 |
|
| 1835 |
-
# Hip turn debug info for front-facing metrics
|
| 1836 |
if has_front_facing_metrics:
|
| 1837 |
st.caption("Front-facing metrics detected")
|
| 1838 |
else:
|
|
|
|
| 381 |
}
|
| 382 |
|
| 383 |
|
| 384 |
+
def get_torso_sidebend_impact_grading(value, confidence):
|
| 385 |
+
"""Grade torso side-bend at impact - professional range ~10-20° (trail-side bend positive)"""
|
| 386 |
if value is None:
|
| 387 |
return {'display_value': 'n/a', 'badge': '⚪', 'status': 'No data available'}
|
| 388 |
|
| 389 |
+
# Use absolute value for grading, but keep sign for display
|
| 390 |
+
abs_value = abs(value)
|
| 391 |
+
|
| 392 |
+
# Professional range ~10-20°, typical amateur ~6-24°
|
| 393 |
+
if 10 <= abs_value <= 20:
|
| 394 |
badge = '🟢'
|
| 395 |
+
label = 'Excellent side-bend'
|
| 396 |
+
elif 6 <= abs_value < 10 or 20 < abs_value <= 24:
|
| 397 |
badge = '🟠'
|
| 398 |
+
label = 'Good side-bend'
|
| 399 |
else:
|
| 400 |
badge = '🔴'
|
| 401 |
+
label = 'Needs work'
|
| 402 |
|
| 403 |
return {
|
| 404 |
'display_value': f'{value:.1f}°',
|
|
|
|
| 408 |
}
|
| 409 |
|
| 410 |
|
| 411 |
+
|
| 412 |
+
def get_hip_shoulder_separation_impact_grading(value, confidence):
|
| 413 |
+
"""Grade hip-shoulder separation at impact - typical range 10-45 degrees"""
|
| 414 |
if value is None:
|
| 415 |
return {'display_value': 'n/a', 'badge': '⚪', 'status': 'No data available'}
|
| 416 |
|
| 417 |
+
# Professional golfers typically show 20-35° separation, recreational 10-25°
|
| 418 |
+
if 20 <= value <= 35:
|
| 419 |
badge = '🟢'
|
| 420 |
+
label = 'Excellent separation'
|
| 421 |
+
elif 15 <= value < 20 or 35 < value <= 45:
|
| 422 |
badge = '🟠'
|
| 423 |
+
label = 'Good separation'
|
| 424 |
else:
|
| 425 |
badge = '🔴'
|
| 426 |
label = 'Needs improvement'
|
|
|
|
| 432 |
'confidence': confidence
|
| 433 |
}
|
| 434 |
|
|
|
|
| 435 |
def get_hip_sway_grading(value, confidence, position="top"):
|
| 436 |
"""Grade hip sway - professional = 3.9" towards target, 30 handicap = 2.5" towards target"""
|
| 437 |
if value is None:
|
|
|
|
| 504 |
|
| 505 |
|
| 506 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 507 |
|
| 508 |
|
| 509 |
def display_new_grading_scheme(core_metrics):
|
|
|
|
| 511 |
# Check if we have front-facing metrics
|
| 512 |
# Front-facing metrics have unique keys that DTL doesn't have
|
| 513 |
has_front_facing_metrics = any(key in core_metrics for key in [
|
| 514 |
+
'torso_side_bend_deg', 'shoulder_tilt_impact_deg', 'hip_sway_top_inches', 'wrist_hinge_top_deg', 'hip_shoulder_separation_impact_deg'
|
| 515 |
])
|
|
|
|
| 516 |
|
| 517 |
if has_front_facing_metrics:
|
| 518 |
st.subheader("Swing Analysis")
|
|
|
|
| 526 |
knee_flexion_data = core_metrics.get("knee_flexion_deg", {})
|
| 527 |
head_drop_data = core_metrics.get("head_drop_top_pct", {})
|
| 528 |
hip_depth_data = core_metrics.get("hip_depth_early_extension", {})
|
|
|
|
| 529 |
|
| 530 |
# Calculate confidence with QC penalties as per feedback
|
| 531 |
def get_confidence(data, metric_type='general'):
|
|
|
|
| 680 |
# Front-facing metrics are now always calculated
|
| 681 |
pass
|
| 682 |
|
| 683 |
+
# Torso Side-Bend at Impact (replaces shoulder tilt)
|
| 684 |
+
torso_sidebend_data = core_metrics.get("torso_side_bend_deg", {})
|
| 685 |
+
if torso_sidebend_data.get('value') is not None:
|
| 686 |
confidence = 0.9 # High confidence for front-facing measurements
|
| 687 |
+
grading = get_torso_sidebend_impact_grading(torso_sidebend_data['value'], confidence)
|
| 688 |
if grading:
|
| 689 |
+
metrics_to_display.append(("Torso Side-Bend @ Impact", grading))
|
| 690 |
+
else:
|
| 691 |
+
# Fallback to old metric name for compatibility
|
| 692 |
+
shoulder_tilt_impact_data = core_metrics.get("shoulder_tilt_impact_deg", {})
|
| 693 |
+
if shoulder_tilt_impact_data.get('value') is not None:
|
| 694 |
+
confidence = 0.9 # High confidence for front-facing measurements
|
| 695 |
+
grading = get_torso_sidebend_impact_grading(shoulder_tilt_impact_data['value'], confidence)
|
| 696 |
+
if grading:
|
| 697 |
+
metrics_to_display.append(("Torso Side-Bend @ Impact", grading))
|
| 698 |
|
| 699 |
+
|
| 700 |
+
# Hip-Shoulder Separation at Impact
|
| 701 |
+
hip_shoulder_sep_data = core_metrics.get("hip_shoulder_separation_impact_deg", {})
|
| 702 |
+
if hip_shoulder_sep_data.get('value') is not None:
|
| 703 |
+
confidence = hip_shoulder_sep_data.get('confidence', 0.8)
|
| 704 |
+
grading = get_hip_shoulder_separation_impact_grading(hip_shoulder_sep_data['value'], confidence)
|
| 705 |
if grading:
|
| 706 |
+
metrics_to_display.append(("Hip-Shoulder Separation @ Impact", grading))
|
| 707 |
|
| 708 |
# Hip Sway at Top
|
| 709 |
hip_sway_top_data = core_metrics.get("hip_sway_top_inches", {})
|
|
|
|
| 767 |
"Knee Flexion": "Measures knee bend angle at address position.",
|
| 768 |
"Head Movement @ Top": "Measures head movement during backswing as percentage of torso length.",
|
| 769 |
"Hip Depth / Early Extension": "Tracks loss of hip flexion through impact.",
|
| 770 |
+
"Torso Side-Bend @ Impact": "Measures torso side-bend angle at ball contact.",
|
| 771 |
+
"Shoulder Tilt @ Impact": "Measures shoulder angle at ball contact.", # Deprecated
|
| 772 |
+
"Hip-Shoulder Separation @ Impact": "Measures hip rotation relative to shoulders at ball contact.",
|
| 773 |
"Hip Sway @ Top": "Measures lateral hip movement at the top of backswing.",
|
| 774 |
"Wrist Hinge @ Top": "Measures wrist hinge angle at the top of backswing."
|
| 775 |
}
|
|
|
|
| 822 |
return f"Your hip depth of **{value}** indicates early extension issues. This movement pattern reduces power transfer and can cause inconsistent contact. Focus on maintaining spine angle and proper hip position throughout the downswing and impact."
|
| 823 |
|
| 824 |
# FRONT-FACING METRICS (4 current metrics)
|
| 825 |
+
elif metric_name == "Torso Side-Bend @ Impact":
|
| 826 |
+
# Interpret the sign
|
| 827 |
+
side_description = "trail-side" if float(value.replace('°', '')) > 0 else "lead-side"
|
| 828 |
+
abs_value = abs(float(value.replace('°', '')))
|
| 829 |
+
|
| 830 |
+
if "🟢" in badge:
|
| 831 |
+
return f"Your torso side-bend of **{value}** at impact shows excellent position. You're bending **{abs_value:.1f}°** toward the **{side_description}** which indicates ideal impact dynamics and power transfer. This optimal torso angle promotes solid contact, optimal ball flight, and consistent distance control."
|
| 832 |
+
elif "🟠" in badge:
|
| 833 |
+
return f"Your torso side-bend of **{value}** at impact is acceptable with room for improvement. You're bending **{abs_value:.1f}°** toward the **{side_description}**. Torso position at impact affects power transfer and ball flight characteristics. Refining your impact position can enhance consistency and distance."
|
| 834 |
+
else:
|
| 835 |
+
return f"Your torso side-bend of **{value}** at impact needs attention. You're bending **{abs_value:.1f}°** toward the **{side_description}**. Proper torso angle at impact is crucial for power transfer and ball flight control. Work on impact position for better contact and consistency."
|
| 836 |
+
|
| 837 |
+
elif metric_name == "Shoulder Tilt @ Impact": # Legacy support
|
| 838 |
if "🟢" in badge:
|
| 839 |
return f"Your shoulder tilt of **{value}** at impact shows excellent position. This proper shoulder angle indicates ideal impact dynamics and power transfer. Good shoulder tilt at impact promotes solid contact, optimal ball flight, and consistent distance control."
|
| 840 |
elif "🟠" in badge:
|
|
|
|
| 842 |
else:
|
| 843 |
return f"Your shoulder tilt of **{value}** at impact needs attention. Proper shoulder angle at impact is crucial for power transfer and ball flight control. Work on impact position for better contact and consistency."
|
| 844 |
|
| 845 |
+
elif metric_name == "Hip-Shoulder Separation @ Impact":
|
| 846 |
if "🟢" in badge:
|
| 847 |
+
return f"Your hip-shoulder separation of **{value}** at impact shows excellent body sequencing. This proper rotation sequence indicates ideal power transfer with the hips leading the shoulders through impact. Good separation promotes solid contact and optimal ball flight."
|
| 848 |
elif "🟠" in badge:
|
| 849 |
+
return f"Your hip-shoulder separation of **{value}** at impact is acceptable with room for improvement. Hip-shoulder sequencing affects power transfer and swing efficiency. Refining your rotation sequence can enhance consistency and distance."
|
| 850 |
else:
|
| 851 |
+
return f"Your hip-shoulder separation of **{value}** at impact needs attention. Proper sequencing with hips leading shoulders is crucial for power transfer and ball flight control. Work on rotation timing for better contact and consistency."
|
| 852 |
|
| 853 |
elif metric_name == "Hip Sway @ Top":
|
| 854 |
if "🟢" in badge:
|
|
|
|
| 967 |
- Knee Flexion @ Setup: {format_metric_value(core_metrics.get('knee_flexion_deg', {}), '°')}
|
| 968 |
- Head Movement @ Top: {format_metric_value(core_metrics.get('head_drop_top_pct', {}), '%')}
|
| 969 |
- Hip Depth / Early Extension: {format_metric_value(core_metrics.get('hip_depth_early_extension', {}), '%')}
|
|
|
|
| 970 |
|
| 971 |
=== DTL-LIMITED METRICS (Approximate) ===
|
| 972 |
- Shoulder Turn Quality: {format_metric_value(core_metrics.get('shoulder_turn_quality', {}))}
|
| 973 |
|
| 974 |
=== FRONT-FACING METRICS ===
|
| 975 |
+
- Torso Side-Bend @ Impact: {format_metric_value(core_metrics.get('torso_side_bend_deg', {}), '°')}
|
| 976 |
+
- Hip-Shoulder Separation @ Impact: {format_metric_value(core_metrics.get('hip_shoulder_separation_impact_deg', {}), '°')}
|
| 977 |
- Hip Sway @ Top: {format_metric_value(core_metrics.get('hip_sway_top_inches', {}), '"')}
|
| 978 |
- Wrist Hinge @ Top: {format_metric_value(core_metrics.get('wrist_hinge_top_deg', {}), '°')}
|
| 979 |
"""
|
|
|
|
| 1820 |
shoulder_swing_plane_status = core_metrics.get("shoulder_tilt_swing_plane_top_deg", {}).get('status', 'n/a')
|
| 1821 |
st.caption(f"Shoulder Tilt/Swing Plane @ Top: {shoulder_swing_plane_raw}° ({shoulder_swing_plane_status})")
|
| 1822 |
|
| 1823 |
+
# Torso side-bend raw value
|
| 1824 |
+
torso_sidebend_raw = core_metrics.get("torso_side_bend_deg", {}).get('value')
|
| 1825 |
+
torso_sidebend_status = core_metrics.get("torso_side_bend_deg", {}).get('status', 'n/a')
|
| 1826 |
+
if torso_sidebend_raw is not None:
|
| 1827 |
+
st.caption(f"Torso Side-Bend @ Impact: {torso_sidebend_raw}° ({torso_sidebend_status})")
|
| 1828 |
+
else:
|
| 1829 |
+
# Fallback to old metric for compatibility
|
| 1830 |
+
shoulder_tilt_raw = core_metrics.get("shoulder_tilt_impact_deg", {}).get('value')
|
| 1831 |
+
shoulder_tilt_status = core_metrics.get("shoulder_tilt_impact_deg", {}).get('status', 'n/a')
|
| 1832 |
+
st.caption(f"Torso Side-Bend @ Impact: {shoulder_tilt_raw}° ({shoulder_tilt_status})")
|
| 1833 |
|
|
|
|
| 1834 |
|
| 1835 |
# Hip depth raw value
|
| 1836 |
hip_depth_raw = core_metrics.get("hip_depth_early_extension", {}).get('value')
|
| 1837 |
hip_depth_status = core_metrics.get("hip_depth_early_extension", {}).get('status', 'n/a')
|
| 1838 |
st.caption(f"Hip Depth / Early Extension: {hip_depth_raw} ({hip_depth_status})")
|
| 1839 |
|
|
|
|
| 1840 |
if has_front_facing_metrics:
|
| 1841 |
st.caption("Front-facing metrics detected")
|
| 1842 |
else:
|