A small, deterministic document-review engine for affordable-housing compliance files (LIHTC tenant recertification packets), with a second document domain (insurance certificates) built on the same core. It reads a structured packet, re-checks the facts against the source documents, and returns severity-classified findings that each cite where they came from. It never states a regulatory conclusion: that decision belongs to the human certifier, and the engine refuses to produce a report if any finding tries to make it.
Status: v0, a working proof built on fictional data only. It has never processed a real packet, has no users, and is not a product. It exists because I spent a summer doing this review by hand and wanted the checking to be reproducible instead of dependent on my attention.
In June 2026 I ran an annual housing-department recertification cycle for the regulated units of a Los Angeles multifamily property: mapping every source document to the agency's 15-item checklist, staging tenant packets for the certifier, and checking automated output against the source documents. That checking turned up errors that the automated audits had passed.
The lesson was specific: the failure points sit at the seam between extraction and the person who has to act on it, and they are only visible to someone who knows the workflow. This engine is an attempt to encode that seam as checks.
Given a packet (household, Tenant Income Certification, supporting documents, and the property's rule table), it runs six single-purpose checks and renders a findings report:
| Check | Catches | Severity it can raise |
|---|---|---|
completeness |
a required document type is missing | Material issue |
income-consistency |
TIC income ≠ income the pay stubs substantiate (recomputed, de-duplicated) | Material issue |
ami-eligibility |
substantiated income vs. the property limit, as an observation | Observation / Clarification |
signatures |
the TIC is unsigned | Critical blocker |
dates |
an income or asset document is outside the freshness window | Administrative defect |
duplicates |
the same document counted twice | Administrative defect |
Every finding carries a SourceRef back to the field or document it came from. The report is a
draft for the certifier, not a determination: see samples/lihtc-sample-report.md.
The same core was then reused for a second domain — certificate-of-insurance review — with six more
checks (src/coi/). That reuse is the one architectural claim the repo makes.
- It does not decide eligibility.
src/boundary.tsscans every finding for conclusion language ("ineligible", "approve", "deny", …) and the engine throws before rendering. There is a test for it, so the guarantee is checked on every run of the suite. - It does not use a model. Every finding comes from exact math, checklist matching and date logic. Extraction from scanned PDFs (where a model would belong) is out of scope for v0.
- It does not touch real data. Every fixture is labelled fictional and the names are made up.
node src/run.ts # review the hardest fictional LIHTC packet
node src/run.ts fixtures/packet-clean.json
node src/coi/run.ts # the insurance-certificate domain
node test/engine.test.ts # 20 tests
node test/coi.test.ts # 13 testsMy own review found a bug the tests had missed: two checks each summed pay stubs independently, so
a duplicated stub double-counted income (from $26k to $52k). The fix consolidated income
substantiation behind one de-duplicating function (src/income.ts) that both checks depend on, so
their numbers cannot disagree, and the tests now assert on the computed value rather than on the
presence of a finding. The decision log is in DECISIONS.md.
I did the manual work this automates, wrote the rules (what a defect is, where the boundary sits, what must be traceable), directed the build, found the defect above, and I run and read the tests. The code was written with heavy AI assistance under that direction. I would not describe myself as a software engineer; I would describe this as a domain user encoding the checks he wished he had.
src/engine.ts orchestration: checks → boundary → rollup → render
src/checks/ one file per check, registered in CHECKS[]
src/boundary.ts the conclusion-language scanner (runtime guarantee, covered by a test)
src/income.ts single source of truth for substantiated income
src/finance.ts exact money math, tested separately
src/coi/ the second domain on the shared core
fixtures/ fictional packets and sample rule tables
samples/ rendered reports from the fixtures
test/ 33 tests across both domains
MIT licensed. Fictional data only; please do not run it on real resident files.