Skip to Content
Living documentation — last reviewed 2026-05-28
FeaturesWorkout ParseWorkout Parse — Code Map

Workout Parse — Code Map

API (apps/api/src/ai/parse/)

  • parse.module.ts — self-contained Nest module. Imports MembershipsModule, ExercisesModule, WorkoutsModule; re-declares AnthropicClient, AgentCostTracker, AgentRateLimitService as its own providers (stateless wrappers over global singletons) to avoid a circular AiModule import.
  • parse.controller.ts — the four routes under /organizations/:orgId/workouts/parse (POST, GET :jobId, POST :jobId/commit, POST :jobId/discard); gate ordering; jobToDraftResponse fallback for jobs without a persisted draft.
  • parse-orchestration.service.tsrunParse: normalize → insert job → extract → meter spend → language gate → multi-workout slicing → shape/superset reconciliation → resolution → assemble ParseDraft → persist + emit.
  • extraction.service.tsextract(normalizedText): forced-tool Sonnet call (record_extraction, zod → JSON Schema via z.toJSONSchema), one validation retry, span sanitization (sanitizeExtractionResult), fixture playback (PARSE_EXTRACTION_FIXTURES=1, hashExtractionInput sha256 prefix). Throws ParseExtractionError.
  • extraction.prompt.tsEXTRACTION_SYSTEM_PROMPT (adapted from the Spotter cookbook, extraction-only rules) + EXTRACTION_TOOL_NAME. System block cached { type: 'ephemeral', ttl: '1h' }.
  • grammar/shape-grammar.tsdetectShape(headerText, bodyLines, hasMention): pure, deterministic canonical-shape detector (amrap/emom/for_time/rounds/tabata/rep_scheme/intervals) incl. parseDuration helper.
  • grammar/superset-signal.tsdetectSupersetGroups: explicit-signal-only superset grouping.
  • resolution.service.tsresolveMentions(orgId, mentions): cleanMention / normalizeMentionKey (exported — orchestration uses them for lookup parity), dedupe, 4-way concurrency pool, D6 confidence mapping, per-mention error isolation.
  • draft-to-payload.tsdraftToSectionsPayload(draft): auto-only movements; excluded raw lines appended to section description (“Needs review…” block).
  • parse-commit.service.tscommit (create-path vs structure-existing-path via WorkoutsService), discard, correction delta via diffSectionsPayload.
  • parse-jobs.repository.ts — thin Drizzle access to ai_parse_jobs (create, update, findById org-scoped).
  • parse-observability.service.tsemit(stage, props) fan-out to Pino + Sentry breadcrumb + PostHog (production only), scrub() blocklist (no raw text/mentions/titles ever leave the server). Event taxonomy: parse.requested|completed|blocked|failed|committed|discarded (+ reserved parse.exercise_created, currently unused).
  • parse-errors.tsParseHttpError ({ code, message, ...extra } wire shape).
  • dto/parse.dto.ts, dto/parse-commit.dto.ts — class-validator DTOs mirroring the shared Zod contracts; commit reuses SectionDto from set-sections.dto.ts verbatim.
  • fixtures/extraction/*.json — 6 recorded extraction fixtures (hash-named) for playback mode.

Eval harness (apps/api/src/ai/parse/eval/)

  • run-eval.tspnpm parse-eval --org … --user … [--clerk …] [--filter …]; runs the real pipeline per golden fixture (direct service invocation, live or PARSE_EXTRACTION_FIXTURES=1), writes reports/<timestamp>.{md,json} (gitignored).
  • eval-metrics.tsgoldenFixtureSchema, summarizeEval, formatEvalMarkdown.
  • golden/*.golden.json — 12 fixtures (10 English incl. one multi-workout, 2 Hebrew expectRejected).
  • README.md — format, recording instructions, how to add real (Erez) content.

Shared (libs/shared/src/lib/parse-schemas/)

  • constants.tsPARSE_MAX_INPUT_CHARS (10,000), PARSE_AUTO_ACCEPT_SCORE (0.6), PARSE_AMBIGUITY_GAP (0.1), PARSE_CANDIDATES_PER_MENTION (5), PARSE_MAX_SECTIONS (20), PARSE_MAX_MOVEMENTS_PER_SECTION (50), PARSE_MODEL (claude-sonnet-4-5), PARSE_FLAG_KEY (workout-parse-transformer).
  • extraction.schema.tsextractionResultSchema (the forced tool output), spanSchema.
  • parse-draft.schema.tsparseDraftSchema + section/movement/candidate/resolution sub-schemas; superRefine validates section config against SECTION_SHAPES[shape].config.
  • parse-api.schema.tsparseRequestSchema, parseCommitRequestSchema, parseErrorCodeValues, response type aliases.
  • payload-diff.tsdiffSectionsPayload(a, b): PayloadDelta (the correction-delta / zero-edit metric).
  • index.ts — barrel; re-exported from @taikan/shared.

Also: libs/shared/src/lib/constants/feature-flags.tsFeatureFlags.WORKOUT_PARSE (same string as PARSE_FLAG_KEY; both deliberately defined).

DB

  • libs/db/src/lib/schema/ai-parse.tsai_parse_jobs + relations. See data-model.md.
  • Migration generated via pnpm db:generate (applied by CI/deploy, never by hand).

Web (apps/web/src/components/overview/workouts/paste-workout/)

  • Route: apps/web/src/app/[lang]/(protected)/dashboard/workouts/new/paste/page.tsxFeatureGate('workout_builder') wrapper.
  • paste-workout-view.tsx — container: compose state, ?jobId= load, handoff intake, resolved-actions tracking, commit bar (commit/discard + unresolved-counter).
  • paste-input.tsx — textarea (dir="auto"), char counter, submit, per-code inline errors, multi-workout warning banner.
  • parse-preview.tsx — split view; owns useBuilderState, reuses SectionEditor (its optional renderMovementAdornment prop is the one sanctioned builder edit); collapsed unparsed-remainder block.
  • source-text-pane.tsx — span-highlighted source text, hover-sync with the tree.
  • draft-to-builder.tsdraftToBuilderSections(draft): SectionState[] (shape/config → builder timer config, resolution → exercise selection / placeholder).
  • parse-meta.tsbuildParseMeta(draft): sidecar maps movementKey → { resolution, mentionText, rawLine }, sectionKey → { shapeSource, shapeConfidence }.
  • confidence-chip.tsxauto/suggested/unresolved chips + section low-confidence chip.
  • movement-resolution-popover.tsx — candidate pick / library search / create org-local exercise / drop-to-note.
  • use-parse-commit.ts — builds the final payload via the builder’s own buildSectionsPayload + deriveScoring (imported from use-builder-save.ts), calls commit/discard, invalidates workout queries, redirects.
  • paste-handoff.ts — sessionStorage handoff (writePasteHandoff / readAndClearPasteHandoff) for the “Structure this” entry.

Entry points (modified): workout-type-selection.tsx (flag-gated paste-workout-card), freeform-workout-form.tsx (flag-gated structure-this-button).

i18n: workouts.parse.* namespace in libs/shared/src/lib/i18n/dictionaries/{en,he,ru}.json.

Testing

  • API unit: colocated *.unit.spec.ts for grammar, superset, extraction, resolution, orchestration, controller, commit, observability, draft-to-payload, eval-metrics; shared specs next to each parse-schema file. Safety net: apps/api/src/workouts/workouts.service.unit.spec.ts (Task C — guards the commit write path).
  • Web: paste-input.int.spec.tsx, parse-preview.int.spec.tsx, movement-resolution-popover.int.spec.tsx (each with a colocated *.driver.tsx), draft-to-builder.unit.spec.ts, use-parse-commit.unit.spec.ts.
  • E2E: apps/web/e2e/specs/paste-workout.spec.ts + apps/web/e2e/drivers/paste-workout.driver.ts. Requires FEATURE_FLAGS=workout-parse-transformer:true + NEXT_PUBLIC_FEATURE_FLAGS=… — defaulted by scripts/test-e2e-web.sh; see docs/testing/strategy.md.
  • Seed: TestingService.seedParseJob (POST /testing/seed/parse-job) — deterministic draft job (grammar-high AMRAP w/ 2 auto movements, low-confidence section, suggested + unresolved movements, remainder) + guaranteed exercises; exposed as testApi.seedParseJob() in apps/web/e2e/fixtures/test-fixture.ts.