Skip to content

COI date rollover and QA automation

Delivered date rollovers on 1,268 production insurance certificates for an insurance agency client, with an independent QA pass and every exception reviewed with the client before delivery.

Date
Stack
Python, PyMuPDF, PyPDF2, Pillow

The finding

An insurance agency client needed the issue and policy dates on 1,268 ACORD 25 certificates of insurance rolled forward, with every certificate looking identical to the original apart from those dates. The work was delivered as an automated rollover plus a separate QA pass, and the certificates that did not fit the rules were left untouched and reviewed with the client instead of guessed at.

Context

This was the second job I did for the same client. In December 2025 they had about 650 certificates arriving in large batch PDFs, and someone was separating and naming them by hand, one certificate holder at a time. I wrote a short Python script for that. In September 2026 the live set had to be renewed: 1,268 certificates across three folder groups, some saved as text PDFs and some printed through Microsoft Print to PDF with no text layer at all.

What was built, and who built it

The splitter (December 2025), written by me. split_pdf.py is a small PyPDF2 script. It walks a batch PDF page by page, extracts the text, finds the line carrying the holder label, cleans the holder name so it is safe as a file name, and saves that page as its own PDF. Pages with no label are reported and skipped. It ran in seconds, where the by-hand version had been taking roughly a minute per certificate.

The rollover (September 2026), directed by me and written with Claude Code. I framed the problem, set the rules and the requirement that nothing but the dates could change, supplied the production files, and worked through the problem inputs as they came up. Claude Code wrote most of the implementation and the QA tooling. I reviewed the generated reports and the before-and-after image crops, took the source-document anomalies to the client, and delivered the output. The tools, all in the public repository:

  • coi_dates.py finds the dates on a certificate, plans which ones change, and rewrites them. Detecting, planning and applying are separate steps, so a run can report what it would change before writing anything.
  • batch_roll_dates.py runs the update over a folder into a mirror folder. Originals are only read. Every output is read back and pixel-diffed against its original outside the date boxes.
  • qa_check.py is a second pass that restates the rules on its own and reads the PDFs a different way, so a bug in the update cannot vouch for itself. It checks pairing, that files open, that untouched files are byte-identical, that each date is correctly changed or left alone, that no other text or pixel moved, and that no fonts were added.
  • overlap_report.py finds certificate holders who already have a certificate elsewhere in the set, and exact duplicates.

The two kinds of PDF needed different handling. In text PDFs the new date is set in the certificate’s own embedded font at each original character’s position. In the Print to PDF files every digit is a vector outline, so digits are recognized by rendering them and matching against reference Helvetica digit images.

Evidence

The rules live at the top of the library as data:

ISSUE_DATE = "10/01/2026"
ROLL_PAIRS = {
    ("11/22/2025", "11/22/2026"): ("11/22/2026", "11/22/2027"),
}
UNCHANGED_PAIRS = {
    ("01/01/2026", "01/01/2027"),   # not yet due, leave as is
}

Any policy row that fits neither pattern is left alone and listed as an outlier. Thirty certificates had rows outside the initial rules. The QA pass also surfaced anomalies that were already in the source PDFs; I took those to the client, who authorized correcting them because the underlying policies had already renewed.

python batch_roll_dates.py --dry-run     # report only
python batch_roll_dates.py --apply       # write the mirror folder plus review crops
python qa_check.py                       # independent check of every pair
python tools/qa_selftest.py              # prove qa_check.py fails when it should

What it means

  • Editing a document in place is only as good as the proof that nothing else moved. A checker that reads the rules independently is what made 1,268 files deliverable without opening each one by hand.
  • Most of the difficulty was in the input: two PDF generators, one file saved with the wrong extension, and dates that were drawings rather than text.
  • Leaving the odd cases alone and asking the client was the right default. A wrong date on an insurance certificate is worse than an unchanged one.