Skip to content

スコア算出APIです。#6

Merged
davy-yusuke merged 7 commits intomainfrom
score
Dec 12, 2025
Merged

スコア算出APIです。#6
davy-yusuke merged 7 commits intomainfrom
score

Conversation

@davy-yusuke
Copy link
Copy Markdown
Contributor

@davy-yusuke davy-yusuke commented Dec 12, 2025

スコアアルゴリズムのAPIです。./score/inputフォルダに動画データとcurlを置きました。

Summary by CodeRabbit

  • New Features

    • Added /api/score API endpoint for video analysis with activity detection (running, walking, squat, lunge, pushup, plank, jumping jack, situp, burpee, standing).
    • Video scoring with configurable parameters (mode, resolution, detection confidence).
    • Returns activity scores with confidence metrics and temporal statistics.
  • Documentation

    • Added project README with algorithm overview.
  • Chores

    • Added Docker containerization for deployment.
    • Added project configuration and build scripts.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Dec 12, 2025

Walkthrough

This PR introduces a new score module—a FastAPI-based video scoring service that analyzes video frames using MediaPipe pose estimation to detect activities (running, walking, squats, etc.), extract pose-derived features, compute temporal statistics, and provide structured scoring summaries. Includes containerization, project configuration, and geometric utilities.

Changes

Cohort / File(s) Summary
Repository Configuration
score/.gitignore, score/.python-version, score/pyproject.toml
Added Python-specific .gitignore with patterns for virtual environments, caches, and IDE metadata; specified Python 3.12 version; configured project metadata, dependencies (FastAPI, MediaPipe, OpenCV, NumPy, Uvicorn, etc.), and package details.
Containerization
score/Dockerfile, score/build.sh, score/run.sh
Created Dockerfile using python:3.12-slim with system dependencies (build-essential, cmake, ffmpeg, libsm6, libxext6, curl), virtual environment setup, and uvicorn entrypoint on port 8000; added scripts to build Docker image and run container with port mapping.
Documentation
score/README.md
Added brief documentation describing three entry point functions: dist (Euclidean distance), angle_at (cosine law angle calculation), and torso_angle_deg (atan2 compass angle).
Core Application Logic
score/app.py
Introduced FastAPI video scoring API with TemporalStats and ScoreSummary data models, analyze_video() function for frame-by-frame pose estimation and activity scoring, and POST /api/score endpoint accepting video file uploads with configurable parameters (mode, max_width, model_complexity, confidence thresholds).
Feature Extraction & Activity Detection
score/features.py
Implemented frame_features_from_xyvis() to compute pose-derived features (angles, distances, normalizations) from keypoints, and Estimator class for streaming activity classification via temporal statistics (medians, standard deviations, periodicity, ankle speeds, motion energy) with mode-based inference and temporal stability logic.
Geometric Utilities
score/score/utils.py
Added utility functions: dist() for Euclidean distance, angle_at() for 3-point angle computation with degenerate handling, torso_angle_deg() for torso orientation, and landmark() for normalizing/scaling landmark coordinates to image dimensions.
API Testing & Examples
score/input/curl.sh
Added example curl command for testing POST /api/score endpoint with sample video upload and parameters (mode, max_width).

Sequence Diagram

