Tucson publishes a condition score for every street it maintains. I built the system a city IT team would run on it: a script that pulls the data on a nightly schedule, a SQL Server database that checks it, and a Power BI report where each council office sees only its own ward.
Tucson voters approved Proposition 411, a 10-year half-cent sales tax, and $590 million of it goes to neighborhood streets. The commission overseeing it picks projects first by the lowest condition score, then balances across wards and equity, so the scores steer where the money goes. Three groups lean on that same data every week.
Residents call about their street. Staff need to know how their ward compares and which of their streets are next in line.
They build the worst-first list, and need to skip streets that were just repaved and catch scores that are out of date.
A newly paved major street can't be cut for five years without extra restoration work. Crews need to know before they dig.
Sources: City of Tucson, Prop 411 Phase 1 release · KGUN 9 on worst-first · City of Tucson right-of-way guide
Asks the city's ArcGIS service for street condition and moratorium data, 1,000 rows at a time. Retries if the service hiccups. At night it asks only for what changed.
Drops the raw rows into a holding table exactly as they arrived, so a bad download never touches the real data.
Fixes known problems (county districts in the ward field, 1900 used as "unknown"), then adds new streets, updates changed ones and records every score change.
Ten checks after every load. A partial download or an impossible score fails the run; stale or missing data is flagged for the GIS team.
A ward scorecard, the worst-first list and a data health page, limited by ward. A map colored by condition.
The Power BI report reads the database's reporting views. The map reads the city's own ArcGIS layers live, styled with the same condition bands.
Share of each ward's pavement area in each condition band. The number on the right is the ward's average score out of 100, weighted by pavement area so a long main road counts more than a short side street.
Bands follow the ASTM D6433 pavement rating scale. Ward 3 has the most pavement in the two worst bands (15.4%).
Most neighborhood scores come from one 2020 survey. A worst-first list built on six-year-old scores can rank the wrong streets.
Re-inspect these before ranking them.19 have a county supervisor district typed in the ward field, and 6 city streets have no ward at all. They drop out of every ward's report.
Fix at the source; the list is in the database.Every street repaired after its last inspection already shows a fresh score. The city's repair records and scores line up.
No action; the check stays on.Recently rebuilt or resurfaced; the last one ends September 2031. The dig-check view lists them with days left.
Left out of every average rather than counted as zero, and listed on the data health page.
Average 64.1 for major streets against 70.5 for neighborhood streets, by pavement area.
The rule is enforced in two places. The database itself filters rows by who is asking, so it holds no matter what tool someone connects with. The Power BI report has matching roles, including one that reads each person's wards from a table, so adding a staff member is one row, not a new report.
| Signed in as | Allowed | Segments they see |
|---|---|---|
| Ward 3 office | Ward 3 only | 1,539 |
| Ward 5 office | Ward 5 only | 1,988 |
| City engineer | All wards, plus county and state roads in the city's layer | 11,655 |
| Ward 3 office, querying the raw tables | Refused; ward staff can only read the report views | 0 |
Counts are from the automated tests and a live run on November 9, 2026.
Each row carries a fingerprint of its values. A second load of the same data reports 11,655 unchanged and writes nothing.
The city keeps only today's score. Every time a score changes, the old and new values are saved, so trend builds up night by night.
If a full download comes back 10% smaller than last time, the load stops before touching the real data. An impossible score fails the run too, and the log says why.
Start, finish, rows read, new, changed, unchanged, status and error message for every layer, every night.
One script sets up Windows Task Scheduler: only the changes every night at 2:15, the whole layer every Sunday so streets the city removed get flagged.
Unit tests for the PowerShell functions, and database tests that build a throwaway copy, load sample streets and check the merge, history, checks and security.
Who needs what, what's in and out of scope, and every requirement traced to the test that proves it.
Epics and stories with acceptance criteria, in a file Jira imports directly.
How a ward office gets street information today, and how it works with this system, in standard BPMN.
Unit, integration, regression and security tests, plus the hands-on checks for the report and the map.
What runs when, where the logs are, what each warning means and what to do about it.
What the data says, what each finding means for a decision, and what the data can't tell you.
Windows PowerShell 5.1, SQL Server 2022 Express and T-SQL, Power BI Desktop with DAX and row-level security, ArcGIS Online with Arcade, Pester for tests, BPMN for process maps, Jira for stories.