NLag commited on
Commit
e2fb7fb
·
1 Parent(s): 5620975

Enhance UnknownExercise class with debug data handling and add test for manual unknown exercise in pipeline

Browse files
src/pozify/exercises/unknown/strategy.py CHANGED
@@ -12,12 +12,23 @@ from pozify.steps.rep_signals import SignalSample
12
  class UnknownExercise(UnknownAnalyzer, ExerciseStrategy):
13
  exercise = "unknown"
14
 
 
 
 
 
 
 
 
 
 
 
 
15
  def build_signal(self) -> tuple[list[SignalSample], dict[str, Any]]:
16
- return [], {"selected_signal": "none", "thresholds": {}, "extrema": [], "accepted_reps": []}
17
 
18
  def count(self) -> tuple[Reps, dict[str, Any]]:
19
  reps = Reps(exercise=self.exercise, reps=[], partial_reps=[{"reason": "unknown_exercise"}])
20
- return reps, {"selected_signal": "none", "thresholds": {}, "extrema": [], "accepted_reps": []}
21
 
22
  def detect_variation(self, analysis: RepAnalysis) -> tuple[str, float, list[str]]:
23
  exercise_spec = get_exercise_spec(analysis.exercise)
 
12
  class UnknownExercise(UnknownAnalyzer, ExerciseStrategy):
13
  exercise = "unknown"
14
 
15
+ def _empty_debug(self) -> dict[str, Any]:
16
+ return {
17
+ "selected_signal": "none",
18
+ "thresholds": {},
19
+ "extrema": [],
20
+ "accepted_reps": [],
21
+ "body_line_mean": 0.0,
22
+ "raw_signal_range": 0.0,
23
+ "usable_signal_samples": 0,
24
+ }
25
+
26
  def build_signal(self) -> tuple[list[SignalSample], dict[str, Any]]:
27
+ return [], self._empty_debug()
28
 
29
  def count(self) -> tuple[Reps, dict[str, Any]]:
30
  reps = Reps(exercise=self.exercise, reps=[], partial_reps=[{"reason": "unknown_exercise"}])
31
+ return reps, self._empty_debug()
32
 
33
  def detect_variation(self, analysis: RepAnalysis) -> tuple[str, float, list[str]]:
34
  exercise_spec = get_exercise_spec(analysis.exercise)
tests/test_pipeline_contracts.py CHANGED
@@ -229,6 +229,20 @@ class PipelineContractTests(unittest.TestCase):
229
  report = result["final_report"]
230
  self.assertEqual(report["exercise"]["exercise"], exercise)
231
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
232
  def test_mock_mode_defaults_to_real_when_video_path_is_present(self) -> None:
233
  with patch.dict(os.environ, {}, clear=True):
234
  self.assertFalse(pipeline._env_mock_mode("sample.mp4"))
 
229
  report = result["final_report"]
230
  self.assertEqual(report["exercise"]["exercise"], exercise)
231
 
232
+ def test_pipeline_runs_with_manual_unknown_exercise(self) -> None:
233
+ result = pipeline.run_pipeline(
234
+ video_path=None,
235
+ profile_input={**PROFILE_INPUT, "intended_exercise": "unknown"},
236
+ mock=True,
237
+ )
238
+
239
+ self._assert_pipeline_artifacts(result)
240
+ report = result["final_report"]
241
+ self.assertEqual(report["exercise"]["exercise"], "unknown")
242
+ self.assertFalse(report["exercise"]["fallback_required"])
243
+ self.assertEqual(report["reps"]["reps"], [])
244
+ self.assertEqual(report["reps"]["partial_reps"], [{"reason": "unknown_exercise"}])
245
+
246
  def test_mock_mode_defaults_to_real_when_video_path_is_present(self) -> None:
247
  with patch.dict(os.environ, {}, clear=True):
248
  self.assertFalse(pipeline._env_mock_mode("sample.mp4"))
tests/test_rep_counter.py CHANGED
@@ -7,7 +7,7 @@ import unittest
7
 
8
  sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
9
 
10
- from pozify.contracts import PoseFrame, PoseSequence, UserProfile, VideoManifest
11
  from pozify.exercises import create_exercise_strategy
12
  from pozify.exercises.push_up import PushUpExercise
13
  from pozify.exercises.shoulder_press import ShoulderPressExercise
@@ -176,6 +176,8 @@ class RepCounterTests(unittest.TestCase):
176
  self.assertEqual(reps.reps, [])
177
  self.assertEqual(reps.partial_reps, [{"reason": "unknown_exercise"}])
178
  self.assertEqual(debug["selected_signal"], "none")
 
 
179
 
180
  def test_exercises_resolve_to_specific_strategies(self) -> None:
181
  sequence = _sequence_for_exercise("push_up", 1)
 
7
 
8
  sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
9
 
10
+ from pozify.contracts import PoseFrame, PoseSequence, UserProfile, VideoManifest, validate_contract
11
  from pozify.exercises import create_exercise_strategy
12
  from pozify.exercises.push_up import PushUpExercise
13
  from pozify.exercises.shoulder_press import ShoulderPressExercise
 
176
  self.assertEqual(reps.reps, [])
177
  self.assertEqual(reps.partial_reps, [{"reason": "unknown_exercise"}])
178
  self.assertEqual(debug["selected_signal"], "none")
179
+ validate_contract("reps.json", reps)
180
+ validate_contract("rep_debug.json", debug)
181
 
182
  def test_exercises_resolve_to_specific_strategies(self) -> None:
183
  sequence = _sequence_for_exercise("push_up", 1)
web/app.js CHANGED
@@ -17,6 +17,16 @@ function label(value) {
17
  return value.replaceAll("_", " ");
18
  }
19
 
 
 
 
 
 
 
 
 
 
 
20
  function Field({ labelText, children, full = false }) {
21
  return h(
22
  "label",
@@ -199,7 +209,7 @@ function App() {
199
 
200
  try {
201
  const response = await fetch("/api/analyze", { method: "POST", body: payload });
202
- const body = await response.json();
203
  if (!response.ok) throw new Error(body.detail || "Analysis failed.");
204
  setResult(body);
205
  setStatus("complete");
 
17
  return value.replaceAll("_", " ");
18
  }
19
 
20
+ async function readResponseBody(response) {
21
+ const text = await response.text();
22
+ if (!text) return {};
23
+
24
+ const contentType = response.headers.get("content-type") || "";
25
+ if (contentType.includes("application/json")) return JSON.parse(text);
26
+
27
+ return { detail: text };
28
+ }
29
+
30
  function Field({ labelText, children, full = false }) {
31
  return h(
32
  "label",
 
209
 
210
  try {
211
  const response = await fetch("/api/analyze", { method: "POST", body: payload });
212
+ const body = await readResponseBody(response);
213
  if (!response.ok) throw new Error(body.detail || "Analysis failed.");
214
  setResult(body);
215
  setStatus("complete");