🔍 Feature Engineering Playground
Mix and match different time-series metrics as RGB channels to discover hidden patterns. The same workout data reveals different "fingerprints" from different perspectives! NEW: Use the Alpha channel to encode a fourth metric (like Cadence), data quality/confidence, or global RPE (Rated Perceived Exertion)!
🎯 The Feature Engineering Magic
This 8x8 "visual fingerprint" transforms 64 minutes of time-series data into a 192-element searchable vector. The same workout can reveal completely different patterns when viewed through different RGB channel combinations. Common combinations (like Power/Cadence/HR) are pre-indexed for blazing-fast searches, while others use intelligent pre-filtering. Try changing the channels above to discover hidden similarities!
R: Heart Rate (50-200)
G: Calories Per Min (0-20)
B: Speed Kph (0-15)
Note the workout_vector field was added automatically during generation.
(And power/cadence are now included!)
{
"_id": "workout_rad_81",
"time_series": {
"heart_rate": [
107.21,
116.61,
120.99,
124.73,
122.65,
160.04,
167.12,
166.98,
177.53,
176.78,
179.79,
185.07,
184.51,
182.17,
185.45,
181.13,
183.26,
181.93,
182.39,
182.14,
175.71,
170.02,
170.7,
160.52,
162.22,
154.3,
150.97,
145.95,
137.5,
127.6,
121.0,
113.38,
108.32,
102.7,
100.94,
92.4,
88.12,
80.97,
83.04,
74.95,
73.88,
68.02,
67.26,
65.24,
62.21,
68.41,
62.85,
65.12,
71.87,
73.48,
69.42,
75.89,
79.69,
76.37,
83.5,
93.67,
97.72,
95.57,
108.39,
96.93,
103.03,
113.41,
116.12,
117.66
],
"calories_per_min": [
7.19,
6.97,
7.62,
8.05,
8.78,
9.65,
10.4,
9.22,
11.06,
10.63,
10.62,
10.5,
9.91,
11.22,
10.19,
11.89,
11.11,
11.81,
9.99,
11.37,
10.0,
9.89,
9.22,
9.31,
8.8,
8.61,
8.08,
7.89,
8.4,
7.84,
7.5,
7.42,
5.59,
6.01,
6.57,
5.42,
4.95,
5.06,
4.52,
4.15,
3.71,
3.41,
4.32,
3.89,
2.38,
2.61,
2.13,
3.76,
3.42,
3.05,
3.73,
2.86,
2.81,
3.18,
4.74,
3.71,
4.75,
4.15,
4.84,
5.45,
5.51,
6.31,
6.32,
6.36
],
"speed_kph": [
2.11,
2.26,
2.23,
2.02,
2.33,
5.12,
5.09,
5.01,
5.05,
5.29,
5.05,
5.21,
5.23,
5.35,
5.21,
5.17,
5.14,
5.16,
5.38,
5.39,
5.35,
5.29,
5.08,
5.34,
5.22,
5.38,
5.21,
5.17,
5.22,
5.14,
5.15,
5.21,
5.04,
5.28,
5.01,
5.26,
5.01,
5.16,
5.11,
5.25,
5.01,
5.4,
5.27,
5.37,
5.02,
5.22,
5.08,
5.38,
5.16,
5.08,
5.12,
5.39,
5.06,
5.22,
5.19,
5.34,
5.28,
5.36,
5.1,
1.43,
1.41,
1.37,
1.24,
1.37
],
"power": [
195.57,
196.34,
200.53,
205.1,
216.14,
210.35,
216.33,
214.9,
222.95,
221.01,
222.09,
219.15,
215.63,
211.35,
214.56,
215.99,
199.98,
196.46,
204.26,
199.58,
189.27,
180.36,
187.55,
177.41,
172.02,
174.37,
165.18,
151.06,
149.49,
148.5,
146.12,
139.18,
134.9,
127.01,
120.96,
125.66,
121.55,
114.22,
119.63,
120.1,
112.68,
112.32,
121.4,
118.39,
122.25,
128.05,
124.86,
131.94,
131.78,
127.54,
133.97,
135.55,
143.86,
146.83,
152.29,
160.45,
159.11,
177.67,
169.78,
179.61,
191.56,
195.71,
186.78,
203.61
],
"cadence": [
85.08,
86.86,
87.61,
86.18,
85.17,
87.07,
86.92,
85.38,
86.36,
85.44,
0.0,
0.0,
0.0,
0.0,
0.0,
87.21,
86.88,
85.35,
85.57,
85.57,
87.15,
86.32,
87.26,
85.56,
87.6,
87.49,
87.19,
85.29,
85.94,
87.17,
85.52,
85.32,
87.27,
86.22,
86.48,
86.59,
86.52,
87.8,
87.53,
87.86,
0.0,
0.0,
0.0,
0.0,
0.0,
85.61,
87.69,
85.79,
85.3,
85.15,
85.13,
87.43,
87.78,
85.35,
87.01,
86.92,
87.51,
87.0,
87.5,
86.07,
85.94,
85.37,
86.97,
86.91
]
},
"data_quality": [
255,
255,
255,
255,
255,
255,
0,
0,
255,
255,
0,
0,
0,
0,
0,
255,
255,
0,
255,
255,
255,
255,
255,
255,
0,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
0,
0,
0,
0,
0,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
255,
0,
255,
255,
255
],
"rpe": 8.0,
"start_time": "2025-10-27 10:11:00",
"workout_type": "Yoga",
"session_tag": "Race Day",
"post_session_notes": {
"hydration_ml": 2166,
"notes": "Felt good"
},
"gear_used": [
{
"item": "shoes_v3",
"kilometers": 102.0
},
{
"item": "hrm_strap",
"battery_life_percent": 42
}
],
"ai_classification": "Pending Analysis",
"ai_summary": "Click 'Generate AI Summary' to analyze",
"llm_analysis_prompt": "(not yet generated)",
"workout_vector": "[97.00... 191 more elements]",
"workout_vector_power_cadence_hr": [
124,
180,
97,
125,
184,
113,
127,
186,
120,
130,
183,
127,
137,
180,
123,
134,
185,
187,
137,
184,
199,
136,
181,
198,
142,
183,
216,
140,
181,
215,
141,
0,
220,
139,
0,
229,
137,
0,
228,
134,
0,
224,
136,
0,
230,
137,
185,
222,
127,
184,
226,
125,
181,
224,
130,
181,
225,
127,
181,
224,
120,
185,
213,
114,
183,
204,
119,
185,
205,
113,
181,
187,
109,
186,
190,
111,
185,
177,
105,
185,
171,
96,
181,
163,
95,
182,
148,
94,
185,
131,
93,
181,
120,
88,
181,
107,
85,
185,
99,
80,
183,
89,
77,
183,
86,
80,
184,
72,
77,
183,
64,
72,
186,
52,
76,
186,
56,
76,
186,
42,
71,
0,
40,
71,
0,
30,
77,
0,
29,
75,
0,
25,
77,
0,
20,
81,
181,
31,
79,
186,
21,
84,
182,
25,
84,
181,
37,
81,
180,
39,
85,
180,
33,
86,
185,
44,
91,
186,
50,
93,
181,
44,
97,
184,
56,
102,
184,
74,
101,
185,
81,
113,
184,
77,
108,
185,
99,
114,
182,
79,
122,
182,
90,
124,
181,
107,
119,
184,
112,
129,
184,
115
],
"workout_vector_power_speed_hr": [
124,
35,
97,
125,
38,
113,
127,
37,
120,
130,
34,
127,
137,
39,
123,
134,
87,
187,
137,
86,
199,
136,
85,
198,
142,
85,
216,
140,
89,
215,
141,
85,
220,
139,
88,
229,
137,
88,
228,
134,
90,
224,
136,
88,
230,
137,
87,
222,
127,
87,
226,
125,
87,
224,
130,
91,
225,
127,
91,
224,
120,
90,
213,
114,
89,
204,
119,
86,
205,
113,
90,
187,
109,
88,
190,
111,
91,
177,
105,
88,
171,
96,
87,
163,
95,
88,
148,
94,
87,
131,
93,
87,
120,
88,
88,
107,
85,
85,
99,
80,
89,
89,
77,
85,
86,
80,
89,
72,
77,
85,
64,
72,
87,
52,
76,
86,
56,
76,
89,
42,
71,
85,
40,
71,
91,
30,
77,
89,
29,
75,
91,
25,
77,
85,
20,
81,
88,
31,
79,
86,
21,
84,
91,
25,
84,
87,
37,
81,
86,
39,
85,
87,
33,
86,
91,
44,
91,
86,
50,
93,
88,
44,
97,
88,
56,
102,
90,
74,
101,
89,
81,
113,
91,
77,
108,
86,
99,
114,
24,
79,
122,
23,
90,
124,
23,
107,
119,
21,
112,
129,
23,
115
],
"workout_vector_speed_cadence_hr": [
35,
180,
97,
38,
184,
113,
37,
186,
120,
34,
183,
127,
39,
180,
123,
87,
185,
187,
86,
184,
199,
85,
181,
198,
85,
183,
216,
89,
181,
215,
85,
0,
220,
88,
0,
229,
88,
0,
228,
90,
0,
224,
88,
0,
230,
87,
185,
222,
87,
184,
226,
87,
181,
224,
91,
181,
225,
91,
181,
224,
90,
185,
213,
89,
183,
204,
86,
185,
205,
90,
181,
187,
88,
186,
190,
91,
185,
177,
88,
185,
171,
87,
181,
163,
88,
182,
148,
87,
185,
131,
87,
181,
120,
88,
181,
107,
85,
185,
99,
89,
183,
89,
85,
183,
86,
89,
184,
72,
85,
183,
64,
87,
186,
52,
86,
186,
56,
89,
186,
42,
85,
0,
40,
91,
0,
30,
89,
0,
29,
91,
0,
25,
85,
0,
20,
88,
181,
31,
86,
186,
21,
91,
182,
25,
87,
181,
37,
86,
180,
39,
87,
180,
33,
91,
185,
44,
86,
186,
50,
88,
181,
44,
88,
184,
56,
90,
184,
74,
89,
185,
81,
91,
184,
77,
86,
185,
99,
24,
182,
79,
23,
182,
90,
23,
181,
107,
21,
184,
112,
23,
184,
115
],
"experiment_id": "data_imaging"
}
This qualitative summary is generated by an LLM (OpenAI API). Crucially, the AI does not perform the math. This app first uses Python (NumPy) to deterministically calculate all key metrics (Avg HR, speed deviation, etc.). The AI is then given these pre-computed facts (viewable via 'Inspect LLM Prompt') to provide a qualitative, radiologist-style interpretation. It must be manually generated by clicking the button.
Click 'Generate AI Summary' to analyze
Using a $vectorSearch pipeline in MongoDB Atlas with index
data_imaging_workout_vector_index
to find the 3 workouts most similar to this one's 192-element vector (excluding itself).
Higher score means more similar pattern.
Why VoyageAI Reranking?
We use a two-stage retrieval pipeline for the most accurate "Workout Twins":
This combines the speed of vector search with the deep semantic understanding of VoyageAI's reranker, giving you more accurate and relevant workout matches.
💡 Feature Engineering Insight: The vector below uses the indexed default channels (HR, Calories, Speed) for fast vector search. But different channel combinations reveal different patterns! Common combinations like Power/Cadence/HR are also indexed for blazing-fast searches. Try selecting Power, Cadence, or other metrics above and click "Update View & Find Similar" to discover workouts that are similar in completely different ways.
🎯 The Problem: Vector search is amazing for fuzzy, "vibe-based" questions ("find more workouts like this"). But it's terrible at precise, factual questions ("...but only the ones on a Monday").
✨ The Solution: Hybrid Search combines vector search (vibe) with text search (facts) and filters (exact matches). This is how people actually think - we blend fuzzy concepts ("vibe," "felt strong") with concrete facts ("Outdoor Run").
💡 Example: "Show me workouts with a vibe similar to my last 'Tempo Run' (vector search)... but only show me ones where my notes said I 'felt strong' (text search)... and where the workout type was 'Outdoor Run' (filter)."
🎯 The Problem: How do you ask the database for "a harder version of this yoga session"? You can't type that into a search bar. But with vector arithmetic, we can discover abstract concepts like "effort" and use them to find workouts that match abstract queries!
Vector(Hard Tempo Run) - Vector(Easy Recovery Run) = "Effort Vector"
Vector(My Easy Yoga Session) + "Effort Vector" = ???
🚀 Why this is genius: We've stopped just retrieving data. We are now exploring the latent space of our workouts. We can ask abstract questions like "what's the halfway point between a run and a bike ride?" or "show me a workout that's 50% less intense than this one."
Automatically find harder versions using neighbors:
💡 Click below to automatically use neighbors
Automatically find easier versions using neighbors:
💡 Click below to automatically use neighbors
Scale this workout's intensity to find variations:
This is where the magic begins: We start with raw time-series data—64 data points per metric, representing one workout session. Each metric (heart rate, calories, speed, power, cadence) tells its own story. The challenge? How do we make this searchable? How do we find similar patterns without comparing every single data point?
💡 Feature Engineering Tip: Notice how we have 5 different metrics but only use 3 at a time. This gives us 5×4×3 = 60 different ways to view the same workout! Each combination reveals different patterns.
Each 64-point array is reshaped into an 8x8 grid. Think of it like taking a long string of 64 characters and wrapping it every 8 characters to form an 8x8 block of text. This turns temporal patterns into visual textures.
Minutes 0..7 become Row 1 (highlighted). Minutes 8..15 become Row 2, etc.
We now have three separate 8x8 grayscale "channels".
The three 8x8 grayscale grids (channels) generated in Step 2 are combined pixel-by-pixel:
Heart Rate data provides the pixel values for the Red channel.
Calories Per Min data provides the pixel values for the Green channel.
Speed Kph data provides the pixel values for the Blue channel.
These channels are layered together...
...to create the final 8x8 RGB color "fingerprint" image.
The 8x8x3 image (8 rows * 8 columns * 3 color channels = 192 values) is "flattened" row by row into a single list of 192 numbers. This vector numerically captures the visual pattern.
Vector = [Pixel(0,0)R, Pixel(0,0)G, Pixel(0,0)B, Pixel(0,1)R, ..., Pixel(7,7)B]
This is the vector stored in MongoDB Atlas and used for blazing-fast $vectorSearch!
🎯 The Feature Engineering Breakthrough
Why is this clever? We've transformed time-series data (which is hard to search) into a fixed-size vector (which is fast to search). Instead of comparing 64×5=320 data points for every workout, we compare one 192-element vector. This enables:
🚀 Try it: Change the RGB channels above and click "Find Similar" to discover workouts that are similar in power/cadence patterns vs. heart rate/calorie patterns. Same data, completely different insights!
Think of this 8x8 image like a tiny X-ray of your workout's effort structure:
This data provides extra context but isn't part of the vector search. The specific fields here vary depending on the workout_type.
Workout Type: Yoga
Session Tag: Race Day
Post-Session Notes: Hydration: 2166 ml, Notes: Felt good