@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.
@@ -0,0 +1,354 @@
1
+ # FDS Mapping Strategies
2
+
3
+ ## Common Source Formats
4
+
5
+ ### Format 1: Simple Exercise Database
6
+ ```json
7
+ {
8
+ "id": "0001",
9
+ "name": "3/4 sit-up",
10
+ "bodyPart": "waist",
11
+ "equipment": "body weight",
12
+ "target": "abs",
13
+ "gifUrl": "http://example.com/0001.gif"
14
+ }
15
+ ```
16
+
17
+ **Mapping Strategy:**
18
+ | Source Field | FDS Field | Transform |
19
+ |--------------|-----------|-----------|
20
+ | `id` | `metadata.externalRefs[0].id` | Keep as external ref |
21
+ | - | `exerciseId` | Generate UUIDv4 |
22
+ | `name` | `canonical.name` | `titleCase` |
23
+ | `name` | `canonical.slug` | `slugify` |
24
+ | - | `canonical.description` | AI enrichment |
25
+ | `bodyPart` | Context for AI | Used in classification |
26
+ | `equipment` | `equipment.required[0]` | Registry lookup |
27
+ | `target` | `targets.primary[0]` | Registry lookup + AI |
28
+ | `gifUrl` | `media[0]` | `toMediaArray` |
29
+ | - | `classification.*` | AI enrichment |
30
+ | - | `metrics.primary` | AI enrichment |
31
+
32
+ ### Format 2: Detailed Exercise with Categories
33
+ ```json
34
+ {
35
+ "exercise_id": "ex-123",
36
+ "title": "Barbell Back Squat",
37
+ "description": "A compound lower body exercise...",
38
+ "category": "legs",
39
+ "subcategory": "quadriceps",
40
+ "difficulty": "intermediate",
41
+ "equipment_needed": ["barbell", "squat rack"],
42
+ "muscles_primary": ["quadriceps", "glutes"],
43
+ "muscles_secondary": ["hamstrings", "core"],
44
+ "video_url": "https://...",
45
+ "instructions": ["Step 1...", "Step 2..."]
46
+ }
47
+ ```
48
+
49
+ **Mapping Strategy:**
50
+ | Source Field | FDS Field | Transform |
51
+ |--------------|-----------|-----------|
52
+ | `exercise_id` | `metadata.externalRefs[0].id` | External ref |
53
+ | - | `exerciseId` | Generate UUIDv4 |
54
+ | `title` | `canonical.name` | Direct |
55
+ | `title` | `canonical.slug` | `slugify` |
56
+ | `description` | `canonical.description` | Direct |
57
+ | `difficulty` | `classification.level` | Map: intermediate→intermediate |
58
+ | `equipment_needed` | `equipment.required[]` | Registry lookup (array) |
59
+ | `muscles_primary` | `targets.primary[]` | Registry lookup (array) |
60
+ | `muscles_secondary` | `targets.secondary[]` | Registry lookup (array) |
61
+ | `video_url` | `media[0]` | `toMediaArray` type=video |
62
+ | `category` + `subcategory` | `classification.*` | AI enrichment with context |
63
+
64
+ ---
65
+
66
+ ## Transform Functions
67
+
68
+ ### slugify
69
+ Converts a string to a URL-safe slug.
70
+
71
+ **Input:** `"Barbell Bench Press"`
72
+ **Output:** `"barbell-bench-press"`
73
+
74
+ **Rules:**
75
+ 1. Lowercase
76
+ 2. Replace spaces with hyphens
77
+ 3. Remove special characters (keep alphanumeric and hyphens)
78
+ 4. Collapse multiple hyphens
79
+ 5. Trim hyphens from start/end
80
+
81
+ **Edge Cases:**
82
+ - Numbers: `"21s Bicep Curl"` → `"21s-bicep-curl"`
83
+ - Special chars: `"Push-Up (Incline)"` → `"push-up-incline"`
84
+ - Fractions: `"3/4 Sit-Up"` → `"three-quarter-sit-up"` (needs special handling)
85
+
86
+ ### titleCase
87
+ Converts to title case for display names.
88
+
89
+ **Input:** `"barbell bench press"`
90
+ **Output:** `"Barbell Bench Press"`
91
+
92
+ **Rules:**
93
+ 1. Capitalize first letter of each word
94
+ 2. Keep acronyms: `"EZ Bar Curl"` stays `"EZ Bar Curl"`
95
+ 3. Handle hyphenated: `"Push-Up"` stays `"Push-Up"`
96
+
97
+ ### registryLookup
98
+ Finds matching registry entry with fuzzy matching.
99
+
100
+ **Options:**
101
+ ```json
102
+ {
103
+ "registry": "muscles",
104
+ "matchField": "name",
105
+ "fuzzyMatch": true,
106
+ "threshold": 0.8,
107
+ "caseSensitive": false
108
+ }
109
+ ```
110
+
111
+ **Matching Priority:**
112
+ 1. Exact match on `name` or `slug`
113
+ 2. Exact match in `aliases`
114
+ 3. Fuzzy match on `name` (Levenshtein distance)
115
+ 4. AI fallback if not found
116
+
117
+ **Examples:**
118
+ - `"abs"` → Matches `"Abs"` (exact) or `"Rectus Abdominis"` (alias)
119
+ - `"body weight"` → Matches `"Body Weight"` (fuzzy, space handling)
120
+ - `"quads"` → Matches `"Quadriceps"` (alias)
121
+
122
+ ### toMediaArray
123
+ Converts URL(s) to FDS media array format.
124
+
125
+ **Options:**
126
+ ```json
127
+ {
128
+ "type": "image",
129
+ "baseUrl": "file:///path/to/local/",
130
+ "urlTransform": {
131
+ "pattern": "http://old.cdn.com/(\\d+)\\.gif",
132
+ "replace": "https://new.cdn.com/$1.gif"
133
+ }
134
+ }
135
+ ```
136
+
137
+ **Input:** `"http://old.cdn.com/0001.gif"`
138
+ **Output:**
139
+ ```json
140
+ [{
141
+ "type": "image",
142
+ "uri": "https://new.cdn.com/0001.gif"
143
+ }]
144
+ ```
145
+
146
+ ### prefixUUID
147
+ Generates or formats UUIDs with optional prefix.
148
+
149
+ **Options:**
150
+ ```json
151
+ {
152
+ "prefix": "ex-",
153
+ "generate": true
154
+ }
155
+ ```
156
+
157
+ **If source has ID:**
158
+ - Input: `"0001"` → Output: `"ex-0001-{uuid}"` or preserve + add external ref
159
+
160
+ **If generate:**
161
+ - Output: `"ex-{generated-uuid-v4}"`
162
+
163
+ ### autoGenerate
164
+ Auto-generates metadata fields.
165
+
166
+ **Options:**
167
+ ```json
168
+ {
169
+ "createdAt": "now",
170
+ "updatedAt": "now",
171
+ "status": "draft",
172
+ "source": "import-job-123"
173
+ }
174
+ ```
175
+
176
+ ---
177
+
178
+ ## Mapping Configuration Examples
179
+
180
+ ### Basic Exercise Import
181
+ ```json
182
+ {
183
+ "$schema": "https://spec.vitness.me/schemas/transformer/v1.0.0/mapping.schema.json",
184
+ "version": "1.0.0",
185
+ "targetSchema": {
186
+ "version": "1.0.0",
187
+ "entity": "exercise"
188
+ },
189
+ "registries": {
190
+ "muscles": { "source": "local", "local": "./registries/muscles.registry.json" },
191
+ "equipment": { "source": "local", "local": "./registries/equipment.registry.json" },
192
+ "muscleCategories": { "source": "local", "local": "./registries/muscle-categories.registry.json" }
193
+ },
194
+ "mappings": {
195
+ "schemaVersion": { "default": "1.0.0" },
196
+ "exerciseId": { "from": null, "transform": "uuid" },
197
+ "canonical.name": { "from": "name", "transform": "titleCase" },
198
+ "canonical.slug": { "from": "name", "transform": "slugify" },
199
+ "canonical.description": {
200
+ "from": null,
201
+ "enrichment": {
202
+ "enabled": true,
203
+ "prompt": "exercise_description",
204
+ "context": ["name", "bodyPart", "target", "equipment"]
205
+ }
206
+ },
207
+ "classification": {
208
+ "from": null,
209
+ "enrichment": {
210
+ "enabled": true,
211
+ "prompt": "exercise_classification",
212
+ "context": ["name", "bodyPart", "target", "equipment"]
213
+ }
214
+ },
215
+ "targets.primary": {
216
+ "from": "target",
217
+ "transform": "registryLookup",
218
+ "options": { "registry": "muscles", "fuzzyMatch": true }
219
+ },
220
+ "equipment.required": {
221
+ "from": "equipment",
222
+ "transform": "registryLookup",
223
+ "options": { "registry": "equipment", "fuzzyMatch": true }
224
+ },
225
+ "metrics.primary": {
226
+ "from": null,
227
+ "enrichment": {
228
+ "enabled": true,
229
+ "prompt": "exercise_metrics",
230
+ "context": ["name", "classification.exerciseType"]
231
+ }
232
+ },
233
+ "media": {
234
+ "from": "gifUrl",
235
+ "transform": "toMediaArray",
236
+ "options": { "type": "image" }
237
+ },
238
+ "metadata": {
239
+ "transform": "autoGenerate",
240
+ "options": {
241
+ "createdAt": "now",
242
+ "updatedAt": "now",
243
+ "status": "draft",
244
+ "source": "exercises-import"
245
+ }
246
+ },
247
+ "metadata.externalRefs": {
248
+ "from": "id",
249
+ "transform": "template",
250
+ "options": {
251
+ "template": [{ "system": "source-db", "id": "{{value}}" }]
252
+ }
253
+ }
254
+ },
255
+ "output": {
256
+ "format": "json",
257
+ "pretty": true,
258
+ "directory": "./output/exercises/",
259
+ "naming": "{{slug}}.json"
260
+ }
261
+ }
262
+ ```
263
+
264
+ ---
265
+
266
+ ## Nested Path Mapping
267
+
268
+ FDS uses nested structures. Use dot notation:
269
+
270
+ - `canonical.name` → `{ canonical: { name: value } }`
271
+ - `targets.primary` → `{ targets: { primary: value } }`
272
+ - `metadata.externalRefs[0].id` → First item in array
273
+
274
+ ### Array Handling
275
+
276
+ **Single value to array:**
277
+ ```json
278
+ {
279
+ "targets.primary": {
280
+ "from": "target",
281
+ "transform": ["registryLookup", "toArray"]
282
+ }
283
+ }
284
+ ```
285
+
286
+ **Multiple source to array:**
287
+ ```json
288
+ {
289
+ "targets.primary": {
290
+ "from": ["primary_muscle", "secondary_muscle"],
291
+ "transform": "registryLookup",
292
+ "options": { "registry": "muscles" }
293
+ }
294
+ }
295
+ ```
296
+
297
+ ---
298
+
299
+ ## Conditional Mapping
300
+
301
+ ```json
302
+ {
303
+ "classification.unilateral": {
304
+ "from": "name",
305
+ "transform": "template",
306
+ "options": {
307
+ "template": "{{value.toLowerCase().includes('single') || value.toLowerCase().includes('one arm') || value.toLowerCase().includes('one leg')}}"
308
+ },
309
+ "condition": "source.name != null"
310
+ }
311
+ }
312
+ ```
313
+
314
+ ---
315
+
316
+ ## Default Values
317
+
318
+ ```json
319
+ {
320
+ "classification.kineticChain": {
321
+ "from": null,
322
+ "default": "mixed"
323
+ }
324
+ }
325
+ ```
326
+
327
+ ---
328
+
329
+ ## Error Handling
330
+
331
+ ### Required Fields
332
+ Mark critical fields as required to fail early:
333
+ ```json
334
+ {
335
+ "canonical.name": {
336
+ "from": "name",
337
+ "required": true
338
+ }
339
+ }
340
+ ```
341
+
342
+ ### Fallback Chain
343
+ ```json
344
+ {
345
+ "canonical.description": {
346
+ "from": ["description", "summary", "notes"],
347
+ "transform": "coalesce",
348
+ "enrichment": {
349
+ "enabled": true,
350
+ "when": "empty"
351
+ }
352
+ }
353
+ }
354
+ ```
@@ -0,0 +1,309 @@
1
+ # FDS Schema Reference
2
+
3
+ ## Exercise Schema (v1.0.0)
4
+
5
+ ### Top-Level Structure
6
+
7
+ ```typescript
8
+ interface FDSExercise {
9
+ schemaVersion: string; // Required: "1.0.0"
10
+ exerciseId: string; // Required: UUIDv4
11
+ canonical: Canonical; // Required
12
+ classification: Classification; // Required
13
+ targets: Targets; // Required
14
+ equipment?: Equipment; // Optional
15
+ constraints?: Constraints; // Optional
16
+ relations?: Relation[]; // Optional
17
+ metrics: Metrics; // Required
18
+ media?: Media[]; // Optional
19
+ attributes?: Record<string, any>;// Optional: x: namespaced
20
+ extensions?: Record<string, any>;// Optional: x: namespaced
21
+ metadata: Metadata; // Required
22
+ }
23
+ ```
24
+
25
+ ### Canonical
26
+
27
+ ```typescript
28
+ interface Canonical {
29
+ name: string; // Required: Display name
30
+ slug: string; // Required: URL-safe identifier, pattern: ^[a-z0-9-]{2,}$
31
+ description?: string; // Optional: Detailed description
32
+ aliases?: string[]; // Optional: Alternative names
33
+ localized?: Localized[]; // Optional: Translations
34
+ }
35
+
36
+ interface Localized {
37
+ lang: string; // Required: ISO 639-1 code
38
+ name: string; // Required
39
+ description?: string;
40
+ aliases?: string[];
41
+ }
42
+ ```
43
+
44
+ ### Classification
45
+
46
+ ```typescript
47
+ interface Classification {
48
+ exerciseType: string; // Required: strength, cardio, mobility, plyometric, balance
49
+ movement: Movement; // Required: See enum
50
+ mechanics: Mechanics; // Required: compound, isolation
51
+ force: Force; // Required: push, pull, static, mixed
52
+ level: Level; // Required: beginner, intermediate, advanced
53
+ unilateral?: boolean; // Optional: default false
54
+ kineticChain?: KineticChain; // Optional: open, closed, mixed
55
+ tags?: string[]; // Optional: free-form tags
56
+ taxonomyRefs?: TaxonomyRef[]; // Optional: external taxonomy references
57
+ }
58
+ ```
59
+
60
+ ### Targets
61
+
62
+ ```typescript
63
+ interface Targets {
64
+ primary: MuscleRef[]; // Required: at least 1
65
+ secondary?: MuscleRef[]; // Optional
66
+ }
67
+
68
+ interface MuscleRef {
69
+ id: string; // Required: UUIDv4
70
+ slug?: string; // Optional
71
+ name: string; // Required
72
+ categoryId: string; // Required: muscle category UUID
73
+ aliases?: string[]; // Optional
74
+ }
75
+ ```
76
+
77
+ ### Equipment
78
+
79
+ ```typescript
80
+ interface Equipment {
81
+ required?: EquipmentRef[];
82
+ optional?: EquipmentRef[];
83
+ }
84
+
85
+ interface EquipmentRef {
86
+ id: string; // Required: UUIDv4
87
+ slug?: string; // Optional
88
+ name: string; // Required
89
+ abbreviation?: string; // Optional
90
+ categories?: string[]; // Optional
91
+ aliases?: string[]; // Optional
92
+ }
93
+ ```
94
+
95
+ ### Metrics
96
+
97
+ ```typescript
98
+ interface Metrics {
99
+ primary: MetricRef; // Required
100
+ secondary?: MetricRef[]; // Optional
101
+ }
102
+
103
+ interface MetricRef {
104
+ type: MetricType; // Required: See enum
105
+ unit: MetricUnit; // Required: See enum
106
+ }
107
+ ```
108
+
109
+ ### Metadata
110
+
111
+ ```typescript
112
+ interface Metadata {
113
+ createdAt: string; // Required: ISO 8601 datetime
114
+ updatedAt: string; // Required: ISO 8601 datetime
115
+ source?: string; // Optional: data source identifier
116
+ version?: string; // Optional: record version
117
+ status: Status; // Required: See enum
118
+ deprecated?: {
119
+ since?: string; // Schema version when deprecated
120
+ replacedBy?: string; // ID of replacement exercise
121
+ };
122
+ externalRefs?: ExternalRef[];
123
+ history?: HistoryEntry[];
124
+ }
125
+
126
+ interface ExternalRef {
127
+ system: string; // Required: external system name
128
+ id: string; // Required: ID in that system
129
+ }
130
+ ```
131
+
132
+ ### Media
133
+
134
+ ```typescript
135
+ interface MediaItem {
136
+ type: "image" | "video" | "doc" | "3d"; // Required
137
+ uri: string; // Required: URI format
138
+ caption?: string;
139
+ license?: string;
140
+ attribution?: string;
141
+ }
142
+ ```
143
+
144
+ ---
145
+
146
+ ## Equipment Schema (v1.0.0)
147
+
148
+ ```typescript
149
+ interface FDSEquipment {
150
+ schemaVersion: string; // Required
151
+ id: string; // Required: UUIDv4
152
+ canonical: {
153
+ name: string; // Required
154
+ slug: string; // Required
155
+ abbreviation?: string;
156
+ description?: string;
157
+ aliases?: string[];
158
+ localized?: Localized[];
159
+ };
160
+ classification?: {
161
+ tags?: string[];
162
+ };
163
+ media?: Media[];
164
+ attributes?: Record<string, any>;
165
+ extensions?: Record<string, any>;
166
+ metadata: Metadata; // Required
167
+ }
168
+ ```
169
+
170
+ ---
171
+
172
+ ## Muscle Schema (v1.0.0)
173
+
174
+ ```typescript
175
+ interface FDSMuscle {
176
+ schemaVersion: string; // Required
177
+ id: string; // Required: UUIDv4
178
+ canonical: {
179
+ name: string; // Required
180
+ slug: string; // Required
181
+ description?: string;
182
+ aliases?: string[];
183
+ localized?: Localized[];
184
+ };
185
+ classification: {
186
+ categoryId: string; // Required: muscle category UUID
187
+ region: RegionGroup; // Required: See enum
188
+ laterality?: Laterality;
189
+ };
190
+ heatmap?: {
191
+ atlasId: string;
192
+ areaIds: string[];
193
+ };
194
+ media?: Media[];
195
+ attributes?: Record<string, any>;
196
+ extensions?: Record<string, any>;
197
+ metadata: Metadata; // Required
198
+ }
199
+ ```
200
+
201
+ ---
202
+
203
+ ## Muscle Category Schema (v1.0.0)
204
+
205
+ ```typescript
206
+ interface FDSMuscleCategory {
207
+ schemaVersion: string; // Required
208
+ id: string; // Required: UUIDv4
209
+ canonical: {
210
+ name: string; // Required
211
+ slug: string; // Required
212
+ description?: string;
213
+ aliases?: string[];
214
+ localized?: Localized[];
215
+ };
216
+ classification?: {
217
+ tags?: string[];
218
+ };
219
+ media?: Media[];
220
+ attributes?: Record<string, any>;
221
+ extensions?: Record<string, any>;
222
+ metadata: Metadata; // Required
223
+ }
224
+ ```
225
+
226
+ ---
227
+
228
+ ## Enumerations
229
+
230
+ ### Movement
231
+ ```typescript
232
+ type Movement =
233
+ | "squat"
234
+ | "hinge"
235
+ | "lunge"
236
+ | "push-horizontal"
237
+ | "push-vertical"
238
+ | "pull-horizontal"
239
+ | "pull-vertical"
240
+ | "carry"
241
+ | "core-anti-extension"
242
+ | "core-anti-rotation"
243
+ | "rotation"
244
+ | "locomotion"
245
+ | "isolation"
246
+ | "other";
247
+ ```
248
+
249
+ ### MetricType
250
+ ```typescript
251
+ type MetricType =
252
+ | "reps"
253
+ | "weight"
254
+ | "duration"
255
+ | "distance"
256
+ | "speed"
257
+ | "pace"
258
+ | "power"
259
+ | "heartRate"
260
+ | "steps"
261
+ | "calories"
262
+ | "height"
263
+ | "tempo"
264
+ | "rpe";
265
+ ```
266
+
267
+ ### MetricUnit
268
+ ```typescript
269
+ type MetricUnit =
270
+ | "count"
271
+ | "kg"
272
+ | "lb"
273
+ | "s"
274
+ | "min"
275
+ | "m"
276
+ | "km"
277
+ | "mi"
278
+ | "m_s"
279
+ | "km_h"
280
+ | "min_per_km"
281
+ | "min_per_mi"
282
+ | "W"
283
+ | "bpm"
284
+ | "kcal"
285
+ | "cm"
286
+ | "in";
287
+ ```
288
+
289
+ ### RegionGroup
290
+ ```typescript
291
+ type RegionGroup =
292
+ | "upper-front"
293
+ | "upper-back"
294
+ | "lower-front"
295
+ | "lower-back"
296
+ | "core"
297
+ | "full-body"
298
+ | "n/a";
299
+ ```
300
+
301
+ ### Status
302
+ ```typescript
303
+ type Status =
304
+ | "draft"
305
+ | "review"
306
+ | "active"
307
+ | "inactive"
308
+ | "deprecated";
309
+ ```
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@vitness/fds-skill",
3
+ "version": "0.1.0",
4
+ "description": "FDS (Fitness Data Standard) AI skill and knowledge base for schema transformation and enrichment",
5
+ "type": "module",
6
+ "main": "./SKILL.md",
7
+ "files": [
8
+ "SKILL.md",
9
+ "CLAUDE.md",
10
+ "knowledge",
11
+ "prompts",
12
+ "examples"
13
+ ],
14
+ "keywords": [
15
+ "fds",
16
+ "fitness",
17
+ "data",
18
+ "standard",
19
+ "ai",
20
+ "skill",
21
+ "knowledge-base",
22
+ "llm",
23
+ "prompt"
24
+ ],
25
+ "author": "VITNESS",
26
+ "license": "MIT",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/vitness-me/fds-spec-website.git",
30
+ "directory": "packages/fds-skill"
31
+ }
32
+ }