Skip to content

Pr 740 fix format#749

Merged
CodeWithCJ merged 7 commits intomainfrom
pr-740-fix-format
Feb 25, 2026
Merged

Pr 740 fix format#749
CodeWithCJ merged 7 commits intomainfrom
pr-740-fix-format

Conversation

@CodeWithCJ
Copy link
Owner

No description provided.

thiagosian and others added 7 commits February 24, 2026 13:09
…urve)

## Summary

Port sleep science features from Thiagosian Health project:

### Backend (Express.js)
- Repository, Service, Routes following SparkyFitness patterns (CommonJS, getClient, JSDoc Swagger)
- 7 endpoints: sleep-debt, calculate-baseline, mctq-stats, daily-need, energy-curve, chronotype, data-sufficiency
- PostgreSQL migration with 3 tables + 2 views + RLS policies

### Frontend (React + TypeScript)
- 6 components: SleepDebtRing (SVG arc), EnergySchedule (Recharts), ChronotypeCard, SleepNeedBreakdown, SleepDebtHistory, SleepDebtBreakdown
- API service with typed interfaces + TanStack Query hooks
- Integrated into existing SleepReport page

### Algorithms (pure TypeScript libs)
- Sleep Debt: exponential decay model (RISE/WHOOP)
- Circadian Math: Two-Process Model (Borbely)
- MCTQ: Munich Chronotype Questionnaire (Roenneberg)
- Energy Curve: 24h curve with 96 points, zone detection, melatonin window

### i18n
- 55 keys in all 16 supported languages
- pt-BR translations sourced from original project

### Tests
- Unit tests for sleep-debt, circadian-math, mctq-calculation (Vitest)

## Test Plan
- `npm run lint` passes
- `npx prettier . --check` passes
- Unit tests: `npx vitest run src/tests/services/`
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @CodeWithCJ, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly expands the application's health tracking capabilities by integrating a sophisticated 'Sleep Science' feature. It provides users with in-depth insights into their sleep patterns, including metrics like sleep debt, chronotype, and a personalized energy curve, enhancing the overall user experience and data utility. The changes span both frontend and backend, ensuring a robust and interactive analytical tool.

Highlights

  • New Sleep Science Feature: Introduced a comprehensive 'Sleep Science' section with advanced analytics including sleep debt, chronotype, dynamic daily sleep need, and a 24-hour energy curve.
  • WHOOP-Inspired Theming: Added a new 'whoop' theme option to the application, complete with custom CSS variables and integration into the theme context.
  • Backend API and Database Enhancements: Implemented new API routes, services, and database schema migrations to support complex sleep science calculations and data storage.
  • Frontend UI and Data Hooks: Developed new React components and TanStack Query hooks for visualizing and interacting with the sleep science data on the frontend.
