Fifteen companies reported the same $34,025,597: picking the money column out of a public dataset
Tell a subcontractor who is owed a few thousand dollars that thirty four million of his money is frozen at an airport authority, and nothing else you say to him will land. That is one column choice away in a file anyone can download.
Washington publishes two files about prevailing wages on public works jobs. Before work starts, every contractor and subcontractor on the job files a Statement of Intent to Pay Prevailing Wages. After the work is done, each one files an Affidavit of Wages Paid, and Labor and Industries has to approve it. Until that affidavit clears, the public body sitting on the retainage is barred from releasing it, and retainage is capped at five percent of the contract under RCW 60.28.011.
Both files sit on the state's open data portal. Read on 2026-08-15, the intent file held 1,307,673 rows and the affidavit file held 1,186,282. So the question looks like a join: which intents have no affidavit against them, and how much is five percent of those.
Both halves of that sentence are traps.
The friendlier column name is the wrong one
An intent row carries two money fields. amount is labelled Estimated Contract Amount. cntrct_amt is the filer's own contract on that project. Measured across a 1,500 row sample:
amount | cntrct_amt | |
|---|---|---|
| What it actually describes | the whole project | the filer's own contract |
| Values shared between companies | 199 of 855 distinct (agency, amount) pairs | 5 of 1,420 values collided |
| Median | $3.05M | $67,275 |
| Worst collision found | 15 companies all reporting $34,025,597 at Spokane International Airport | none of that kind in the sample |
Five percent of that median own-contract is about $3,364. Five percent of the Spokane figure is over $1.7 million. Same row, same job, two numbers about 500 times apart, and only one of them is anything the filer could ever claim.
// ALWAYS cntrct_amt. `amount` is the PROJECT value, shared by every company on
// the job.
export const contractAmount = (row) => Number(row?.cntrct_amt ?? NaN);
export const estRetainage = (row) => Math.round(contractAmount(row) * RETAINAGE_RATE);

A missing row is a form that was never separately filed
The second trap is the join itself. An intent with no matching affidavit is a gap in a filing record, and there are three ordinary reasons for that gap which have nothing to do with anybody holding money.
At or under $5,000, RCW 39.12.040(2) allows a combined intent and affidavit procedure, so the affidavit never appears as its own row. The absence there is the form's doing.
Under 24 months old, the work usually is not finished yet, so no affidavit is due. That failure lands on exactly the recent jobs a contractor remembers best, which is the fastest way to lose a reader who is already asking whether this is a scam.
The third one runs the opposite way from intuition. The older a record is, the more it looks like money that has been stuck for years. Past three years it is the reverse: under RCW 63.30.040(4) a debt of a business association is presumed abandoned three years after the obligation to pay arises, so there is nothing left at the awarding agency to release. An intent filed in 2004 with no affidavit against it is not a frozen payment, whatever the set difference says. That rule used to live in chapter 63.29 RCW, which was repealed effective 2023-01-01; the three year clock moved into chapter 63.30 intact, so the citation died while the rule did not.
That makes the answer bounded at both ends: nothing under $5,000, nothing newer than 24 months, nothing older than 48. Rows outside the window are dropped from the headline total rather than shown with a caveat under it, because a number a reader can see is stale does the damage before the caveat is read.
Two round trips, nothing cached
The bulk version of this walks all 1.18M affidavit rows to build the cleared set, which is fine in a weekly loader and impossible inside a web request. For one company the direction inverts. Fetch that company's intents first, indexed by name or UBI, then ask the affidavit file about those specific intent IDs in a single in (...) query. Two live calls, no committed copy of the data, so the answer describes the file as it stands when the question is asked.
Above two million dollars the report says in plain words that a missing affidavit probably means the job is still being built, because a prime on a job that size is not sitting on frozen retainage.
This is how we built HeldBack: https://heldback.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.