{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "$id": "https://pacedrift.com/schema/v2/runexport.json",
  "title": "RunExport v2",
  "description": "Open JSON schema for HealthKit running workout exports. See https://github.com/pacedrift/schema for documentation, glossary, and reference parsers.",
  "type": "object",
  "additionalProperties": false,
  "required": ["exportedAt", "rangeStart", "rangeEnd", "schemaVersion", "runs"],
  "properties": {
    "exportedAt": {
      "type": "string",
      "format": "date-time",
      "description": "ISO-8601 timestamp recording when this export was generated."
    },
    "rangeStart": {
      "type": "string",
      "format": "date-time",
      "description": "ISO-8601 lower bound of the queried date range. Workouts whose startDate falls before this should not appear."
    },
    "rangeEnd": {
      "type": "string",
      "format": "date-time",
      "description": "ISO-8601 upper bound of the queried date range."
    },
    "schemaVersion": {
      "type": "integer",
      "const": 2,
      "description": "Schema version identifier. v2 documents must declare 2."
    },
    "runs": {
      "type": "array",
      "description": "Running workouts in the queried date range, sorted by endDate descending.",
      "items": { "$ref": "#/definitions/Run" }
    }
  },
  "definitions": {
    "Run": {
      "type": "object",
      "additionalProperties": false,
      "required": [
        "uuid",
        "startDate",
        "endDate",
        "durationSeconds",
        "source",
        "activityType",
        "workoutMetadata",
        "samples",
        "events",
        "route"
      ],
      "properties": {
        "uuid": {
          "type": "string",
          "format": "uuid",
          "description": "Stable identifier from the source HKWorkout. Same workout exported twice produces the same uuid."
        },
        "startDate": {
          "type": "string",
          "format": "date-time"
        },
        "endDate": {
          "type": "string",
          "format": "date-time"
        },
        "durationSeconds": {
          "type": "number",
          "minimum": 0,
          "description": "Wall-clock duration of the workout. Includes pauses if the recorder uses wall-clock duration; producers should document their convention."
        },
        "distanceMeters": {
          "type": ["number", "null"],
          "minimum": 0,
          "description": "Total distance from HealthKit's deduplicated workout statistics. Prefer this over re-summing raw distance samples (which may contain duplicate-source entries)."
        },
        "activeEnergyKcal": {
          "type": ["number", "null"],
          "minimum": 0
        },
        "totalEnergyKcal": {
          "type": ["number", "null"],
          "minimum": 0,
          "description": "Often equal to activeEnergyKcal for runs; producers may treat them as the same value if total is not separately tracked."
        },
        "elevationAscendedMeters": {
          "type": ["number", "null"],
          "description": "From HKMetadataKeyElevationAscended when present. Consumers may compute their own value from the GPS altitude track if this is null."
        },
        "source": {
          "type": "string",
          "description": "Human-readable name of the recording application."
        },
        "sourceBundleID": {
          "type": ["string", "null"],
          "description": "Reverse-DNS bundle identifier of the recording application, e.g. com.runbuddy.prod."
        },
        "device": {
          "type": ["string", "null"],
          "description": "Device that produced the recording, e.g. \"Apple Watch\". Null if recorded on iPhone alone."
        },
        "activityType": {
          "type": "string",
          "enum": ["running"],
          "description": "v2 documents only contain running workouts. Future versions may expand this set."
        },
        "workoutMetadata": {
          "type": "object",
          "description": "Verbatim HealthKit workout metadata, with all values stringified for portability.",
          "additionalProperties": { "type": "string" }
        },
        "samples": {
          "type": "object",
          "description": "Per-metric time series, keyed by metric name. Open dictionary — consumers must accept arbitrary keys and ignore unknown metrics. Well-known keys: heartRate (bpm), distanceWalkingRunning (m), stepCount (steps), activeEnergyBurned (kcal), flightsClimbed (flights), runningSpeed (m/s), runningPower (W), runningStrideLength (m), runningVerticalOscillation (m), runningGroundContactTime (s). See glossary.md.",
          "additionalProperties": {
            "type": "array",
            "items": { "$ref": "#/definitions/QuantitySample" }
          }
        },
        "events": {
          "type": "array",
          "description": "Lap markers, pauses, segments emitted during the workout.",
          "items": { "$ref": "#/definitions/WorkoutEvent" }
        },
        "route": {
          "type": "array",
          "description": "GPS track. Empty array for treadmill / indoor runs.",
          "items": { "$ref": "#/definitions/LocationPoint" }
        }
      }
    },
    "QuantitySample": {
      "type": "object",
      "additionalProperties": false,
      "required": ["startDate", "endDate", "value"],
      "properties": {
        "startDate": { "type": "string", "format": "date-time" },
        "endDate":   { "type": "string", "format": "date-time" },
        "value":     {
          "type": "number",
          "description": "Sample value in the unit fixed for this metric — see glossary.md."
        }
      }
    },
    "WorkoutEvent": {
      "type": "object",
      "additionalProperties": false,
      "required": ["type", "startDate", "endDate", "metadata"],
      "properties": {
        "type": {
          "type": "string",
          "enum": [
            "pause",
            "resume",
            "lap",
            "marker",
            "motionPaused",
            "motionResumed",
            "segment",
            "pauseOrResumeRequest",
            "unknown"
          ],
          "description": "Lower-cased HKWorkoutEventType. \"unknown\" is the fallthrough for future enum additions."
        },
        "startDate": { "type": "string", "format": "date-time" },
        "endDate": {
          "type": "string",
          "format": "date-time",
          "description": "Equal to startDate for instantaneous events (lap, marker). Different for spans (segment, pause)."
        },
        "metadata": {
          "type": "object",
          "additionalProperties": { "type": "string" }
        }
      }
    },
    "LocationPoint": {
      "type": "object",
      "additionalProperties": false,
      "required": ["timestamp", "latitude", "longitude"],
      "properties": {
        "timestamp": { "type": "string", "format": "date-time" },
        "latitude":  { "type": "number", "minimum": -90,  "maximum": 90 },
        "longitude": { "type": "number", "minimum": -180, "maximum": 180 },
        "altitude": {
          "type": ["number", "null"],
          "description": "Metres above sea level. May be unreliable on lower-end GPS devices; pair with verticalAccuracy."
        },
        "speedMetersPerSecond": {
          "type": ["number", "null"],
          "minimum": 0,
          "description": "Instantaneous speed at this point. Null if HealthKit reported a negative value (the convention for unknown)."
        },
        "course": {
          "type": ["number", "null"],
          "minimum": 0,
          "maximum": 360,
          "description": "Direction of travel in degrees (0 = north). Null if unknown."
        },
        "horizontalAccuracy": {
          "type": ["number", "null"],
          "minimum": 0,
          "description": "Radius of horizontal uncertainty in metres. 68% confidence per Apple's docs."
        },
        "verticalAccuracy": {
          "type": ["number", "null"],
          "minimum": 0
        }
      }
    }
  }
}
