Skip to content

Build Strong CSV converter #8

Description

@radupana

Part of #73 — Converter Architecture

Overview

Implement a Strong CSV to openweight converter. Strong is the most popular strength training app and the most common source of workout data people want to migrate. This is the first app-specific converter and will validate the shared converter architecture.

Strong CSV Format

Known columns

Column Type Maps to Notes
Date datetime (format varies) WorkoutLog.date See date format notes below
Workout Name string WorkoutLog.name Workout name/title
Duration string (e.g. "1h 23m") WorkoutLog.durationSeconds Human-readable duration, needs parsing
Exercise Name string ExerciseLog.exercise.name App-specific exercise name
Set Order integer Set ordering within exercise 1-based
Weight number SetLog.weight In user's preferred unit
Reps integer SetLog.reps Rep count
Distance number SetLog.distance Distance value
Seconds integer SetLog.durationSeconds For timed exercises
Notes string SetLog.notes Per-set notes
Workout Notes string WorkoutLog.notes Workout-level notes
RPE number SetLog.rpe May be present in newer exports

Known quirks

  • Date format varies by locale: US exports use "2024-01-15 10:30:00" but other locales may differ. Must handle multiple date formats.
  • Duration is human-readable: "1h 23m 45s" format, needs regex parsing to extract seconds.
  • No unit column: Weight unit depends on the user's app setting at export time. Converter needs --unit kg|lb flag to specify.
  • No set type: Strong does not distinguish warmup/working/failure sets. All sets are treated equally. Conversion report should note this as data that cannot be inferred.
  • No superset info: Strong does not export superset groupings.
  • No RPE in most exports: RPE column may be absent in older exports. Handle gracefully.
  • Exercise names include equipment: e.g., "Bench Press (Barbell)", "Lateral Raise (Dumbbell)". Can be parsed to extract equipment.
  • Export may be paywalled: Strong Pro required for CSV export in some versions.
  • Must export in English: Non-English exports may have translated column headers.

Data loss (Strong to openweight)

Strong's format is minimal, so conversion is mostly additive. However:

  • No set types can be inferred (all become untyped)
  • No superset groupings
  • No RPE (in most exports)
  • No tempo, RIR, or rest data

This should be documented in the conversion report.

Implementation

Strong-specific transformer (tools/converters/src/converters/strong.ts)

const STRONG_COLUMNS: ColumnMap = {
  "Date":           "date",
  "Workout Name":   "workoutName",
  "Duration":       "workoutDuration",
  "Exercise Name":  "exerciseName",
  "Set Order":      "setOrder",
  "Weight":         "weight",
  "Reps":           "reps",
  "Distance":       "distance",
  "Seconds":        "durationSeconds",
  "Notes":          "notes",
  "Workout Notes":  "workoutNotes",
  "RPE":            "rpe",
};

Key implementation details

  1. Duration parsing: Parse "1h 23m 45s" format with regex. Handle variations ("1h 23m", "45m", "1h").
  2. Equipment extraction: Optionally parse "Exercise (Equipment)" pattern to split into exercise.name + exercise.equipment.
  3. Workout grouping: Group rows by Date + Workout Name to form individual WorkoutLog objects.
  4. Exercise grouping: Within a workout, group by Exercise Name maintaining Set Order.
  5. Unit handling: Accept --unit flag. Default to preserving as-is with a warning in report if no unit specified.

Test Plan

  • Fixture-based tests with sample Strong CSV data
  • Verify workout grouping by date + name
  • Verify exercise grouping and set ordering
  • Verify duration parsing for various formats
  • Verify equipment extraction from exercise names
  • Verify handling of missing optional columns (RPE, Distance, Seconds)
  • Verify empty/null field handling
  • Roundtrip: converted output passes validateWorkoutLog()
  • Edge cases: single-exercise workouts, timed-only exercises, bodyweight exercises (weight = 0)

Depends On

Part Of

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions