On this page
Site Overview Query Language Field Reference Pipes Aggregates Timeslice Risk Signals Map View Example Queries

Site Overview

Phoenix Permits scrapes and parses the City of Phoenix's public Planning & Development permit records (PDD), enriches them with geocoding and derived risk signals, and exposes the result through a set of pages built around one shared query language.

PageWhat it's for
/searchFilterable permit table + embedded map, split-pane. Best for browsing/exploring visually.
/analysis"Trouble Sleeping?" — EXTH/EXTR overnight-construction permits within a set distance of residential parcels, with duration/night-hours highlighting.
/queryThe full query language (this doc). Rows, bar charts, and a risk-colored map view.
/chainsGroups permits by project/address into renewal chains (e.g. BLD → EXTH → EXTR renewals).
/contractorsContractor-level rollups: permit counts, valuation totals, approver relationships.
/patternsCross-permit statistical patterns (approver concentration, timing anomalies).
/incomeCity permit-fee revenue rollups ("City Income").
/sql-searchScheduled/saved searches — any /query search can be saved and run on a recurring schedule, optionally emailing results.

Query Language

The /query bar speaks a Sumo Logic-flavored language: a filter expression, optionally followed by one or more | pipe stages. Nothing here ever touches raw SQL — every field is resolved through a fixed whitelist and every value is parameterized.

field=value exact match, case-insensitive field="Exact Case" quoted = case-sensitive exact match field=val* wildcard (prefix/suffix/contains — * anywhere) field!=value not equal field>value / field<value / >= / <= numeric or date comparison field=null / field!=null field=(*A* or *B*) multiple wildcard alternatives AND / OR / NOT / ( ) boolean grouping (AND is implicit between terms) "bare text" full-text search across address/description/owner/etc. issued>now(-30d) relative time — d/h/m offsets from now()

Unquoted values are case-insensitive; quoting a value makes it case-sensitive. A bare word with no field name (or a quoted phrase) runs a trigram search across address, description, scope of work, type number, owner, and superintendent.

Field Reference

AliasColumnType
type / permit_typepermit_typetext
type_numbertype_numbertext
statusstatustext
addressaddresstext
zipziptext
district / distdisttext (source-reported — see caveat below)
apnapntext
zoningzoningtext
project / project_idproject_idtext
approver / issued_byissued_bytext
entered_byentered_bytext
superintendentsuperintendent_nametext (EXTH/EXTR only)
ownerowner_nametext
descriptionpermit_descriptiontext
workdescription_of_worktext
work_days / work_hourswork_days / work_hourstext (EXTH/EXTR only)
issued / issued_dateissued_datedate
expires / expires_dateexpires_datedate
work_from / work_towork_date_from / work_date_todate
landed / created_atcreated_attimestamp — when we scraped it
valuationvaluationnumeric
sq_ftsq_ftnumeric
lat / lnglat / lngnumeric
contractorcontractors.nametext (auto-joins)
licensecontractors.licensetext (auto-joins)
result / inspector / inspection_type / disciplineinspections.*text (auto-joins)
scheduled_date / completed_dateinspections.*date (auto-joins)

Caveat on district: this is the raw value from the source PDD listing and has real data-quality gaps (some rows contain OCR/parsing artifacts instead of a real 1–8 district number). For reliable district assignment, prefer geometry — join through council_districts via ST_Contains, the same way mv_exth_near_residential does — rather than trusting this field directly for anything district-ranking related.

Pipes

| where <expr> additional filter, same syntax as the main expression | sort field [asc|desc], ... multi-field sort | limit N cap results (max 25,000) | topk N field1,field2 top N groups by count | percentile 95,99 field percentile_cont over a numeric field or an aggregate output

Aggregates

One or more comma-separated aggregate calls, sharing one by group-field list — matches Sumo's real multi-function grammar. Function-call form and the legacy bare shorthand both work:

| count by district | sum(valuation) by contractor | count(port) as n, avg(bytes) by src,dst -- multiple aggregates, one query | count_distinct(contractor) by district | min(issued), max(issued) by permit_type | first(status), last(status) by project -- ordered by issued_date

Available functions: count, count_distinct, sum, avg, min, max, first, last. sum/avg require a numeric or boolean field (boolean is cast to 0/1). Aggregate results render as Rows or a vertical Bar chart with a click-to-exclude legend below it that rescales the chart.

Timeslice

Buckets issued_date into fixed windows, must be paired with an aggregate that groups by it:

| timeslice 1d | count by _timeslice | timeslice 1h | count by _timeslice, district | timeslice 24h 3 | count by _timeslice -- 3 equal subdivisions of a 24h bucket (8h each) | timeslice 150 buckets | count by _timeslice -- N buckets spanning the filtered time range | timeslice 1mo | sum(valuation) by _timeslice | timeslice 1y | count by _timeslice

Fixed-duration units (s/m/h/d/w) can be subdivided into equal parts; calendar units (mo/y) can't, since months/years vary in length.

Risk Signals

Computed for EXTH / EXTR (extended construction hours) permits by the refresh_exth_signals recurring job. All are queryable and all show up in the map view's marker color and popup.

FieldMeaning
self_approvedThe same person both entered and approved (issued_by = entered_by).
flagged_approverApproved by an identity on the flagged-approver list.
backdated_daysDays between the permit's stated effective/start date and its recorded issue date — a positive value means it was issued after work was already supposed to have started.
seven_day_coverageThe permitted work-days field covers all seven days of the week.
new_addressFirst EXTH/EXTR permit ever recorded at this address.
renewal_countHow many prior permits in the same renewal chain (same address + description) came before this one.
near_res_ftDistance in feet to the single nearest residential parcel (uncapped — the /analysis page's own view caps this at the 500ft statutory line; querying the field directly does not).
risk_scoreWeighted sum of the above (new_address 30, renewal_count≥3 20, seven_day_coverage 15, near_res_ft<500 15, self_approved 10, flagged_approver 10, backdated_days>0 25). Not a legal conclusion — a triage signal for which permits are worth reading in full.

Note on near_res_ft coverage: the underlying residential-parcel dataset is a targeted import (~6k parcels near known permits), not a full citywide layer. Distances under roughly 1,000–1,500ft are reliable; larger values may just reflect a gap in parcel coverage near that permit rather than a true distance.

Map View

On /query, any row-level (non-aggregate) result with lat/lng exposes a Map view alongside Rows. Each permit renders as a marker colored by risk_score (green <20, yellow 20–49, orange 50–74, red ≥75, gray if no score). Click a marker for a popup with the key fields and a "View details" link into the full permit modal.

Example Queries

# Active overnight permits, most recent first type=(EXTH or EXTR) status=OPEN | sort issued desc # High-risk EXTH/EXTR — repeat renewals, flagged approver, close to residential type=(EXTH or EXTR) flagged_approver=true renewal_count>=2 near_res_ft<500 # Approver concentration on overnight work type=EXTH | count by approver | sort _count desc # Daily volume over the last 90 days issued>now(-90d) | timeslice 1d | count by _timeslice # High-value BLD permits by contractor type=BLD valuation>1000000 | sum(valuation) by contractor | sort _sum desc # Failed inspections by inspector _source=inspections result=FAIL | count by inspector | sort _count desc