added sync and backup

This commit is contained in:
2026-06-12 06:25:45 -04:00
parent 6df25bcd8f
commit dbae49dce2
7 changed files with 1279 additions and 202 deletions

109
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,109 @@
// VS Code debug configurations for openrun.
// Requires the "Python" + "Python Debugger" extensions from Microsoft.
// Press F5 in any file to pick a configuration.
{
"version": "0.2.0",
"configurations": [
{
"name": "Streamlit: openrun-web",
"type": "debugpy",
"request": "launch",
"module": "streamlit",
"args": [
"run",
"${workspaceFolder}/src/openrun/web/app.py",
"--server.port=8501",
"--browser.gatherUsageStats=false"
],
// justMyCode=false lets you step into openrun + Streamlit internals.
// Set to true if you only ever want to break in your own code.
"justMyCode": false,
"console": "integratedTerminal",
"cwd": "${workspaceFolder}"
},
{
"name": "Pytest: all tests",
"type": "debugpy",
"request": "launch",
"module": "pytest",
"args": ["tests", "-v"],
"justMyCode": false,
"console": "integratedTerminal",
"cwd": "${workspaceFolder}"
},
{
"name": "Pytest: current file",
"type": "debugpy",
"request": "launch",
"module": "pytest",
"args": ["${file}", "-v"],
"justMyCode": false,
"console": "integratedTerminal",
"cwd": "${workspaceFolder}"
},
{
"name": "Python: current file",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"justMyCode": false,
"console": "integratedTerminal",
"cwd": "${workspaceFolder}"
},
{
"name": "CLI: openrun-init",
"type": "debugpy",
"request": "launch",
"module": "openrun.setup",
"justMyCode": false,
"console": "integratedTerminal",
"cwd": "${workspaceFolder}"
},
{
"name": "CLI: openrun-ingest <path>",
"type": "debugpy",
"request": "launch",
"module": "openrun.ingest.garmin_export",
"args": ["${input:exportPath}"],
"justMyCode": false,
"console": "integratedTerminal",
"cwd": "${workspaceFolder}"
},
{
"name": "CLI: openrun-link-fit <path>",
"type": "debugpy",
"request": "launch",
"module": "openrun.ingest.fit_linker",
"args": ["${input:exportPath}"],
"justMyCode": false,
"console": "integratedTerminal",
"cwd": "${workspaceFolder}"
},
{
"name": "CLI: openrun-link-fit --relink <new-root>",
"type": "debugpy",
"request": "launch",
"module": "openrun.ingest.fit_linker",
"args": ["${input:exportPath}", "--relink"],
"justMyCode": false,
"console": "integratedTerminal",
"cwd": "${workspaceFolder}"
},
{
"name": "CLI: openrun-sync",
"type": "debugpy",
"request": "launch",
"module": "openrun.ingest.garmin_api",
"justMyCode": false,
"console": "integratedTerminal",
"cwd": "${workspaceFolder}"
}
],
"inputs": [
{
"id": "exportPath",
"type": "promptString",
"description": "Path to a Garmin export folder or .zip"
}
]
}

55
NEXT_SESSION.md Normal file
View File

