fit-file-parser 6.0.2 → 6.1.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,44 @@
1
1
  # Change Log
2
2
 
3
+ ## 6.1.2 - 2026-09-22
4
+
5
+ ### Changed
6
+
7
+ - Keep the manufacturer, Garmin product, sport, sub-sport, and course-point
8
+ lookup maps in one authoritative internal module shared by the full decoder
9
+ profile and the lightweight `fit-file-parser/profile` entry point.
10
+ - Prevent lookup-only consumers from loading the full decoder profile and its
11
+ message definitions.
12
+ - Enforce the lookup entry point's dependency graph and a 25 KB minified bundle
13
+ ceiling in the profile checks run by CI.
14
+
15
+ ### Compatibility
16
+
17
+ - Profile mappings, parsed output, and the public TypeScript API are unchanged.
18
+
19
+ ## 6.1.1 - 2026-09-21
20
+
21
+ ### Added
22
+
23
+ - Export lookup helpers for FIT manufacturer, Garmin product, sport, and
24
+ sub-sport identifiers from the package root.
25
+ - Export the same helpers from the lightweight `fit-file-parser/profile`
26
+ entry point for lookup-only consumers.
27
+ - Add reverse sport, sub-sport, and course-point identifier lookups.
28
+ - Add lightweight `fit-file-parser/raw` and `fit-file-parser/encoder` entry
29
+ points for profile-independent message scanning and encoding.
30
+ - Add a strict raw-message reader with CRC, definition, developer-field, and
31
+ compressed-timestamp validation.
32
+ - Export an opt-in Garmin product display-name helper without changing parsed
33
+ `product` or `product_name` fields.
34
+ - Decode mountain enduro and mountain downhill sub-sport identifiers.
35
+
36
+ ### Compatibility
37
+
38
+ - Existing parsed output remains unchanged except that sub-sport IDs 153 and
39
+ 154 now resolve to `mountain_enduro` and `mountain_downhill`.
40
+ - Existing Garmin product display names ported from SportsLib are preserved.
41
+
3
42
  ## 6.0.2 - 2026-09-21
4
43
 
5
44
  ### Changed
package/PROFILE.md CHANGED
@@ -1,11 +1,18 @@
1
1
  # FIT Profile Maintenance
2
2
 
3
- The parser uses one static interoperability table in `src/profile.ts` for
4
- normal installs, builds, tests, and releases. It contains message and field
5
- identifiers, names, wire types, array behavior, scales, offsets, units, enum
6
- labels, and product identifiers. `npm run profile:check` pins full-table
7
- fingerprints and the resulting message, field, type, enum-value, and product
8
- counts so accidental drift fails CI.
3
+ The parser uses one static interoperability profile for normal installs,
4
+ builds, tests, and releases. Message definitions and general enum tables live
5
+ in `src/profile.ts`; the manufacturer, Garmin product, sport, sub-sport, and
6
+ course-point tables live in `src/profile-lookup-data.ts`. The full profile
7
+ composes those exact objects, so lookup-only consumers and the decoder share
8
+ one authoritative value for every mapping. `npm run profile:check` pins
9
+ full-table fingerprints and the resulting message, field, type, enum-value,
10
+ and product counts so accidental drift fails CI.
11
+
12
+ `npm run profile:budget` also bundles the lookup entry point in isolation. It
13
+ fails if anything beyond the lookup helpers and their authoritative data enters
14
+ that graph, or if the minified bundle exceeds 25,000 bytes. Review any budget
15
+ change together with the corresponding profile growth.
9
16
 
10
17
  ## Unknown and newer fields
11
18
 
@@ -28,8 +35,9 @@ table cannot decode.
28
35
 
29
36
  Every mapping change requires focused regression coverage. Do not guess
30
37
  adjacent identifiers or add a mapping solely to make `unmapped_messages`
31
- disappear. Update `src/profile.ts`, the relevant tests, and the public types,
32
- then verify the complete compatibility contract:
38
+ disappear. Update the owning table in `src/profile.ts` or
39
+ `src/profile-lookup-data.ts`, the relevant tests, and the public types, then
40
+ verify the complete compatibility contract:
33
41
 
