How every row is produced, and from where.

The home page says "same query, same result, any day you want to re-run it against the county's open data." That sentence is only worth anything if the query is published. This is the query.

1. Where the data comes from

Two public sources, both free and open, both queried directly with no intermediary:

Source idWhat it isEndpoint
miamidade-countyMiami-Dade County permit data (ArcGIS FeatureServer). This is the only source that publishes a contractor phone number.https://services.arcgis.com/8Pc9XBTAsYuxx9Ny/arcgis/rest/services/miamidade_permit_data/FeatureServer/0
miami-cityCity of Miami "Building Permits Since 2014" (ArcGIS FeatureServer). Publishes no phone number on any record — this is the source of the declared-floor-scope, name-only file.https://services1.arcgis.com/CvuPhqcTQpZPT9qY/arcgis/rest/services/Building_Permits_Since_2014/FeatureServer/0
orlando-cityCity of Orlando permits (Socrata dataset ryhf-m453 on data.cityoforlando.net). Configured, pulled daily, and produces zero EarlyFloor rows: it published a contractor phone on none of its 11,521 July records.https://data.cityoforlando.net/resource/ryhf-m453.json

The county fields we read, and what we call them:

PermitNumber            -> permit_id
PermitType              -> (classifier input)
DetailDescriptionComments -> (classifier input)
ProposedUseDescription  -> (classifier input)
PropertyAddress         -> address
City                    -> city
EstimatedValue          -> valuation
ContractorName          -> permit_applicant
ContractorPhone         -> applicant_phone
OwnerName               -> owner_or_builder
LastInspectionDate      -> last_inspection
CoCcDate                -> co_date
ResidentialCommercial   -> (occupancy input)
Why permit_applicant and not contractor. The county field is whoever filed the permit, which on repair and alteration permits is routinely a trade sub rather than the general contractor. Confirmed case: permit 2026056864, a $76,000 "REPAIR DUE TO FIRE", lists an air-conditioning company. The column is named for what it actually is.

2. The date window

Each run pulls permits whose issue date falls in a trailing window — currently 7 days — and every published file is named with the date it was generated. The window start and end of the run behind the current numbers are printed on the home page and in claims.json. Files are never edited after generation; a new run writes a new dated file beside the old one.

3. What makes a permit "interior remodel"

A first-match-wins classifier runs over the permit type and description, lowercased. The rule that produces this feed is remodel-interior, and these are its literal patterns:

/remodel/       /renovat/        /kitchen/
/bathroom/      /\bbath\b/       /interior build.?out/
/tenant improvement/             /interior alter/
/\baddition\b/

Order matters, and trade-specific rules run first — so a permit reading "MECH A/C CHANGEOUT" is classified hvac and never reaches this feed even if the description also mentions a bathroom. Each shipped row carries an evidence column stating the exact pattern that matched and the exact county text it matched in, for example:

matched /remodel/ in "MBLD INT REMODEL — CONDO/APT INDIVIDUAL UNIT"

Declared floor scope

Separately, a row is flagged county_declared_floor_scope = yes when the county's own text matches:

/wood\/laminate\/tile\/vinyl|floor.?covering|\bcarpet\b|\bflooring\b|\btile work\b|\bvinyl plank\b/i

These are the strongest rows we have, because the permitting authority itself states the job involves floor coverings. In practice they come from miami-city, which publishes no phone numbers, so they ship in their own file with contact = name-only.

Occupancy

Occupancy is derived from the same text. "SINGLE FAM RES-CLUST-ZERO LOT-TOWN HOUSE" and "CONDO/APT INDIVIDUAL UNIT" are residential; "MULTI-FAMILY", "UNITS OR MORE", "APARTMENT" are multifamily; retail, office, hotel, restaurant, warehouse and similar are commercial. Anything unmatched is unknown and is counted with commercial, never with residential.

4. What is filtered out, and why

  1. The phone gate. A row only enters the flooring phone feed if applicant_phone contains at least 7 digits. Name-only rows are not silently dropped — they go to the declared-scope file with contact = name-only so you know what you are holding.
  2. Valuation floor: $2,500. Below that a permit is a trivial ticket. This floor is deliberately far lower than the rest of the platform uses, because for a flooring crew the small ticket is the product. A permit with declared floor scope bypasses the floor entirely — positive evidence beats a dollar proxy.
  3. Corrupt valuations. Rows with implausible valuations are excluded from lead output. They stay in the raw denominator (the "permits scanned" count) but never reach a customer row.
  4. Government owners. Permits whose owner of record is a government body are excluded.
  5. Flooring firms. If the permit applicant's own name reads as a flooring company, the row is dropped — we do not sell a flooring contractor a job another flooring contractor already has. The count excluded this way is published in the stats file.
  6. Project de-duplication. Permits are collapsed by project before counting, so one building with several permits is not sold to you as several jobs.

5. The trade guess, and its measured hit rate

applicant_trade_guess exists to warn you that the filer may be a trade sub. It is a name heuristic only — nine regexes over the company name (flooring, hvac, electrical, plumbing, roofing, fire-protection, pool, glass-windows, solar). The adapters carry no licence number, so a sub trading as "MABER CONSTRUCTION LLC" is undetectable by design.

Its hit rate is published, not assumed. The stats file records how many rows the heuristic matched. On the current run it matched a trade on 0 of 21 rows. A run where the column does nothing must not look identical to a run where it works, so the number goes on the page. Where the heuristic misses, the column reads general-or-unknown rather than blank — blank was ambiguous between "we checked and it looks general" and "we have no idea".

The real fix is a Florida DBPR licence-class lookup on the applicant, which would also be what turns the name-only declared-scope rows into callable ones. It is not built. Until it is, this column is a weak filter and the page says so.

6. What we do not measure

7. Checking us

Every shipped row carries its permit_id, its source and the evidence text. Take any permit ID from the sample page, query the endpoint above for that permit number, and compare the address, valuation, applicant and description against what we shipped. If a row does not match its source, that is a defect and I want to hear about it.

The generated counts themselves live in claims.json, written by the same run that writes the CSVs. The home page reads its numbers from that file, so the page cannot state a count the data does not contain.