File size: 3,621 Bytes
4948993
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
"""Tests for formscout/types.py — contract validation."""
import pytest

from formscout.types import (
    IngestResult, SegmentResult, Pose2DResult, Body3DResult,
    MovementResult, BiomechFeatures, ScoreResult, RetrievalResult,
    JudgeResult, ReportResult, PipelineState,
)


class TestIngestResult:
    def test_frozen(self):
        r = IngestResult(frames=[], fps=30.0, duration=2.0, n_people=1, width=1920, height=1080)
        with pytest.raises(Exception):
            r.fps = 60.0

    def test_defaults(self):
        r = IngestResult(frames=[], fps=30.0, duration=0.0, n_people=0, width=0, height=0)
        assert r.confidence == 1.0
        assert r.notes == ""


class TestMovementResult:
    def test_valid_tests(self):
        r = MovementResult(test_name="deep_squat", side="na", confidence=0.9)
        assert r.test_name == "deep_squat"

    def test_invalid_test_raises(self):
        with pytest.raises(ValueError, match="test_name"):
            MovementResult(test_name="jumping_jacks", side="na", confidence=0.5)

    def test_invalid_side_raises(self):
        with pytest.raises(ValueError, match="side"):
            MovementResult(test_name="deep_squat", side="both", confidence=0.5)


class TestBiomechFeatures:
    def test_valid_views(self):
        f = BiomechFeatures(
            test_name="deep_squat", view="2d", side="na",
            angles={}, alignments={}, symmetry_delta=None, timing={},
            confidence=0.8,
        )
        assert f.view == "2d"

    def test_invalid_view_raises(self):
        with pytest.raises(ValueError, match="view"):
            BiomechFeatures(
                test_name="deep_squat", view="4d", side="na",
                angles={}, alignments={}, symmetry_delta=None, timing={},
                confidence=0.8,
            )


class TestScoreResult:
    def test_valid_score(self):
        r = ScoreResult(score=3, rationale="good", confidence=0.9)
        assert r.score == 3

    def test_invalid_score_raises(self):
        with pytest.raises(ValueError):
            ScoreResult(score=4, rationale="bad", confidence=0.9)

    def test_score_minus_one_invalid_when_not_needs_human(self):
        with pytest.raises(ValueError):
            ScoreResult(score=-1, rationale="x", confidence=0.5)


class TestJudgeResult:
    def test_needs_human_score_must_be_none(self):
        with pytest.raises(ValueError):
            JudgeResult(
                score=2, rationale="pain", compensation_tags=[],
                corrective_hint="", confidence=0.5, needs_human=True,
            )

    def test_needs_human_with_none_score(self):
        r = JudgeResult(
            score=None, rationale="pain observed", compensation_tags=[],
            corrective_hint="", confidence=0.5, needs_human=True,
        )
        assert r.needs_human is True
        assert r.score is None

    def test_valid_score(self):
        r = JudgeResult(
            score=2, rationale="ok", compensation_tags=["heel_rise"],
            corrective_hint="work on ankle mobility", confidence=0.85,
        )
        assert r.score == 2


class TestPipelineState:
    def test_mutable(self):
        s = PipelineState(video_path="/tmp/test.mp4")
        s.ingest = IngestResult(frames=[], fps=30.0, duration=1.0, n_people=1, width=640, height=480)
        assert s.ingest is not None

    def test_defaults(self):
        s = PipelineState(video_path="test.mp4")
        assert s.ingest is None
        assert s.errors == []
        assert s.warnings == []