@@ -0,0 +1,55 @@
# Session handoff — 2026-06-12
Working plan agreed with the user: step through P0→P4 below, in order.
Read this file instead of re-exploring the repo; README.md + ROADMAP.md cover
architecture and conventions. Delete this file when the list is done.
## State (verified 2026-06-12, P0 session)
- All 111 tests pass (`uv run pytest`).
- **P0 DONE** (commit 6df25bcd, NOT yet pushed — push denied to agent, run
`git push origin main`): .gitignore fixed and Takeout dump (23,673 files,
personal health data) untracked; note it is still in remote history from
earlier pushes — rewrite history if that ever matters. `.gitlab-ci.yml`
added (assumes GitLab at g.o00.io; swap if Gitea/Forgejo). `gitlab` remote
URL typo (ttps://) fixed. `scripts/backup_db.sh` snapshots the DB, keeps 14.
- DB: `garmin/data/garmin.db` is canonical — 378 activities (→ 2026-05-10),
349 FITs linked, wellness through 2026-05-17.
- The stray `../data/garmin.db` (vault root) was a Takeout-ZIP ingest run from
the wrong cwd on 2026-06-08 (7 extra activities May 17Jun 2, epoch-ms
timestamps, no FITs). Copied to
`data/backups/vault-root-takeout-ingest-2026-06-08.db`; the original at
`../data/` is redundant once live sync runs — user should delete it.
- **Sync is blocked on auth**: `.secrets/` is empty. User runs
`uv run openrun-auth` (password + MFA), then
`uv run openrun-sync --days 35` (covers the wellness gap since May 17;
activities + FITs are incremental automatically).
- `race_plan` table is EMPTY and `manual_activities` empty, but openrun.toml
has races: 30K 2026-06-13, 50K 2026-07-25, 50 MILE 2026-09-12.
- Known bugs: 18 activities have epoch-ms floats in `start_time_local`
(should be ISO strings) — normalize on ingest + one-time migration;
`datetime.utcnow()` deprecation warnings in `src/openrun/ingest/garmin_api.py`.
## Plan
- **P0 — protect the work**: .gitignore, private remote, CI running pytest,
pick canonical DB + backup story. (commit done)
- **P1 — use it for the ultra build**: live sync (`openrun-auth` +
`openrun-sync`, fall back to `../garmin-pgc/` python-garminconnect backend if
garth hits Cloudflare 429), then populate `race_plan` through 2026-09-12
using `banister_forecast` + `calibrate_tl_per_km`; log off-watch work.
- **P2 — data quality**: fix the 18 mixed-format timestamps, utcnow warnings,
add `schema_version` + tiny migration runner before any public release.
- **P3 — merge sync fork**: fold `../garmin-pgc/` into openrun as
`openrun-sync --backend=garminconnect` instead of a sibling project.
- **P4 — roadmap items reordered for the ultra**: TrainingReadinessDTO +
RunRacePredictions ingest (ROADMAP 1.2), route map (3.2). Defer DBSCAN,
multi-athlete, distribution until after September.
## Gaps identified (not yet scheduled)
- Subjective data: RPE/soreness/injury notes (user hand-writes these in
../RunningLogs.md; March right-leg injury) — tie into DB, plot vs ACWR/TSB.
- Ultra metrics: weekly vert, time-on-feet, back-to-back long-run detection,
grade-adjusted pace (`elevation_gain` already in schema, unused).
- Sync automation (launchd/cron weekly `openrun-sync`).

47
NatesNotes.md Normal file
View File

@@ -0,0 +1,47 @@
# Nate Personal Notes
## Eisley
### Exercises
### Gait / posture
- knee forward
- stepping too far forward
- lose momentum, hard strike, pulling
- instead, run on ice
- feet land underneath, push backward
- this has revealed weakness and asymmetry in feet/ankles/legs
- in particular, left foot is very difficult to load medial side during pushoff
- 170 bpm is ideal
- This has made HR consistently maintainable < 145
- I end up running slower to maintain HR
- datafield on watch showing big HR/Cadence - only important fields for training
- feeling of winding/turning my feet sup on lateral, inf on medial
- aligns with medial MTP avoidance, good queue
## Anatomy Observations
- Natural cadence is 161. Body falls back to this if I don't think about it
- Feeling of legs out of socket, or tailbone out of place, or sacrum twisted(?)
- asymettery in motion of each leg
- tightness and weakness where collarbones join
- feel of trying to pull that area down seems right
- likewise in shoulders, feel out of socket or twisted/tight
## Other PT Findings
### Connor Harris
- Big toe ball exercise
- side faces wall
- forearm against wall, opposite leg slightly lateral
- raise wall-side leg marcher
- lift body w/far leg, focus on pressing into big toe ball
- imagine trying to lift toes from ground
## Race Logistics
- I have undernourished
- 80g carb/hr goal
- 3 strides in / 2 out breathing
- noticed diaphragm very often in same place on same foot
-

File diff suppressed because one or more lines are too long

View File

@@ -4,7 +4,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"# 02 \u2014 Running\n",
"# 02 Running\n",
"\n",
"Volume, pace, HR efficiency, training load."
]
@@ -34,7 +34,7 @@
},
{
"cell_type": "code",
"execution_count": 2,
"execution_count": null,
"metadata": {},
"outputs": [
{
@@ -71,7 +71,7 @@
},
{
"cell_type": "code",
"execution_count": 3,
"execution_count": null,
"metadata": {},
"outputs": [
{
@@ -94,7 +94,7 @@
" ax.scatter(g['avg_hr'], g['pace_min_per_km'], s=g['distance_km'] * 6, alpha=0.5, label=str(yr))\n",
"ax.invert_yaxis() # faster pace = smaller number, want top\n",
"ax.set_xlabel('Avg HR (bpm)')\n",
"ax.set_ylabel('Pace (min/km, faster \u2191)')\n",
"ax.set_ylabel('Pace (min/km, faster )')\n",
"ax.set_title('Pace vs. HR by run, sized by distance')\n",
"ax.legend(title='Year')\n",
"ax.grid(alpha=0.3)\n",
@@ -111,7 +111,7 @@
},
{
"cell_type": "code",
"execution_count": 4,
"execution_count": null,
"metadata": {},
"outputs": [
{
@@ -138,10 +138,10 @@
"monthly_easy = easy.set_index('start_time_local')['pace_min_per_km'].resample('ME').median()\n",
"\n",
"fig, ax = plt.subplots(figsize=(13, 4))\n",
"ax.scatter(easy['start_time_local'], easy['pace_min_per_km'], alpha=0.3, s=easy['distance_km']*5, label='runs (HR<150, \u22653km)')\n",
"ax.scatter(easy['start_time_local'], easy['pace_min_per_km'], alpha=0.3, s=easy['distance_km']*5, label='runs (HR<150, 3km)')\n",
"monthly_easy.plot(ax=ax, color='C3', lw=2, marker='o', label='Monthly median')\n",
"ax.invert_yaxis()\n",
"ax.set_ylabel('Pace (min/km, faster \u2191)')\n",
"ax.set_ylabel('Pace (min/km, faster )')\n",
"ax.set_title('Easy-pace trend (proxy for aerobic fitness)')\n",
"ax.legend()\n",
"ax.grid(alpha=0.3)\n",
@@ -151,7 +151,28 @@
{
"cell_type": "markdown",
"metadata": {},
"source": "## Training load \u2014 Banister CTL / ATL / TSB\n\nGarmin reports a per-activity training load. The standard endurance-training lens (TrainingPeaks \"Performance Management Chart\") tracks three derived numbers:\n\n- **CTL** *Chronic Training Load* \u2014 EWMA of daily load with \u03c4 = 42 days. **Fitness.**\n- **ATL** *Acute Training Load* \u2014 same EWMA with \u03c4 = 7 days. **Fatigue.**\n- **TSB** *Training Stress Balance* \u2014 yesterday's CTL minus yesterday's ATL. **Form.**\n\nTSB interpretation:\n\n| TSB | meaning |\n|---|---|\n| < \u221230 | severely fatigued (injury risk) |\n| \u221210 to \u221230 | productive overload \u2014 heart of a build |\n| \u221210 to 0 | balanced building |\n| 0 to +10 | sharpening |\n| **+10 to +25** | **fresh / peaked \u2014 race-day target** |\n| > +25 | detrained (taper too long) |\n\nThis replaces the older 7/28-day rolling ACWR plot \u2014 same data, EWMAs are smoother and TSB gives you race-day-readiness directly."
"source": [
"## Training load — Banister CTL / ATL / TSB\n",
"\n",
"Garmin reports a per-activity training load. The standard endurance-training lens (TrainingPeaks \"Performance Management Chart\") tracks three derived numbers:\n",
"\n",
"- **CTL** *Chronic Training Load* — EWMA of daily load with τ = 42 days. **Fitness.**\n",
"- **ATL** *Acute Training Load* — same EWMA with τ = 7 days. **Fatigue.**\n",
"- **TSB** *Training Stress Balance* — yesterday's CTL minus yesterday's ATL. **Form.**\n",
"\n",
"TSB interpretation:\n",
"\n",
"| TSB | meaning |\n",
"|---|---|\n",
"| < 30 | severely fatigued (injury risk) |\n",
"| 10 to 30 | productive overload — heart of a build |\n",
"| 10 to 0 | balanced building |\n",
"| 0 to +10 | sharpening |\n",
"| **+10 to +25** | **fresh / peaked — race-day target** |\n",
"| > +25 | detrained (taper too long) |\n",
"\n",
"This replaces the older 7/28-day rolling ACWR plot — same data, EWMAs are smoother and TSB gives you race-day-readiness directly."
]
},
{
"cell_type": "code",
@@ -172,14 +193,14 @@
"ax1.set_ylabel('training load')\n",
"ax1.legend(loc='upper left')\n",
"ax1.grid(alpha=0.3)\n",
"ax1.set_title('Performance Management Chart \u2014 CTL / ATL / TSB')\n",
"ax1.set_title('Performance Management Chart CTL / ATL / TSB')\n",
"\n",
"# bottom panel: form\n",
"ax2.plot(pmc.index, pmc['TSB'], color='#264653', lw=1.5)\n",
"ax2.axhspan(10, 25, color='#2a9d8f', alpha=0.12, label='race-ready (+10 to +25)')\n",
"ax2.axhspan(-30, -10, color='#e9c46a', alpha=0.12, label='productive overload (\u221230 to \u221210)')\n",
"ax2.axhspan(-30, -10, color='#e9c46a', alpha=0.12, label='productive overload (30 to 10)')\n",
"ax2.axhline(0, color='gray', lw=0.6)\n",
"ax2.axhline(-30, color='#e76f51', ls='--', lw=0.8, label='injury-risk floor (\u221230)')\n",
"ax2.axhline(-30, color='#e76f51', ls='--', lw=0.8, label='injury-risk floor (30)')\n",
"ax2.set_ylabel('TSB (form)')\n",
"ax2.legend(loc='lower left', fontsize=9)\n",
"ax2.grid(alpha=0.3)\n",
@@ -204,7 +225,7 @@
},
{
"cell_type": "code",
"execution_count": 6,
"execution_count": null,
"metadata": {},
"outputs": [
{
@@ -347,4 +368,4 @@
},
"nbformat": 4,
"nbformat_minor": 5
}
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long