sequenceDiagram
    participant Client as HTTP Client
    participant API as FastAPI App
    participant CV as OpenCV
    participant MP as MediaPipe Pose
    participant Est as Estimator
    participant Util as Utils

    Client->>API: POST /api/score (video file)
    API->>API: Validate file extension
    API->>API: Save temp file
    
    API->>CV: Open video
    CV-->>API: Video stream
    
    loop For each frame
        CV->>CV: Read frame
        CV-->>API: Frame data
        API->>MP: Estimate pose
        MP-->>API: Landmarks (33 points)
        API->>Util: landmark() - scale/normalize
        Util-->>API: Normalized landmarks array
        
        API->>API: frame_features_from_xyvis()
        API->>Util: dist(), angle_at(), torso_angle_deg()
        Util-->>API: Geometric features
        API->>API: Build feature dict
        
        API->>Est: push(features)
        Est->>Est: Buffer frame, update temporal stats
        Est-->>API: ✓
    end
    
    API->>Est: detect()
    Est->>Est: _temporal_stats() - compute medians, FFT, speeds
    Est->>Est: _logistic() - scale scores
    Est->>Est: form_score() - percentile for label
    Est-->>API: (label, score, confidence)
    
    API->>API: Build ScoreSummary
    API->>CV: Release video
    API->>API: Delete temp file
    API-->>Client: ScoreSummary JSON (200 OK)
    
    alt Error occurs
        API-->>Client: HTTP 400/500 + error details
        API->>API: Cleanup temp file
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

  • score/app.py: Video processing pipeline with MediaPipe integration, file handling, and temporal statistics; requires verification of frame processing logic, error handling, and resource cleanup in all paths.
  • score/features.py: Dense activity detection logic with temporal statistics (FFT periodicity, motion energy), mode-based inference, and temporal stability enforcement; multiple scoring methods and thresholds warrant careful validation.
  • score/score/utils.py: Geometric computations with edge case handling (degenerate angles, visibility defaults, coordinate clamping); verify mathematical correctness and boundary conditions.
  • Cross-module integration: Ensure consistency between feature extraction, temporal aggregation, and activity classification.

Poem

🐰 A hop, a skip, a jump so high,
Our API now scores the moves you try!
From frames and poses, features bloom,
Detecting action in every room! 🎬✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is in Japanese and translates to 'Score calculation API,' which accurately describes the main change: introducing a complete scoring API system with FastAPI, video analysis, and related infrastructure.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch score

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🧹 Nitpick comments (7)
score/Dockerfile (1)

5-12: Consider --no-install-recommends to keep image smaller.
Not required, but typically helps for slim images.

score/score/utils.py (1)

42-56: Remove redundant int() calls around round().

round() already returns an integer in Python 3 when called with a single argument. The static analysis tool correctly identified this.

 def landmark(landmarks: Sequence[Any], image_width: int, image_height: int) -> np.ndarray:
     # 33*3
     out: np.ndarray = np.zeros((33, 3), dtype=np.float32)
     for i, lm in enumerate(landmarks):
-        x = int(round(lm.x * image_width))
-        y = int(round(lm.y * image_height))
+        x = round(lm.x * image_width)
+        y = round(lm.y * image_height)
         # x ∈ [0, W−1]
         x = min(max(x, 0), image_width - 1)
         # y ∈ [0, H−1]
         y = min(max(y, 0), image_height - 1)
         vis = getattr(lm, "visibility", None) or getattr(lm, "presence", None) or 1.0
         out[i, 0] = x
         out[i, 1] = y
         out[i, 2] = float(vis)
     return out
score/app.py (2)

186-187: Chain exceptions for better debugging.

Use raise ... from e to preserve the exception chain and provide better stack traces.

     except Exception as e:
-        raise HTTPException(status_code=500, detail=f"Failed to save uploaded file: {e}")
+        raise HTTPException(status_code=500, detail=f"Failed to save uploaded file: {e}") from e

200-201: Chain exceptions for better debugging.

Same issue as above—use exception chaining.

     except Exception as e:
-        raise HTTPException(status_code=500, detail=f"Processing failed: {e}")
+        raise HTTPException(status_code=500, detail=f"Processing failed: {e}") from e
score/features.py (3)

102-121: Inefficient list comprehension on every push.

Line 120 creates a new filtered list on every push() call to check standing initialization. This O(n) operation per frame could be optimized by tracking the count incrementally.

Consider tracking the count of high-knee frames incrementally:

+        self._high_knee_count: int = 0

