Back to blog
May 9, 20265 min readWorkCapacity

Power Oracle now computes fitness across broad time and modal domains

New `fitness_score`, `modality_curves`, and `coverage_matrix` fields on the athlete curve endpoint. The geometric mean of per-modality work-capacity AUCs, sampled via cumulative chains.

power-oraclefitnessmeasurementcrossfit

Power Oracle now computes a Glassman-faithful fitness score directly.

The practical takeaway: if you call GET /v1/athletes/{uuid}/curve for an athlete with sufficient data, the response now includes a single-number fitness_score in joules, plus per-modality power-duration curves and a 3×3 coverage matrix. The legacy points, envelope_points, and work_capacity_auc fields are unchanged — backward-compatible — and the new fields populate in addition.

This is the same API, the same compute event store, and the same physics. We just answer a more honest question now.

What the directive demands

Greg Glassman defined fitness as work capacity across broad time and modal domains.

That sentence has been the directive of WorkCapacity since day one. It implies two breadths — time (short / medium / long efforts) and modal (cardiovascular, gymnastics, weightlifting). A score that ignores either one isn't measuring fitness; it's measuring a slice of it.

Until this release, Power Oracle's work_capacity_auc only had the time axis. Modal coverage wasn't modeled at all. An athlete who only does heavy 30-second cleans could post a comparable work_capacity_auc to an athlete with broad modal coverage. Glassman would call those two athletes very different. The number couldn't tell.

A user noticed an adjacent symptom first: deleting a personal-best 60-second effort increased their work capacity by 20×. That was a separate bug — the envelope rule was inverted, requiring longer-duration workouts to beat shorter ones. We shipped the fix on its own. Then we tackled the bigger gap.

Three modalities × three time domains

The new model treats Glassman's two breadths as a 3×3 coverage matrix:

Short (≤5 min)Medium (5-20 min)Long (>20 min)
Monostructuralrun, row, bike
Gymnasticspull-ups, squats, burpees
Weightliftingcleans, snatches, deadlifts

Every movement in the Power Oracle library is now classified into one of those three modalities. A specialist has a few cells lit up brilliantly and the rest empty. A generalist has data in every cell. The score has to reward the generalist; that's the directive.

Why geometric mean

Adding the cells together rewards the specialist — one massive cell drowns out the gaps. Taking the minimum punishes any gap brutally. Neither matches "broad" in the sense Glassman meant.

The geometric mean does. It collapses to zero if any modality is empty, scales with breadth, and refuses to overweight any single cell. The Fitness Score is computed as:

fitness_score = ³√(AUC_monostructural × AUC_gymnastics × AUC_weightlifting)

(AUC = area under the power-duration curve, in joules. Higher is better; more work sustained across more durations means a larger area.)

Reported in joules. Honest physical units, not an arbitrary 0-100. Mixed-modality work (Fran-style chains spanning multiple modalities) is held in a mixed bucket — exposed in the response, but not part of the score because it can't be cleanly attributed to any single modality.

The score is also monotone non-decreasing in data volume holding underlying performance fixed. Logging more workouts in a modality you'd previously left empty cannot lower your score. We test this property explicitly because the alternative — penalizing an athlete for finally trying a new modality — would invert the directive.

Sampling fitness from CrossFit data

Cycling has a head start: continuous power telemetry. The standard Mean Maximal Power curve answers, for each duration t, what's the highest average power sustained for any t-second window. You can't game it by sprinting once — at longer t, the average has to include everything that came after.

CrossFit doesn't have continuous power. It has splits.

We adapted cycling's formulation: walk through a workout's splits in order, group them into chains (consecutive splits separated by ≤30 seconds of rest), and at each split boundary plot a cumulative point:

X = cumulative active-work seconds since chain start
Y = cumulative work / cumulative active seconds

A 5-round-for-time workout becomes a chain of cumulative points that monotonically degrades from short-fresh-fast to long-fatigued-slower. A "log workout" with no splits — just a total time and a movement list — becomes a single-point chain. Less data, but the same math, and a valid lower bound on sustainable power at that duration. Most CrossFit data in the world is total-time-only; gating fitness measurement on real-time tracking would have eliminated most of the addressable population.

The Pareto envelope across all chains, per modality, is what feeds the per-modality AUC.

What the response looks like

Existing fields on GET /v1/athletes/{uuid}/curve are unchanged. The new fields slot in alongside:

{
  "athlete_uuid": "…",
  "total_workouts": 47,

  "points":           [  ],   // unchanged
  "envelope_points":  [  ],   // unchanged
  "work_capacity_auc": {  },  // unchanged

  "fitness_score": {
    "value": 412800.0,
    "unit": "J",
    "components": {
      "monostructural_auc": { "value": 168200, "unit": "J" },
      "gymnastics_auc":     { "value": 124800, "unit": "J" },
      "weightlifting_auc":  { "value": 198100, "unit": "J" }
    },
    "coverage": {
      "qualifying_modalities": ["monostructural", "gymnastics", "weightlifting"],
      "total_chains": 81,
      "chains_per_modality": {
        "monostructural": 22, "gymnastics": 18,
        "weightlifting":  29, "mixed": 12
      }
    }
  },

  "modality_curves": {
    "monostructural": { "envelope_points": [  ], "auc": {  } },
    "gymnastics":     {  },
    "weightlifting":  {  },
    "mixed":          {  }
  },

  "coverage_matrix": {
    "cells": [
      {
        "modality": "monostructural", "time_domain": "short",
        "count": 8, "best_avg_power_watts": 612,
        "auc_contribution": { "value": 56400, "unit": "J" }
      },

    ]
  }
}

If an athlete has fewer than five chains in a modality, that modality is excluded from qualifying_modalities and not factored into fitness_score.value. With zero qualifying modalities, fitness_score.value is null. With one or two, the geometric mean is computed over what qualifies and coverage makes the partial state explicit.

Limitations we're being honest about

  1. Mixed-modality chains are held separately and don't contribute to the per-modality score. The data is preserved; we just can't attribute it cleanly. Per-movement modeling within mixed chains would fix this and is harder.
  2. Active-vs-elapsed time ambiguity for log-workouts — without split boundaries, we can't separate work from rest. A paced workout looks identical to a continuous one. Consistent bias across log-workout data, doesn't pollute relative ranking, but does suppress absolute scores slightly for athletes who pace strategically.
  3. No cohort comparison yet. The score is meaningful within an athlete's own trajectory. Cross-athlete percentile distributions are a future aggregation feature.
  4. No per-movement drill-down endpoint yet. The data model preserves enough metadata that this is a future API addition, not a re-architecture.

The full design — every decision, every tradeoff, every open question — lives at issue #77. If you're consuming the API and want to weigh in on the open questions (mixed-modality strategy, cold-start floor mechanics, score units), the issue is the place.

We'd rather ship an honest approximation of Glassman's directive than keep returning a confident answer to the wrong question. This is the first Power Oracle release where work_capacity_auc plus fitness_score together meaningfully cover both of his "broad domains."