Skip to content

Scoresheet pipeline

CSV in, PDF out: CSV → Handlebars → Chromium → per-chunk PDFs → merged PDF, in sage-tools-api/src/scoresheets/. Entirely independent of the sync pipeline — no shared state, no shared endpoints.

Composition

  • ScoresheetConfig.mjs is the type registry — each registered type maps to a templates/<type>.html + <type>.css pair. Four types ship today: Standard (with a referee column), No Referee, No Referee Wide, and Best of 3. Adding a new type is a new template pair and a ScoresheetConfig.mjs entry — nothing else needs touching.
  • index.mjs composes the pipeline: parse the uploaded CSV, render each match through the selected type's Handlebars template, render the populated HTML through headless Chromium (Puppeteer) into per-chunk PDFs, merge into one final PDF.

The pipeline is wired lazily, not eagerly, in index.mjs's composition root. Puppeteer is only imported on the first scoresheet request — the sync pipeline is wired eagerly, so a sync-only cold start (the common case for this service) never pays Puppeteer's import cost at all. Don't move that import to the top level.

Where the CSV comes from

tools/scoresheet-generator.html can source the csv part of the request from either an uploaded file, or — for a registered event — the matchesCsv string in that day's published event-data snapshot (<event-key>/data/<day-key>.json), fetched client-side and handed to the same FormData path as a Blob. That snapshot's CSV already has the exact header this pipeline's CsvService expects (matched by header name, not position), so no transformation happens on either side — this pipeline has no awareness of where its input came from. See sage-docs/docs/specs/implemented/scoresheet-event-picker-spec.md for the picker's own design.

Endpoints

  • POST /scoresheets/generate — multipart (csv file + evt, type, out, blanks), returns a PDF directly.
  • POST /scoresheets/generate/stream — same inputs, but returns NDJSON progress lines as each chunk renders, then a final {"phase":"done","pdfBase64":...} line.
  • POST /v3/scoresheets — the /v3 twin of both: the PDF, or the NDJSON stream when the request says Accept: application/x-ndjson. This is what tools/scoresheet-generator.html calls, and its progress bar reads the stream; the two routes above stay served, frozen.

Once streaming has started, the HTTP status is locked at 200 — a failure midstream arrives as a {"phase":"error"} line in the body, not an HTTP error status, since headers are already sent.

Deployment

Runs on node:22-slim (Puppeteer requires Node ≥22.12), with system Chromium installed via apt rather than Puppeteer's own bundled download — PUPPETEER_SKIP_DOWNLOAD + PUPPETEER_EXECUTABLE_PATH in the Dockerfile. See Deployment for the full Cloud Run setup.


Features: Scoresheet Generator usage