What to build
Today there is no module that owns the Submission concept. Scoring lives in lib/builder-guide-data.ts, status labels and SLA in lib/intake-config.ts, similarity input-shaping in app/api/submissions/[id]/similarity/route.ts, and the persistence (insert into submissions + denormalize into submission_details) is hand-rolled in app/api/submissions/route.ts. There is no Submission type — just a tuple of facts assembled at every callsite.
The bugs in this flow won't be in any one extracted helper — they'll be in how scoring + denormalization + status transitions + similarity get stitched together. That's the locality gap the deepening targets.
Introduce a module that owns the Submission lifecycle end-to-end:
- The
Submission type (with its fields, status, score, tier)
- A factory that turns builder-guide answers into a scored Submission
- The persistence path (the two-table insert into
submissions + submission_details)
- Status transitions (using the labels/SLA already in
intake-config.ts)
- The call into
lib/similarity.ts with the right profile shape
API routes become thin HTTP adapters. The scoring tables stay in builder-guide-data.ts (data) but are consumed via the new module.
Acceptance criteria
Blocked by
None - can start immediately
What to build
Today there is no module that owns the Submission concept. Scoring lives in
lib/builder-guide-data.ts, status labels and SLA inlib/intake-config.ts, similarity input-shaping inapp/api/submissions/[id]/similarity/route.ts, and the persistence (insert intosubmissions+ denormalize intosubmission_details) is hand-rolled inapp/api/submissions/route.ts. There is noSubmissiontype — just a tuple of facts assembled at every callsite.The bugs in this flow won't be in any one extracted helper — they'll be in how scoring + denormalization + status transitions + similarity get stitched together. That's the locality gap the deepening targets.
Introduce a module that owns the Submission lifecycle end-to-end:
Submissiontype (with its fields, status, score, tier)submissions+submission_details)intake-config.ts)lib/similarity.tswith the right profile shapeAPI routes become thin HTTP adapters. The scoring tables stay in
builder-guide-data.ts(data) but are consumed via the new module.Acceptance criteria
lib/submission.tsexists and exports theSubmissiontype plus the factory, persistence, and status-transition functionsapp/api/submissions/route.tsis a thin handler that delegates to the module — no inline SQL or denormalizationapp/api/submissions/[id]/similarity/route.tsgets its similarity profile from the module, not by re-shaping the row inlineintake-config.tsandbuilder-guide-data.tssurvive as data tables; the orchestration moves tosubmission.tsnpm run buildpasses; manual smoke test: a submission can be created via the wizard and viewed at/intake/[token]Blocked by
None - can start immediately