Skip to content

Documents Feature

Auto-generate professional consultation reports and prescriptions from appointment form data.

NOT BUILT — reconciled 2026-08-02

No pdf_templates, no appointment_documents, no pdf.Renderer capability, no routes, and zero documents.* permission rows in any migration. Ships in migrations 000047 + 000048, last in the chain — it depends on F3 (the signed form is the source of truth) and F5 (documents attach to appointments).

document_templates is REJECTED — this is settled, do not re-open it

A migration document in the legacy repo specifies per-org HTML/CSS templates with margins (document_templates) and recommends it as "the best starting point for the new F6 Documents spec." It is not. data-model.md records that both designs were evaluated, the block-based pdf_templates design won, and document_templates was deleted. Architecture docs beat foreign migration docs.

Mine the legacy document for three things only: its template funcmap, its DocumentData struct shape, and its error → HTTP mapping.

Other corrections to this page:

  • The "Current State (Strapi)" table describes the legacy system being replaced, not a prior state of this platform. Nothing document-shaped has ever existed here.
  • Authorization is per-org permission codes, not roles; every per-org route group mounts RequireURLOrgMatchesScope("id") (P47).
  • document_url stores the S3 key, not a URL. Reads are presigned and short-lived (15 min), with a document.pdf_accessed audit row on every access.
  • Patient demographics on a generated document resolve through the renderer's own org-scoped query, not as a template-authoring convention — it reads the portable profile only through a patients row at the printing org. The legacy system prints full demographics unconditionally for any patient at any clinic.
  • CNP on a document is opt-in per template, default off (settled 2026-08-02) — see the CNP section below.
  • Regeneration produces a new row, never an in-place file swap.
  • The PDF engine choice below is presented as decided. It is still open — see features.md → F6.2.

See features.md → F6 and leo-port-map.md §3 → F6.

What this enables

Consultation reports: Specialist completes appointment form → system auto-generates branded PDF with findings, observations, and recommendations.

Prescriptions: Doctor fills prescription form (medication orders, PT exercises, rest recommendations) → auto-generate prescription PDF signed by specialist.

Private field filtering: Report shows diagnosis and notes to specialist, but patient PDF hides internal clinical notes—privacy automatically handled.

Digital signatures: Specialist's signature auto-embedded in generated PDF (image from their profile).

Compliance: Documents immutable after specialist signs—if you regenerate from same form data, PDF is identical. Audit trail of who created/signed.

How it works

  1. Appointment created: System adds consultation report and prescription forms (if appointment template defines them)
  2. Specialist fills form: Writes findings, recommendations, prescription orders
  3. Form signed: Specialist reviews and signs form
  4. PDF auto-generated: Uses PDF template + form data → creates professional PDF with clinic logo, findings, signature
  5. Patient accesses: Logs in, downloads PDF report (private fields hidden)
  6. Archived: PDF cached and immutable; always regenerates identically from signed form

Technical Reference

Overview

Reports and prescriptions are core outputs of the RestartiX platform. Every appointment can produce a report (consultation summary) and/or a prescription (treatment plan). This feature provides automated PDF generation from form data using a template-driven rendering system.

What Are Documents?

Documents in RestartiX are form-based outputs that transform structured appointment data into professional PDFs. Both reports and prescriptions are unified under the appointment_documents table, distinguished by a type column, but maintain separate business logic and regulatory requirements.

Document TypePurposeWho CreatesRegulatory
ReportConsultation summary, specialist observations, recommendationsAuto-created with appointment (from template)Standard medical record retention
PrescriptionMedication/treatment plan, actionable ordersAuto-created with appointment (from template)Prescription regulations apply per jurisdiction

Key Features

  • Automated PDF Generation: On-demand generation from form data using chromedp (headless Chrome)
  • PDF Templates Integration: Uses the PDF Templates feature for visual template design with block-based editor
  • Audience Filtering: Different PDF views for patients (privacy-filtered) vs specialists/admins (full data)
  • Caching Strategy: Immutable signed documents cached to S3, preview PDFs generated on every request
  • Digital Signatures: Image-based specialist signatures embedded in PDFs
  • Form Integration: PDFs generated from signed form data with field validation

