kinemotion 0.15.3__tar.gz → 0.17.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of kinemotion might be problematic. Click here for more details.

Files changed (87) hide show
  1. kinemotion-0.17.0/.github/workflows/test.yml +81 -0
  2. {kinemotion-0.15.3 → kinemotion-0.17.0}/.gitignore +2 -0
  3. {kinemotion-0.15.3 → kinemotion-0.17.0}/CHANGELOG.md +11 -0
  4. {kinemotion-0.15.3 → kinemotion-0.17.0}/CLAUDE.md +49 -2
  5. {kinemotion-0.15.3 → kinemotion-0.17.0}/PKG-INFO +1 -1
  6. {kinemotion-0.15.3 → kinemotion-0.17.0}/pyproject.toml +30 -1
  7. kinemotion-0.17.0/sonar-project.properties +42 -0
  8. {kinemotion-0.15.3 → kinemotion-0.17.0}/uv.lock +69 -1
  9. {kinemotion-0.15.3 → kinemotion-0.17.0}/.dockerignore +0 -0
  10. {kinemotion-0.15.3 → kinemotion-0.17.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  11. {kinemotion-0.15.3 → kinemotion-0.17.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  12. {kinemotion-0.15.3 → kinemotion-0.17.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  13. {kinemotion-0.15.3 → kinemotion-0.17.0}/.github/pull_request_template.md +0 -0
  14. {kinemotion-0.15.3 → kinemotion-0.17.0}/.github/workflows/docs.yml +0 -0
  15. {kinemotion-0.15.3 → kinemotion-0.17.0}/.github/workflows/release.yml +0 -0
  16. {kinemotion-0.15.3 → kinemotion-0.17.0}/.pre-commit-config.yaml +0 -0
  17. {kinemotion-0.15.3 → kinemotion-0.17.0}/.readthedocs.yml +0 -0
  18. {kinemotion-0.15.3 → kinemotion-0.17.0}/.tool-versions +0 -0
  19. {kinemotion-0.15.3 → kinemotion-0.17.0}/CODE_OF_CONDUCT.md +0 -0
  20. {kinemotion-0.15.3 → kinemotion-0.17.0}/CONTRIBUTING.md +0 -0
  21. {kinemotion-0.15.3 → kinemotion-0.17.0}/Dockerfile +0 -0
  22. {kinemotion-0.15.3 → kinemotion-0.17.0}/GEMINI.md +0 -0
  23. {kinemotion-0.15.3 → kinemotion-0.17.0}/LICENSE +0 -0
  24. {kinemotion-0.15.3 → kinemotion-0.17.0}/README.md +0 -0
  25. {kinemotion-0.15.3 → kinemotion-0.17.0}/SECURITY.md +0 -0
  26. {kinemotion-0.15.3 → kinemotion-0.17.0}/docs/README.md +0 -0
  27. {kinemotion-0.15.3 → kinemotion-0.17.0}/docs/api/cmj.md +0 -0
  28. {kinemotion-0.15.3 → kinemotion-0.17.0}/docs/api/core.md +0 -0
  29. {kinemotion-0.15.3 → kinemotion-0.17.0}/docs/api/dropjump.md +0 -0
  30. {kinemotion-0.15.3 → kinemotion-0.17.0}/docs/api/overview.md +0 -0
  31. {kinemotion-0.15.3 → kinemotion-0.17.0}/docs/development/errors-findings.md +0 -0
  32. {kinemotion-0.15.3 → kinemotion-0.17.0}/docs/development/validation-plan.md +0 -0
  33. {kinemotion-0.15.3 → kinemotion-0.17.0}/docs/development/wallball-norep-detection.md +0 -0
  34. {kinemotion-0.15.3 → kinemotion-0.17.0}/docs/guides/bulk-processing.md +0 -0
  35. {kinemotion-0.15.3 → kinemotion-0.17.0}/docs/guides/camera-setup.md +0 -0
  36. {kinemotion-0.15.3 → kinemotion-0.17.0}/docs/guides/cmj-guide.md +0 -0
  37. {kinemotion-0.15.3 → kinemotion-0.17.0}/docs/index.md +0 -0
  38. {kinemotion-0.15.3 → kinemotion-0.17.0}/docs/reference/parameters.md +0 -0
  39. {kinemotion-0.15.3 → kinemotion-0.17.0}/docs/reference/pose-systems.md +0 -0
  40. {kinemotion-0.15.3 → kinemotion-0.17.0}/docs/research/sports-biomechanics-pose-estimation.md +0 -0
  41. {kinemotion-0.15.3 → kinemotion-0.17.0}/docs/technical/framerate.md +0 -0
  42. {kinemotion-0.15.3 → kinemotion-0.17.0}/docs/technical/imu-metadata.md +0 -0
  43. {kinemotion-0.15.3 → kinemotion-0.17.0}/docs/technical/real-time-analysis.md +0 -0
  44. {kinemotion-0.15.3 → kinemotion-0.17.0}/docs/technical/triple-extension.md +0 -0
  45. {kinemotion-0.15.3 → kinemotion-0.17.0}/docs/translations/es/camera-setup.md +0 -0
  46. {kinemotion-0.15.3 → kinemotion-0.17.0}/examples/bulk/README.md +0 -0
  47. {kinemotion-0.15.3 → kinemotion-0.17.0}/examples/bulk/bulk_processing.py +0 -0
  48. {kinemotion-0.15.3 → kinemotion-0.17.0}/examples/bulk/simple_example.py +0 -0
  49. {kinemotion-0.15.3 → kinemotion-0.17.0}/examples/programmatic_usage.py +0 -0
  50. {kinemotion-0.15.3 → kinemotion-0.17.0}/mkdocs.yml +0 -0
  51. {kinemotion-0.15.3 → kinemotion-0.17.0}/requirements-docs.txt +0 -0
  52. {kinemotion-0.15.3 → kinemotion-0.17.0}/samples/cmjs/README.md +0 -0
  53. {kinemotion-0.15.3 → kinemotion-0.17.0}/src/kinemotion/__init__.py +0 -0
  54. {kinemotion-0.15.3 → kinemotion-0.17.0}/src/kinemotion/api.py +0 -0
  55. {kinemotion-0.15.3 → kinemotion-0.17.0}/src/kinemotion/cli.py +0 -0
  56. {kinemotion-0.15.3 → kinemotion-0.17.0}/src/kinemotion/cmj/__init__.py +0 -0
  57. {kinemotion-0.15.3 → kinemotion-0.17.0}/src/kinemotion/cmj/analysis.py +0 -0
  58. {kinemotion-0.15.3 → kinemotion-0.17.0}/src/kinemotion/cmj/cli.py +0 -0
  59. {kinemotion-0.15.3 → kinemotion-0.17.0}/src/kinemotion/cmj/debug_overlay.py +0 -0
  60. {kinemotion-0.15.3 → kinemotion-0.17.0}/src/kinemotion/cmj/joint_angles.py +0 -0
  61. {kinemotion-0.15.3 → kinemotion-0.17.0}/src/kinemotion/cmj/kinematics.py +0 -0
  62. {kinemotion-0.15.3 → kinemotion-0.17.0}/src/kinemotion/core/__init__.py +0 -0
  63. {kinemotion-0.15.3 → kinemotion-0.17.0}/src/kinemotion/core/auto_tuning.py +0 -0
  64. {kinemotion-0.15.3 → kinemotion-0.17.0}/src/kinemotion/core/cli_utils.py +0 -0
  65. {kinemotion-0.15.3 → kinemotion-0.17.0}/src/kinemotion/core/debug_overlay_utils.py +0 -0
  66. {kinemotion-0.15.3 → kinemotion-0.17.0}/src/kinemotion/core/filtering.py +0 -0
  67. {kinemotion-0.15.3 → kinemotion-0.17.0}/src/kinemotion/core/pose.py +0 -0
  68. {kinemotion-0.15.3 → kinemotion-0.17.0}/src/kinemotion/core/smoothing.py +0 -0
  69. {kinemotion-0.15.3 → kinemotion-0.17.0}/src/kinemotion/core/video_io.py +0 -0
  70. {kinemotion-0.15.3 → kinemotion-0.17.0}/src/kinemotion/dropjump/__init__.py +0 -0
  71. {kinemotion-0.15.3 → kinemotion-0.17.0}/src/kinemotion/dropjump/analysis.py +0 -0
  72. {kinemotion-0.15.3 → kinemotion-0.17.0}/src/kinemotion/dropjump/cli.py +0 -0
  73. {kinemotion-0.15.3 → kinemotion-0.17.0}/src/kinemotion/dropjump/debug_overlay.py +0 -0
  74. {kinemotion-0.15.3 → kinemotion-0.17.0}/src/kinemotion/dropjump/kinematics.py +0 -0
  75. {kinemotion-0.15.3 → kinemotion-0.17.0}/src/kinemotion/py.typed +0 -0
  76. {kinemotion-0.15.3 → kinemotion-0.17.0}/tests/__init__.py +0 -0
  77. {kinemotion-0.15.3 → kinemotion-0.17.0}/tests/test_adaptive_threshold.py +0 -0
  78. {kinemotion-0.15.3 → kinemotion-0.17.0}/tests/test_api.py +0 -0
  79. {kinemotion-0.15.3 → kinemotion-0.17.0}/tests/test_aspect_ratio.py +0 -0
  80. {kinemotion-0.15.3 → kinemotion-0.17.0}/tests/test_cli_imports.py +0 -0
  81. {kinemotion-0.15.3 → kinemotion-0.17.0}/tests/test_cmj_analysis.py +0 -0
  82. {kinemotion-0.15.3 → kinemotion-0.17.0}/tests/test_cmj_kinematics.py +0 -0
  83. {kinemotion-0.15.3 → kinemotion-0.17.0}/tests/test_com_estimation.py +0 -0
  84. {kinemotion-0.15.3 → kinemotion-0.17.0}/tests/test_contact_detection.py +0 -0
  85. {kinemotion-0.15.3 → kinemotion-0.17.0}/tests/test_filtering.py +0 -0
  86. {kinemotion-0.15.3 → kinemotion-0.17.0}/tests/test_kinematics.py +0 -0
  87. {kinemotion-0.15.3 → kinemotion-0.17.0}/tests/test_polyorder.py +0 -0
@@ -0,0 +1,81 @@
1
+ name: Test & Quality
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ pull_request:
8
+ branches:
9
+ - main
10
+
11
+ jobs:
12
+ test:
13
+ name: Test (Python ${{ matrix.python-version }})
14
+ runs-on: ubuntu-latest
15
+ strategy:
16
+ matrix:
17
+ python-version: ["3.10", "3.11", "3.12"]
18
+
19
+ steps:
20
+ - name: Checkout repository
21
+ uses: actions/checkout@v4
22
+ with:
23
+ fetch-depth: 0 # Shallow clones should be disabled for better SonarQube analysis
24
+
25
+ - name: Set up Python ${{ matrix.python-version }}
26
+ uses: actions/setup-python@v5
27
+ with:
28
+ python-version: ${{ matrix.python-version }}
29
+
30
+ - name: Set up uv
31
+ uses: astral-sh/setup-uv@v5
32
+ with:
33
+ version: "0.8.17"
34
+
35
+ - name: Install dependencies
36
+ run: uv sync
37
+
38
+ - name: Run linting
39
+ run: |
40
+ uv run ruff check --output-format=github
41
+ continue-on-error: true # Don't fail the build on linting errors
42
+
43
+ - name: Run type checking
44
+ run: |
45
+ uv run pyright
46
+ continue-on-error: true # Don't fail the build on type errors
47
+
48
+ - name: Run tests with coverage
49
+ run: |
50
+ uv run pytest --cov --cov-report=xml --cov-report=term
51
+
52
+ - name: Upload coverage artifact
53
+ if: matrix.python-version == '3.12'
54
+ uses: actions/upload-artifact@v4
55
+ with:
56
+ name: coverage-report
57
+ path: coverage.xml
58
+ retention-days: 5
59
+
60
+ sonarqube:
61
+ name: SonarQube Scan
62
+ needs: test
63
+ runs-on: ubuntu-latest
64
+ if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
65
+
66
+ steps:
67
+ - name: Checkout repository
68
+ uses: actions/checkout@v4
69
+ with:
70
+ fetch-depth: 0 # Disable shallow clone for better analysis
71
+
72
+ - name: Download coverage artifact
73
+ uses: actions/download-artifact@v4
74
+ with:
75
+ name: coverage-report
76
+
77
+ - name: SonarQube Scan
78
+ uses: SonarSource/sonarcloud-github-action@master
79
+ env:
80
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
81
+ SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
@@ -39,7 +39,9 @@ env/
39
39
  # Testing
40
40
  .pytest_cache/
41
41
  .coverage
42
+ .coverage.*
42
43
  htmlcov/
44
+ coverage.xml
43
45
 
44
46
  # Output files
45
47
  *.mov
@@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  <!-- version list -->
9
9
 
10
+ ## v0.17.0 (2025-11-10)
11
+
12
+ ### Features
13
+
14
+ - **ci**: Add SonarQube Cloud integration for coverage reporting
15
+ ([`cdc710f`](https://github.com/feniix/kinemotion/commit/cdc710f7a4c215e570eaa2b58a13f994ea7bae7c))
16
+
17
+
18
+ ## v0.16.0 (2025-11-10)
19
+
20
+
10
21
  ## v0.15.3 (2025-11-10)
11
22
 
12
23
  ### Bug Fixes
@@ -19,10 +19,39 @@ uv run kinemotion cmj-analyze video.mp4
19
19
 
20
20
  **Development:**
21
21
  ```bash
22
- uv run pytest # Run all 75 tests
22
+ uv run pytest # Run all 75 tests with coverage (50.75%)
23
+ uv run pytest --cov-report=html # Generate HTML coverage report
23
24
  uv run ruff check --fix && uv run pyright # Lint + type check
24
25
  ```
25
26
 
27
+ **Coverage Reports:**
28
+ - Terminal: Automatic with `uv run pytest`
29
+ - HTML: `htmlcov/index.html` (open in browser)
30
+ - XML: `coverage.xml` (for CI integration)
31
+
32
+ **SonarQube Cloud Integration:**
33
+
34
+ The project integrates with SonarQube Cloud for continuous code quality and coverage tracking.
35
+
36
+ Setup (one-time):
37
+ 1. Visit [SonarCloud](https://sonarcloud.io/) and sign in with GitHub
38
+ 2. Import the `feniix/kinemotion` repository
39
+ 3. Generate a token: My Account > Security > Generate Tokens
40
+ 4. Add token to GitHub: Repository > Settings > Secrets and variables > Actions
41
+ - Name: `SONAR_TOKEN`
42
+ - Value: Your generated token
43
+
44
+ Configuration files:
45
+ - `sonar-project.properties` - SonarQube project configuration
46
+ - `.github/workflows/test.yml` - CI workflow with SonarQube scan
47
+
48
+ The workflow automatically:
49
+ - Runs tests with coverage on every PR and push to main
50
+ - Uploads coverage.xml to SonarQube Cloud
51
+ - Runs quality gate checks
52
+
53
+ View results: https://sonarcloud.io/project/overview?id=feniix_kinemotion
54
+
26
55
  ## Architecture
27
56
 
28
57
  ### Module Structure
@@ -139,7 +168,7 @@ OpenCV vs NumPy ordering:
139
168
  ```bash
140
169
  uv run ruff check --fix # Auto-fix linting
141
170
  uv run pyright # Type check (strict)
142
- uv run pytest # All 70 tests
171
+ uv run pytest # All 75 tests with coverage
143
172
  ```
144
173
 
145
174
  ### Standards
@@ -148,6 +177,24 @@ uv run pytest # All 70 tests
148
177
  - Ruff (100 char lines)
149
178
  - Conventional Commits (see below)
150
179
  - **Code duplication target: < 3%**
180
+ - **Test coverage: ≥ 50% (current: 50.75% with branch coverage)**
181
+
182
+ ### Coverage Analysis
183
+
184
+ Current coverage: **50.75%** (2184 statements, 752 branches)
185
+
186
+ **Well-tested modules (>80%):**
187
+ - Core filtering: 87.80%
188
+ - Core pose tracking: 88.46%
189
+ - Drop jump kinematics: 83.94%
190
+ - CMJ kinematics: 94.67%
191
+
192
+ **Needs improvement (<30%):**
193
+ - CLI modules: 22-23% (expected - minimal integration tests)
194
+ - Debug overlays: 10-36% (visualization code)
195
+ - Joint angles: 6.20% (feature module)
196
+
197
+ View detailed report: `uv run pytest --cov-report=html && open htmlcov/index.html`
151
198
 
152
199
  ### Avoiding Code Duplication
153
200
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: kinemotion
3
- Version: 0.15.3
3
+ Version: 0.17.0
4
4
  Summary: Video-based kinematic analysis for athletic performance
5
5
  Project-URL: Homepage, https://github.com/feniix/kinemotion
6
6
  Project-URL: Repository, https://github.com/feniix/kinemotion
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "kinemotion"
3
- version = "0.15.3"
3
+ version = "0.17.0"
4
4
  description = "Video-based kinematic analysis for athletic performance"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10,<3.13"
@@ -50,6 +50,7 @@ packages = ["src/kinemotion"]
50
50
  [dependency-groups]
51
51
  dev = [
52
52
  "pytest>=7.4.3",
53
+ "pytest-cov>=5.0.0",
53
54
  "black>=23.12.0",
54
55
  "ruff>=0.1.8",
55
56
  "pyright>=1.1.380",
@@ -61,6 +62,34 @@ dev = [
61
62
  ]
62
63
 
63
64
 
65
+ [tool.pytest.ini_options]
66
+ testpaths = ["tests"]
67
+ addopts = [
68
+ "--cov=src/kinemotion",
69
+ "--cov-report=term-missing",
70
+ "--cov-report=html",
71
+ "--cov-report=xml",
72
+ "--cov-branch",
73
+ ]
74
+
75
+ [tool.coverage.run]
76
+ source = ["src/kinemotion"]
77
+ branch = true
78
+ parallel = true
79
+
80
+ [tool.coverage.report]
81
+ exclude_lines = [
82
+ "pragma: no cover",
83
+ "def __repr__",
84
+ "raise AssertionError",
85
+ "raise NotImplementedError",
86
+ "if __name__ == .__main__.:",
87
+ "if TYPE_CHECKING:",
88
+ ]
89
+ precision = 2
90
+ show_missing = true
91
+ fail_under = 50.0 # Current baseline: 50.75% - prevent regression
92
+
64
93
  [tool.ruff]
65
94
  line-length = 100
66
95
  target-version = "py310"
@@ -0,0 +1,42 @@
1
+ # Project identification
2
+ sonar.projectKey=feniix_kinemotion
3
+ sonar.projectName=Kinemotion
4
+ sonar.projectVersion=0.16.0
5
+ sonar.organization=feniix
6
+
7
+ # Project metadata
8
+ sonar.projectDescription=Video-based kinematic analysis for athletic performance using MediaPipe pose tracking
9
+ sonar.links.homepage=https://github.com/feniix/kinemotion
10
+ sonar.links.scm=https://github.com/feniix/kinemotion
11
+ sonar.links.issue=https://github.com/feniix/kinemotion/issues
12
+ sonar.links.ci=https://github.com/feniix/kinemotion/actions
13
+
14
+ # Source code
15
+ sonar.sources=src/kinemotion
16
+ sonar.tests=tests
17
+
18
+ # Python version
19
+ sonar.python.version=3.10, 3.11, 3.12
20
+
21
+ # Encoding
22
+ sonar.sourceEncoding=UTF-8
23
+
24
+ # Coverage report path (generated by pytest-cov)
25
+ sonar.python.coverage.reportPaths=coverage.xml
26
+
27
+ # Exclusions
28
+ sonar.exclusions=\
29
+ **/__pycache__/**,\
30
+ **/*.pyc,\
31
+ **/venv/**,\
32
+ **/.venv/**,\
33
+ **/htmlcov/**,\
34
+ **/dist/**,\
35
+ **/build/**,\
36
+ **/*.egg-info/**
37
+
38
+ # Test exclusions (don't analyze test files as production code)
39
+ sonar.test.inclusions=tests/**/*.py
40
+
41
+ # Coverage exclusions (already covered by sonar.tests)
42
+ sonar.coverage.exclusions=tests/**/*.py
@@ -346,6 +346,58 @@ wheels = [
346
346
  { url = "https://files.pythonhosted.org/packages/0c/58/bd257695f39d05594ca4ad60df5bcb7e32247f9951fd09a9b8edb82d1daa/contourpy-1.3.3-pp311-pypy311_pp73-win_amd64.whl", hash = "sha256:3d1a3799d62d45c18bafd41c5fa05120b96a28079f2393af559b843d1a966a77", size = 225315, upload-time = "2025-07-26T12:02:58.801Z" },
347
347
  ]
348
348
 
349
+ [[package]]
350
+ name = "coverage"
351
+ version = "7.11.3"
352
+ source = { registry = "https://pypi.org/simple" }
353
+ sdist = { url = "https://files.pythonhosted.org/packages/d2/59/9698d57a3b11704c7b89b21d69e9d23ecf80d538cabb536c8b63f4a12322/coverage-7.11.3.tar.gz", hash = "sha256:0f59387f5e6edbbffec2281affb71cdc85e0776c1745150a3ab9b6c1d016106b", size = 815210, upload-time = "2025-11-10T00:13:17.18Z" }
354
+ wheels = [
355
+ { url = "https://files.pythonhosted.org/packages/fd/68/b53157115ef76d50d1d916d6240e5cd5b3c14dba8ba1b984632b8221fc2e/coverage-7.11.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0c986537abca9b064510f3fd104ba33e98d3036608c7f2f5537f869bc10e1ee5", size = 216377, upload-time = "2025-11-10T00:10:27.317Z" },
356
+ { url = "https://files.pythonhosted.org/packages/14/c1/d2f9d8e37123fe6e7ab8afcaab8195f13bc84a8b2f449a533fd4812ac724/coverage-7.11.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:28c5251b3ab1d23e66f1130ca0c419747edfbcb4690de19467cd616861507af7", size = 216892, upload-time = "2025-11-10T00:10:30.624Z" },
357
+ { url = "https://files.pythonhosted.org/packages/83/73/18f05d8010149b650ed97ee5c9f7e4ae68c05c7d913391523281e41c2495/coverage-7.11.3-cp310-cp310-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:4f2bb4ee8dd40f9b2a80bb4adb2aecece9480ba1fa60d9382e8c8e0bd558e2eb", size = 243650, upload-time = "2025-11-10T00:10:32.392Z" },
358
+ { url = "https://files.pythonhosted.org/packages/63/3c/c0cbb296c0ecc6dcbd70f4b473fcd7fe4517bbef8b09f4326d78f38adb87/coverage-7.11.3-cp310-cp310-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:e5f4bfac975a2138215a38bda599ef00162e4143541cf7dd186da10a7f8e69f1", size = 245478, upload-time = "2025-11-10T00:10:34.157Z" },
359
+ { url = "https://files.pythonhosted.org/packages/b9/9a/dad288cf9faa142a14e75e39dc646d968b93d74e15c83e9b13fd628f2cb3/coverage-7.11.3-cp310-cp310-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:8f4cbfff5cf01fa07464439a8510affc9df281535f41a1f5312fbd2b59b4ab5c", size = 247337, upload-time = "2025-11-10T00:10:35.655Z" },
360
+ { url = "https://files.pythonhosted.org/packages/e3/ba/f6148ebf5547b3502013175e41bf3107a4e34b7dd19f9793a6ce0e1cd61f/coverage-7.11.3-cp310-cp310-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:31663572f20bf3406d7ac00d6981c7bbbcec302539d26b5ac596ca499664de31", size = 244328, upload-time = "2025-11-10T00:10:37.459Z" },
361
+ { url = "https://files.pythonhosted.org/packages/e6/4d/b93784d0b593c5df89a0d48cbbd2d0963e0ca089eaf877405849792e46d3/coverage-7.11.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:9799bd6a910961cb666196b8583ed0ee125fa225c6fdee2cbf00232b861f29d2", size = 245381, upload-time = "2025-11-10T00:10:39.229Z" },
362
+ { url = "https://files.pythonhosted.org/packages/3a/8d/6735bfd4f0f736d457642ee056a570d704c9d57fdcd5c91ea5d6b15c944e/coverage-7.11.3-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:097acc18bedf2c6e3144eaf09b5f6034926c3c9bb9e10574ffd0942717232507", size = 243390, upload-time = "2025-11-10T00:10:40.984Z" },
363
+ { url = "https://files.pythonhosted.org/packages/db/3d/7ba68ed52d1873d450aefd8d2f5a353e67b421915cb6c174e4222c7b918c/coverage-7.11.3-cp310-cp310-musllinux_1_2_riscv64.whl", hash = "sha256:6f033dec603eea88204589175782290a038b436105a8f3637a81c4359df27832", size = 243654, upload-time = "2025-11-10T00:10:42.496Z" },
364
+ { url = "https://files.pythonhosted.org/packages/14/26/be2720c4c7bf73c6591ae4ab503a7b5a31c7a60ced6dba855cfcb4a5af7e/coverage-7.11.3-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:dd9ca2d44ed8018c90efb72f237a2a140325a4c3339971364d758e78b175f58e", size = 244272, upload-time = "2025-11-10T00:10:44.39Z" },
365
+ { url = "https://files.pythonhosted.org/packages/90/20/086f5697780df146dbc0df4ae9b6db2b23ddf5aa550f977b2825137728e9/coverage-7.11.3-cp310-cp310-win32.whl", hash = "sha256:900580bc99c145e2561ea91a2d207e639171870d8a18756eb57db944a017d4bb", size = 218969, upload-time = "2025-11-10T00:10:45.863Z" },
366
+ { url = "https://files.pythonhosted.org/packages/98/5c/cc6faba945ede5088156da7770e30d06c38b8591785ac99bcfb2074f9ef6/coverage-7.11.3-cp310-cp310-win_amd64.whl", hash = "sha256:c8be5bfcdc7832011b2652db29ed7672ce9d353dd19bce5272ca33dbcf60aaa8", size = 219903, upload-time = "2025-11-10T00:10:47.676Z" },
367
+ { url = "https://files.pythonhosted.org/packages/92/92/43a961c0f57b666d01c92bcd960c7f93677de5e4ee7ca722564ad6dee0fa/coverage-7.11.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:200bb89fd2a8a07780eafcdff6463104dec459f3c838d980455cfa84f5e5e6e1", size = 216504, upload-time = "2025-11-10T00:10:49.524Z" },
368
+ { url = "https://files.pythonhosted.org/packages/5d/5c/dbfc73329726aef26dbf7fefef81b8a2afd1789343a579ea6d99bf15d26e/coverage-7.11.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8d264402fc179776d43e557e1ca4a7d953020d3ee95f7ec19cc2c9d769277f06", size = 217006, upload-time = "2025-11-10T00:10:51.32Z" },
369
+ { url = "https://files.pythonhosted.org/packages/a5/e0/878c84fb6661964bc435beb1e28c050650aa30e4c1cdc12341e298700bda/coverage-7.11.3-cp311-cp311-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:385977d94fc155f8731c895accdfcc3dd0d9dd9ef90d102969df95d3c637ab80", size = 247415, upload-time = "2025-11-10T00:10:52.805Z" },
370
+ { url = "https://files.pythonhosted.org/packages/56/9e/0677e78b1e6a13527f39c4b39c767b351e256b333050539861c63f98bd61/coverage-7.11.3-cp311-cp311-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:0542ddf6107adbd2592f29da9f59f5d9cff7947b5bb4f734805085c327dcffaa", size = 249332, upload-time = "2025-11-10T00:10:54.35Z" },
371
+ { url = "https://files.pythonhosted.org/packages/54/90/25fc343e4ce35514262451456de0953bcae5b37dda248aed50ee51234cee/coverage-7.11.3-cp311-cp311-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d60bf4d7f886989ddf80e121a7f4d140d9eac91f1d2385ce8eb6bda93d563297", size = 251443, upload-time = "2025-11-10T00:10:55.832Z" },
372
+ { url = "https://files.pythonhosted.org/packages/13/56/bc02bbc890fd8b155a64285c93e2ab38647486701ac9c980d457cdae857a/coverage-7.11.3-cp311-cp311-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:c0a3b6e32457535df0d41d2d895da46434706dd85dbaf53fbc0d3bd7d914b362", size = 247554, upload-time = "2025-11-10T00:10:57.829Z" },
373
+ { url = "https://files.pythonhosted.org/packages/0f/ab/0318888d091d799a82d788c1e8d8bd280f1d5c41662bbb6e11187efe33e8/coverage-7.11.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:876a3ee7fd2613eb79602e4cdb39deb6b28c186e76124c3f29e580099ec21a87", size = 249139, upload-time = "2025-11-10T00:10:59.465Z" },
374
+ { url = "https://files.pythonhosted.org/packages/79/d8/3ee50929c4cd36fcfcc0f45d753337001001116c8a5b8dd18d27ea645737/coverage-7.11.3-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a730cd0824e8083989f304e97b3f884189efb48e2151e07f57e9e138ab104200", size = 247209, upload-time = "2025-11-10T00:11:01.432Z" },
375
+ { url = "https://files.pythonhosted.org/packages/94/7c/3cf06e327401c293e60c962b4b8a2ceb7167c1a428a02be3adbd1d7c7e4c/coverage-7.11.3-cp311-cp311-musllinux_1_2_riscv64.whl", hash = "sha256:b5cd111d3ab7390be0c07ad839235d5ad54d2ca497b5f5db86896098a77180a4", size = 246936, upload-time = "2025-11-10T00:11:02.964Z" },
376
+ { url = "https://files.pythonhosted.org/packages/99/0b/ffc03dc8f4083817900fd367110015ef4dd227b37284104a5eb5edc9c106/coverage-7.11.3-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:074e6a5cd38e06671580b4d872c1a67955d4e69639e4b04e87fc03b494c1f060", size = 247835, upload-time = "2025-11-10T00:11:04.405Z" },
377
+ { url = "https://files.pythonhosted.org/packages/17/4d/dbe54609ee066553d0bcdcdf108b177c78dab836292bee43f96d6a5674d1/coverage-7.11.3-cp311-cp311-win32.whl", hash = "sha256:86d27d2dd7c7c5a44710565933c7dc9cd70e65ef97142e260d16d555667deef7", size = 218994, upload-time = "2025-11-10T00:11:05.966Z" },
378
+ { url = "https://files.pythonhosted.org/packages/94/11/8e7155df53f99553ad8114054806c01a2c0b08f303ea7e38b9831652d83d/coverage-7.11.3-cp311-cp311-win_amd64.whl", hash = "sha256:ca90ef33a152205fb6f2f0c1f3e55c50df4ef049bb0940ebba666edd4cdebc55", size = 219926, upload-time = "2025-11-10T00:11:07.936Z" },
379
+ { url = "https://files.pythonhosted.org/packages/1f/93/bea91b6a9e35d89c89a1cd5824bc72e45151a9c2a9ca0b50d9e9a85e3ae3/coverage-7.11.3-cp311-cp311-win_arm64.whl", hash = "sha256:56f909a40d68947ef726ce6a34eb38f0ed241ffbe55c5007c64e616663bcbafc", size = 218599, upload-time = "2025-11-10T00:11:09.578Z" },
380
+ { url = "https://files.pythonhosted.org/packages/c2/39/af056ec7a27c487e25c7f6b6e51d2ee9821dba1863173ddf4dc2eebef4f7/coverage-7.11.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5b771b59ac0dfb7f139f70c85b42717ef400a6790abb6475ebac1ecee8de782f", size = 216676, upload-time = "2025-11-10T00:11:11.566Z" },
381
+ { url = "https://files.pythonhosted.org/packages/3c/f8/21126d34b174d037b5d01bea39077725cbb9a0da94a95c5f96929c695433/coverage-7.11.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:603c4414125fc9ae9000f17912dcfd3d3eb677d4e360b85206539240c96ea76e", size = 217034, upload-time = "2025-11-10T00:11:13.12Z" },
382
+ { url = "https://files.pythonhosted.org/packages/d5/3f/0fd35f35658cdd11f7686303214bd5908225838f374db47f9e457c8d6df8/coverage-7.11.3-cp312-cp312-manylinux1_i686.manylinux_2_28_i686.manylinux_2_5_i686.whl", hash = "sha256:77ffb3b7704eb7b9b3298a01fe4509cef70117a52d50bcba29cffc5f53dd326a", size = 248531, upload-time = "2025-11-10T00:11:15.023Z" },
383
+ { url = "https://files.pythonhosted.org/packages/8f/59/0bfc5900fc15ce4fd186e092451de776bef244565c840c9c026fd50857e1/coverage-7.11.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl", hash = "sha256:4d4ca49f5ba432b0755ebb0fc3a56be944a19a16bb33802264bbc7311622c0d1", size = 251290, upload-time = "2025-11-10T00:11:16.628Z" },
384
+ { url = "https://files.pythonhosted.org/packages/71/88/d5c184001fa2ac82edf1b8f2cd91894d2230d7c309e937c54c796176e35b/coverage-7.11.3-cp312-cp312-manylinux2014_aarch64.manylinux_2_17_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:05fd3fb6edff0c98874d752013588836f458261e5eba587afe4c547bba544afd", size = 252375, upload-time = "2025-11-10T00:11:18.249Z" },
385
+ { url = "https://files.pythonhosted.org/packages/5c/29/f60af9f823bf62c7a00ce1ac88441b9a9a467e499493e5cc65028c8b8dd2/coverage-7.11.3-cp312-cp312-manylinux_2_31_riscv64.manylinux_2_39_riscv64.whl", hash = "sha256:0e920567f8c3a3ce68ae5a42cf7c2dc4bb6cc389f18bff2235dd8c03fa405de5", size = 248946, upload-time = "2025-11-10T00:11:20.202Z" },
386
+ { url = "https://files.pythonhosted.org/packages/67/16/4662790f3b1e03fce5280cad93fd18711c35980beb3c6f28dca41b5230c6/coverage-7.11.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:4bec8c7160688bd5a34e65c82984b25409563134d63285d8943d0599efbc448e", size = 250310, upload-time = "2025-11-10T00:11:21.689Z" },
387
+ { url = "https://files.pythonhosted.org/packages/8f/75/dd6c2e28308a83e5fc1ee602f8204bd3aa5af685c104cb54499230cf56db/coverage-7.11.3-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:adb9b7b42c802bd8cb3927de8c1c26368ce50c8fdaa83a9d8551384d77537044", size = 248461, upload-time = "2025-11-10T00:11:23.384Z" },
388
+ { url = "https://files.pythonhosted.org/packages/16/fe/b71af12be9f59dc9eb060688fa19a95bf3223f56c5af1e9861dfa2275d2c/coverage-7.11.3-cp312-cp312-musllinux_1_2_riscv64.whl", hash = "sha256:c8f563b245b4ddb591e99f28e3cd140b85f114b38b7f95b2e42542f0603eb7d7", size = 248039, upload-time = "2025-11-10T00:11:25.07Z" },
389
+ { url = "https://files.pythonhosted.org/packages/11/b8/023b2003a2cd96bdf607afe03d9b96c763cab6d76e024abe4473707c4eb8/coverage-7.11.3-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e2a96fdc7643c9517a317553aca13b5cae9bad9a5f32f4654ce247ae4d321405", size = 249903, upload-time = "2025-11-10T00:11:26.992Z" },
390
+ { url = "https://files.pythonhosted.org/packages/d6/ee/5f1076311aa67b1fa4687a724cc044346380e90ce7d94fec09fd384aa5fd/coverage-7.11.3-cp312-cp312-win32.whl", hash = "sha256:e8feeb5e8705835f0622af0fe7ff8d5cb388948454647086494d6c41ec142c2e", size = 219201, upload-time = "2025-11-10T00:11:28.619Z" },
391
+ { url = "https://files.pythonhosted.org/packages/4f/24/d21688f48fe9fcc778956680fd5aaf69f4e23b245b7c7a4755cbd421d25b/coverage-7.11.3-cp312-cp312-win_amd64.whl", hash = "sha256:abb903ffe46bd319d99979cdba350ae7016759bb69f47882242f7b93f3356055", size = 220012, upload-time = "2025-11-10T00:11:30.234Z" },
392
+ { url = "https://files.pythonhosted.org/packages/4f/9e/d5eb508065f291456378aa9b16698b8417d87cb084c2b597f3beb00a8084/coverage-7.11.3-cp312-cp312-win_arm64.whl", hash = "sha256:1451464fd855d9bd000c19b71bb7dafea9ab815741fb0bd9e813d9b671462d6f", size = 218652, upload-time = "2025-11-10T00:11:32.165Z" },
393
+ { url = "https://files.pythonhosted.org/packages/19/8f/92bdd27b067204b99f396a1414d6342122f3e2663459baf787108a6b8b84/coverage-7.11.3-py3-none-any.whl", hash = "sha256:351511ae28e2509c8d8cae5311577ea7dd511ab8e746ffc8814a0896c3d33fbe", size = 208478, upload-time = "2025-11-10T00:13:14.908Z" },
394
+ ]
395
+
396
+ [package.optional-dependencies]
397
+ toml = [
398
+ { name = "tomli", marker = "python_full_version <= '3.11'" },
399
+ ]
400
+
349
401
  [[package]]
350
402
  name = "cycler"
351
403
  version = "0.12.1"
@@ -648,7 +700,7 @@ wheels = [
648
700
 
649
701
  [[package]]
650
702
  name = "kinemotion"
651
- version = "0.15.3"
703
+ version = "0.17.0"
652
704
  source = { editable = "." }
653
705
  dependencies = [
654
706
  { name = "click" },
@@ -668,6 +720,7 @@ dev = [
668
720
  { name = "pre-commit" },
669
721
  { name = "pyright" },
670
722
  { name = "pytest" },
723
+ { name = "pytest-cov" },
671
724
  { name = "python-semantic-release" },
672
725
  { name = "ruff" },
673
726
  ]
@@ -690,6 +743,7 @@ dev = [
690
743
  { name = "pre-commit", specifier = ">=3.6.0" },
691
744
  { name = "pyright", specifier = ">=1.1.380" },
692
745
  { name = "pytest", specifier = ">=7.4.3" },
746
+ { name = "pytest-cov", specifier = ">=5.0.0" },
693
747
  { name = "python-semantic-release", specifier = ">=9.8.2" },
694
748
  { name = "ruff", specifier = ">=0.1.8" },
695
749
  ]
@@ -1434,6 +1488,20 @@ wheels = [
1434
1488
  { url = "https://files.pythonhosted.org/packages/a8/a4/20da314d277121d6534b3a980b29035dcd51e6744bd79075a6ce8fa4eb8d/pytest-8.4.2-py3-none-any.whl", hash = "sha256:872f880de3fc3a5bdc88a11b39c9710c3497a547cfa9320bc3c5e62fbf272e79", size = 365750, upload-time = "2025-09-04T14:34:20.226Z" },
1435
1489
  ]
1436
1490
 
1491
+ [[package]]
1492
+ name = "pytest-cov"
1493
+ version = "7.0.0"
1494
+ source = { registry = "https://pypi.org/simple" }
1495
+ dependencies = [
1496
+ { name = "coverage", extra = ["toml"] },
1497
+ { name = "pluggy" },
1498
+ { name = "pytest" },
1499
+ ]
1500
+ sdist = { url = "https://files.pythonhosted.org/packages/5e/f7/c933acc76f5208b3b00089573cf6a2bc26dc80a8aece8f52bb7d6b1855ca/pytest_cov-7.0.0.tar.gz", hash = "sha256:33c97eda2e049a0c5298e91f519302a1334c26ac65c1a483d6206fd458361af1", size = 54328, upload-time = "2025-09-09T10:57:02.113Z" }
1501
+ wheels = [
1502
+ { url = "https://files.pythonhosted.org/packages/ee/49/1377b49de7d0c1ce41292161ea0f721913fa8722c19fb9c1e3aa0367eecb/pytest_cov-7.0.0-py3-none-any.whl", hash = "sha256:3b8e9558b16cc1479da72058bdecf8073661c7f57f7d3c5f22a1c23507f2d861", size = 22424, upload-time = "2025-09-09T10:57:00.695Z" },
1503
+ ]
1504
+
1437
1505
  [[package]]
1438
1506
  name = "python-dateutil"
1439
1507
  version = "2.9.0.post0"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes