Skip to content

feat: Runkeeper source plugin #21

@jschloman

Description

@jschloman

Summary

Add a RunkeeperPlugin source plugin that loads activity history from a Runkeeper data export. Runkeeper exports a summary CSV alongside individual GPX files per activity — the summary CSV is the primary input for Phase 1, giving a clean what-when record of every workout.

Architecture

This plugin follows the project-wide source plugin contract:

  1. Download-then-display — the plugin reads only from a previously downloaded local export file. No outbound network or API calls are made at Streamlit runtime. If the data file is absent, fetch() raises FileNotFoundError with a message directing the user to obtain and place the export file.

  2. Data sovereignty — the plugin has no knowledge of other sources. fetch() returns only this source's canonical DataFrame. No filtering, joining, or merging with other sources is performed here; that is the exclusive responsibility of DataBroker.

Data export

How to get: runkeeper.com → Settings → Export Data → "Export Data" button (immediate ZIP download)

Format: ZIP containing:

  • cardioActivities.csv — one row per activity (summary)
  • Individual .gpx files per activity (GPS tracks, not needed for Phase 1)

Key fields in cardioActivities.csv:

Field Maps to
Date (YYYY-MM-DD HH:MM:SS) timestamp
Type sublabel (e.g. "Running", "Cycling")
Route Name label
Distance (km) preserve as distance_km
Duration (HH:MM:SS) preserve as duration_s
Average Pace preserve
Average Speed (km/h) preserve
Calories Burned preserve
Average Heart Rate (bpm) preserve as avg_hr
Notes preserve
GPX File preserve as gpx_file (filename reference)

Plugin spec

  • PLUGIN_TYPE: what-when
  • PLUGIN_ID: runkeeper
  • DISPLAY_NAME: Runkeeper Activities
  • Config fields: export_dir (path to the unzipped Runkeeper export directory; loads cardioActivities.csv from within it)
  • Parse Date string to Unix timestamp
  • Convert Duration (HH:MM:SS string) to integer seconds for duration_s
  • label → Route Name (fall back to Type if blank), category → "fitness"

Normalized output schema

timestamp       int       Unix timestamp of activity start
label           str       Route name or activity type if unnamed
sublabel        str       Activity type ("Running", "Cycling", etc.)
category        str       "fitness"
source_id       str       "runkeeper"
distance_km     float     Distance in kilometres (NaN if not recorded)
duration_s      int       Duration in seconds
avg_hr          float     Average heart rate (NaN if not recorded)

Acceptance criteria

  • RunkeeperPlugin registered via @register and added to load_builtin_plugins()
  • Date and Duration fields parsed correctly
  • Unnamed routes fall back to activity type for label
  • validate_schema() passes on output
  • Unit tests covering: basic load, unnamed route, missing heart rate, zero-distance activity, empty CSV
  • Plugin contains zero HTTP/network code — reads only from the local export file
  • Plugin contains zero references to other source plugins, their schemas, or join keys
  • All existing tests continue to pass

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions