Sprint 6: report review workflow, CI/K8s, and client tabs.
Add coach review APIs, pose calibration thresholds, Gitea CI, Kubernetes skeleton, H5 practice page, and mini program tab bar. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
"""Configurable thresholds for pose metric calibration."""
|
||||
|
||||
CALIBRATION = {
|
||||
"head_forward_scale": 180.0,
|
||||
"shoulder_asymmetry_scale": 220.0,
|
||||
"pelvic_tilt_scale": 4.5,
|
||||
"normal_max": 35,
|
||||
"low_max": 55,
|
||||
"medium_max": 75,
|
||||
"review_required_min": 75,
|
||||
}
|
||||
|
||||
METRIC_NAMES = ("头前伸", "高低肩", "骨盆倾斜")
|
||||
@@ -15,7 +15,7 @@ RIGHT_HIP = 24
|
||||
Landmark = dict[str, float]
|
||||
FrameLandmarks = dict[int, Landmark]
|
||||
|
||||
METRIC_NAMES = ("头前伸", "高低肩", "骨盆倾斜")
|
||||
from app.ai.calibration import CALIBRATION, METRIC_NAMES
|
||||
|
||||
|
||||
def _point(frame: FrameLandmarks, idx: int) -> Landmark | None:
|
||||
@@ -43,7 +43,7 @@ def score_head_forward(frame: FrameLandmarks) -> float | None:
|
||||
return None
|
||||
mid_x = (left["x"] + right["x"]) / 2
|
||||
offset = abs(nose["x"] - mid_x) / width
|
||||
return min(100.0, max(0.0, offset * 180))
|
||||
return min(100.0, max(0.0, offset * CALIBRATION["head_forward_scale"]))
|
||||
|
||||
|
||||
def score_shoulder_asymmetry(frame: FrameLandmarks) -> float | None:
|
||||
@@ -53,7 +53,7 @@ def score_shoulder_asymmetry(frame: FrameLandmarks) -> float | None:
|
||||
if not left or not right or not width:
|
||||
return None
|
||||
diff = abs(left["y"] - right["y"]) / width
|
||||
return min(100.0, max(0.0, diff * 220))
|
||||
return min(100.0, max(0.0, diff * CALIBRATION["shoulder_asymmetry_scale"]))
|
||||
|
||||
|
||||
def score_pelvic_tilt(frame: FrameLandmarks) -> float | None:
|
||||
@@ -63,15 +63,15 @@ def score_pelvic_tilt(frame: FrameLandmarks) -> float | None:
|
||||
return None
|
||||
angle = abs(math.degrees(math.atan2(right["y"] - left["y"], right["x"] - left["x"])))
|
||||
tilt = min(angle, 180 - angle)
|
||||
return min(100.0, max(0.0, tilt * 4.5))
|
||||
return min(100.0, max(0.0, tilt * CALIBRATION["pelvic_tilt_scale"]))
|
||||
|
||||
|
||||
def value_to_level(value: float) -> str:
|
||||
if value < 35:
|
||||
if value < CALIBRATION["normal_max"]:
|
||||
return "normal"
|
||||
if value < 55:
|
||||
if value < CALIBRATION["low_max"]:
|
||||
return "low"
|
||||
if value < 75:
|
||||
if value < CALIBRATION["medium_max"]:
|
||||
return "medium"
|
||||
return "high"
|
||||
|
||||
@@ -111,12 +111,16 @@ def build_screening_report(frames: list[FrameLandmarks]) -> dict:
|
||||
|
||||
worst = max((m["value"] for m in metrics), default=0)
|
||||
risk_level = value_to_level(worst)
|
||||
if worst >= CALIBRATION["review_required_min"]:
|
||||
risk_level = "review_required"
|
||||
if risk_level == "normal":
|
||||
summary = "体态指标整体正常,建议保持日常活动与姿势习惯"
|
||||
elif risk_level == "low":
|
||||
summary = "存在轻度体态偏差,建议开始基础纠正训练"
|
||||
elif risk_level == "medium":
|
||||
summary = "建议关注头前伸与高低肩,开始针对性训练"
|
||||
elif risk_level == "review_required":
|
||||
summary = "指标偏高,已提交机构教练复核"
|
||||
else:
|
||||
summary = "多项指标偏高,建议尽快安排专业评估与干预"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user