34
42
  ```sh
35
43
  npm run codegen
package/README.md CHANGED
@@ -240,8 +240,9 @@ unchanged.
240
240
  ## Profile-backed output
241
241
 
242
242
  Recognized message names, field names, enum values, wire types, scales,
243
- offsets, arrays, and units come from the community-maintained table in
244
- `src/profile.ts`. Its maintenance and verification contract is documented in
243
+ offsets, arrays, and units come from the community-maintained profile in
244
+ `src/profile.ts` and `src/profile-lookup-data.ts`. Its maintenance and
245
+ verification contract is documented in
245
246
  [`PROFILE.md`](./PROFILE.md).
246
247
 
247
248
  Public names use `snake_case` while preserving established alphanumeric tokens
@@ -257,6 +258,57 @@ unknown enum IDs remain numbers, and invalid entries retained inside FIT arrays
257
258
  are `null`. All profile fields are optional because each FIT message definition
258
259
  chooses which fields are present.
259
260
 
261
+ Applications that need profile lookups independently of parsing can use the
262
+ exported manufacturer, Garmin product, sport, sub-sport, and course-point helpers:
263
+
264
+ ```javascript
265
+ import {
266
+ getFitCoursePointId,
267
+ getFitGarminProductDisplayName,
268
+ getFitManufacturerName,
269
+ getFitSportId,
270
+ getFitSportName,
271
+ getFitSubSportId,
272
+ getFitSubSportName,
273
+ } from 'fit-file-parser'
274
+
275
+ getFitManufacturerName(1) // "garmin"
276
+ getFitGarminProductDisplayName(4655) // "Edge MTB"
277
+ getFitSportName(2) // "cycling"
278
+ getFitSubSportName(153) // "mountain_enduro"
279
+ getFitSportId('cycling') // 2
280
+ getFitSubSportId('indoor_cycling') // 6
281
+ getFitCoursePointId('rest_area') // 29
282
+ ```
283
+
284
+ These helpers read the same maintained profile used by the decoder. The product
285
+ display helper is opt-in and does not synthesize or overwrite parsed
286
+ `product_name` values.
287
+
288
+ Lookup-only consumers can import the same helpers from
289
+ `fit-file-parser/profile` without loading the parser entry point.
290
+
291
+ ### Lightweight raw messages
292
+
293
+ Consumers that need strict FIT framing, CRC validation, compressed timestamps,
294
+ and exact fields without loading the semantic profile can use the lightweight
295
+ raw entry point:
296
+
297
+ ```javascript
298
+ import { readFitMessages } from 'fit-file-parser/raw'
299
+
300
+ const { messages } = readFitMessages(content, {
301
+ messageNumbers: [18, 26, 72],
302
+ maxInputBytes: 64 * 1024 * 1024,
303
+ })
304
+ ```
305
+
306
+ The result retains native and developer fields as defensive `Uint8Array`
307
+ copies. It does not apply names, enum formatting, scales, units, or
308
+ provider-specific interpretation. Invalid input throws `FitMessageReaderError`
309
+ with a stable `code` such as `invalid_header`, `invalid_crc`, or
310
+ `invalid_structure`.
311
+
260
312
  ## Inputs
261
313
 
262
314
  Both parser methods accept:
@@ -283,7 +335,7 @@ base types, sizes, and values in their raw FIT representation. Applying FIT
283
335
  scales and offsets is the caller's responsibility.
284
336
 
285
337
  ```javascript
286
- import { FitBaseType, FitEncoder } from 'fit-file-parser'
338
+ import { FitBaseType, FitEncoder } from 'fit-file-parser/encoder'
287
339
 
288
340
  const encoder = new FitEncoder()
