Skip to Content
Living documentation — last reviewed 2026-05-28
FeaturesWorkout ParseWorkout Parse (paste to structure)

Workout Parse (paste to structure)

The freeform → structured workout transformer (FIT-245 / ADR-0014). A coach pastes workout text they already have (Excel, notes, Instagram captions) and gets the exact set_sections payload the structured builder produces — with a side-by-side preview and a movement-adjustment step before anything is committed.

What

A dedicated, non-conversational parse pipeline in apps/api/src/ai/parse/ (deliberately not the Spotter chat orchestrator — no conversation state, no SSE, no confirm cards). One synchronous POST /organizations/:orgId/workouts/parse call runs:

paste text → LLM extraction (Sonnet, ONE forced-tool call): movement mentions + section structure + char spans. No exercise IDs. No authority over section shape. → deterministic grammar: detects canonical shapes (amrap/emom/for_time/tabata/ rep_scheme/rounds/intervals); reconciles with the LLM's guess — grammar wins → embedding resolution: each mention → ExerciseSearchService.search (hybrid RRF) → top-N candidates + confidence (auto / suggested / unresolved) → ParseDraft (IR) persisted to ai_parse_jobs, returned to the client → web preview: original text ↔ the real builder canvas, side by side → adjust: pick candidate / search library / create org-local exercise / drop-to-note → commit: existing WorkoutsService write path; correction delta recorded on the job

Core doctrine: the LLM does the least it can. Under-structure, never mis-structure. Everything deduced is an editable suggestion; unclaimed text becomes notes, never a fabricated section; supersets only on explicit signal (A1/A2, the literal word “superset”).

Why

  • Programming is the most time-consuming task in a gym day, and most coaches already have the content as text. Re-typing it into the builder is slow enough that they paste into the freeform field instead — losing shape detection, exercise linking, and analytics.
  • The correction delta recorded at commit (payload-diff.ts) turns “is the parse good?” into a computable product metric: % zero-edit commits, mean edit distance.
  • The preview reuses the builder’s own editing surface (SectionEditor + useBuilderState), so committed workouts are indistinguishable from hand-built ones and there is no second editing UI to maintain.

Who

  • Owner / admin / coach — full access. Member role is 403’d at the controller.
  • Gated by tier AND flag: the workout_builder tier feature (Pro/Elite) plus the PostHog flag workout-parse-transformer (default OFF, per-org rollout, fail-closed). See rollout-runbook.md.

Capabilities

  • Two entry points (D12): the “Paste & structure” card on the workout-type chooser (/dashboard/workouts/new/paste), and the “Structure this” button on the freeform form (hands the current description + workout id off via sessionStorage; commit then structures that workout instead of creating a new one).
  • Server-persisted drafts — every parse leaves an ai_parse_jobs audit row with the normalized input text, the IR draft, the auto-resolved payload, cost, and stage timings. Preview survives refresh via ?jobId= + the GET endpoint.
  • Strict English-only v1 (D3) — extraction detects language; non-English → job rejected_non_english, HTTP 422, localized client error.
  • Multi-workout paste: detect + warn + parse first (D4) — workoutCount > 1 parses only the first workout’s span and shows a warning banner.
  • Same spend meter as Spotter (D9) — AgentRateLimitService.preCheck before the LLM call, AgentCostTracker.recordTurn after; budget breach → 429 ai_budget_exceeded. Spend lands in ai_usage_daily.
  • Fixture playbackPARSE_EXTRACTION_FIXTURES=1 replays recorded extraction outputs (sha256-keyed) so unit tests and e2e never depend on a live model call.
  • Eval harness — golden corpus + metrics runner (apps/api/src/ai/parse/eval/, pnpm parse-eval) measuring % resolved, % correct shape, % zero-edit, mean edit distance, latency, and cost per parse. See its README.
  • Enabling fix — org-local exercise create/update now enqueues embedding enrichment (D10), so exercises created in the adjustment panel are resolvable in future parses.
  • workouts — the commit path (WorkoutsService.create / setSections) and the builder UI the preview reuses.
  • exercises — hybrid search (ExerciseSearchService) powering resolution; org-local create for the “create new exercise” adjustment action.
  • spotter-agent — shared building blocks: AnthropicClient, cost tracker, rate limiter, ai_usage_daily.
  • event-tracking — PostHog facade used for the feature flag and the parse.* event taxonomy.

Status

  • FIT-245 — implemented, flag-gated OFF. API parse module + commit/discard, web paste flow, eval harness, e2e coverage all in place. Human to-dos before rollout: apply the ai_parse_jobs migration, create the PostHog flag, build the dashboard, collect real (Erez) golden content, run the live eval. See rollout-runbook.md.

Gaps

  • Hebrew parsing is the top deferred item — the first validation customer’s content is Hebrew, and v1 rejects it by design. The golden corpus includes Hebrew samples to measure the gap.
  • Multi-workout import (a week’s programming in one paste) is deferred (FIT-38); v1 parses the first workout only.
  • Sync POST holds the connection ~5–15s; if volume grows this moves to async + polling.
  • Re-navigating to a committed/discarded job shows the preview without a terminal-state banner (no dedicated copy yet — commit bar is simply absent).
  • Confidence thresholds (0.6 score / 0.1 gap) are seeded from the Spotter resolve-batch heuristic, not yet tuned against real content.