Extracting Structured Fields From a Permit Application Form
9 min read · updated August 11, 2026
A building permit application is a form, which makes it look like the easy case. It is not, for one structural reason: its two most important fields — which property, and which people — are both recorded in ways that a general-purpose extractor destroys, and neither has a checksum to tell you it happened.
The parcel number is a string with no checksum
The parcel identifier — assessor’s parcel number, parcel identification number, tax map key, depending on the jurisdiction — is the key that joins a permit to a property, an assessment roll, a tax record and every prior permit at the same address. It is assigned by a county or municipal assessor, and its format is decided by that assessor. There is no national scheme.
Most formats are positional and hierarchical: a book or map number, then a page or block, then a parcel, sometimes then a unit for a condominium, separated by hyphens or dots. The punctuation is structural rather than decorative, and the leading zeros within each group are significant. Three failures follow, and each of them is irreversible once the data has been written.
- Storing it as a number. Anything that infers a numeric type turns 0123-456-078 into an arithmetic expression or, at best, strips the leading zeros. A spreadsheet in the middle of the pipeline does this without asking.
- Normalising away the punctuation and keeping only that. Stripping separators is the right way to match two identifiers, because the same parcel is printed formatted on one document and unformatted on another. It is the wrong thing to store, because the grouping cannot be reconstructed without knowing the county’s scheme. Keep the verbatim printed form and a normalised match key as two fields.
- Assuming a length. Lengths differ between counties and sometimes within one county after a re-mapping. A validator built from the first hundred documents will reject the next county entirely.
What makes this document genuinely different from the checksummed identifiers elsewhere in this cluster is the absence of arithmetic. There is no mod-10, no transliteration table, no computed final character. A parcel number cannot be self-validated, so the only real validation is a lookup against the assessor’s roll or the jurisdiction’s parcel service — and a design that assumes a checksum will exist somewhere, as it does for a vehicle identification number or a bank account, will simply never validate this field — everything in the checksum-validated identifier field is unavailable here, which is the useful thing to know before designing the validator. Say so in the schema by giving the field a verified_against provenance rather than a confidence score.
Four roles that look like one address block
A permit application names several parties: the applicant, the property owner, the licensed contractor, and often a design professional or an authorised agent. They are separate legal roles with separate consequences, and forms print them as stacked blocks with identical internal layout — name, address, city, state, postal code, telephone, email. Visually they are one repeated pattern, which is exactly why a model reading the page as prose merges them or attributes fields to the wrong one.
The schema that survives is a list of parties, each with a role from a closed set, rather than named fields like owner_name and contractor_name at the top level. The list form handles the two cases that flat fields cannot: two owners on one property, and the same person appearing in two roles.
{
"parties": [
{"role": "applicant", "name": "<name>", "same_as": null},
{"role": "owner", "name": "<name>", "same_as": "applicant"},
{"role": "contractor", "name": "<name>",
"license_number": "<as printed>", "license_state": "<state>"}
]
}The same_as field is there because of a piece of form mechanics that causes real damage: a checkbox reading same as applicant, with the block beneath it left blank. Three readings are possible and they are not equivalent — the owner is the applicant, the owner is unknown, or the field was skipped. Only the checkbox distinguishes them, so the checkbox is a field, and an unchecked box next to an empty block is a missing-data finding rather than a copy.
The owner-builder case deserves its own note. Where an owner acts as their own contractor there is usually a separate signed declaration on the form, because it waives protections that come with a licensed contractor. An extraction that quietly writes the applicant into the contractor slot has erased a legally distinct condition. Model it as a declaration flag, not as an inferred party.
Three addresses that are not the same address
Permits carry at least three location descriptions and they answer different questions. The job site address is where the work happens. The mailing address is where correspondence goes, which for a commercial owner is a head office in another state. And the legal description — a lot and block reference to a recorded plat, or a metes and bounds description — is the authoritative description of the land, and it is prose that must be captured verbatim because paraphrasing it changes what it describes.
Address normalisation is legitimate for the job site, because you want to join it to other records; it is destructive for the legal description. Two fields, two treatments. The same instinct applies to a subdivision name and a unit number that a normaliser will happily fold into a street line.
Classification fields come from an adopted code edition
Several fields on the form are controlled vocabularies rather than free text: the occupancy or use classification, the construction type, and the permit type itself. These come from the model building code the jurisdiction has adopted, and jurisdictions adopt different editions on different timetables. A value is therefore only interpretable together with the code edition in force for that jurisdiction on the application date. Capture the code as printed and record the jurisdiction; do not expand it to a description in the extraction step, for the same reason the appraisal page argues against expanding a condition rating.
The declared valuation or job cost field is worth singling out because it is usually the fee driver. Permit fees are computed from valuation by a published fee schedule with bands, so where you hold the schedule you have a check: the printed fee should fall in the band the valuation implies. A mismatch is more often a misread valuation — a missing digit, or a figure written in thousands — than a fee error.
Form mechanics that produce silent errors
- Checkbox states are ternary. Checked, unchecked, and unreadable — a light pencil mark, a printed box that scanned as grey, an X drawn between two boxes. A boolean cannot carry the third state and something has to invent a value. Make it a three-valued field and route the third to review, which is illegible field handling applied to a mark rather than to text.
- N/A is not blank. A field marked not applicable is an answer; a blank field is an omission that may make the application incomplete, and the two want different treatment — missing required field handling covers the distinction. A strip-whitespace step erases it.
- Continuation pages shift columns. A scope of work that overflows onto a second sheet often changes the page geometry, and a coordinate model fitted to page one puts page two’s values in the wrong fields — column misalignment across pages in its form-field guise. Fit per page, and verify the page identity from its own header.
- Rubber stamps and wet signatures overlap fields. A received stamp across the applicant block is the single most common cause of a mangled name, and it is also the field that dates the application.
- Revised applications. A resubmission carries the original permit number with a revision suffix. Treat the pair as the key, or the second submission overwrites the first and the change history disappears.