Then in push():

         if not self.standing_initialized and feat["avg_knee"] > 150:
             if self.standing_hip_y is None:
                 self.standing_hip_y = feat["hip_y"]
             else:
                 self.standing_hip_y = 0.95 * self.standing_hip_y + 0.05 * feat["hip_y"]
-
-            if len([b for b in self.buf if b.get("valid") and b["avg_knee"] > 150]) > self.fps * 1.0:
+            self._high_knee_count += 1
+            if self._high_knee_count > self.fps * 1.0:
                 self.standing_initialized = True

Note: You'd also need to decrement the count when old frames fall out of the window if precision matters.


198-286: Consider extracting magic numbers to named constants.

The detect method contains many hardcoded thresholds (e.g., k=0.8, x0=2.0, 0.45, 0.3, etc.) that control activity classification. While functional, these would be easier to tune and understand as named constants or a configuration object.

Example:

# At module level or in class
RUNNING_FREQ_TARGET = 2.2
RUNNING_PERIODIC_K = 0.8
RUNNING_PERIODIC_X0 = 2.0
# ... etc

288-345: Consider using a dispatch table instead of if-elif chain.

The form_score method has a long if-elif chain for each activity type. A dictionary-based dispatch would be more maintainable and extensible.

Example pattern:

def form_score(self, label: str, med: Optional[Dict[str, float]]) -> float:
    if med is None:
        return 0.0
    
    score_funcs = {
        "running": self._score_running,
        "walking": self._score_walking,
        # ... etc
    }
    
    func = score_funcs.get(label)
    return func(med) if func else 0.0
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0063717 and c828953.

⛔ Files ignored due to path filters (2)
  • score/input/input.mp4 is excluded by !**/*.mp4
  • score/uv.lock is excluded by !**/*.lock
📒 Files selected for processing (11)
  • score/.gitignore (1 hunks)
  • score/.python-version (1 hunks)
  • score/Dockerfile (1 hunks)
  • score/README.md (1 hunks)
  • score/app.py (1 hunks)
  • score/build.sh (1 hunks)
  • score/features.py (1 hunks)
  • score/input/curl.sh (1 hunks)
  • score/pyproject.toml (1 hunks)
  • score/run.sh (1 hunks)
  • score/score/utils.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
score/features.py (1)
score/score/utils.py (3)
  • dist (6-7)
  • angle_at (10-27)
  • torso_angle_deg (31-40)
score/app.py (1)
score/features.py (5)
  • frame_features_from_xyvis (9-74)
  • Estimator (76-345)
  • push (102-121)
  • detect (198-286)
  • _temporal_stats (123-192)
🪛 Ruff (0.14.8)
score/features.py

85-85: Value being cast to int is already an integer

Remove unnecessary int call

(RUF046)


286-286: Value being cast to int is already an integer

Remove unnecessary int call

(RUF046)

score/app.py

52-52: Unused function argument: annotated_output_path

(ARG001)


56-56: Avoid specifying long messages outside the exception class

(TRY003)


70-70: Avoid specifying long messages outside the exception class

(TRY003)


78-78: Value being cast to int is already an integer

Remove unnecessary int call

(RUF046)


79-79: Value being cast to int is already an integer

Remove unnecessary int call

(RUF046)


170-170: Do not perform function call File in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


176-176: Unused function argument: save_annotated

(ARG001)


186-186: Do not catch blind exception: Exception

(BLE001)


187-187: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


199-199: Consider moving this statement to an else block

(TRY300)


200-200: Do not catch blind exception: Exception

(BLE001)


201-201: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


205-206: try-except-pass detected, consider logging the exception

(S110)


205-205: Do not catch blind exception: Exception

(BLE001)


210-210: Possible binding to all interfaces

(S104)

score/score/utils.py

14-14: Comment contains ambiguous (MINUS SIGN). Did you mean - (HYPHEN-MINUS)?

(RUF003)


16-16: Comment contains ambiguous (MINUS SIGN). Did you mean - (HYPHEN-MINUS)?

