A scanned checkbox needs three states, and two of them send the identical email
A tree crew drops a limb through a clubhouse roof. Whether the homeowners association pays for that or the tree company's insurer does can turn on a quarter-inch tick box, in a column headed...
A tree crew drops a limb through a clubhouse roof. Whether the homeowners association pays for that or the tree company's insurer does can turn on a quarter-inch tick box, in a column headed ADDL INSD, on a scanned PDF a property manager filed eleven months ago.
That page is an ACORD 25 certificate of insurance. A ticked box in that column means the association is named on the vendor's policy and can claim under it. Blank means it is not. The certificate is what a manager puts in front of a board, and if it ever matters, in front of an adjuster.
A machine reading that page gets three answers, not two: ticked, plainly blank, and unreadable.
The schema has to have somewhere to put "I could not tell"
If the field is declared as a plain boolean, the model has to pick one. It will pick one, and it will sound just as sure as it does on a clean scan. So the field is declared with room for the third answer:
additionalInsured: {
type: ["boolean", "null"],
description:
"The ADDL INSD column. true if ticked, false if plainly blank, null if it cannot be read with confidence.",
}
The reader's instructions say why, in the sentence that governs the whole product: "Return true only when the box is clearly marked, false only when it is clearly blank, and null when the scan is ambiguous, cut off, or too degraded to call. A wrong true here means an association believes it has coverage it does not have."

Blank and unreadable are different findings
if (req.additionalInsuredRequired) {
if (policy.additionalInsured === false) {
findings.push({ code: "ADDITIONAL_INSURED_MISSING", severity: "blocker", ... });
compliant = false;
} else if (policy.additionalInsured === null) {
findings.push({ code: "ADDITIONAL_INSURED_UNKNOWN", severity: "review", ... });
compliant = false;
}
}
Both branches set compliant = false. A blank box is the vendor's paperwork problem and it blocks. An unreadable box is our reading problem and it goes to a person. Neither one is a green tick, which is the rule the engine is built around: unknown is never a pass.
One requirement has no box anywhere on the form
Boards routinely ask that a vendor's liability cover be "primary and non-contributory", meaning the vendor's insurer pays first and the association's own policy is not dragged in alongside it. The ACORD 25 has no column for it. It appears, if it appears at all, as free text in the Description of Operations block.
That asymmetry decides the verdict. Finding the phrase confirms it. Not finding the phrase confirms nothing, because the form was never going to carry it. So the engine's worst possible answer here is "unconfirmed", never "missing", and it is always a review item.
Agents type that phrase a dozen ways, so the matcher flattens hyphens, slashes and commas, then collapses "non contributory" and "noncontributory" onto one spelling before comparing. A test feeds it "Coverage is primary and non-contributory.", "primary and noncontributory per written contract" and "PRIMARY AND NON CONTRIBUTORY", and expects the review item to stay off all three.
| What the page shows | Stored value | Finding code and severity | Overall verdict |
|---|---|---|---|
| ADDL INSD ticked | true | none | counts toward compliant |
| ADDL INSD plainly blank | false | ADDITIONAL_INSURED_MISSING, blocker | non_compliant |
| ADDL INSD too degraded to call | null | ADDITIONAL_INSURED_UNKNOWN, review | needs_review |
| No box exists (primary and non-contributory) | free text, or nothing | PRIMARY_NONCONTRIB_UNCONFIRMED, review | needs_review |
Three verdicts, two emails
The distinction that matters to a board disappears at the point of action. The draft email to the vendor's insurance agent is built from the finding code, and ADDITIONAL_INSURED_MISSING and ADDITIONAL_INSURED_UNKNOWN fall through to the same sentence, word for word: name the association as additional insured by endorsement and attach the endorsement, because the ADDL INSD box alone is not evidence of it. Whether the box was blank or unreadable, the agent does the same afternoon's work.
One code produces no ask at all. EXTRACTION_UNCERTAIN, raised when the reader flagged a field it could not resolve, returns null from that switch, with a comment saying there is nothing to ask the agent for: it is our read that was uncertain, not their paper.
This is how we built CoverCheck: https://covercheck.thecompound.tech/?utm_source=compound-devto&utm_medium=social&utm_campaign=compound
---
One shipped product, taken apart, once a month. What it does, what it cost to build, what the pipeline behind it looks like, and what the numbers did, read off the repository and the live site, not written from memory. Join the list.