Sign-off PDF¶
The sign-off PDF is the final, immutable artefact that iFlow produces for a clinical case. This page describes how the Approve-and-Sign flow assembles it atomically and what guarantees it provides.
Endpoints¶
The signoff HTMX routes live under /<report_id>/signoff/:
| Method | Path | Purpose |
|---|---|---|
GET |
/confirm-modal |
Open confirmation modal; kick off async preview render |
POST |
/prepare-pdf |
Trigger (re)render of the preview PDF |
GET |
/pdf-status |
Polled by modal spinner while render is in flight |
GET |
/pdf |
Stream the currently rendered (preview) PDF |
POST |
/commit |
Atomic finalize — Approve-and-Sign |
GET |
/completed-modal |
Post-finalize completion popup with download link |
Atomic Approve-and-Sign¶
The core method is approve_and_sign() on the Report service. It executes
all of the following in a single database transaction:
- Validate the report is in
approvedorpending_review(falls through toapproveif the latter). - Take the preview PDF path (already rendered by
prepare_signoff_pdf) and treat it as the signed PDF path. - Set on the
Report:status = signedsigned_by = <user_id>signed_at = now()signed_pdf_path = orders/{slug}/signed-report.pdf
- Call
MinerServiceClient.advance_order_on_sign(order_id)which transitions the order fromsign_offtocompletedin Miner. - Commit.
On any failure, the transaction rolls back: the report does not become
signed and no PDF is left behind. One exception: if Miner returns 409
because the order is already past sign_off, the signoff routine treats
that as success and proceeds.
prepare_signoff_pdf()¶
The preview render used by the signoff confirmation modal is produced
synchronously by prepare_signoff_pdf():
- Computes the signed path via
signed_report_path(display_id)→orders/{slug}/signed-report.pdf. - Renders the DOCX to PDF using the template selected for the report.
- Stamps the signer's e-signature onto the final page if the template contains a sign-off placeholder table (see below).
- Uploads the result to the project bucket via admin credentials (not the caller's credentials), so a clinical user without bucket write access can still produce the signed PDF.
- Stores a small cache record under
Report.properties["signoff_preview"]with{"path": ..., "status": "ready" | "failed" | "missing_profile", ...}. - Refuses to run if the report is already
signed— the path is immutable.
Every time the user opens the confirmation modal, any stale
signoff_preview is cleared first so the render is always produced from
the latest report state and signer profile data. This is how updating
your profile signature and reopening the modal actually takes effect.
This means the confirmation modal shows the same PDF that will become the signed artifact. Curators have one last chance to cancel before the atomic commit locks it in.
E-signature stamping¶
If the selected DOCX template ends with a sign-off placeholder table (the
last table on the final or penultimate page), prepare_signoff_pdf()
overlays it with the signer's identity and signature image before upload.
The stamp contains:
Approved on: <weekday>, <day> <month> <year>, <time> (UTC)Approved by: <display name>[, <job title>]- The signer's uploaded signature PNG below the text
Templates without such a placeholder table (e.g. non-clinical internal reports) flow through the same path unchanged — the stamper detects the missing placeholder and returns the PDF untouched.
Signer profile requirements¶
When the template has a sign-off placeholder, the signer's iFlow profile must have both:
- Display name — shown under "Approved by". Synced from Zitadel, so usually present for any real user.
- Signature image — a PNG uploaded at
/account/profilein the Admin Console. Size-limited to 1 MB.
Optionally, a job title on the profile is appended after the display name on the stamp.
If either required field is missing, the status becomes missing_profile
and the confirmation modal shows the specific missing-data error
("Signer has no signature uploaded" or "Signer has no display name in
profile"). The "Generate Final Report" button stays disabled — no retry
button is offered: the signer closes the modal, updates their profile,
reopens the modal, and the fresh render takes over.
This operation cannot be undone
approve_and_sign() writes an immutable PDF, marks the report
signed, and advances the order to completed — all in one
transaction. The only way to change a signed report is an
amendment.
Immutability of the signed PDF¶
Once approve_and_sign() commits:
signed_pdf_pathon the report is set and never modified.- The uploaded PDF at
orders/{slug}/signed-report.pdfis written once. Subsequent render attempts that try to overwrite it are blocked by the_check_write()guard insideprepare_signoff_pdf(). - An amendment produces a different PDF at a versioned path (e.g.
orders/{slug}/signed-report-amended-1.pdf) so the original signed artifact is always recoverable.
All-or-nothing transaction
If any step of the commit fails (PDF upload, DB write, order transition), the whole transaction rolls back. You will never end up with a half-signed report.
Why admin credentials¶
Uploading the signed PDF uses admin credentials rather than the signing
user's credentials so that sign-off does not depend on the physician's
personal bucket access. The physician's identity is captured on the report
(signed_by), not on the bucket write.
What the user sees¶
The user experience during sign-off is the modal choreography driven by the endpoints above:
- Click Approve and Sign →
/confirm-modalopens. The background render task starts fresh (any stale preview state is cleared first). - Spinner polls
/pdf-statuswhile the DOCX render, e-signature stamp, and GCS upload run. - When the preview is ready, the spinner is replaced by a link to the rendered PDF and the Generate Final Report button unlocks.
- Alternative: if the template requires a signature but the profile is
incomplete, the spinner is replaced by a
missing_profilewarning with the specific missing field, and Generate Final Report stays disabled. - User confirms by clicking Generate Final Report →
/commitruns atomically; spinner shows during the commit. /completed-modalreplaces the spinner with a success popup and a Download signed PDF link.