Workout Parse — Code Map
API (apps/api/src/ai/parse/)
parse.module.ts— self-contained Nest module. ImportsMembershipsModule,ExercisesModule,WorkoutsModule; re-declaresAnthropicClient,AgentCostTracker,AgentRateLimitServiceas its own providers (stateless wrappers over global singletons) to avoid a circularAiModuleimport.parse.controller.ts— the four routes under/organizations/:orgId/workouts/parse(POST, GET:jobId, POST:jobId/commit, POST:jobId/discard); gate ordering;jobToDraftResponsefallback for jobs without a persisted draft.parse-orchestration.service.ts—runParse: normalize → insert job → extract → meter spend → language gate → multi-workout slicing → shape/superset reconciliation → resolution → assembleParseDraft→ persist + emit.extraction.service.ts—extract(normalizedText): forced-tool Sonnet call (record_extraction,zod → JSON Schemaviaz.toJSONSchema), one validation retry, span sanitization (sanitizeExtractionResult), fixture playback (PARSE_EXTRACTION_FIXTURES=1,hashExtractionInputsha256 prefix). ThrowsParseExtractionError.extraction.prompt.ts—EXTRACTION_SYSTEM_PROMPT(adapted from the Spotter cookbook, extraction-only rules) +EXTRACTION_TOOL_NAME. System block cached{ type: 'ephemeral', ttl: '1h' }.grammar/shape-grammar.ts—detectShape(headerText, bodyLines, hasMention): pure, deterministic canonical-shape detector (amrap/emom/for_time/rounds/tabata/rep_scheme/intervals) incl.parseDurationhelper.grammar/superset-signal.ts—detectSupersetGroups: explicit-signal-only superset grouping.resolution.service.ts—resolveMentions(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.ts—draftToSectionsPayload(draft): auto-only movements; excluded raw lines appended to section description (“Needs review…” block).parse-commit.service.ts—commit(create-path vs structure-existing-path viaWorkoutsService),discard, correction delta viadiffSectionsPayload.parse-jobs.repository.ts— thin Drizzle access toai_parse_jobs(create,update,findByIdorg-scoped).parse-observability.service.ts—emit(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(+ reservedparse.exercise_created, currently unused).parse-errors.ts—ParseHttpError({ code, message, ...extra }wire shape).dto/parse.dto.ts,dto/parse-commit.dto.ts— class-validator DTOs mirroring the shared Zod contracts; commit reusesSectionDtofromset-sections.dto.tsverbatim.fixtures/extraction/*.json— 6 recorded extraction fixtures (hash-named) for playback mode.
Eval harness (apps/api/src/ai/parse/eval/)
run-eval.ts—pnpm parse-eval --org … --user … [--clerk …] [--filter …]; runs the real pipeline per golden fixture (direct service invocation, live orPARSE_EXTRACTION_FIXTURES=1), writesreports/<timestamp>.{md,json}(gitignored).eval-metrics.ts—goldenFixtureSchema,summarizeEval,formatEvalMarkdown.golden/*.golden.json— 12 fixtures (10 English incl. one multi-workout, 2 HebrewexpectRejected).README.md— format, recording instructions, how to add real (Erez) content.
Shared (libs/shared/src/lib/parse-schemas/)
constants.ts—PARSE_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.ts—extractionResultSchema(the forced tool output),spanSchema.parse-draft.schema.ts—parseDraftSchema+ section/movement/candidate/resolution sub-schemas;superRefinevalidates sectionconfigagainstSECTION_SHAPES[shape].config.parse-api.schema.ts—parseRequestSchema,parseCommitRequestSchema,parseErrorCodeValues, response type aliases.payload-diff.ts—diffSectionsPayload(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.ts — FeatureFlags.WORKOUT_PARSE (same string as PARSE_FLAG_KEY; both deliberately defined).
DB
libs/db/src/lib/schema/ai-parse.ts—ai_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.tsx—FeatureGate('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; ownsuseBuilderState, reusesSectionEditor(its optionalrenderMovementAdornmentprop 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.ts—draftToBuilderSections(draft): SectionState[](shape/config → builder timer config, resolution → exercise selection / placeholder).parse-meta.ts—buildParseMeta(draft): sidecar mapsmovementKey → { resolution, mentionText, rawLine },sectionKey → { shapeSource, shapeConfidence }.confidence-chip.tsx—auto/suggested/unresolvedchips + 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 ownbuildSectionsPayload+deriveScoring(imported fromuse-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.tsfor 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. RequiresFEATURE_FLAGS=workout-parse-transformer:true+NEXT_PUBLIC_FEATURE_FLAGS=…— defaulted byscripts/test-e2e-web.sh; seedocs/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 astestApi.seedParseJob()inapps/web/e2e/fixtures/test-fixture.ts.