"GxP-compliant PDF report" is a phrase that gets used loosely. This post breaks down what GxP and 21 CFR Part 11 actually require from an electronic report, what that means for a Python data pipeline, and the five patterns that reliably fail audit.
What GxP requires from a report
There is no "GxP PDF standard" — GxP is a family (GLP, GMP, GCP, GDP, GVP) with different emphases. For data-analysis reports coming out of a Python pipeline, the consistent requirements are:
- Attributable — every data point and decision traces to a person and a role.
- Legible — readable now and 10 years from now, without proprietary viewers.
- Contemporaneous — captured when the action happens, not reconstructed.
- Original — the first record is preserved even when derivatives are created.
- Accurate — correct and complete, with changes tracked.
This is the ALCOA+ framework. If you are building a report pipeline, these five properties are your acceptance criteria.
21 CFR Part 11 in practice
For electronic records and signatures, Part 11 adds:
- Audit trail of every create/modify/delete, with user, timestamp, and reason.
- Electronic signatures that are unique, non-transferable, and linked to the record.
- Controls against falsification — you cannot silently overwrite a signed PDF.
- Validation of the system that produces the record (the Python pipeline itself).
Many teams read Part 11 as "digital signature = DocuSign on a PDF". That is the easy part. The hard part is the audit trail upstream of the signature: every step that produced each number in the report must be recoverable.
Architecture of a GxP-friendly Python pipeline
1. Separate data, transforms, and presentation
Your pipeline should look like: raw data → typed intermediate records → computed results → rendered PDF. Each arrow is a function with an input hash and an output hash. This lets you prove that nothing else affected the numbers.
@dataclass(frozen=True)
class IntakeRecord:
source_file: str
sha256: str
row_count: int
ingested_at: datetime
ingested_by: str
Freezing the dataclass and computing hashes at every stage is boring but it is what makes the audit trail meaningful.
2. Deterministic statistics
Seed every randomness source. Pin NumPy, SciPy, scikit-learn, pandas, and Python itself. Store a requirements.lock in the report metadata. A report that cannot be regenerated bit-for-bit from the same inputs will fail a sharp inspection.
3. Report generation with ReportLab or WeasyPrint
For static regulatory reports, ReportLab (Platypus) and WeasyPrint are the two serious options. ReportLab gives you precise control over pagination, headers, and footers — useful when your QA group has specific layout rules. WeasyPrint lets you write the report in semantic HTML/CSS, which is easier to review and diff.
4. Digital signatures on the PDF
Use pyhanko for PAdES-compliant signatures in Python. A compliant signature includes:
- Hash of the full PDF at signing time.
- Timestamp from a qualified TSA (RFC 3161).
- Certificate chain that resolves to a trusted root.
- Signature reason, location, and signer identity embedded in the PDF.
Anything less is a logo that says "signed" — it will not hold up.
5. Audit trail — the part that actually matters
Your audit trail is a separate artifact — typically an append-only log — that records every action: dataset intake, QC decisions, parameter overrides, re-runs, signatures. Each entry has user, timestamp, action, before/after state, and a reason. This log should be independent of the PDF: the PDF is the deliverable, the audit trail is the evidence.
Five patterns that fail audit
- "The report is the record." The PDF is a view; the underlying data, code, and audit trail are the record. If you cannot produce the three, you do not have a record, you have a PDF.
- Silent re-runs. A user re-runs the pipeline and the file replaces the previous PDF. Every run must create a new versioned artifact — never overwrite.
- Environment drift. Report was generated with pandas 2.0.3 six months ago; today the server has pandas 2.2.2. Numbers move slightly. Pin, lock, and record environment in the report metadata.
- Non-deterministic plots. Same data, different plot — inspectors notice. Fix font rendering, RNG seeds, and color mapping.
- Manual edits to PDFs. Someone opens the signed PDF in Acrobat, fixes a typo, re-saves. The signature is invalidated but nobody regenerates. Make your pipeline fail loud when the signature is broken.
A minimal GxP-ready stack
- Data layer: Postgres with row-level timestamps and an append-only audit table.
- Pipeline: Python 3.12 with pinned dependencies and typed records.
- Statistics: SciPy / scikit-learn with seeded RNGs.
- Report: ReportLab or WeasyPrint with templates under version control.
- Signing: pyhanko with a qualified TSA.
- Storage: immutable object store (S3 with Object Lock, or equivalent).
- Audit: JSONL log shipped to a tamper-evident store on every write.
How AiLabrix handles this
AiLabrix implements the stack above out of the box. Every analysis run produces a versioned PDF report, a structured stage log, a data profile, and an append-only audit record. The 22 agents in the pipeline each emit typed outputs that feed the report template, so the path from raw CSV to signed page is recoverable step by step. The platform is self-hosted, so the audit trail and PDFs live on your infrastructure. With a local LLM backend (Ollama) no data leaves your network; with an optional cloud LLM backend (OpenAI or Anthropic) only derived statistical findings are sent to the provider — never the raw dataset — and every call is logged.
If you are building out a Python reporting pipeline and want a reference implementation, write to [email protected].
See AiLabrix on your data
Drop in a CSV. The 26-agent pipeline produces a signed GxP report with full audit trail.
Request a 30-minute demo →