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.
Two public sources, both free and open, both queried directly with no intermediary:
| Source id | What it is | Endpoint |
|---|---|---|
miamidade-county | Miami-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-city | City 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-city | City 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)
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.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.
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"
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 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.
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.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.
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.
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.