2025 05 17.RuntimeMetricsDeepDive - SergeiGolos/wod-wiki GitHub Wiki
This document provides a detailed explanation of the runtime metrics system within Wod.Wiki. It covers how metrics are defined, generated, aggregated, tracked internally, and displayed in the user interface. Understanding this system is crucial for developers working on the runtime engine, UI components related to results display, or extending workout tracking capabilities.
Runtime metrics are the quantitative record of a user's performance during a workout. They provide valuable feedback, enable progress tracking, and form the basis for analytics. Accurate and comprehensive metrics are key to the user experience.
The core data structure for representing metrics is RuntimeMetric
.
-
RuntimeMetric
:-
effort: string
: The name or description of the exercise or effort. -
repetitions: number
: The count of repetitions performed. -
resistance?: MetricValue
: Optional. The weight or resistance used. -
distance?: MetricValue
: Optional. The distance covered.
-
-
MetricValue
:-
value: number
: The numerical value of the metric. -
unit: string
: The unit of measurement (e.g., "kg", "lb", "m", "km", "ft").
-
This structure ensures consistency in how different types of performance data are handled throughout the application.
-
Initialization:
RuntimeMetric
objects are typically initialized fromPrecompiledNode
sources (viaFragment
s likeEffortFragment
,RepFragment
, etc.) using reader functions (e.g.,getMetrics
). -
RuntimeBlock
Role: TheRuntimeBlock
base class plays a key role. During its initialization (often in the constructor oronEnter
), it creates aMetricsContext
for the block instance. This might involve aMetricsFactory
for consistent creation. -
MetricsContext
: Each block instance possesses aMetricsContext
(this.context.metrics
) which holds:- The
RuntimeMetric
data for the block. - Is governed by a
MetricsRelationshipType
which defines how its metrics relate to its parent and child blocks.
- The
-
MetricsRelationshipType
:-
ADD
: Metrics from child blocks are summed up and added to this block's metrics (e.g.,RootBlock
,TimedGroupBlock
). -
MULTIPLY
: Metrics from this block are multiplied by a factor (e.g., number of rounds in aRepeatingBlock
). TheRepeatingBlock
itself might also generate "Round Complete" type metrics. -
INHERIT
: The block directly uses or passes through the metrics defined at its level, typically from its sourcePrecompiledNode
.
-
-
Creation & Management: A
ResultSpan
is created for each execution instance of a block. This is typically managed by theBlockContext
under the direction ofRuntimeBlock
's lifecycle methods (onEnter
,onNext
,onLeave
). -
Enhancement: Concrete block types override a method within
RuntimeBlock
(e.g.,enhanceResultSpan
or similar, or directly inonLeave
) to populate theirResultSpan
with specific metrics derived during their execution, or by aggregating metrics from their children (obtained by callingchild.getMetrics()
). -
Emission: Upon block completion (usually in
onLeave
), the finalizedResultSpan
(containing duration, effort, reps, resistance, distance, etc.) is emitted, typically via aWriteResultAction
. This action makes the metrics available to the UI and for aggregation by parent blocks.
-
RepeatingBlock
(Handles Rounds): TheRepeatingBlock
'sMetricsContext
relationship is typicallyMULTIPLY
if it's simply repeating a single child or a composed set of children. However, its behavior becomes more nuanced based on thegroupType
implied by its structure or childLapFragment
s (+
,-
operators).-
Compose (
groupType: 'compose'
, from+
operator):- Conceptual Aggregation: All child blocks within the compose group are executed as a single unit for each round of the
RepeatingBlock
. - Metrics: For one round of the
RepeatingBlock
, the metrics from all composed children are typically ADDED together. This sum (representing one full composed iteration) is then MULTIPLIED by the total number of rounds in theRepeatingBlock
. - Example:
(3) rounds of (A + B)
. Metrics(A) and Metrics(B) are ADDED. (Metrics(A) + Metrics(B)) is then MULTIPLIED by 3.
- Conceptual Aggregation: All child blocks within the compose group are executed as a single unit for each round of the
-
Round-Robin (
groupType: 'round'
, from-
operator):- Conceptual Aggregation: Each round of the
RepeatingBlock
cycles through one child from the round-robin set. - Metrics: The
RepeatingBlock
's total metrics would be the SUM of metrics from each child executed across all rounds. IfRepeatingBlock
itself has aMetricsRelationshipType
ofADD
, it would sum theResultSpan
metrics from each child activation. - Example:
(3) rounds of (A - B - C)
. This means A runs, then B, then C (that's 1 parent round). Then A, B, C again (2nd parent round), etc. TheRepeatingBlock
's total metrics = Metrics(A1)+Metrics(B1)+Metrics(C1) + Metrics(A2)+Metrics(B2)+Metrics(C2) + Metrics(A3)+Metrics(B3)+Metrics(C3).
- Conceptual Aggregation: Each round of the
-
Repeat (Default,
groupType: 'repeat'
):- Conceptual Aggregation: Each child element individually completes all rounds of the parent
RepeatingBlock
before the next child begins. - Metrics: This is the most complex. If child A (itself potentially a
RepeatingBlock
or a simpleEffortBlock
) completes all its parent-defined rounds, its total metrics are calculated. Then child B does the same. The parentRepeatingBlock
would then SUM the total metrics from child A's full execution and child B's full execution. - Example:
(Parent: 2 rounds) of (ChildA: (3 rounds of X) then ChildB: 5 Y)
.- ChildA runs (3 rounds of X) for ParentRound1, then ChildA runs (3 rounds of X) for ParentRound2. Total for A = Metrics(3X) * 2 (if parent relationship is MULTIPLY, or just Metrics(3X) + Metrics(3X) if ADD).
- Then ChildB runs 5 Y for ParentRound1, then ChildB runs 5 Y for ParentRound2. Total for B = Metrics(5Y) * 2 (or summed).
- The overall
RepeatingBlock
metrics would be Total(A) + Total(B).
- Conceptual Aggregation: Each child element individually completes all rounds of the parent
-
-
TimedGroupBlock
(e.g., AMRAPs/EMOMs):- Typically has a
MetricsRelationshipType
ofADD
. - It sums the metrics from all
ResultSpan
s generated by its child blocks during its timed duration. - If it contains children that are part of a compose (
+
) or round-robin (-
) structure (though less common for a top-level AMRAP container), those sub-groups would aggregate their metrics as described above, and their total would then be added to theTimedGroupBlock
's sum.
- Typically has a
-
RuntimeStack
: As blocks are pushed onto and popped from theRuntimeStack
, theirBlockContext
(containingMetricsContext
andResultSpan
data) is managed. -
Data Flow:
- Raw fragments are parsed into
PrecompiledNode
s. -
RuntimeJit
strategies compilePrecompiledNode
s intoIRuntimeBlock
s. -
RuntimeBlock
initializesMetricsContext
for each block, using initial metrics from the node. - As a block executes (
enter
,next
,leave
phases):- It may update its own
MetricsContext
based on its logic. - It may trigger child blocks.
- Upon completion of a child, the parent block may aggregate the child's
ResultSpan
metrics into its ownMetricsContext
according to itsMetricsRelationshipType
.
- It may update its own
- When a block itself completes, its
enhanceResultSpan()
method finalizes itsResultSpan
, which is then stored/emitted.
- Raw fragments are parsed into
-
BlockContext
: Holds theMetricsContext
and the list ofResultSpan
s generated by a block (if it can run multiple times or has sub-elements that generate spans).
The UI presents metrics to the user in a clear and understandable manner.
-
Key UI Components:
-
WodResults
: Displays a summary or detailed breakdown of completed workout metrics. -
WodTable
: May show metrics in a tabular format, possibly per round or per exercise. -
EventsView
: Could show a log ofResultSpan
emissions, providing a granular view of metrics generation. -
WodResultsSectionHead
: Shows summary metrics with even spacing.
-
-
Visual Cues:
- Icons are used to differentiate metric types (e.g., 💪 for resistance, 📏 for distance).
- Units are always displayed alongside values.
-
Real-time Updates: Some UI elements might update metrics in real-time as blocks complete and emit
ResultSpan
s, while others might update only at the end of the workout. -
Clarity: The UI should clearly distinguish between metrics for individual efforts, rounds, and the total workout.
The group operators affect how metrics are aggregated:
-
Round-Robin Operator (
-
): Metrics for each child item are typically logged individually as they are completed in their respective turns within the parent round. Aggregation at the parent level would sum these individualResultSpan
s. -
Compose Operator (
+
): All children are executed as a single unit per parent round. TheMetricsContext
of the parent (or aTimedGroupBlock
if applicable) would useADD
relationship to sum metrics from all children for that composed unit. -
Repeat (No Operator): Each child individually goes through all parent rounds. The
RepeatingBlock
(parent) would likely use aMULTIPLY
relationship for each child's metrics over the rounds, or sum theResultSpan
s from each child's full set of repetitions.
The specific IRuntimeBlock
and IRuntimeBlockStrategy
implementations for these group operations will define the precise metric aggregation logic.
-
Parsing:
- Parent
PrecompiledNode
(Repeating, 3 rounds)- Child
PrecompiledNode
("Push Press", 5 reps, 60kg) ->EffortFragment
,RepFragment
,ResistanceFragment
- Child
PrecompiledNode
("10 Cal Row") ->EffortFragment
,RepFragment
(value 10, unit "Cal")
- Child
- Parent
-
Runtime - Round 1:
-
RepeatingBlock
(3 rounds)enters
.MetricsContext
(MULTIPLY). -
EffortBlock
("Push Press")enters
.MetricsContext
(INHERIT).- Base metrics: { effort: "Push Press", reps: 5, resistance: {value: 60, unit: "kg"} }.
-
EffortBlock
leaves
.ResultSpan
_PP1 created with these metrics.
-
EffortBlock
("10 Cal Row")enters
.MetricsContext
(INHERIT).- Base metrics: { effort: "Row", reps: 10, unit: "Cal" }.
-
EffortBlock
leaves
.ResultSpan
_Row1 created.
-
RepeatingBlock
'sMetricsContext
might aggregateResultSpan
_PP1 andResultSpan
_Row1 for Round 1 summary if needed, or just store them.
-
-
Runtime - Rounds 2 & 3: Similar flow, creating
ResultSpan
_PP2,ResultSpan
_Row2,ResultSpan
_PP3,ResultSpan
_Row3. -
RepeatingBlock
Completion:- When
RepeatingBlock
leaves
, itsenhanceResultSpan()
is called. - It calculates its total metrics. This could be:
- Sum of all child
ResultSpan
s: (PP1+Row1) + (PP2+Row2) + (PP3+Row3). - Or, if its base metric was "1 round", it might be { effort: "Rounds", reps: 3 } and the child spans are associated.
- Sum of all child
- A final
ResultSpan
_RepeatingBlock is generated.
- When
-
UI Display:
- Could show:
- Total workout summary from
RootBlock
(which aggregatedResultSpan
_RepeatingBlock). - Per-round breakdown (PP1+Row1, PP2+Row2, PP3+Row3).
- Individual exercise contributions (total Push Press reps/weight, total Row cals).
- Total workout summary from
- Could show:
- Adding New Metric Types:
- Extend the
RuntimeMetric
interface intimer.types.ts
. - Update
MetricValue
if necessary. - Modify parser/lexer and fragment types to capture the new data.
- Update reader functions (like
getMetrics
) to populate the new metric fields. - Adjust
MetricsContext
and relevant blockenhanceResultSpan()
methods to handle/aggregate the new metric. - Update UI components to display the new metric type.
- Extend the