(RUF003)


30-30: Comment contains ambiguous (MINUS SIGN). Did you mean - (HYPHEN-MINUS)?

(RUF003)


32-32: Comment contains ambiguous (MINUS SIGN). Did you mean - (HYPHEN-MINUS)?

(RUF003)


38-38: Comment contains ambiguous (MINUS SIGN). Did you mean - (HYPHEN-MINUS)?

(RUF003)


46-46: Value being cast to int is already an integer

Remove unnecessary int call

(RUF046)


47-47: Value being cast to int is already an integer

Remove unnecessary int call

(RUF046)


48-48: Comment contains ambiguous (MINUS SIGN). Did you mean - (HYPHEN-MINUS)?

(RUF003)


50-50: Comment contains ambiguous (MINUS SIGN). Did you mean - (HYPHEN-MINUS)?

(RUF003)

🪛 Shellcheck (0.11.0)
score/run.sh

[error] 1-1: Tips depend on target shell and yours is unknown. Add a shebang or a 'shell' directive.

(SC2148)

score/build.sh

[error] 1-1: Tips depend on target shell and yours is unknown. Add a shebang or a 'shell' directive.

(SC2148)

score/input/curl.sh

[error] 1-1: Tips depend on target shell and yours is unknown. Add a shebang or a 'shell' directive.

(SC2148)

🔇 Additional comments (9)
score/.python-version (1)

1-1: Good pin for local tooling (pyenv/asdf).
Matches the service’s Python 3.12 baseline.

score/.gitignore (1)

1-216: Solid, comprehensive Python/tooling ignores.
No concerns from what’s included here.

score/score/utils.py (3)

1-7: LGTM!

The dist function is a clean implementation of Euclidean distance using numpy's linalg.norm.


10-27: LGTM!

The angle_at function correctly handles the degenerate case (zero-length vectors) and clamps the cosine value to avoid domain errors in arccos. The implementation is mathematically sound.


31-40: LGTM!

The torso_angle_deg function correctly computes the torso angle and normalizes it to [0, 90] range using min(ang, 180 - ang).

score/app.py (1)

208-210: Binding to all interfaces (0.0.0.0) in development mode.

Running with host="0.0.0.0" exposes the service to all network interfaces, which may be acceptable for containerized deployments but could be a security concern during local development.

Consider using 127.0.0.1 for local development or making the host configurable via environment variable:

 if __name__ == "__main__":
     import uvicorn
-    uvicorn.run("app:app", host="0.0.0.0", port=8000, reload=True)
+    import os
+    host = os.environ.get("HOST", "127.0.0.1")
+    uvicorn.run("app:app", host=host, port=8000, reload=True)
score/features.py (3)

9-74: LGTM!

The frame_features_from_xyvis function is well-structured with clear landmark extraction and feature computation. The visibility threshold check (0.12) provides a reasonable guard against unreliable pose estimates.


76-100: LGTM!

The Estimator constructor properly initializes bounded deques for efficient sliding window management and sets sensible defaults.


123-192: LGTM!

The _temporal_stats method computes comprehensive temporal statistics including FFT-based periodicity analysis. The implementation handles edge cases (empty buffers, single-element arrays) appropriately.

Comment on lines +13 to +14
from score.utils import landmark
from features import frame_features_from_xyvis, Estimator
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Inconsistent import paths.

The imports use different path styles: from score.utils import landmark (absolute) vs from features import ... (relative/implicit). This may cause import errors depending on how the module is run.

 from score.utils import landmark
