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 jobCore 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_buildertier feature (Pro/Elite) plus the PostHog flagworkout-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_jobsaudit 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 > 1parses only the first workout’s span and shows a warning banner. - Same spend meter as Spotter (D9) —
AgentRateLimitService.preCheckbefore the LLM call,AgentCostTracker.recordTurnafter; budget breach → 429ai_budget_exceeded. Spend lands inai_usage_daily. - Fixture playback —
PARSE_EXTRACTION_FIXTURES=1replays 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.
Related features
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 theparse.*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_jobsmigration, 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.6score /0.1gap) are seeded from the Spotter resolve-batch heuristic, not yet tuned against real content.