Skip to content

Client Telemetry#42

Merged
matt2jog merged 8 commits into
mainfrom
telemetry
Apr 28, 2026
Merged

Client Telemetry#42
matt2jog merged 8 commits into
mainfrom
telemetry

Conversation

@talbarr14
Copy link
Copy Markdown
Collaborator

@talbarr14 talbarr14 commented Apr 23, 2026

Summary by CodeRabbit

  • New Features

    • Added comprehensive daily surveys for mood, workouts, body metrics, steps, and meals.
    • Added telemetry endpoints to track, update, and query steps, weights, moods, workouts, and meals.
    • Added support for progress photos in body-metrics records.
  • Improvements

    • Step counts and weight inputs now enforce sensible validation limits.
    • Telemetry dates standardized to UTC-midnight datetimes to improve consistency.
  • Tests

    • Added end-to-end telemetry tests covering create/update/query and weight CRUD.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 23, 2026

Warning

Rate limit exceeded

@matt2jog has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 25 minutes and 9 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 941ba306-9810-4f84-89d9-3531f2011605

📥 Commits

Reviewing files that changed from the base of the PR and between ca97f85 and 775748c.

📒 Files selected for processing (4)
  • src/api/app.py
  • src/api/roles/client/client.py
  • src/api/roles/client/domain.py
  • src/api/roles/client/fitness.py
📝 Walkthrough

Walkthrough

Adds a client telemetry system: new telemetry router and endpoints, expanded telemetry-related DB models (datetime-based dates, new survey tables, weight validation), new client fitness daily-survey workflows, helper logic for UTC-midnight dates, and test coverage for telemetry endpoints and CRUD.

Changes

Cohort / File(s) Summary
App Integration
src/api/app.py
Registers the new client telemetry router with the FastAPI app.
Client Role Core
src/api/roles/client/client.py
Replaces date-only telemetry handling with UTC-midnight datetime via a new _today_utc_midnight() helper.
Domain Schemas
src/api/roles/client/domain.py
Adds Pydantic models for step-count and weight updates (StepCountUpdateInput/Output, WeightUpdateInput) with validators; adds DunderInput placeholder.
Fitness API
src/api/roles/client/fitness.py
Adds daily-survey CRUD/workflow: get/start/submit endpoints for mood, workout, body-metrics, steps, and meal; helpers to get-or-create today's telemetry and per-type survey rows; submission persists completion records and stores completion IDs.
Telemetry Router
src/api/roles/client/telemetry.py
New router exposing step upsert (PUT /update_steps), paginated queries for steps/weights/moods/workouts/meals, and weight update/delete endpoints with client authorization checks.
Database Models
src/database/telemetry/models.py
Changes ClientTelemetry.date from datedatetime; adds HealthMetrics.weight validator, progress_pic_url, and client_telemetry_id; sets DailyMoodSurvey boolean defaults; adds DailyWorkoutSurvey, DailyBodyMetricsSurvey, DailyStepsSurvey, DailyMealSurvey.
Tests
tests/test_client.py
Adds tests for step creation/update and query, weight CRUD (update/delete) and queries, and query endpoints for moods/workouts/meals (empty-list assertions).

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant API
    participant DB

    Client->>API: GET /roles/client/fitness/{type}/today
    API->>DB: get_or_create ClientTelemetry (today UTC-midnight)
    DB-->>API: ClientTelemetry
    API->>DB: get_or_create Daily{Type}Survey for client & telemetry
    DB-->>API: DailySurvey (is_started/is_finished flags)
    API-->>Client: Survey state + completion_id
Loading
sequenceDiagram
    participant Client
    participant API
    participant DB

    Client->>API: PUT /roles/client/telemetry/update_steps
    API->>DB: fetch ClientTelemetry for client
    DB-->>API: ClientTelemetry
    API->>DB: create_or_update StepCount linked to telemetry
    DB-->>API: StepCount (persisted)
    API-->>Client: updated step count

    Client->>API: GET /roles/client/telemetry/query/steps
    API->>DB: query StepCount (paginated) for client
    DB-->>API: list of StepCount rows
    API-->>Client: steps list response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • RafaelBGirgis

Poem

🐰 I hopped in with code and a twitchy nose,
New surveys and steps where the data flows,
Midnight UTC marks each telemetry day,
We log, start, submit — then hop away,
Carrots, tests, and endpoints: a rabbit's small prose.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.30% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Client Telemetry' is vague and generic, using a broad term that doesn't convey the specific nature or scope of the changes. Use a more descriptive title that captures the primary change, such as 'Add client telemetry tracking endpoints and daily survey workflows' or 'Implement telemetry data collection for steps, weight, and fitness surveys'.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch telemetry

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: 4

