Skip to content
scsiwyg
sign insign up
get startedmcpcommunityapiplaygroundswaggersign insign up
Quality Controls·PDF Reports That Match the Binder: Generating Inspection Documents Clients Actually Accept17 Apr 2026David Olsson
Quality Controls

PDF Reports That Match the Binder: Generating Inspection Documents Clients Actually Accept

#pdf#reports#feature#building-in-public#aimqc#devlog#inspection

David OlssonDavid Olsson

Clients reject inspection reports that do not match their template. Not because the data is wrong. Because the font is different, or the logo is in the wrong corner, or the signature line is above the date instead of below it. This sounds petty. It is not. The binder is a legal document. Consistency is how it gets accepted. We built PDF export to match the binder format first, and let the data populate into it.


The constraint

Every client in Alberta oil and gas construction has a QC binder format. Some are internal standards. Some are owner-operator requirements. Some are inherited from the prime contractor. They vary in layout, branding, field ordering, and signature requirements.

When a QC team submits inspection records at turnover, the records go through a document control review. A report that does not match the expected template format gets rejected — not for content, for form. Resubmission costs time. On projects approaching mechanical completion, time is the most expensive thing.

The software cannot change this constraint. It has to accommodate it.

How we generate PDFs

We use @react-pdf/renderer to build PDF documents from React components. Each report type (Torque, Weld, NDE) has its own PDF template component that mirrors the client binder format: header layout, logo position, field sequence, and signature block placement.

// Simplified TorqueReportPDF structure
export function TorqueReportPDF({ report }: { report: TorqueReport }) {
  return (
    <Document>
      <Page size="LETTER" style={styles.page}>
        <PDFHeader reportNumber={report.reportNumber} />
        <View style={styles.section}>
          <PDFField label="Flange Tag" value={report.flangeTag} />
          <PDFField label="Flange Size" value={report.flangeSize} />
          <PDFField label="Torque Value" value={`${report.torqueValue} ${report.torqueUnit}`} />
          {/* ... */}
        </View>
        <PDFSignatureBlock
          performedBy={report.performedBy}
          witnessedBy={report.witnessedBy}
        />
      </Page>
    </Document>
  )
}

Shared components (PDFHeader, PDFSignatureBlock, PDFStyles) maintain consistency across report types. The visual language — spacing, font sizes, line weights — is defined once and inherited.

Burned-in drawing stamps

For drawing exports, we use a different approach. pdf-lib loads the original drawing PDF and burns the stamp positions directly into the document at the correct page locations.

The result is a drawing that looks like the physical sticker version the QC team would have produced manually. The colored circles (orange for torque, purple for weld, green for NDE) appear at the exact positions where stamps were placed in the digital workspace.

This is the artifact that goes into the turnover binder. It needs to look right.

Client-specific templates

Different clients have different header formats, different field ordering, and different signature requirements. The template system is designed to accommodate this. A client-specific FormTemplate can override the default layout for any report type.

Form templates are JSON-schema-driven: the field definitions, their order, their labels, and the signature block configuration are all expressed in the schema. A client upload of a new form ZIP package can update the template without a code deployment.

Export flow

The export is client-side: the PDF is generated in the browser and downloaded directly, without a server round-trip for the file. This keeps exports fast and avoids server-side PDF rendering costs.

typescript
const handleExport = async () => {
  const blob = await pdf(<TorqueReportPDF report={report} />).toBlob()
  saveAs(blob, `${report.reportNumber}.pdf`)
}

For drawing exports with burned-in stamps, the export runs server-side via an API route — the original drawing file is fetched from storage, processed with pdf-lib, and streamed back to the client.

Why this matters

A QC team that produces correct, client-accepted reports the first time spends less time on resubmission and more time on site. The software is not just a database. It is a document production system for a regulated handover process. The output has to be right.


David Olsson is CTO at AIMQC. Contact: dolsson@aimqc.com

Share
𝕏 Post