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_urlstores the S3 key, not a URL. Reads are presigned and short-lived (15 min), with adocument.pdf_accessedaudit 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
patientsrow 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
- Appointment created: System adds consultation report and prescription forms (if appointment template defines them)
- Specialist fills form: Writes findings, recommendations, prescription orders
- Form signed: Specialist reviews and signs form
- PDF auto-generated: Uses PDF template + form data → creates professional PDF with clinic logo, findings, signature
- Patient accesses: Logs in, downloads PDF report (private fields hidden)
- 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 Type | Purpose | Who Creates | Regulatory |
|---|---|---|---|
| Report | Consultation summary, specialist observations, recommendations | Auto-created with appointment (from template) | Standard medical record retention |
| Prescription | Medication/treatment plan, actionable orders | Auto-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
- Form is the source of truth - Document content comes from
forms.valuesandforms.fields. The document is a rendered view of form data, not a separate data entry point. - PDF Templates for layout - Visual layout comes from the PDF Templates feature. Form templates reference
pdf_template_idto specify which PDF layout to use. Same form data can render with different PDF templates (standard report, certificate, detailed analysis, etc.). - 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. Thepdf_template_versionis recorded to ensure consistent regeneration. - Private field filtering - Fields marked
private: trueare excluded from patient-facing PDFs but included in specialist/admin PDFs. - Synchronous generation, async optional - PDFs are generated on-demand when requested. No background job queue required initially.
Current State (Strapi)
| Aspect | Current Implementation |
|---|---|
| Storage | report and prescription Strapi content types, each with document (single file) and files (multiple) media fields |
| Content | Form-based - each document references a form entity which holds field definitions and values |
| PDF generation | None. Specialists manually upload files or the frontend renders form data on screen |
| Signing | Strapi draftAndPublish sets published_at. No digital signatures |
| Specialist signature | Media file on specialist.signature - an uploaded image, not cryptographic |
| Template rendering | None. 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:
| Option | Trade |
|---|---|
chromedp / headless-shell sidecar (~150 MB image) | Full CSS fidelity; matches the block editor's browser preview exactly. Heaviest runtime. |
| Gotenberg | Cleaner process isolation, same Chrome underneath, another service to run. |
| Go-native | No 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 lockedPatient Visibility Rules
| Document State | Specialist/Admin | Patient |
|---|---|---|
| Form pending | Can view metadata | Can view metadata |
| Form in_progress | Can preview PDF | Cannot preview PDF |
| Form completed | Can preview PDF | Cannot preview PDF |
| Form signed, published=false | Can view final PDF | Cannot access PDF |
| Form signed, published=true | Can view final PDF | Can view final PDF (audience=patient forced) |
Report vs. Prescription Differences
| Aspect | Report | Prescription |
|---|---|---|
| Purpose | Consultation summary, specialist observations, recommendations | Rehabilitation treatment plan, exercise prescriptions, therapeutic equipment recommendations |
| Who fills form | Specialist fills analysis fields, patient fills survey fields | Specialist only |
| Specialist signature | Included in PDF | Required in PDF - generation fails without it |
| Patient can download | Yes (when published, private fields excluded) | Yes (when published) |
| PDF audience filtering | Private fields excluded for patient audience | All fields visible to patient (prescription transparency) |
| Immutability | After form signing | After form signing |
| Multiple per appointment | No (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:
- Flatten groups
- Prune
is_private— a private group hides all its children - Prune empty values
- 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 65to 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
- Schema → architecture/data-model.md → Area 11 (Documents) — appointment_documents and pdf_template tables
- api.md - API endpoints for document operations and PDF generation
- pdf-generation.md - PDF generation pipeline and engine details
- templates.md - Template system, field placeholders, and customization
- signatures.md - Digital signature handling and audit trail
- caching.md - Performance optimization and cache invalidation
Related Features
- 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
- pdf_templates: Create default report and prescription PDF templates per organization (via PDF Templates feature)
- form_templates: Link form templates to PDF templates via
pdf_template_idcolumn - appointment_documents: Migrate existing
reportsandprescriptionsStrapi content types toappointment_documentsrows - Existing uploaded files: Copy
report.documentandreport.filesURLs toappointment_document_files - 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