Changelog
  • SparkyFitnessFrontend/public/locales/en/translation.json
    • Added new translation keys for the 'Sleep Science' feature, covering various sleep metrics and descriptions.
  • SparkyFitnessFrontend/src/api/SleepScience/sleepScience.ts
    • Added new API functions for fetching sleep debt, calculating baseline, retrieving MCTQ stats, daily sleep need, energy curve, chronotype, and data sufficiency.
    • Defined TypeScript interfaces for all new sleep science data structures.
  • SparkyFitnessFrontend/src/api/keys/sleepScience.ts
    • Added new React Query keys for efficient caching and invalidation of sleep science data.
  • SparkyFitnessFrontend/src/contexts/ThemeContext.tsx
    • Extended the ThemeSetting type to include a 'whoop' option.
    • Implemented logic to apply and remove WHOOP-specific CSS variables to the root element when the 'whoop' theme is activated.
    • Updated theme toggling functionality to cycle through the new 'whoop' theme.
  • SparkyFitnessFrontend/src/hooks/SleepScience/useSleepScience.ts
    • Added new React Query hooks (useSleepDebtQuery, useMCTQStatsQuery, useDailyNeedQuery, useEnergyCurveQuery, useChronotypeQuery, useDataSufficiencyQuery) for fetching sleep science data.
    • Implemented useCalculateBaselineMutation for triggering baseline sleep need calculations with success/error messaging.
  • SparkyFitnessFrontend/src/lib/sleep/whoop-colors.ts
    • Added a new file defining WHOOP_COLORS constants for a WHOOP-inspired color palette.
    • Included utility functions getRecoveryColor, getRecoveryInsight, and getRecoveryLabel for recovery score interpretation.
    • Defined whoopCSSVariables to map WHOOP colors to CSS custom properties for theme integration.
  • SparkyFitnessFrontend/src/pages/Reports/SleepAnalyticsCharts.tsx
    • Integrated the new SleepScienceSection component into the Sleep Analytics Charts page to display advanced sleep insights.
  • SparkyFitnessFrontend/src/pages/Reports/SleepReport.tsx
    • Updated import statements to reflect the new structure of sleep-related components.
  • SparkyFitnessFrontend/src/pages/Reports/SleepScience/ChronotypeCard.tsx
    • Added a new React component to display chronotype classification and associated circadian markers (avg wake/sleep, nadir, acrophase, melatonin window).
  • SparkyFitnessFrontend/src/pages/Reports/SleepScience/EnergySchedule.tsx
    • Added a new React component to visualize the 24-hour energy curve based on the Two-Process Model of sleep regulation, including energy zones and melatonin window.
  • SparkyFitnessFrontend/src/pages/Reports/SleepScience/SleepDebtBreakdown.tsx
    • Added a new React component to present a daily breakdown of sleep debt, showing total sleep time, deviation from ideal, and weighted impact.
  • SparkyFitnessFrontend/src/pages/Reports/SleepScience/SleepDebtHistory.tsx
    • Added a new React component to display a 14-day history of sleep debt using a bar chart, including trend indicators.
  • SparkyFitnessFrontend/src/pages/Reports/SleepScience/SleepDebtRing.tsx
    • Added a new React component to visually represent current sleep debt using a circular progress ring, categorized by severity (low, moderate, high, critical).
  • SparkyFitnessFrontend/src/pages/Reports/SleepScience/SleepNeedBreakdown.tsx
    • Added a new React component to visualize the dynamic daily sleep need, breaking it down into baseline, strain addition, debt addition, and nap subtraction.
  • SparkyFitnessFrontend/src/pages/Reports/SleepScience/SleepScienceSection.tsx
    • Added a new React component that acts as the main container for all sleep science insights, orchestrating data fetching and displaying various cards and charts.
  • SparkyFitnessFrontend/src/services/api.ts
    • Modified the apiCall function to filter out undefined values from query parameters, preventing them from being serialized as 'undefined' strings in the URL.
  • SparkyFitnessServer/SparkyFitnessServer.js
    • Integrated new sleepScienceRoutes into the main Express application, enabling access to the new sleep science API endpoints.
  • SparkyFitnessServer/constants/sleepScienceConstants.js
    • Added a new file defining constants for sleep science calculations, including decay lambda, default sleep need, debt window, debt thresholds, MCTQ configuration parameters, and chronotype boundaries.
  • SparkyFitnessServer/db/migrations/20260224000000_add_sleep_science.sql
    • Added a new SQL migration script to introduce several database changes for sleep science.
    • Added new columns to the profiles table for sleep_need_method, sleep_need_confidence, sleep_need_based_on_days, sleep_need_last_calculated, sd_workday_hours, sd_freeday_hours, baseline_sleep_need, and social_jetlag_hours.
    • Created new tables: sleep_need_calculations for historical records of sleep need calculations, daily_sleep_need for caching daily sleep need breakdowns, and day_classification_cache for automatic workday/freeday classification.
    • Added a helper function calculate_mid_sleep and two new views: v_mctq_analysis for raw MCTQ data processing and v_mctq_stats for aggregated MCTQ statistics.
  • SparkyFitnessServer/db/rls_policies.sql
    • Updated Row Level Security policies to include the newly created sleep_need_calculations, daily_sleep_need, and day_classification_cache tables, ensuring data security.
  • SparkyFitnessServer/models/sleepScienceRepository.js
    • Added a new repository file to handle all database interactions related to sleep science, including fetching sleep history, managing sleep profiles, saving calculations, and upserting daily sleep needs and day classifications.
  • SparkyFitnessServer/routes/sleepScienceRoutes.js
    • Added a new router file defining API endpoints for sleep science, including routes for calculating sleep debt, baseline sleep need, MCTQ stats, daily sleep need, energy curve, chronotype, and data sufficiency checks.
  • SparkyFitnessServer/services/sleepScienceService.js
    • Added a new service file containing the core business logic for all sleep science calculations, such as weighted sleep debt, MCTQ baseline determination, 24-hour energy curve generation based on the Two-Process Model, and chronotype classification.
