@vitness/fds-skill 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +55 -0
- package/SKILL.md +441 -0
- package/examples/expected-outputs/simple-exercise-transformed.json +225 -0
- package/examples/mapping-configs/simple-exercise-mapping.json +167 -0
- package/examples/source-schemas/simple-exercise-db.json +65 -0
- package/knowledge/enrichment.md +409 -0
- package/knowledge/mappings.md +354 -0
- package/knowledge/schemas.md +309 -0
- package/package.json +32 -0
- package/prompts/exercise-classification.md +173 -0
- package/prompts/exercise-description.md +178 -0
- package/prompts/exercise-metrics.md +278 -0
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
# FDS AI Enrichment Guide
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
AI enrichment fills gaps when source data lacks fields required by FDS. This guide covers when, how, and what to enrich.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Enrichment Decision Matrix
|
|
10
|
+
|
|
11
|
+
| Source Has | FDS Requires | Action |
|
|
12
|
+
|------------|--------------|--------|
|
|
13
|
+
| `name` | `canonical.name` | Direct map (titleCase) |
|
|
14
|
+
| `name` | `canonical.slug` | Transform (slugify) |
|
|
15
|
+
| - | `canonical.description` | **AI Enrich** |
|
|
16
|
+
| - | `canonical.aliases` | **AI Enrich** |
|
|
17
|
+
| `bodyPart` + `target` | `classification.*` | **AI Enrich** (5 fields) |
|
|
18
|
+
| `target` | `targets.primary` | Registry lookup + AI fallback |
|
|
19
|
+
| `equipment` | `equipment.required` | Registry lookup + AI fallback |
|
|
20
|
+
| - | `metrics.primary` | **AI Enrich** |
|
|
21
|
+
| `gifUrl` | `media` | Transform (toMediaArray) |
|
|
22
|
+
| - | `metadata.*` | Auto-generate |
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Field-Specific Enrichment
|
|
27
|
+
|
|
28
|
+
### 1. canonical.description
|
|
29
|
+
|
|
30
|
+
**When to enrich:** Always when missing
|
|
31
|
+
|
|
32
|
+
**Context needed:**
|
|
33
|
+
- `name` (required)
|
|
34
|
+
- `bodyPart` or `target` (helpful)
|
|
35
|
+
- `equipment` (helpful)
|
|
36
|
+
- `classification` if already determined (helpful)
|
|
37
|
+
|
|
38
|
+
**Output format:**
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"description": "1-3 sentence description of the exercise"
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
**Quality criteria:**
|
|
46
|
+
- 50-150 words
|
|
47
|
+
- Mentions target muscles
|
|
48
|
+
- Explains movement or purpose
|
|
49
|
+
- Suitable for general audience
|
|
50
|
+
- No form instructions (those go in separate field)
|
|
51
|
+
|
|
52
|
+
### 2. canonical.aliases
|
|
53
|
+
|
|
54
|
+
**When to enrich:** When missing and name has common variations
|
|
55
|
+
|
|
56
|
+
**Context needed:**
|
|
57
|
+
- `name` (required)
|
|
58
|
+
|
|
59
|
+
**Output format:**
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"aliases": ["variation 1", "variation 2"]
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Guidelines:**
|
|
67
|
+
- Include common abbreviations (DB, BB, KB)
|
|
68
|
+
- Include regional variations (push-up vs press-up)
|
|
69
|
+
- Include equipment-less name if applicable
|
|
70
|
+
- Max 5 aliases
|
|
71
|
+
- Don't include the main name
|
|
72
|
+
|
|
73
|
+
### 3. classification (All 5 Required Fields)
|
|
74
|
+
|
|
75
|
+
**When to enrich:** Always when any field missing
|
|
76
|
+
|
|
77
|
+
**Context needed:**
|
|
78
|
+
- `name` (required)
|
|
79
|
+
- `bodyPart` (very helpful)
|
|
80
|
+
- `target` muscle (very helpful)
|
|
81
|
+
- `equipment` (helpful)
|
|
82
|
+
|
|
83
|
+
**Output format:**
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"exerciseType": "strength",
|
|
87
|
+
"movement": "push-horizontal",
|
|
88
|
+
"mechanics": "compound",
|
|
89
|
+
"force": "push",
|
|
90
|
+
"level": "intermediate"
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Decision logic:**
|
|
95
|
+
|
|
96
|
+
#### exerciseType
|
|
97
|
+
```
|
|
98
|
+
Is it primarily for building strength/muscle? → "strength"
|
|
99
|
+
Is it for cardiovascular endurance? → "cardio"
|
|
100
|
+
Is it for flexibility/range of motion? → "mobility"
|
|
101
|
+
Is it explosive/jumping? → "plyometric"
|
|
102
|
+
Is it for stability/proprioception? → "balance"
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
#### movement (See SKILL.md for full decision tree)
|
|
106
|
+
Key patterns:
|
|
107
|
+
- Squats, leg press → `squat`
|
|
108
|
+
- Deadlifts, hip hinges → `hinge`
|
|
109
|
+
- Lunges, split squats → `lunge`
|
|
110
|
+
- Bench press, push-ups → `push-horizontal`
|
|
111
|
+
- Overhead press → `push-vertical`
|
|
112
|
+
- Rows → `pull-horizontal`
|
|
113
|
+
- Pull-ups, pulldowns → `pull-vertical`
|
|
114
|
+
- Farmer walks → `carry`
|
|
115
|
+
- Planks, dead bugs → `core-anti-extension`
|
|
116
|
+
- Pallof press → `core-anti-rotation`
|
|
117
|
+
- Russian twists → `rotation`
|
|
118
|
+
- Running, cycling → `locomotion`
|
|
119
|
+
- Curls, extensions → `isolation`
|
|
120
|
+
|
|
121
|
+
#### mechanics
|
|
122
|
+
```
|
|
123
|
+
Multiple joints moving? → "compound"
|
|
124
|
+
Single joint moving? → "isolation"
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
#### force
|
|
128
|
+
```
|
|
129
|
+
Pushing away from body? → "push"
|
|
130
|
+
Pulling toward body? → "pull"
|
|
131
|
+
Holding position? → "static"
|
|
132
|
+
Both/alternating? → "mixed"
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
#### level
|
|
136
|
+
```
|
|
137
|
+
Simple technique, low strength needed? → "beginner"
|
|
138
|
+
Moderate technique or strength? → "intermediate"
|
|
139
|
+
Complex technique or high strength? → "advanced"
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### 4. metrics.primary (and secondary)
|
|
143
|
+
|
|
144
|
+
**When to enrich:** Always when missing
|
|
145
|
+
|
|
146
|
+
**Context needed:**
|
|
147
|
+
- `name` (required)
|
|
148
|
+
- `classification.exerciseType` (very helpful)
|
|
149
|
+
- `classification.movement` (helpful)
|
|
150
|
+
|
|
151
|
+
**Output format:**
|
|
152
|
+
```json
|
|
153
|
+
{
|
|
154
|
+
"primary": { "type": "reps", "unit": "count" },
|
|
155
|
+
"secondary": [
|
|
156
|
+
{ "type": "weight", "unit": "kg" }
|
|
157
|
+
]
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
**Decision logic:**
|
|
162
|
+
```
|
|
163
|
+
Strength exercise (rep-based)?
|
|
164
|
+
→ primary: reps/count
|
|
165
|
+
→ secondary: weight/kg (if weighted)
|
|
166
|
+
|
|
167
|
+
Timed hold (plank, wall sit)?
|
|
168
|
+
→ primary: duration/s
|
|
169
|
+
|
|
170
|
+
Distance cardio (run, row)?
|
|
171
|
+
→ primary: distance/km
|
|
172
|
+
→ secondary: duration/min, pace/min_per_km
|
|
173
|
+
|
|
174
|
+
Time-based cardio (bike, elliptical)?
|
|
175
|
+
→ primary: duration/min
|
|
176
|
+
→ secondary: heartRate/bpm, calories/kcal
|
|
177
|
+
|
|
178
|
+
Plyometric?
|
|
179
|
+
→ primary: reps/count
|
|
180
|
+
→ secondary: height/cm (if jumping)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### 5. targets.primary (Registry Lookup Fallback)
|
|
184
|
+
|
|
185
|
+
**When to enrich:** When registry lookup fails
|
|
186
|
+
|
|
187
|
+
**Context needed:**
|
|
188
|
+
- `name` (required)
|
|
189
|
+
- `bodyPart` (helpful)
|
|
190
|
+
- Source `target` value (required - this is what failed lookup)
|
|
191
|
+
|
|
192
|
+
**Task:** Map the source muscle name to the closest FDS muscle
|
|
193
|
+
|
|
194
|
+
**Output format:**
|
|
195
|
+
```json
|
|
196
|
+
{
|
|
197
|
+
"muscleId": "c3d4e5f6-3333-4000-8000-000000000022",
|
|
198
|
+
"muscleName": "Rectus Abdominis",
|
|
199
|
+
"muscleSlug": "rectus-abdominis",
|
|
200
|
+
"categoryId": "a1b2c3d4-1111-4000-8000-000000000004"
|
|
201
|
+
}
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
206
|
+
## Enrichment Prompts
|
|
207
|
+
|
|
208
|
+
### System Prompt (All Enrichments)
|
|
209
|
+
|
|
210
|
+
```
|
|
211
|
+
You are an expert exercise physiologist and fitness data specialist with comprehensive knowledge of:
|
|
212
|
+
- Exercise biomechanics and movement patterns
|
|
213
|
+
- Muscle anatomy and function
|
|
214
|
+
- Exercise classification systems
|
|
215
|
+
- The FDS (Fitness Data Standard) specification
|
|
216
|
+
|
|
217
|
+
Your responses must be:
|
|
218
|
+
1. Accurate and evidence-based
|
|
219
|
+
2. Consistent with FDS schema requirements
|
|
220
|
+
3. Formatted as valid JSON
|
|
221
|
+
4. Concise but complete
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Classification Prompt
|
|
225
|
+
|
|
226
|
+
```
|
|
227
|
+
Classify this exercise according to FDS v1.0.0:
|
|
228
|
+
|
|
229
|
+
Exercise: {{name}}
|
|
230
|
+
Body Part: {{bodyPart}}
|
|
231
|
+
Target Muscle: {{target}}
|
|
232
|
+
Equipment: {{equipment}}
|
|
233
|
+
|
|
234
|
+
Required fields (use EXACT enum values):
|
|
235
|
+
- exerciseType: strength | cardio | mobility | plyometric | balance
|
|
236
|
+
- movement: squat | hinge | lunge | push-horizontal | push-vertical | pull-horizontal | pull-vertical | carry | core-anti-extension | core-anti-rotation | rotation | locomotion | isolation | other
|
|
237
|
+
- mechanics: compound | isolation
|
|
238
|
+
- force: push | pull | static | mixed
|
|
239
|
+
- level: beginner | intermediate | advanced
|
|
240
|
+
|
|
241
|
+
Respond with JSON only:
|
|
242
|
+
{
|
|
243
|
+
"exerciseType": "...",
|
|
244
|
+
"movement": "...",
|
|
245
|
+
"mechanics": "...",
|
|
246
|
+
"force": "...",
|
|
247
|
+
"level": "..."
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Description Prompt
|
|
252
|
+
|
|
253
|
+
```
|
|
254
|
+
Write a concise description for this exercise:
|
|
255
|
+
|
|
256
|
+
Exercise: {{name}}
|
|
257
|
+
Target: {{target}}
|
|
258
|
+
Equipment: {{equipment}}
|
|
259
|
+
Type: {{exerciseType}}
|
|
260
|
+
Movement: {{movement}}
|
|
261
|
+
|
|
262
|
+
Requirements:
|
|
263
|
+
- 1-3 sentences (50-150 words)
|
|
264
|
+
- Mention primary muscles targeted
|
|
265
|
+
- Explain purpose/benefit
|
|
266
|
+
- No form instructions
|
|
267
|
+
- Suitable for general fitness audience
|
|
268
|
+
|
|
269
|
+
Respond with JSON only:
|
|
270
|
+
{
|
|
271
|
+
"description": "..."
|
|
272
|
+
}
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Metrics Prompt
|
|
276
|
+
|
|
277
|
+
```
|
|
278
|
+
Determine appropriate tracking metrics for this exercise:
|
|
279
|
+
|
|
280
|
+
Exercise: {{name}}
|
|
281
|
+
Type: {{exerciseType}}
|
|
282
|
+
Movement: {{movement}}
|
|
283
|
+
|
|
284
|
+
Valid metric types: reps, weight, duration, distance, speed, pace, power, heartRate, steps, calories, height, tempo, rpe
|
|
285
|
+
|
|
286
|
+
Valid units:
|
|
287
|
+
- count (reps, tempo, rpe)
|
|
288
|
+
- kg, lb (weight)
|
|
289
|
+
- s, min (duration)
|
|
290
|
+
- m, km, mi (distance)
|
|
291
|
+
- m_s, km_h (speed)
|
|
292
|
+
- min_per_km, min_per_mi (pace)
|
|
293
|
+
- W (power)
|
|
294
|
+
- bpm (heartRate)
|
|
295
|
+
- kcal (calories)
|
|
296
|
+
- cm, in (height)
|
|
297
|
+
|
|
298
|
+
Respond with JSON only:
|
|
299
|
+
{
|
|
300
|
+
"primary": { "type": "...", "unit": "..." },
|
|
301
|
+
"secondary": [{ "type": "...", "unit": "..." }]
|
|
302
|
+
}
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## Enrichment Configuration
|
|
308
|
+
|
|
309
|
+
### Full Enrichment (Default)
|
|
310
|
+
|
|
311
|
+
```json
|
|
312
|
+
{
|
|
313
|
+
"enrichment": {
|
|
314
|
+
"enabled": true,
|
|
315
|
+
"defaultFields": "all"
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### Selective Enrichment
|
|
321
|
+
|
|
322
|
+
```json
|
|
323
|
+
{
|
|
324
|
+
"enrichment": {
|
|
325
|
+
"enabled": true,
|
|
326
|
+
"defaultFields": ["classification", "metrics"],
|
|
327
|
+
"skipFields": ["canonical.description"]
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
### Field-Level Override
|
|
333
|
+
|
|
334
|
+
```json
|
|
335
|
+
{
|
|
336
|
+
"mappings": {
|
|
337
|
+
"canonical.description": {
|
|
338
|
+
"enrichment": {
|
|
339
|
+
"enabled": false
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
"classification": {
|
|
343
|
+
"enrichment": {
|
|
344
|
+
"enabled": true,
|
|
345
|
+
"when": "always"
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
---
|
|
353
|
+
|
|
354
|
+
## Rate Limiting & Batching
|
|
355
|
+
|
|
356
|
+
### Recommended Settings
|
|
357
|
+
|
|
358
|
+
```json
|
|
359
|
+
{
|
|
360
|
+
"enrichment": {
|
|
361
|
+
"options": {
|
|
362
|
+
"temperature": 0.3,
|
|
363
|
+
"maxRetries": 3,
|
|
364
|
+
"rateLimit": {
|
|
365
|
+
"requestsPerMinute": 60
|
|
366
|
+
}
|
|
367
|
+
},
|
|
368
|
+
"batch": {
|
|
369
|
+
"enabled": true,
|
|
370
|
+
"concurrency": 5,
|
|
371
|
+
"chunkSize": 10
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
### Cost Optimization
|
|
378
|
+
|
|
379
|
+
1. **Batch similar fields together** - One AI call for classification (5 fields) vs 5 separate calls
|
|
380
|
+
2. **Use registry lookups first** - Only fall back to AI when lookup fails
|
|
381
|
+
3. **Cache enrichment results** - Same exercise name → same classification
|
|
382
|
+
4. **Use appropriate model** - Claude 3.5 Sonnet for quality, Claude 3 Haiku for speed/cost
|
|
383
|
+
|
|
384
|
+
---
|
|
385
|
+
|
|
386
|
+
## Validation
|
|
387
|
+
|
|
388
|
+
All AI-generated content should be validated against FDS schema:
|
|
389
|
+
|
|
390
|
+
```typescript
|
|
391
|
+
const result = await enrichmentEngine.enrich(data, mappings, context);
|
|
392
|
+
|
|
393
|
+
// Validate each enriched field
|
|
394
|
+
for (const [field, value] of Object.entries(result.data)) {
|
|
395
|
+
const isValid = validator.validateField(field, value);
|
|
396
|
+
if (!isValid) {
|
|
397
|
+
console.warn(`AI generated invalid value for ${field}, using fallback`);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### Common Validation Issues
|
|
403
|
+
|
|
404
|
+
| Field | Issue | Solution |
|
|
405
|
+
|-------|-------|----------|
|
|
406
|
+
| `movement` | Invalid enum value | Re-prompt with explicit enum list |
|
|
407
|
+
| `level` | Mixed case | Normalize to lowercase |
|
|
408
|
+
| `metrics.unit` | Wrong pairing | Validate type+unit combinations |
|
|
409
|
+
| `description` | Too long | Truncate or re-prompt |
|