Files
phaseflow/specs/cycle-tracking.md
Petru Paler 6a8d55c0b9 Document spec gaps: auth, phase scaling, observability, testing
Address 21 previously undefined behaviors across specs:

- Authentication: Replace email/password with OIDC (Pocket-ID)
- Cycle tracking: Add fixed-luteal phase scaling formula with examples
- Calendar: Document period logging behavior (preserve predictions)
- Garmin: Clarify connection is required (no phase-only mode)
- Dashboard: Add UI states, dark mode, onboarding, accessibility
- Notifications: Document timezone batching approach
- New specs: observability.md (health, metrics, logging)
- New specs: testing.md (unit + integration strategy)
- Main spec: Add backup/recovery, known limitations, API updates

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-11 07:49:56 +00:00

3.9 KiB

Cycle Tracking Specification

Job to Be Done

When I log my period start date, I want the app to calculate my current cycle day and phase, so that training and nutrition guidance is accurate.

Core Concepts

Cycle Day

Day 1 = first day of menstruation.

Calculation:

cycleDay = ((currentDate - lastPeriodDate) mod cycleLength) + 1

Default cycle length: 31 days (configurable per user).

Cycle Phases

Phase boundaries scale based on cycle length using a fixed luteal, variable follicular approach. The luteal phase (14 days) is biologically consistent; the follicular phase expands or contracts with cycle length.

Phase Calculation Formula:

Given cycleLength (user-configurable, default 31):

Phase Days Weekly Limit Training Type
MENSTRUAL 1-3 30 min Gentle rebounding only
FOLLICULAR 4 to (cycleLength - 16) 120 min Strength + rebounding
OVULATION (cycleLength - 15) to (cycleLength - 14) 80 min Peak performance
EARLY_LUTEAL (cycleLength - 13) to (cycleLength - 7) 100 min Moderate training
LATE_LUTEAL (cycleLength - 6) to cycleLength 50 min Gentle rebounding ONLY

Examples by Cycle Length:

Phase 28-day 31-day 35-day
MENSTRUAL 1-3 1-3 1-3
FOLLICULAR 4-12 4-15 4-19
OVULATION 13-14 16-17 20-21
EARLY_LUTEAL 15-21 18-24 22-28
LATE_LUTEAL 22-28 25-31 29-35

API Endpoints

POST /api/cycle/period

Log a new period start date.

Request Body:

{
  "startDate": "2024-01-10"
}

Behavior:

  1. Update user.lastPeriodDate
  2. Create PeriodLog record for history
  3. Recalculate today's cycle day and phase

GET /api/cycle/current

Returns current cycle information.

Response:

{
  "cycleDay": 12,
  "phase": "FOLLICULAR",
  "daysUntilNextPhase": 3,
  "phaseConfig": {
    "weeklyLimit": 120,
    "dailyAvg": 17,
    "trainingType": "Strength + rebounding"
  }
}

Cycle Utilities (src/lib/cycle.ts)

Functions:

  • getCycleDay(lastPeriodDate, cycleLength, currentDate) - Calculate day in cycle
  • getPhase(cycleDay) - Determine current phase
  • getPhaseConfig(phase) - Get phase configuration
  • getPhaseLimit(phase) - Get weekly intensity limit

Constants:

  • PHASE_CONFIGS - Array of phase definitions

Period History

PeriodLog Schema

interface PeriodLog {
  id: string;
  user: string;
  startDate: Date;
  created: Date;
}

History Display (/history)

  • List of all logged period dates
  • Calculated cycle lengths between periods
  • Average cycle length over time
  • Ability to edit/delete entries

Configurable Cycle Length

Users with irregular cycles can adjust:

  • Default: 31 days
  • Range: 21-45 days
  • Affects phase day boundaries per formula above

Known Limitations

The following scenarios are out of scope for MVP:

  • Pregnancy - Cycle tracking pauses; app not designed for pregnancy
  • Menopause - Irregular/absent cycles not supported
  • Hormonal birth control - May suppress or alter natural cycle phases
  • Anovulatory cycles - App assumes ovulation occurs; no detection for skipped ovulation

Users in these situations should consult healthcare providers for personalized guidance.

Success Criteria

  1. Cycle day resets to 1 on period log
  2. Phase transitions at correct day boundaries
  3. Weekly limits adjust per phase automatically
  4. History shows all logged periods

Acceptance Tests

  • getCycleDay returns 1 on period start date
  • getCycleDay handles cycle rollover correctly
  • getPhase returns correct phase for each day range
  • getPhase scales correctly for 28-day cycle
  • getPhase scales correctly for 35-day cycle
  • POST /api/cycle/period updates user record
  • GET /api/cycle/current returns accurate phase info
  • Days beyond cycle length default to LATE_LUTEAL