-from features import frame_features_from_xyvis, Estimator
+from score.features import frame_features_from_xyvis, Estimator
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
from score.utils import landmark
from features import frame_features_from_xyvis, Estimator
from score.utils import landmark
from score.features import frame_features_from_xyvis, Estimator
🤖 Prompt for AI Agents
In score/app.py around lines 13 to 14, the imports are inconsistent: one uses
the package path (from score.utils import landmark) while the other uses a bare
module name (from features import ...), which can break depending on how the
package is executed; make them consistent by changing the second import to the
package-qualified form (e.g., from score.features import
frame_features_from_xyvis, Estimator) or, if this file is intended to be an
intra-package import, use a relative import (e.g., from .features import
frame_features_from_xyvis, Estimator); ensure the package has an __init__.py and
adjust PYTHONPATH/run configuration if necessary.

Comment on lines +45 to +53
def analyze_video(
input_path: Union[str, Path],
mode: str = "auto",
max_width: Optional[int] = None,
model_complexity: int = 0,
min_det_conf: float = 0.5,
min_track_conf: float = 0.5,
annotated_output_path: Optional[Union[str, Path]] = None,
) -> ScoreSummary:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Remove unused annotated_output_path parameter.

This parameter is declared but never used in the function body. Either implement the annotated video output functionality or remove the parameter to avoid confusion.

 def analyze_video(
     input_path: Union[str, Path],
     mode: str = "auto",
     max_width: Optional[int] = None,
     model_complexity: int = 0,
     min_det_conf: float = 0.5,
     min_track_conf: float = 0.5,
-    annotated_output_path: Optional[Union[str, Path]] = None,
 ) -> ScoreSummary:
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def analyze_video(
input_path: Union[str, Path],
mode: str = "auto",
max_width: Optional[int] = None,
model_complexity: int = 0,
min_det_conf: float = 0.5,
min_track_conf: float = 0.5,
annotated_output_path: Optional[Union[str, Path]] = None,
) -> ScoreSummary:
def analyze_video(
input_path: Union[str, Path],
mode: str = "auto",
max_width: Optional[int] = None,
model_complexity: int = 0,
min_det_conf: float = 0.5,
min_track_conf: float = 0.5,
) -> ScoreSummary:
🧰 Tools
🪛 Ruff (0.14.8)

52-52: Unused function argument: annotated_output_path

(ARG001)

🤖 Prompt for AI Agents
In score/app.py around lines 45 to 53, the function analyze_video declares an
unused parameter annotated_output_path; remove this parameter from the function
signature and any external callers, or implement annotated video output: if
keeping it, add logic to write the annotated frames/video to the provided path
(validate path, open writer, draw annotations per frame, close writer) and
update the return to include the output path; otherwise delete the parameter and
update type hints and all references to avoid unused-argument confusion.

out_w: int = int(round(in_w * scale))
out_h: int = int(round(in_h * scale))

out = None
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Dead code: out variable initialized but never assigned.

The out variable is initialized to None and checked in finally, but it's never assigned a VideoWriter instance. This suggests incomplete implementation of annotated video output.

Either implement the video writer functionality or remove the dead code:

-    out = None
-
     estimator = Estimator(fps=fps, window_seconds=3.0, stable_threshold=3, mode=mode)

And in the finally block:

     finally:
         cap.release()
-        if out is not None:
-            out.release()
         pose.close()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
out = None
estimator = Estimator(fps=fps, window_seconds=3.0, stable_threshold=3, mode=mode)
Suggested change
out = None
finally:
cap.release()
pose.close()
🤖 Prompt for AI Agents
In score/app.py around line 81, the variable `out` is initialized to None but
never assigned or used, leaving dead code and an unsafe finally block; either
remove `out` and its release logic or implement annotated video output: create a
cv2.VideoWriter with correct codec, frame size and fps before processing, assign
it to `out`, write annotated frames to `out` inside the processing loop, and in
the finally block check and call `out.release()` only if `out` is not None; also
add any necessary cv2 import or parameter handling for output path, codec, and
fps.

Comment on lines +168 to +177
@app.post("/api/score", response_model=ScoreSummary)
async def api_score(
file: UploadFile = File(...),
mode: str = Form("auto"),
max_width: Optional[int] = Form(None),
model_complexity: int = Form(0),
min_det_conf: float = Form(0.5),
min_track_conf: float = Form(0.5),
save_annotated: bool = Form(False),
):
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Remove unused save_annotated parameter.