Design Principles

  1. Form is the source of truth - Document content comes from forms.values and forms.fields. The document is a rendered view of form data, not a separate data entry point.
  2. PDF Templates for layout - Visual layout comes from the PDF Templates feature. Form templates reference pdf_template_id to specify which PDF layout to use. Same form data can render with different PDF templates (standard report, certificate, detailed analysis, etc.).
  3. Immutable after signing - Once a form is signed (status = 'signed'), the generated PDF is final. Re-generation from the same signed form must produce the identical document. The pdf_template_version is recorded to ensure consistent regeneration.
  4. Private field filtering - Fields marked private: true are excluded from patient-facing PDFs but included in specialist/admin PDFs.
  5. Synchronous generation, async optional - PDFs are generated on-demand when requested. No background job queue required initially.

Current State (Strapi)

AspectCurrent Implementation
Storagereport and prescription Strapi content types, each with document (single file) and files (multiple) media fields
ContentForm-based - each document references a form entity which holds field definitions and values
PDF generationNone. Specialists manually upload files or the frontend renders form data on screen
SigningStrapi draftAndPublish sets published_at. No digital signatures
Specialist signatureMedia file on specialist.signature - an uploaded image, not cryptographic
Template renderingNone. Form templates define fields, not visual layouts

Go Target Architecture

PDF Engine — OPEN, not decided

This section reads as settled. It is not.

The engine choice blocks the SOUP row, the ECS task shape, and the MDR posture, and it has not been made. Three candidates:

OptionTrade
chromedp / headless-shell sidecar (~150 MB image)Full CSS fidelity; matches the block editor's browser preview exactly. Heaviest runtime.
GotenbergCleaner process isolation, same Chrome underneath, another service to run.
Go-nativeNo Chrome at all — but the block editor's "what you designed is what prints" promise collapses.

Separately open: synchronous vs. queued generation. Synchronous matches the legacy UX (generate → downloads immediately). Queued reuses the proven exercise_renders claim / attempts / backoff / dead-letter shape that already runs in production for video renders.

Profile CPU on Fargate explicitly before committing. The media service was already burned once by ffmpeg reading host core count instead of the cgroup limit and getting CFS-throttled — a headless-Chrome sidecar is the same class of hazard.

Whatever wins ships as the pdf.Renderer capability in internal/core/pdf/, registered via capabilities.WrapInternal, with a SOUP row in the same PR (cmd/check-soup fails the build otherwise).

The design below assumes chromedp, which is the leading candidate:

  • Full CSS/HTML support for pixel-perfect rendering
  • Handles complex layouts, tables, images, signatures
  • Supports embedded images (specialist signatures, logos, patient uploads)
  • Same Chrome rendering as browser previews
  • Container image: chromedp/headless-shell (~150MB)

Rendering is server-side, always. The legacy system renders PDFs in the staff browser and pulls images by remote URL. Here, images are fetched server-side and base64-inlined; PDFs are self-contained with no external URL references, which is a hard compliance rule, not a preference.

Document Lifecycle

Appointment created with template


Form created (status=pending)
  ├─ Form template has pdf_template_id = 5
  └─ PDF template defines visual layout


Document exists (published=false, form attached)


Specialist/patient fills form (pending → in_progress → completed)


PDF preview available (live render using pdf_template, not cached)


Form signed (completed → signed, signed_at timestamp set)


Document generated
  ├─ Reads form.values and form.fields
  ├─ Merges with PDF template (id=5, version=3)
  ├─ Renders HTML → PDF via chromedp
  ├─ Records pdf_template_id=5, pdf_template_version=3
  └─ Uploads to S3


Published (published=true) - PDF cached to S3, template version locked

Patient Visibility Rules

Document StateSpecialist/AdminPatient
Form pendingCan view metadataCan view metadata
Form in_progressCan preview PDFCannot preview PDF
Form completedCan preview PDFCannot preview PDF
Form signed, published=falseCan view final PDFCannot access PDF
Form signed, published=trueCan view final PDFCan view final PDF (audience=patient forced)

Report vs. Prescription Differences

AspectReportPrescription
PurposeConsultation summary, specialist observations, recommendationsRehabilitation treatment plan, exercise prescriptions, therapeutic equipment recommendations
Who fills formSpecialist fills analysis fields, patient fills survey fieldsSpecialist only
Specialist signatureIncluded in PDFRequired in PDF - generation fails without it
Patient can downloadYes (when published, private fields excluded)Yes (when published)
PDF audience filteringPrivate fields excluded for patient audienceAll fields visible to patient (prescription transparency)
ImmutabilityAfter form signingAfter form signing
Multiple per appointmentNo (UNIQUE constraint: one report per appointment)No (UNIQUE constraint: one prescription per appointment)