Activity
  • CodeWithCJ created this pull request to introduce a new 'Sleep Science' feature and a 'WHOOP' theme.
  • The pull request includes significant additions to both frontend and backend components to support the new functionality.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a comprehensive 'Sleep Science' feature, including backend services, database migrations, and frontend components for analyzing and displaying sleep debt, chronotype, and energy curves. The implementation is well-structured and based on established sleep science models. My review focuses on improving API consistency, database query performance, and code maintainability for this new feature set.

sdFreeday: number | null;
socialJetlag: number | null;
} | null;
latestCalculation: Record<string, unknown> | null;
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The type Record<string, unknown> for latestCalculation is not type-safe and hides the actual data structure. This can lead to runtime errors and makes the code harder to maintain.

It's better to define a specific interface that matches the shape of the data returned by the backend. Assuming the backend is updated to provide consistent camelCase properties (as suggested in a related comment), you could define and use an interface like this:

export interface SleepNeedCalculation {
  id: string;
  userId: string;
  calculatedAt: string;
  method: string;
  calculatedNeed: number;
  confidence: string;
  basedOnDays: number;
  sdWorkday?: number | null;
  sdFreeday?: number | null;
  sdWeek?: number | null;
  socialJetlagHours?: number | null;
  // ... and so on for other properties
}
Suggested change
latestCalculation: Record<string, unknown> | null;
latestCalculation: SleepNeedCalculation | null;

Comment on lines +1 to +9
/**
* Whoop Color System - Cores Oficiais (Máxima Precisão)
*
* Baseado nas especificações exatas do aplicativo Whoop:
* - Background Gradient: #283339 (topo) -> #101518 (base)
* - Recovery Status: #16EC06 (Verde), #FFDE00 (Amarelo), #FF0026 (Vermelho)
* - Métricas: #0093E7 (Strain), #7BA1BB (Sleep), #67AEE6 (Recovery)
* - CTA: #00F19F (Teal)
*/
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The comments in this file are written in Portuguese, while the rest of the codebase, including file names and variable names, is in English. This inconsistency can make the code harder to understand and maintain for a broader team.

Please translate the comments to English to maintain consistency across the project. For example, "Cores Oficiais (Máxima Precisão)" could be "Official Colors (Maximum Precision)".

Comment on lines +13 to +42
const result = await client.query(
`SELECT
se.entry_date AS date,
se.duration_in_seconds,
se.bedtime,
se.wake_time,
COALESCE(
(SELECT SUM(ss.duration_in_seconds) FROM sleep_entry_stages ss WHERE ss.entry_id = se.id AND ss.stage_type = 'deep'), 0
) / 60.0 AS "deepSleepMinutes",
COALESCE(
(SELECT SUM(ss.duration_in_seconds) FROM sleep_entry_stages ss WHERE ss.entry_id = se.id AND ss.stage_type = 'rem'), 0
) / 60.0 AS "remSleepMinutes",
COALESCE(
(SELECT SUM(ss.duration_in_seconds) FROM sleep_entry_stages ss WHERE ss.entry_id = se.id AND ss.stage_type = 'light'), 0
) / 60.0 AS "lightSleepMinutes",
COALESCE(
(SELECT SUM(ss.duration_in_seconds) FROM sleep_entry_stages ss WHERE ss.entry_id = se.id AND ss.stage_type = 'awake'), 0
) / 60.0 AS "awakeMinutes",
se.duration_in_seconds / 3600.0 AS "sleepDurationHours",
EXTRACT(EPOCH FROM se.bedtime) * 1000 AS "sleepStartTimestampGMT",
EXTRACT(EPOCH FROM se.wake_time) * 1000 AS "sleepEndTimestampGMT",
se.sleep_score AS "sleepScore"
FROM sleep_entries se
WHERE se.user_id = $1
AND se.entry_date >= CURRENT_DATE - INTERVAL '1 day' * $2
AND se.duration_in_seconds > 0
ORDER BY se.entry_date DESC`,
[userId, days]
);
return result.rows;
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The query in getSleepHistory uses four separate correlated subqueries to calculate the duration of each sleep stage. This can be inefficient as it may execute the subquery for each row returned from sleep_entries.