Similar to annotated_output_path, this parameter is declared but never used. The annotated video feature appears incomplete.

 @app.post("/api/score", response_model=ScoreSummary)
 async def api_score(
     file: UploadFile = File(...),
     mode: str = Form("auto"),
     max_width: Optional[int] = Form(None),
     model_complexity: int = Form(0),
     min_det_conf: float = Form(0.5),
     min_track_conf: float = Form(0.5),
-    save_annotated: bool = Form(False),
 ):
🧰 Tools
🪛 Ruff (0.14.8)

170-170: Do not perform function call File in argument defaults; instead, perform the call within the function, or read the default from a module-level singleton variable

(B008)


176-176: Unused function argument: save_annotated

(ARG001)

🤖 Prompt for AI Agents
In score/app.py around lines 168 to 177, the save_annotated parameter is
declared on the api_score endpoint but never used; remove save_annotated: bool =
Form(False) from the function signature, update any OpenAPI docs or tests that
reference this parameter, and delete any dead code or comments related to the
annotated-video feature; run linters/tests to ensure no remaining references
break the build.

Comment on lines +178 to +179
if not file.filename.lower().endswith((".mp4", ".mov", ".m4v")):
raise HTTPException(status_code=400, detail="Unsupported file extension. Please upload MP4/MOV/M4V.")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Potential bypass via case sensitivity.

file.filename could be None for some upload scenarios. Consider adding a null check.

-    if not file.filename.lower().endswith((".mp4", ".mov", ".m4v")):
+    if not file.filename or not file.filename.lower().endswith((".mp4", ".mov", ".m4v")):
         raise HTTPException(status_code=400, detail="Unsupported file extension. Please upload MP4/MOV/M4V.")
🤖 Prompt for AI Agents
In score/app.py around lines 178 to 179, the current filename check assumes
file.filename is non-null which can be bypassed; add a null check for file and
file.filename first and raise the same 400 HTTPException if missing, otherwise
perform the case-insensitive extension check (e.g., use
file.filename.lower().endswith(...)) so uploads with no filename or a None
filename are rejected before calling .lower().