The projection algorithm, in order

Carried from the live system and duplicated there across report and prescription — port it once, as a single type-parameterised builder:

  1. Flatten groups
  2. Prune is_private — a private group hides all its children
  3. Prune empty values
  4. Elide groups that are now empty

Plus: age is computed at the appointment date, not today. And the private-field pruning runs at the renderer — not as something a template author is trusted to remember.

CNP on generated documents — settled 2026-08-02

CNP is required on some documents, not all → opt-in per pdf_template, default off.

It is pii_regulated: encrypted BYTEA via internal/core/crypto, stored once on patient_profiles, never duplicated. The renderer reaches it through an explicit data-classification.md egress target and calls classification.AllowedFor rather than hand-building the field list (P39). A template that has not opted in cannot reference the field at all.

The legacy system stores CNP as a plaintext value row and prints it on every report and prescription. Romanian specifics (Law 190/2018 has quirks on processing the CNP even with consent) are an F11.0.5 counsel item, not an engineering assumption.

Layout facts worth preserving

  • A4 geometry: paddingTop 140 / paddingBottom 65 to clear the fixed header and footer; footer reads "Pagina n / total"
  • The prescription layout is the report layout minus the "Servicii efectuate" row and minus the support box
  • Goniometer measurements are written as form values keyed <key>_left / <key>_right / <key>_file
  • Postural-analysis and other clinical images attach to the appointment, not to the report entity — the legacy report-bound model forces a dead-end ("a report must exist first")
  • "Reports pending" = appointments where ended_at < now() AND no published report, self-scoped when the caller is a specialist. This is what actually drives report completion in a real clinic.
  • The exact Romanian label set and two clinician-authored Romanian annex documents (~1,180 lines of clinical prose) live in the legacy repo's report templates. That prose is domain content, not code — extract it now, park it as seed content for platform-tier pdf_templates, with strings through next-intl. It must not live in TSX again.

Prescription naming collision — OPEN

protocols.kind='prescription' is shipped and live in production, where it means a specialist-assigned exercise program. This feature's prescription is a rețetă medicală PDF. Two different things, one word, one of them already carrying production data. Rename the document type or qualify it in the glossary before the F6 migration — a live production meaning does not get quietly overloaded.

Prescription regulatory scope

Platform prescriptions are rehabilitation prescriptions — not medication prescriptions. They describe exercise programs, therapeutic equipment, and treatment recommendations issued by physiotherapists/kinesitherapists.

Not in scope: Romania's SIPE/e-Rețeta system (Ordinul 900/2006) for medication prescriptions. That system requires CNAS integration and qualified electronic signatures (QES under eIDAS), and applies to physicians prescribing drugs. Rehabilitation prescriptions do not fall under SIPE.

The prescription format is clinic-defined via the PDF template editor. There is no mandated national format for rehabilitation prescriptions in Romania. The platform ensures prescriptions include the specialist's credentials (name, title, license number) and digital signature, and are immutable after signing.

Documentation

  • PDF Templates - Visual template designer for PDF layouts (block-based editor, Go template syntax, component library)
  • Forms - Source data for document generation (form templates link to PDF templates via pdf_template_id)
  • Appointments - Documents are attached to appointments
  • Custom Fields - Patient profile data accessible in PDF templates via {{.FormValues}}

Migration from the legacy system

Phase 3 work, planned only after the feature build closes and against the real completed schema. The steps below are a sketch, not a plan.

Data Migration Steps

  1. pdf_templates: Create default report and prescription PDF templates per organization (via PDF Templates feature)
  2. form_templates: Link form templates to PDF templates via pdf_template_id column
  3. appointment_documents: Migrate existing reports and prescriptions Strapi content types to appointment_documents rows
  4. Existing uploaded files: Copy report.document and report.files URLs to appointment_document_files
  5. Form data: Handled by the form system migration - once forms are migrated, documents can generate PDFs using linked PDF templates

Backwards Compatibility

  • Existing manually-uploaded documents continue to work (served from appointment_document_files)
  • New documents get auto-generated PDFs via the pipeline
  • No breaking change - PDF generation is additive