A more performant approach is to use a single LEFT JOIN with a subquery that uses conditional aggregation (FILTER clause). This calculates all stage durations in one pass over the sleep_entry_stages table.

To support this, also ensure a composite index exists on sleep_entry_stages (entry_id, stage_type).

    const result = await client.query(
      `SELECT
        se.entry_date AS date,
        se.duration_in_seconds,
        se.bedtime,
        se.wake_time,
        COALESCE(stages.deep_sleep_minutes, 0) AS "deepSleepMinutes",
        COALESCE(stages.rem_sleep_minutes, 0) AS "remSleepMinutes",
        COALESCE(stages.light_sleep_minutes, 0) AS "lightSleepMinutes",
        COALESCE(stages.awake_minutes, 0) AS "awakeMinutes",
        se.duration_in_seconds / 3600.0 AS "sleepDurationHours",
        EXTRACT(EPOCH FROM se.bedtime) * 1000 AS "sleepStartTimestampGMT",
        EXTRACT(EPOCH FROM se.wake_time) * 1000 AS "sleepEndTimestampGMT",
        se.sleep_score AS "sleepScore"
      FROM sleep_entries se
      LEFT JOIN (
        SELECT
          entry_id,
          SUM(duration_in_seconds) FILTER (WHERE stage_type = 'deep') / 60.0 AS deep_sleep_minutes,
          SUM(duration_in_seconds) FILTER (WHERE stage_type = 'rem') / 60.0 AS rem_sleep_minutes,
          SUM(duration_in_seconds) FILTER (WHERE stage_type = 'light') / 60.0 AS light_sleep_minutes,
          SUM(duration_in_seconds) FILTER (WHERE stage_type = 'awake') / 60.0 AS awake_minutes
        FROM sleep_entry_stages
        GROUP BY entry_id
      ) stages ON stages.entry_id = se.id
      WHERE se.user_id = $1
        AND se.entry_date >= CURRENT_DATE - INTERVAL '1 day' * $2
        AND se.duration_in_seconds > 0
      ORDER BY se.entry_date DESC`,
      [userId, days]
    );
    return result.rows;

Comment on lines +422 to +451
async function getMCTQStats(userId) {
log('info', `Getting MCTQ stats for user ${userId}`);

const profile = await sleepScienceRepository.getSleepProfile(userId);
const latestCalc = await sleepScienceRepository.getLatestCalculation(userId);
const dayClassifications = await sleepScienceRepository.getDayClassifications(userId);

return {
profile: profile
? {
baselineSleepNeed: Number(profile.baseline_sleep_need) || DEFAULT_SLEEP_NEED_HOURS,
method: profile.sleep_need_method || 'default',
confidence: profile.sleep_need_confidence || 'low',
basedOnDays: profile.sleep_need_based_on_days || 0,
lastCalculated: profile.sleep_need_last_calculated,
sdWorkday: profile.sd_workday_hours ? Number(profile.sd_workday_hours) : null,
sdFreeday: profile.sd_freeday_hours ? Number(profile.sd_freeday_hours) : null,
socialJetlag: profile.social_jetlag_hours ? Number(profile.social_jetlag_hours) : null,
}
: null,
latestCalculation: latestCalc,
dayClassifications: dayClassifications.map((d) => ({
dayOfWeek: d.day_of_week,
classifiedAs: d.classified_as,
meanWakeHour: d.mean_wake_hour ? Number(d.mean_wake_hour) : null,
varianceMinutes: d.variance_minutes ? Number(d.variance_minutes) : null,
sampleCount: d.sample_count,
})),
};
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The getMCTQStats function returns an object where the profile sub-object has its properties converted from snake_case to camelCase, but the latestCalculation sub-object is returned with its original snake_case properties from the database. This inconsistency makes it harder to define strong types on the frontend and can lead to bugs.

To ensure a consistent API response structure, please transform the latestCalc object to use camelCase properties, just as you've done for the profile object.

@CodeWithCJ CodeWithCJ merged commit 030e96f into main Feb 25, 2026
6 checks passed
@CodeWithCJ CodeWithCJ deleted the pr-740-fix-format branch February 26, 2026 19:50
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