289
341
  encoder.writeMessage(0, [
@@ -319,6 +371,9 @@ Scalar 64-bit values use `bigint`. Strings, numeric arrays, and other
319
371
  variable-length values use exact-size `Uint8Array` values. Invalid field
320
372
  definitions or numeric ranges throw before a partial message is written.
321
373
 
374
+ The encoder remains available from the package root; the `/encoder` entry
375
+ point avoids loading the decoder and semantic profile.
376
+
322
377
  ## TypeScript and module formats
323
378
 
324
379
  The package includes TypeScript declarations and exports
@@ -389,9 +444,10 @@ a reference package, compares their complete default outputs, and reports only
389
444
  aggregate counts. Files rejected by both strict parsers are retried in force
390
445
  mode.
391
446
 
392
- Edit `src/profile.ts` only through reviewed profile changes with focused
393
- regression coverage and a documented source. Do not edit `src/fit_types.ts`
394
- manually; run `npm run codegen` after changing the maintained profile.
447
+ Edit `src/profile.ts` and `src/profile-lookup-data.ts` only through reviewed
448
+ profile changes with focused regression coverage and a documented source. Do
449
+ not edit `src/fit_types.ts` manually; run `npm run codegen` after changing the
450
+ maintained profile.
395
451
 
396
452
  Repository-specific automation guidance is tracked in
397
453
  [`.agent/README.md`](./.agent/README.md). More examples are available in the
@@ -3,6 +3,9 @@ import type { ParsedFit } from './fit_types.js';
3
3
  export { FitBaseType, FitEncoder } from './fit-encoder.js';
4
4
  export type { FitEncoderField, FitEncoderOptions } from './fit-encoder.js';
5
5
  export type { ParsedFit, ParsedRawDeveloperField, ParsedRawFitField, ParsedRawFitMessage, ParsedRawFitMessageDeveloperField, } from './fit_types.js';
6
+ export { getFitCoursePointId, getFitGarminProductDisplayName, getFitGarminProductName, getFitManufacturerName, getFitSportId, getFitSportName, getFitSubSportId, getFitSubSportName, } from './profile-lookup.js';
7
+ export { FitMessageReaderError, fitTimestampToUnixMilliseconds, getFitBaseTypeId, readFitMessages, readFitStringField, readFitUnsignedField, } from './raw-message-reader.js';
8
+ export type { FitMessageReaderErrorCode, FitMessageReaderIssue, FitMessageReaderIssueCode, FitMessageReaderOptions, FitMessageReaderResult, FitRawDeveloperField, FitRawField, FitRawMessage, } from './raw-message-reader.js';
6
9
  export interface FitParserOptions {
7
10
  force?: boolean;
8
11
  speedUnit?: string;
@@ -1,11 +1,27 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.FitEncoder = exports.FitBaseType = void 0;
3
+ exports.readFitUnsignedField = exports.readFitStringField = exports.readFitMessages = exports.getFitBaseTypeId = exports.fitTimestampToUnixMilliseconds = exports.FitMessageReaderError = exports.getFitSubSportName = exports.getFitSubSportId = exports.getFitSportName = exports.getFitSportId = exports.getFitManufacturerName = exports.getFitGarminProductName = exports.getFitGarminProductDisplayName = exports.getFitCoursePointId = exports.FitEncoder = exports.FitBaseType = void 0;
4
4
  const binary_js_1 = require("./binary.js");
5
5
  const helper_js_1 = require("./helper.js");
6
6
  var fit_encoder_js_1 = require("./fit-encoder.js");
7
7
  Object.defineProperty(exports, "FitBaseType", { enumerable: true, get: function () { return fit_encoder_js_1.FitBaseType; } });
8
8
  Object.defineProperty(exports, "FitEncoder", { enumerable: true, get: function () { return fit_encoder_js_1.FitEncoder; } });
9
+ var profile_lookup_js_1 = require("./profile-lookup.js");
10
+ Object.defineProperty(exports, "getFitCoursePointId", { enumerable: true, get: function () { return profile_lookup_js_1.getFitCoursePointId; } });
11
+ Object.defineProperty(exports, "getFitGarminProductDisplayName", { enumerable: true, get: function () { return profile_lookup_js_1.getFitGarminProductDisplayName; } });
12
+ Object.defineProperty(exports, "getFitGarminProductName", { enumerable: true, get: function () { return profile_lookup_js_1.getFitGarminProductName; } });
13
+ Object.defineProperty(exports, "getFitManufacturerName", { enumerable: true, get: function () { return profile_lookup_js_1.getFitManufacturerName; } });
14
+ Object.defineProperty(exports, "getFitSportId", { enumerable: true, get: function () { return profile_lookup_js_1.getFitSportId; } });
15
+ Object.defineProperty(exports, "getFitSportName", { enumerable: true, get: function () { return profile_lookup_js_1.getFitSportName; } });
16
+ Object.defineProperty(exports, "getFitSubSportId", { enumerable: true, get: function () { return profile_lookup_js_1.getFitSubSportId; } });
17
+ Object.defineProperty(exports, "getFitSubSportName", { enumerable: true, get: function () { return profile_lookup_js_1.getFitSubSportName; } });
18
+ var raw_message_reader_js_1 = require("./raw-message-reader.js");
19
+ Object.defineProperty(exports, "FitMessageReaderError", { enumerable: true, get: function () { return raw_message_reader_js_1.FitMessageReaderError; } });
20
+ Object.defineProperty(exports, "fitTimestampToUnixMilliseconds", { enumerable: true, get: function () { return raw_message_reader_js_1.fitTimestampToUnixMilliseconds; } });
21
+ Object.defineProperty(exports, "getFitBaseTypeId", { enumerable: true, get: function () { return raw_message_reader_js_1.getFitBaseTypeId; } });
22
+ Object.defineProperty(exports, "readFitMessages", { enumerable: true, get: function () { return raw_message_reader_js_1.readFitMessages; } });
23
+ Object.defineProperty(exports, "readFitStringField", { enumerable: true, get: function () { return raw_message_reader_js_1.readFitStringField; } });
24
+ Object.defineProperty(exports, "readFitUnsignedField", { enumerable: true, get: function () { return raw_message_reader_js_1.readFitUnsignedField; } });
9
25
  class FitParser {
10
26
  constructor(options = {}) {
11
27
  var _a, _b, _c, _d;
@@ -213,7 +213,7 @@ export type SportEvent = 'uncategorized' | 'geocaching' | 'fitness' | 'recreatio
213
213
  export type SquatExerciseName = 'leg_press' | 'back_squat_with_body_bar' | 'back_squats' | 'weighted_back_squats' | 'balancing_squat' | 'weighted_balancing_squat' | 'barbell_back_squat' | 'barbell_box_squat' | 'barbell_front_squat' | 'barbell_hack_squat' | 'barbell_hang_squat_snatch' | 'barbell_lateral_step_up' | 'barbell_quarter_squat' | 'barbell_siff_squat' | 'barbell_squat_snatch' | 'barbell_squat_with_heels_raised' | 'barbell_stepover' | 'barbell_step_up' | 'bench_squat_with_rotational_chop' | 'weighted_bench_squat_with_rotational_chop' | 'body_weight_wall_squat' | 'weighted_wall_squat' | 'box_step_squat' | 'weighted_box_step_squat' | 'braced_squat' | 'crossed_arm_barbell_front_squat' | 'crossover_dumbbell_step_up' | 'dumbbell_front_squat' | 'dumbbell_split_squat' | 'dumbbell_squat' | 'dumbbell_squat_clean' | 'dumbbell_stepover' | 'dumbbell_step_up' | 'elevated_single_leg_squat' | 'weighted_elevated_single_leg_squat' | 'figure_four_squats' | 'weighted_figure_four_squats' | 'goblet_squat' | 'kettlebell_squat' | 'kettlebell_swing_overhead' | 'kettlebell_swing_with_flip_to_squat' | 'lateral_dumbbell_step_up' | 'one_legged_squat' | 'overhead_dumbbell_squat' | 'overhead_squat' | 'partial_single_leg_squat' | 'weighted_partial_single_leg_squat' | 'pistol_squat' | 'weighted_pistol_squat' | 'plie_slides' | 'weighted_plie_slides' | 'plie_squat' | 'weighted_plie_squat' | 'prisoner_squat' | 'weighted_prisoner_squat' | 'single_leg_bench_get_up' | 'weighted_single_leg_bench_get_up' | 'single_leg_bench_squat' | 'weighted_single_leg_bench_squat' | 'single_leg_squat_on_swiss_ball' | 'weighted_single_leg_squat_on_swiss_ball' | 'squat' | 'weighted_squat' | 'squats_with_band' | 'staggered_squat' | 'weighted_staggered_squat' | 'step_up' | 'weighted_step_up' | 'suitcase_squats' | 'sumo_squat' | 'sumo_squat_slide_in' | 'weighted_sumo_squat_slide_in' | 'sumo_squat_to_high_pull' | 'sumo_squat_to_stand' | 'weighted_sumo_squat_to_stand' | 'sumo_squat_with_rotation' | 'weighted_sumo_squat_with_rotation' | 'swiss_ball_body_weight_wall_squat' | 'weighted_swiss_ball_wall_squat' | 'thrusters' | 'uneven_squat' | 'weighted_uneven_squat' | 'waist_slimming_squat' | 'wall_ball' | 'wide_stance_barbell_squat' | 'wide_stance_goblet_squat' | 'zercher_squat' | 'kbs_overhead' | 'squat_and_side_kick' | 'squat_jumps_in_n_out' | 'pilates_plie_squats_parallel_turned_out_flat_and_heels' | 'releve_straight_leg_and_knee_bent_with_one_leg_variation' | 'alternating_box_dumbbell_step_ups' | 'dumbbell_overhead_squat_single_arm' | 'dumbbell_squat_snatch' | 'medicine_ball_squat' | 'wall_ball_squat_and_press' | 'squat_american_swing' | 'air_squat' | 'dumbbell_thrusters' | 'overhead_barbell_squat' | number;
214
214
  export type StairStepperExerciseName = 'stair_stepper' | number;
215
215
  export type StrokeType = 'no_event' | 'other' | 'serve' | 'forehand' | 'backhand' | 'smash' | number;
216
- export type SubSport = 'generic' | 'treadmill' | 'street' | 'trail' | 'track' | 'spin' | 'indoor_cycling' | 'road' | 'mountain' | 'downhill' | 'recumbent' | 'cyclocross' | 'hand_cycling' | 'track_cycling' | 'indoor_rowing' | 'elliptical' | 'stair_climbing' | 'lap_swimming' | 'open_water' | 'flexibility_training' | 'strength_training' | 'warm_up' | 'match' | 'exercise' | 'challenge' | 'indoor_skiing' | 'cardio_training' | 'indoor_walking' | 'e_bike_fitness' | 'bmx' | 'casual_walking' | 'speed_walking' | 'bike_to_run_transition' | 'run_to_bike_transition' | 'swim_to_bike_transition' | 'atv' | 'motocross' | 'backcountry' | 'resort' | 'rc_drone' | 'wingsuit' | 'whitewater' | 'skate_skiing' | 'yoga' | 'pilates' | 'indoor_running' | 'gravel_cycling' | 'e_bike_mountain' | 'commuting' | 'mixed_surface' | 'navigate' | 'track_me' | 'map' | 'single_gas_diving' | 'multi_gas_diving' | 'gauge_diving' | 'apnea_diving' | 'apnea_hunting' | 'virtual_activity' | 'obstacle' | 'breathing' | 'ccr_diving' | 'sail_race' | 'expedition' | 'ultra' | 'indoor_climbing' | 'bouldering' | 'hiit' | 'indoor_grinding' | 'hunting_with_dogs' | 'amrap' | 'emom' | 'tabata' | 'esport' | 'triathlon' | 'duathlon' | 'brick' | 'swim_run' | 'adventure_race' | 'trucker_workout' | 'pickleball' | 'padel' | 'indoor_wheelchair_walk' | 'indoor_wheelchair_run' | 'indoor_hand_cycling' | 'field' | 'ice' | 'ultimate' | 'platform' | 'squash' | 'badminton' | 'racquetball' | 'table_tennis' | 'overland' | 'trolling_motor' | 'fly_canopy' | 'fly_paraglide' | 'fly_paramotor' | 'fly_pressurized' | 'fly_navigate' | 'fly_timer' | 'fly_altimeter' | 'fly_wx' | 'fly_vfr' | 'fly_ifr' | 'dynamic_apnea' | 'enduro' | 'rucking' | 'rally' | 'pool_triathlon' | 'e_bike_enduro' | 'all' | number;
216
+ export type SubSport = 'generic' | 'treadmill' | 'street' | 'trail' | 'track' | 'spin' | 'indoor_cycling' | 'road' | 'mountain' | 'downhill' | 'recumbent' | 'cyclocross' | 'hand_cycling' | 'track_cycling' | 'indoor_rowing' | 'elliptical' | 'stair_climbing' | 'lap_swimming' | 'open_water' | 'flexibility_training' | 'strength_training' | 'warm_up' | 'match' | 'exercise' | 'challenge' | 'indoor_skiing' | 'cardio_training' | 'indoor_walking' | 'e_bike_fitness' | 'bmx' | 'casual_walking' | 'speed_walking' | 'bike_to_run_transition' | 'run_to_bike_transition' | 'swim_to_bike_transition' | 'atv' | 'motocross' | 'backcountry' | 'resort' | 'rc_drone' | 'wingsuit' | 'whitewater' | 'skate_skiing' | 'yoga' | 'pilates' | 'indoor_running' | 'gravel_cycling' | 'e_bike_mountain' | 'commuting' | 'mixed_surface' | 'navigate' | 'track_me' | 'map' | 'single_gas_diving' | 'multi_gas_diving' | 'gauge_diving' | 'apnea_diving' | 'apnea_hunting' | 'virtual_activity' | 'obstacle' | 'breathing' | 'ccr_diving' | 'sail_race' | 'expedition' | 'ultra' | 'indoor_climbing' | 'bouldering' | 'hiit' | 'indoor_grinding' | 'hunting_with_dogs' | 'amrap' | 'emom' | 'tabata' | 'esport' | 'triathlon' | 'duathlon' | 'brick' | 'swim_run' | 'adventure_race' | 'trucker_workout' | 'pickleball' | 'padel' | 'indoor_wheelchair_walk' | 'indoor_wheelchair_run' | 'indoor_hand_cycling' | 'field' | 'ice' | 'ultimate' | 'platform' | 'squash' | 'badminton' | 'racquetball' | 'table_tennis' | 'overland' | 'trolling_motor' | 'fly_canopy' | 'fly_paraglide' | 'fly_paramotor' | 'fly_pressurized' | 'fly_navigate' | 'fly_timer' | 'fly_altimeter' | 'fly_wx' | 'fly_vfr' | 'fly_ifr' | 'dynamic_apnea' | 'enduro' | 'rucking' | 'rally' | 'pool_triathlon' | 'e_bike_enduro' | 'mountain_enduro' | 'mountain_downhill' | 'all' | number;
217
217
  export type SupportedExdScreenLayouts = 'full_screen' | 'half_vertical' | 'half_horizontal' | 'half_vertical_right_split' | 'half_horizontal_bottom_split' | 'full_quarter_split' | 'half_vertical_left_split' | 'half_horizontal_top_split' | number;
218
218
  export type SuspensionExerciseName = 'chest_fly' | 'chest_press' | 'crunch' | 'curl' | 'dip' | 'face_pull' | 'glute_bridge' | 'hamstring_curl' | 'hip_drop' | 'inverted_row' | 'knee_drive_jump' | 'knee_to_chest' | 'lat_pullover' | 'lunge' | 'mountain_climber' | 'pendulum' | 'pike' | 'plank' | 'power_pull' | 'pull_up' | 'push_up' | 'reverse_mountain_climber' | 'reverse_plank' | 'rollout' | 'row' | 'side_lunge' | 'side_plank' | 'single_leg_deadlift' | 'single_leg_squat' | 'sit_up' | 'split' | 'squat' | 'squat_jump' | 'tricep_press' | 'y_fly' | number;
219
219
  export type SwimStroke = 'freestyle' | 'backstroke' | 'breaststroke' | 'butterfly' | 'drill' | 'mixed' | 'im' | 'im_by_round' | 'rimo' | number;
@@ -0,0 +1,12 @@
1
+ export type FitProfileValueMap = Record<number, string | number>;
2
+ /**
3
+ * Lookup-oriented slices of the maintained FIT profile.
4
+ *
5
+ * The full decoder composes these exact objects into PROFILE_TYPES, keeping
6
+ * this module authoritative without loading message definitions in lookup-only consumers.
7
+ */
8
+ export declare const FIT_PROFILE_COURSE_POINTS: FitProfileValueMap;
9
+ export declare const FIT_PROFILE_GARMIN_PRODUCTS: FitProfileValueMap;
10
+ export declare const FIT_PROFILE_MANUFACTURERS: FitProfileValueMap;
11
+ export declare const FIT_PROFILE_SPORTS: FitProfileValueMap;
12
+ export declare const FIT_PROFILE_SUB_SPORTS: FitProfileValueMap;