🧹 Nitpick comments (1)
src/api/roles/client/telemetry.py (1)

1-9: Import framework symbols directly instead of through fitness.py.

Line 1 couples this router to another route module for APIRouter, Session, select, PaginationParams, and datetime, and line 9 declares an unused today variable. Direct imports make this module safer to import and reduce circular-import risk.

♻️ Proposed import cleanup
-from src.api.roles.client.fitness import APIRouter, PaginationParams, Session, _get_or_create_telemetry, datetime, select
+from datetime import datetime
+from fastapi import APIRouter, Depends, HTTPException
+from sqlmodel import Session, select
+
+from src.api.dependencies import get_client_account, PaginationParams
+from src.api.roles.client.fitness import _get_or_create_telemetry
 from src.database.telemetry.models import ClientTelemetry, CompletedMealActivity, HealthMetrics, StepCount, CompletedSurvey, DailyMoodSurvey, CompletedWorkout
 from src.api.roles.client.domain import StepCountUpdateInput, StepCountUpdateOutput, WeightUpdateInput
 from src.database.session import get_session
 from src.database.account.models import Account
-from fastapi import Depends, HTTPException
-from src.api.dependencies import get_client_account
-
-today = datetime.utcnow().date()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/api/roles/client/telemetry.py` around lines 1 - 9, This module currently
re-exports framework/runtime symbols from src.api.roles.client.fitness
(APIRouter, Session, select, PaginationParams, datetime) and defines an unused
today variable; replace those indirect imports by importing framework symbols
directly (e.g., APIRouter from fastapi, Session from sqlalchemy.orm, select from
sqlalchemy, datetime from the standard datetime module) and import
PaginationParams and _get_or_create_telemetry from the modules where they are
actually defined (locate their declarations and import them directly), then
remove the unused today variable declaration; update import statements to
reference the correct modules and keep only the names used in this file
(APIRouter, PaginationParams, Session, _get_or_create_telemetry, datetime,
select).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/api/roles/client/domain.py`:
- Around line 12-28: The module-level `@field_validator` and the function
step_count_must_be_non_negative are not attached to the model and the condition
0 > v > 100000 is impossible; move the validator into the StepCountUpdateInput
class as a `@field_validator`("step_count") class method (or define it as a model
method) and change the condition to reject values outside the allowed range
(e.g., if v < 0 or v > 100000: raise ValueError("Step count must be a
non-negative integer up to 100000")), and likewise add a corresponding
`@field_validator` inside WeightUpdateInput (e.g., ensure weight >= 0 and any
upper bound you need) to raise ValueError on invalid input so both
StepCountUpdateInput and WeightUpdateInput perform proper validation.

In `@src/api/roles/client/fitness.py`:
- Around line 108-119: The code uses a date() for "today" but
ClientTelemetry.date is a UTC-midnight datetime, so change the "today" value in
_get_or_create_telemetry to a UTC-midnight datetime (e.g.,
datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0)) and use
that datetime when querying ClientTelemetry.date and when constructing the new
ClientTelemetry(client_id=..., date=...). This ensures the equality check
against ClientTelemetry.date and the created record use the same UTC-midnight
datetime value.
- Around line 232-248: The current submit handlers create a Completed* row
(e.g., CompletedSurvey) and commit before linking it to the parent survey, which
can leave orphan rows if the second commit fails; change the flow to call
db.flush() after creating the detail/completed object to get its generated id,
then set survey.is_finished and survey.completed_survey_id and finally
db.add(survey) and db.commit() once so both the detail and survey update are
persisted atomically; apply this pattern in the mood survey handler
(CompletedSurvey), the health metrics, step count, meal activity handlers, and
consolidate the workout submit handler so the detail flush and the survey update
are committed together (use db.flush(), db.add(), db.commit(), and db.refresh()
as needed).
- Around line 50-55: Add request-level validators on the payload classes: in
BodyMetricsSurveySubmitPayload add a `@field_validator` for weight that ensures
it's an integer > 0 and raises a ValueError with a clear message if not; in
StepsSurveySubmitPayload add a `@field_validator` for step_count that enforces 0
<= step_count <= 100000 and raises a ValueError otherwise. Use the
SQLModel/Pydantic field_validator decorator pattern already used in the codebase
(e.g., similar to StepCountUpdateInput) and ensure the error messages are
descriptive so FastAPI request validation returns useful feedback; apply the
same validation pattern to the other survey payload classes with the same fields
elsewhere in the file.

---

Nitpick comments:
In `@src/api/roles/client/telemetry.py`:
- Around line 1-9: This module currently re-exports framework/runtime symbols
from src.api.roles.client.fitness (APIRouter, Session, select, PaginationParams,
datetime) and defines an unused today variable; replace those indirect imports
by importing framework symbols directly (e.g., APIRouter from fastapi, Session
from sqlalchemy.orm, select from sqlalchemy, datetime from the standard datetime
module) and import PaginationParams and _get_or_create_telemetry from the
modules where they are actually defined (locate their declarations and import
them directly), then remove the unused today variable declaration; update import
statements to reference the correct modules and keep only the names used in this
file (APIRouter, PaginationParams, Session, _get_or_create_telemetry, datetime,
select).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 61cf46c2-45e8-447f-b864-284e2ab517a4

📥 Commits

Reviewing files that changed from the base of the PR and between 562b58a and 2ef4ce2.

📒 Files selected for processing (7)
  • src/api/app.py
  • src/api/roles/client/client.py
  • src/api/roles/client/domain.py
  • src/api/roles/client/fitness.py
  • src/api/roles/client/telemetry.py
  • src/database/telemetry/models.py
  • tests/test_client.py

Comment thread src/api/roles/client/domain.py Outdated
Comment thread src/api/roles/client/fitness.py
Comment thread src/api/roles/client/fitness.py Outdated
Comment thread src/api/roles/client/fitness.py
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: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/api/roles/client/fitness.py`:
- Around line 71-79: The validator validate_meal_choice on class
MealSurveySubmitPayload currently only ensures at least one of
client_prescribed_meal_id or on_demand_meal_id is present; change it to enforce
exactly one by checking that (client_prescribed_meal_id is None) ==
(on_demand_meal_id is None) and raise a ValueError when both are None or both
are set, returning self only when exactly one is provided; apply the same
exact-one validation pattern to the other analogous validator found later in the
file.
- Around line 42-49: The WorkoutSurveySubmitPayload currently allows negative
numbers and empty payloads; update validation on the WorkoutSurveySubmitPayload
(fields completed_reps, completed_sets, completed_duration, estimated_calories)
to enforce non-negative (>=0) integers and reject negatives, and require that
either a workout reference (workout_plan_activity_id or workout_activity_id) is
provided OR an explicit "skipped/rest" flag/state is set before setting
is_finished; implement these checks where the payload is processed (e.g., in the
handler that sets is_finished) to return a validation error if neither reference
nor explicit skipped/rest is present or any numeric field is negative—apply the
same validation logic to the corresponding payload handling at the other
occurrence mentioned (lines ~327-346).
- Around line 122-153: Add DB-level unique constraints for ClientTelemetry on
(client_id, date) and for each survey model on client_telemetry_id, then make
_get_or_create_telemetry and _get_or_create_daily_survey concurrency-safe by
catching DB integrity/unique-constraint failures: attempt the insert as now, but
on IntegrityError (or equivalent) roll back the session and re-query the
existing row and return it; alternatively perform an upsert if your ORM/DB
supports it (use the unique constraint fields). Specifically, modify
_get_or_create_telemetry to create and commit inside a try/except that catches
IntegrityError, does db.rollback(), re-selects the telemetry by client_id/date
and returns it; do the same pattern for _get_or_create_daily_survey using
survey_model and client_telemetry_id so concurrent requests return the single
canonical row. Ensure you reference ClientTelemetry, _get_or_create_telemetry,
_get_or_create_daily_survey, and survey_model.client_telemetry_id in the
changes.
- Around line 336-340: The code persists client-supplied IDs (e.g.,
payload.workout_plan_activity_id into CompletedWorkout and
payload.client_prescribed_meal_id into CompletedMealActivity) without verifying
they belong to the authenticated account (acc.client_id); before creating
CompletedWorkout, query the DB to confirm payload.workout_plan_activity_id is
part of a workout plan owned by acc.client_id (join ClientWorkoutPlan by
workout_plan_id and ensure the activity exists in that plan) and, before
creating CompletedMealActivity, verify payload.client_prescribed_meal_id exists
in ClientPrescribedMeal with client_id == acc.client_id; if validation fails,
return an appropriate authorization/validation error instead of persisting.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 282fc19e-2081-4afc-9e58-9f4fa3f994c4

📥 Commits

Reviewing files that changed from the base of the PR and between 2ef4ce2 and ca97f85.

📒 Files selected for processing (2)
  • src/api/roles/client/domain.py
  • src/api/roles/client/fitness.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/api/roles/client/domain.py

Comment thread src/api/roles/client/fitness.py
Comment on lines +71 to +79
class MealSurveySubmitPayload(SQLModel):
client_prescribed_meal_id: Optional[int] = None
on_demand_meal_id: Optional[int] = None

@model_validator(mode="after")
def validate_meal_choice(self):
if self.client_prescribed_meal_id is None and self.on_demand_meal_id is None:
raise ValueError("Either client_prescribed_meal_id or on_demand_meal_id is required")
return self
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

Require exactly one meal source.

The current validator allows both client_prescribed_meal_id and on_demand_meal_id, so a single completed meal can be linked to two different sources. Enforce exactly one to keep meal telemetry unambiguous.

🛡️ Proposed validator fix
     `@model_validator`(mode="after")
     def validate_meal_choice(self):
-        if self.client_prescribed_meal_id is None and self.on_demand_meal_id is None:
-            raise ValueError("Either client_prescribed_meal_id or on_demand_meal_id is required")
+        has_prescribed_meal = self.client_prescribed_meal_id is not None
+        has_on_demand_meal = self.on_demand_meal_id is not None
+        if has_prescribed_meal == has_on_demand_meal:
+            raise ValueError("Exactly one of client_prescribed_meal_id or on_demand_meal_id is required")
         return self

Also applies to: 541-545

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/api/roles/client/fitness.py` around lines 71 - 79, The validator
validate_meal_choice on class MealSurveySubmitPayload currently only ensures at
least one of client_prescribed_meal_id or on_demand_meal_id is present; change
it to enforce exactly one by checking that (client_prescribed_meal_id is None)
== (on_demand_meal_id is None) and raise a ValueError when both are None or both
are set, returning self only when exactly one is provided; apply the same
exact-one validation pattern to the other analogous validator found later in the
file.

Comment on lines +122 to +153
def _get_or_create_telemetry(db: Session, client_id: int) -> ClientTelemetry:
today = datetime.utcnow().replace(hour=0, minute=0, second=0, microsecond=0)
telemetry = db.exec(
select(ClientTelemetry).where(
ClientTelemetry.client_id == client_id,
ClientTelemetry.date == today
)
).first()

if telemetry is None:
telemetry = ClientTelemetry(client_id=client_id, date=today)
db.add(telemetry)
db.commit()
db.refresh(telemetry)

return telemetry


def _get_or_create_daily_survey(db: Session, client_id: int, survey_model):
telemetry = _get_or_create_telemetry(db, client_id)
survey = db.exec(
select(survey_model).where(
survey_model.client_telemetry_id == telemetry.id
)
).first()

if survey is None:
survey = survey_model(is_seen=True, is_started=False, is_finished=False, client_telemetry_id=telemetry.id)
db.add(survey)
db.commit()
db.refresh(survey)

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:

#!/bin/bash
# Description: Inspect telemetry models for unique constraints/indexes that protect daily get-or-create flows.
rg -n -C4 'class ClientTelemetry|class DailyMoodSurvey|class DailyWorkoutSurvey|class DailyBodyMetricsSurvey|class DailyStepsSurvey|class DailyMealSurvey|UniqueConstraint|unique=True|__table_args__' --type py

Repository: matt2jog/group_project_490_backend

Length of output: 6118


🏁 Script executed:

cat -n src/database/telemetry/models.py

Repository: matt2jog/group_project_490_backend

Length of output: 6334


Make daily get-or-create concurrency-safe.

Two simultaneous requests to /today or /start endpoints can both miss the existing telemetry/survey row in a race condition and create duplicate records. Add DB-level unique constraints on ClientTelemetry(client_id, date) and each survey model's client_telemetry_id, then update the get-or-create logic to handle insert collisions via retry or upsert.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/api/roles/client/fitness.py` around lines 122 - 153, Add DB-level unique
constraints for ClientTelemetry on (client_id, date) and for each survey model
on client_telemetry_id, then make _get_or_create_telemetry and
_get_or_create_daily_survey concurrency-safe by catching DB
integrity/unique-constraint failures: attempt the insert as now, but on
IntegrityError (or equivalent) roll back the session and re-query the existing
row and return it; alternatively perform an upsert if your ORM/DB supports it
(use the unique constraint fields). Specifically, modify
_get_or_create_telemetry to create and commit inside a try/except that catches
IntegrityError, does db.rollback(), re-selects the telemetry by client_id/date
and returns it; do the same pattern for _get_or_create_daily_survey using
survey_model and client_telemetry_id so concurrent requests return the single
canonical row. Ensure you reference ClientTelemetry, _get_or_create_telemetry,
_get_or_create_daily_survey, and survey_model.client_telemetry_id in the
changes.

Comment thread src/api/roles/client/fitness.py
@matt2jog matt2jog merged commit ce949bc into main Apr 28, 2026
3 checks passed
@matt2jog matt2jog deleted the telemetry branch May 5, 2026 23:13
This was referenced May 6, 2026
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.

2 participants