Comment on lines +5 to +25
RUN apt-get update && apt-get install -y \
build-essential \
cmake \
ffmpeg \
libsm6 \
libxext6 \
curl \
&& apt-get clean && rm -rf /var/lib/apt/lists/*

COPY pyproject.toml uv.lock ./
COPY score/ ./score
COPY app.py .
COPY features.py .

RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"

RUN pip install --upgrade pip setuptools wheel

RUN pip install .

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Docker build ignores uv.lock, creating non-reproducible installs.

The Dockerfile copies uv.lock on line 8 but then runs pip install . (line 22), which resolves dependencies from pyproject.toml at build time. Since pyproject.toml specifies flexible versions (e.g., numpy >=1.19.0, opencv-python >=4.5.0), builds at different times will pull different minor/patch versions, breaking reproducibility.

Use uv to sync directly from the lockfile instead:

 RUN pip install --upgrade pip setuptools wheel
+RUN pip install uv
-RUN pip install .
+RUN uv sync --frozen

This ensures every build uses exact versions pinned in uv.lock, eliminating the reproducibility gap.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
RUN apt-get update && apt-get install -y \
build-essential \
cmake \
ffmpeg \
libsm6 \
libxext6 \
curl \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
COPY pyproject.toml uv.lock ./
COPY score/ ./score
COPY app.py .
COPY features.py .
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
RUN pip install --upgrade pip setuptools wheel
RUN pip install .
RUN apt-get update && apt-get install -y \
build-essential \
cmake \
ffmpeg \
libsm6 \
libxext6 \
curl \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
COPY pyproject.toml uv.lock ./
COPY score/ ./score
COPY app.py .
COPY features.py .
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
RUN pip install --upgrade pip setuptools wheel
RUN pip install uv
RUN uv sync --frozen
🤖 Prompt for AI Agents
In score/Dockerfile around lines 5 to 25 the build copies uv.lock but still runs
a plain pip install ., causing dependency resolution from pyproject.toml rather
than exact versions from uv.lock; to fix, after creating and activating the venv
and upgrading pip/setuptools/wheel, install the uv CLI into the venv and run uv
to sync the environment from uv.lock so the exact pinned versions are installed,
then continue with pip install . for the local package; ensure uv.lock is copied
before the sync step and keep the install/sync steps inside the Dockerfile build
sequence.

Comment on lines +1 to +4
curl -X POST "http://localhost:8000/api/score" \
-F "file=@input.mp4" \
-F "mode=running" \
-F "max_width=640" No newline at end of file
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Improve curl script ergonomics (shebang, fail fast, stable file path).

+#!/usr/bin/env bash
+set -euo pipefail
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+
-curl -X POST "http://localhost:8000/api/score" \
-  -F "file=@input.mp4" \
+curl --fail-with-body -sS -X POST "http://localhost:8000/api/score" \
+  -F "file=@${SCRIPT_DIR}/input.mp4" \
   -F "mode=running" \
   -F "max_width=640"
🧰 Tools
🪛 Shellcheck (0.11.0)

[error] 1-1: Tips depend on target shell and yours is unknown. Add a shebang or a 'shell' directive.

(SC2148)

🤖 Prompt for AI Agents
In score/input/curl.sh around lines 1 to 4, the script lacks a shebang,
fail-fast behavior, and a stable file path; update the script to start with a
shebang (#!/usr/bin/env bash), enable strict/fail-fast mode (set -euo pipefail
or at least set -e), resolve the input file relative to the script directory
(e.g. INPUT="$(dirname "$0")/input.mp4") to avoid cwd issues, and make curl fail
loudly by adding --fail --show-error (and optionally --silent) to the curl
invocation while keeping the form fields.

Comment on lines +1 to +14
[project]
name = "score"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"fastapi>=0.124.2",
"mediapipe>=0.10.13",
"numpy>=1.26.4",
"opencv-python>=4.11.0.86",
"python-multipart>=0.0.20",
"uvicorn>=0.38.0",
]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

git ls-files | grep -E "(uv\.lock|poetry\.lock|requirements|Dockerfile|\.github)" | head -20

Repository: befoafsuger/rizap2025

Length of output: 124


🏁 Script executed:

find . -maxdepth 2 -type f \( -name "uv.lock" -o -name "Dockerfile" -o -name "*.yml" -o -name "*.yaml" \) 2>/dev/null

Repository: befoafsuger/rizap2025

Length of output: 181


🌐 Web query:

mediapipe 0.10.13 Python 3.12 compatibility support

💡 Result:

Yes — MediaPipe 0.10.13 has prebuilt wheels for CPython 3.12 (cp312) on PyPI (Windows/macOS/manylinux variants), so it is compatible with Python 3.12 — install with pip (e.g. pip install mediapipe==0.10.13). [1][2]

Sources:

  • MediaPipe 0.10.13 files on PyPI (shows cp312 wheels). [1]
  • MediaPipe build docs (Python build pipeline tested up through 3.12). [2]

References
[1] https://pypi.org/project/mediapipe/0.10.13/#files
[2] https://ai.google.dev/edge/mediapipe/solutions/build_python


🌐 Web query:

opencv-python 4.11.0.86 Python 3.12 wheels support

💡 Result:

Yes — opencv-python 4.11.0.86 includes fixes for and provides wheels for Python 3.12. [1][2]

Sources:

  • opencv-python release notes (4.11.0.86): "Added Python 3.13 support · Fixed build with Python 3.12". [1]
  • opencv-python project documentation / CI: lists Python 3.12 among supported Python versions for pre-built wheels. [2]

🏁 Script executed:

cat score/Dockerfile

Repository: befoafsuger/rizap2025

Length of output: 593


🏁 Script executed:

cat .github/workflows/format.yml

Repository: befoafsuger/rizap2025

Length of output: 1132


Versioning strategy: >= constraints are not enforced by the lockfile in Docker builds.

The uv.lock file exists but the Dockerfile uses RUN pip install . instead of installing from the lock, which resolves dependencies using the >= constraints in pyproject.toml. This creates a risk of dependency drift between local development and production. Consider updating the Dockerfile to enforce the lock (e.g., RUN pip install -e . --require-hashes or using uv pip install if using uv as the dependency manager).

Good news: mediapipe 0.10.13 and opencv-python 4.11.0.86 are both compatible with Python 3.12—no concerns there.

Comment on lines +1 to +10
# score algorithm

def dist
-> ユークリッド距離

def angle_at
-> 余弦定理 角度計算

def torso_angle_deg
-> atan2の方位角算出 No newline at end of file
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Make README valid Markdown (code fences or bullet list), avoid def pseudo-syntax.
Right now it looks like malformed Python and won’t render nicely.

 # score algorithm

-def dist
--> ユークリッド距離
+## Utilities
+
+- `dist`: ユークリッド距離

-def angle_at
--> 余弦定理 角度計算
+- `angle_at`: 余弦定理で角度計算

-def torso_angle_deg
--> atan2の方位角算出
+- `torso_angle_deg`: `atan2` で方位角算出
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# score algorithm
def dist
-> ユークリッド距離
def angle_at
-> 余弦定理 角度計算
def torso_angle_deg
-> atan2の方位角算出
# score algorithm
## Utilities
- `dist`: ユークリッド距離
- `angle_at`: 余弦定理で角度計算
- `torso_angle_deg`: `atan2` で方位角算出
🤖 Prompt for AI Agents
In score/README.md around lines 1 to 10, the current content uses "def"
pseudo-syntax and looks like malformed Python; replace it with valid Markdown by
converting each entry into either a bullet list or fenced code blocks with brief
descriptions — e.g., a bullet list of function names (dist, angle_at,
torso_angle_deg) followed by short descriptions (Euclidean distance; angle via
law of cosines; torso angle using atan2) or use fenced code blocks to show
function signatures and then plain text descriptions below each; remove the
"def" prefix so the file renders as proper Markdown.

@@ -0,0 +1 @@
docker run -d --name score-api -p 8000:8000 score-api
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add shebang + handle “container already exists” for local dev.

+#!/usr/bin/env bash
+set -euo pipefail
+
+docker rm -f score-api >/dev/null 2>&1 || true
-docker run -d --name score-api -p 8000:8000 score-api
+docker run -d --name score-api -p 8000:8000 score-api
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
docker run -d --name score-api -p 8000:8000 score-api
#!/usr/bin/env bash
set -euo pipefail
docker rm -f score-api >/dev/null 2>&1 || true
docker run -d --name score-api -p 8000:8000 score-api
🧰 Tools
🪛 Shellcheck (0.11.0)

[error] 1-1: Tips depend on target shell and yours is unknown. Add a shebang or a 'shell' directive.

(SC2148)

🤖 Prompt for AI Agents
In score/run.sh around line 1, the script lacks a shebang and doesn't handle the
case where a container named score-api already exists; add a shebang (e.g.,
#!/usr/bin/env bash) at the top, make the script executable, and before running
docker run check for an existing container (docker ps -aq -f name=^/score-api$),
and if found stop and remove it (docker stop ... && docker rm ...) or exit with
a clear message; then run docker run -d --name score-api -p 8000:8000 score-api.
Ensure the check is robust (matches exact name) and uses exit codes to surface
failures.

@davy-yusuke davy-yusuke merged commit dbbd61e into main Dec 12, 2025
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant