How this deployment grew
districtry Illinois began as a Chicago-only explorer and grew outward one county at a time — 91 of the state's 102 counties as of 26 August 2026. The counties that are missing are missing for reasons this project measured and wrote down, not for want of looking, and every one of them is on the record in the app's own data-gaps panel. This page is the deployment's record: what it ships, what it re-reads on a schedule, and the corrections its own machinery has caught.
The numbers below are measured from the app's own shipped data files every time anything changes — they cannot drift from what the map actually serves. The changelog entries are dated snapshots: each was true on its own date, and none is edited after the fact.
The changelog
- 15 September 2026
Naming the justices who were published all along
Illinois elects its Supreme Court by district — five of them, Cook County being the first and electing three of the seven justices — and this map has drawn those five districts since it shipped without ever naming a justice. The reason recorded in the code was that no roster source existed. That was true of the boundary file, which carries a district number and nothing else, and it was never true of the court: illinoiscourts.gov names all seven justices and says which district each was elected from, on a page anyone can read. Nobody had looked. The card now names them, with the Chief Justice marked, refreshed every week. The same card now answers a second court, and that is a fact about Illinois law rather than a convenience. The state constitution divides Illinois into five judicial districts "for the selection of Supreme and Appellate Court Judges" — one set of districts, two benches. So the district already on the map is also the Appellate Court district: the card names the branch that hears appeals from your circuit court, where its courthouse is, its telephone, its clerk, and each justice sitting there with a link to their own page. Drawing a second set of boundaries for it would have drawn the same five shapes again. Two things are deliberately not claimed. The Appellate Court elects 18 justices in the First District and six in each of the others, but the Supreme Court may assign additional judges where the caseload needs them, and the court's directory lists everyone sitting without saying which is which — so the card says how many it lists and leaves it there. And the weekly job that refreshes all this re-reads which counties each district covers and checks them against the statute that drew them, all 102, refusing to publish if the two disagree. These districts changed in 2021 for the first time since 1964, which is exactly the kind of boundary that moves once while nobody is watching.
- 12 September 2026
Checking a list of addresses, not just one
The map has always answered one address at a time. That is the right shape for a resident and the wrong shape for anyone holding a list — a petition sheet, a canvass list, a membership roll. A new panel takes a pasted list, runs each line against whichever boundaries you tick, and hands back a table and a CSV. Nothing in it re-implements a layer: the boundaries, the coverage tests and the district names are the same ones the cards and the hover popup already use, so a row in the table and a card on the map cannot disagree. It is built to say what it cannot settle, because the places it would be wrong are exactly the places people consult it. A geocoded point is an estimate, and most of these district lines run down street centrelines and alleys, so an address near a line can resolve to the wrong side of it. Every row therefore carries the distance from the point to the nearest edge of the district it landed in, and anything inside the review distance you set is flagged rather than answered. A line the geocoder resolved only to a street rather than to a house number is flagged too, and so is one it could not find at all. Clicking a flagged row puts that point on the map, which is where a person settles it. What it does not know is worth stating plainly: nothing here knows whether anyone is registered to vote at an address, which is the fact a petition signature actually turns on. The election authority's own record governs that. This says which district a point falls in and which rows need a human. The list stays in your browser. Only the address text goes out, one lookup a second so a shared geocoder is asked no more than one person typing would ask it, and the privacy page now measures that flow and names it rather than describing the app as having only a search box.
- 26 August 2026
Clay, the ninety-first county
Clay County's board districts ship, taking the map to 91 of Illinois's 102 counties. Its fourteen lettered districts were never the problem — the county publishes them, and seven are independently confirmed by certified 2026 primary returns — but the census draws one Clay City precinct where the county runs two districts through it, and nothing published said where that line falls. County Clerk Amy Britton answered in two sentences: District A is the village of Clay City, District B the unincorporated area around it. That is a corporate-limits line the census already draws, so the split was resolved block by block — 833 people inside the village, 333 outside, every block sorting cleanly to one side, and the two blocks that sit on the margin shipped with their measured precinct rather than absorbed quietly. Two of the districts sit further from equal population than this project's own ceiling normally allows; they ship only because the Clerk confirmed the map is current, and the measured figure is recorded rather than smoothed. Clay ships its board and not its precincts, because its two published surfaces disagree on how many precincts it has, and a precinct card would state more than is known.
- 25 August 2026
A county held back by its own numbers, and the letter that released it
Wayne County's district composition had been corroborated twice over for weeks: the county's own board page states which precincts make up each of its seven districts, and its certified November 2024 General — 27 precinct-level results grouped by which board candidate each precinct actually voted on — partitions the same 27 precincts the same way, agreeing on six of seven exactly. The one difference is the county seat, where the board page omits two Fairfield precincts that the certified ballot shows voting in District 7, so the page is incomplete rather than the ballots wrong. None of that was the blocker. Composed against an ideal of 2,311 people, one district deviates by 32.4% — past the 30% ceiling this project enforces everywhere else, and both sources agreed on that district exactly, which is what made the number worth pausing over instead of dismissing as a mis-transcription. Clerk Elizabeth Woodrow was asked directly whether the plan had moved since it was drawn. Her reply: “Nothing has changed with this map for years.” The ceiling was raised for Wayne alone rather than widened for the fleet, and the measured deviation is on the record.
- 23 August 2026
Four counties in one day, and a lesson about labels
Richland, Jackson, Douglas and Vermilion all join. Vermilion is the largest county the project has added and the one that teaches the most: it publishes three board-district layers, and the best-labelled one is obsolete. A service described as “Governmental and school districts” holds the 2011 plan, while the plan actually in force is an undocumented layer with a typo in its own name. Currency is a measurement, not a reading of a name — a plan drawn to a census balances on that census, and across 4,943 census blocks the layer that ships runs within 0.7% of equal population where the better-labelled one runs 15.1% out. The builder now refuses to write if that comparison ever reverses, in either direction. Jackson, the same day, is the first county in the fleet whose districts split precincts and are built anyway: its adopted map is a vector PDF whose districts are real polygon objects, so the shapes were read from the file rather than sampled from its pixels, and nothing traced ships — the map only ever chose between the two districts the county's own canvass had already named.
- 22 August 2026
A county is not blocked because its website is
Knox County had been recorded as refusing every request. That was true of knoxcountyil.gov, and it was written down as though it described the county. Knox has four hosts: the website denies, a public GIS server answers and carries the county's twenty townships, the same site's document store has no block at all, and a fourth is simply dead. Knox joined as the 80th county — the first to join by closing an enclave rather than extending the frontier, filling a hole that seven already-served neighbours had left around it. Its districts come from the county's own adopted map, checked against certified returns and census population before shipping; a faster method that read the map's fill colours was tried first, produced a clean and confident answer, and was wrong, which is why that arithmetic check is now a gate in the builder rather than a comment in it. The frontier probe now sweeps GIS subdomains too, because the old host list could never have found the one host that mattered.
- 21 August 2026
Two counties that were never dark
Gallatin's record said the county was dark on every route tried, and that its clerk's own mail domain did not resolve. Neither was true. The site answers normally and serves an incomplete certificate chain — the leaf without its intermediate — which every automated client reports as a hard failure and which no browser notices, so a misconfiguration had been recorded as an absence. Coles County had taught the pattern days earlier; sweeping all 27 frontier counties' clerk domains for the same shape found Gallatin and confirmed Vermilion, whose record had likewise said its site could not be reached. The fix supplies the missing intermediate and pins its fingerprint, rather than turning certificate verification off.
- 20 August 2026
Four websites that answer perfectly and serve nothing
A sweep of all 406 municipal websites this app links found four that return a clean HTTP 200 and no content a reader wants: one is a domain-for-sale lander, one an entirely empty body, and two the default placeholder page that ships with an unconfigured web server. This is the one failure a status-code check cannot see — the link gate asks whether a URL answers, and all four answer flawlessly. The tell is content, and the cheap version of it is size: every one of the four came back under 1,200 bytes, against real municipal front pages that run to tens of thousands. None of them entered the roster by mistake — each is the website that municipality's own county publishes for it, so the counties are citing them too, and this project does not overwrite a county's published value on its own initiative. They are recorded as gaps instead.
- 18 August 2026
A clerk said there were no maps, and the county built anyway
Clark County's Clerk answered the standing request with one sentence: the County Board is elected by districts, and no maps are available. That settles the board's form and refuses its geometry in the same breath. But Clark's districts are unions of whole precincts, so the county's own certified canvasses describe them completely, and the census voting districts — matching all 23 precinct names and summing to the county's exact population — supply the polygons. It is the first county in the fleet built entirely from election returns, with no map at any stage. Because a certified return names who won an election rather than who holds the seat today, every roster row names the election that seated the member instead of claiming a currency the returns cannot show.
- 17 August 2026
The first boundary this project ever bought
Jo Daviess County's GIS office sells its data under a signed licence rather than publishing it. Its board-district shapefile ships displayed under the county's written permission — a purchased licence plus the IT/GIS Director's authorisation by e-mail — with the raw file retained offline rather than committed to this repository. It is the 63rd county, and the first whose boundary was paid for.
- 8 August 2026
The guard that measured the wrong thing
Brown County's website switched on Cloudflare's e-mail obfuscation, so every published mailto: link became an encoded attribute instead. The scraper read mailto: only, and returned seven board members carrying zero e-mail addresses — while the guard that checks Brown seats seven passed happily. Seven rows in, seven rows out, guard satisfied, contact gone, on a tool whose whole purpose is telling residents how to reach their board. A count guard answers “did this break?” and cannot answer “does this still say what it said?”, so a retention gate now compares every roster field against the same file before the change and fails when a field stops being published — no threshold to configure, and a field is protected the moment it first ships. The obvious follow-up question was then measured rather than reassured about: all 25 other scrapers reading mailto: were fetched and checked, and none served the obfuscated form.
- 7 August 2026
A dead link nobody was checking
Macon County's precinct card had been linking maconcountyil.gov for three days. That hostname has no DNS record at all — not a redirect, not a 404, no address associated with it. The county is at maconcounty.illinois.gov. Nothing in this repository checked the URLs the cards actually render: one gate covered the dataset endpoints the layers fetch and another covered layer counts, while the link a reader clicks was unguarded. A link gate now extracts its own surface from the app's authored pages and every roster file — around 1,200 URLs across some 540 hosts — so a new county is covered the day it ships, with nothing to remember to update. It separates links this project chose, which fail, from links carried through from a county's own published roster, which warn.
- 4 August 2026
Contiguity stops deciding which county comes next
Until this day a county could only join if it touched the area already served, which meant the shape of the map was choosing the work rather than the data being available. That gate was retired: a county now joins whenever a county-keyed layer answers in it, wherever it sits. The served area is allowed to be disjoint, and has been four times since — each island rejoining as soon as a neighbouring county filled the gap, and the outline recomputed from the counties themselves every time rather than patched by hand. The honesty test it replaced is unchanged: a county is never joined just because some statewide layer happens to give a rich answer there.
Checked on a schedule
Every scheduled job lands its changes as a reviewed pull request — data about real officeholders never ships without a person looking at the diff.
| What it refreshes | Job | When |
|---|---|---|
| IL Senate + House rosters (ilga.gov scrape) | update-ilga-roster.yml | Mon 13:00 UTC |
| U.S. House roster (unitedstates/congress-legislators) | update-congress-roster.yml | Mon 13:00 UTC |
| CPD district info (Cloudflare-challenged scrape via Playwright) | update-cpd-roster.yml | Tue 13:00 UTC |
| CCPSA district council rosters | update-ccpsa-roster.yml | Wed 13:00 UTC |
| Will County Board roster (willcountyboard.com scrape) | update-will-county-board-roster.yml | Thu 13:00 UTC |
| Kendall County Board roster (kendallcountyil.gov scrape, Playwright fallback for the Akamai front) | update-kendall-county-board-roster.yml | Thu 14:00 UTC |
| McHenry County Board roster (mchenrycountyil.gov scrape, Playwright fallback) | update-mchenry-county-board-roster.yml | Thu 15:00 UTC |
| Kane County Board roster (the county's SharePoint list API) | update-kane-county-board-roster.yml | Thu 16:00 UTC |
| Lake County Board leadership tags (directory scrape, Internet Archive fallback) | update-lake-county-board-roles.yml | Thu 17:00 UTC |
| Board of Review commissioner roster (cookcountyboardofreview.com scrape) | update-ccbr-roster.yml | Fri 13:00 UTC |
| Illinois county clerk roster (ISBE election-authority directory scrape) | update-county-clerk-roster.yml | Sat 13:00 UTC |
| DuPage County Board roster (dupagecounty.gov directory scrape) | update-dupage-county-board-roster.yml | Sun 13:00 UTC |
| Winnebago County Board contact (wincoil.gov board page; base64 e-mails, name-matched to the county GIS) | update-winnebago-county-board-roster.yml | Wed 15:00 UTC |
| Sangamon County Board roster (29 per-district member pages on sangamonil.gov) | update-sangamon-county-board-roster.yml | Tue 15:00 UTC |
| Livingston County Board roster (livingstoncountyil.gov directory scrape) | update-livingston-county-board-roster.yml | Tue 14:00 UTC |
| Municipal governing bodies (Cook Clerk DOEO API, Will Clerk directory, DMMC directory, Kane/McHenry/Kendall/Ogle/Carroll clerk yearbooks, Stephenson county villages page, Lake county GIS contact, LaSalle clerk PDF, Winnebago WinGIS officeholder layers, Freeport city site) | update-municipal-officials.yml | Wed 14:00 UTC |
| DeKalb County Board roster (dekalbcounty.org members page; Sucuri-fronted, the scraper retries past the captcha stub) | update-dekalb-county-board-roster.yml | Fri 14:00 UTC |
| Ogle County Board roster (oglecountyil.gov staff directory) | update-ogle-county-board-roster.yml | Sat 14:00 UTC |
| Stephenson County Board roster (stephensoncountyil.gov board page) | update-stephenson-county-board-roster.yml | Sun 14:00 UTC |
| Carroll County Board roster (carrollcountyil.gov board directory) | update-carroll-county-board-roster.yml | Mon 14:00 UTC |
| Lee County Board roster (leecountyil.com Member Contact List PDF) | update-lee-county-board-roster.yml | Tue 16:00 UTC |
| Rock Island County Board roster (rockislandcountyil.gov board page) | update-rock-island-county-board-roster.yml | Wed 16:00 UTC |
| source freshness + redistricting watch (opens/updates one tracking issue) | validate-sources.yml | 1st of month 14:00 UTC |
| LaSalle County Board roster (county directory scrape) | update-lasalle-county-board-roster.yml | Sun 15:00 UTC |
| Henry County precinct polling places (Clerk's own directory) | update-henry-precinct-polling.yml | Fri 18:00 UTC |
| Woodford County Board roster (woodford-county.org directory scrape) | update-woodford-county-board-roster.yml | Mon 16:00 UTC |
| Logan County Board roster (logancountyil.gov board-page scrape) | update-logan-county-board-roster.yml | Mon 17:00 UTC |
| Boone County Board roster (boonecountyil.gov board-page scrape) | update-boone-county-board-roster.yml | Tue 17:00 UTC |
| Grundy County Board roster (grundycountyil.gov board-page scrape) | update-grundy-county-board-roster.yml | Wed 17:00 UTC |
| Jefferson County Board roster (jeffersoncounty.illinois.gov board-table scrape) | update-jefferson-county-board-roster.yml | Thu 19:00 UTC |
| Macon County Board roster (maconcounty.illinois.gov board-members scrape) | update-macon-county-board-roster.yml | Fri 17:00 UTC |
| Menard County commissioners roster (menardcountyil.gov board-table scrape) | update-menard-commissioners-roster.yml | Thu 20:00 UTC |
| Montgomery County Board roster (montgomerycountyil.gov board-page scrape) | update-montgomery-county-board-roster.yml | Wed 18:00 UTC |
| Henry County Board roster (henrycty.com district-keyed directory scrape) | update-henry-county-board-roster.yml | Thu 18:00 UTC |
| Peoria County Board roster (GIS roster spine + per-member page contact scrape) | update-peoria-county-board-roster.yml | Fri 15:00 UTC |
| Tazewell County Board roster (tazewell-il.gov member-page scrape) | update-tazewell-county-board-roster.yml | Fri 16:00 UTC |
| Iroquois County Board roster (county board-page table scrape) | update-iroquois-county-board-roster.yml | Sat 15:00 UTC |
| At-large county boards (Monroe, Randolph) rendered on the County card | update-county-commissioners-roster.yml | Sat 16:00 UTC |
| De Witt County Board roster + district-composition drift check | update-dewitt-county-board-roster.yml | Sun 15:00 UTC |
| Washington County Board roster + township-composition drift check | update-washington-county-board-roster.yml | Sun 16:00 UTC |
| Cass County Board roster + seat-count tripwire on the derived boundary | update-cass-county-board-roster.yml | Sun 17:00 UTC |
| Marshall County Board roster + township-composition drift check | update-marshall-county-board-roster.yml | Mon 18:00 UTC |
| McDonough County Board roster + derived-boundary drift check (mcg.mcdonough.il.us, HTTP-only) | update-mcdonough-county-board-roster.yml | Mon 19:00 UTC |
| Fulton County Board roster (fultoncountyil.gov Members page — a district-grouped grid joined by name to hidden per-member popups for the e-mails) | update-fulton-county-board-roster.yml | Tue 20:00 UTC |
| Stark County Board roster (starkco.illinois.gov) + drift check on the five boundaries built from the Clerk's archived Google My Maps export | update-stark-county-board-roster.yml | Tue 19:00 UTC |
| Mason County Board roster SOURCE watch (scan — transcribed by hand, never scraped) | watch-mason-roster-source.yml | Tue 18:00 UTC |
| Shelby County Board roster (shelbycounty-il.gov coboard.aspx scrape, cross-checked against contacts.aspx) + drift check on the composition the derived boundary is dissolved from | update-shelby-county-board-roster.yml | Wed 19:00 UTC |
| White County Board roster + precinct polling (whitecounty-il.gov board + elections pages; district join from the certified-canvass table, any unplaceable name fails) + watch on the Elections page's adopted-map link | update-white-county-board-roster.yml | Sat 17:00 UTC |
| Jo Daviess County Board roster (jodaviesscountyil.gov board page + 16 member directory pages, paced; seats counted named+vacant = 17, districts 1..17 asserted against the purchased boundary's constant, home addresses never parsed) | update-jodaviess-county-board-roster.yml | Sat 18:00 UTC |
| Coles County Board roster (colesco.illinois.gov board page + committees page cross-check; the fetch supplies the intermediate certificate the county's server omits, pinned by hash. Exists because the county's own board-district GIS layer carries a 2022 member column that gets six of twelve names wrong — the app takes geometry from the service and people from here) | update-coles-county-board-roster.yml | Sun 18:00 UTC |
| Clark County Board roster (the Clerk's own certified canvasses at il-clark.accessliberty.com — the county's only board document is a scan, so the roster is the election returns; the run also re-checks each district's tabulated precinct list against the shipped dissolve, which is the re-precincting/redistricting tripwire) | update-clark-county-board-roster.yml | Mon 20:00 UTC |
| Crawford County Board roster (the county's own board page) + the redistricting tripwire: the same run re-reads the district composition from the Clerk's results feed at il-crawford.pollresults.net and fails if it stops matching the shipped dissolve | update-crawford-county-board-roster.yml | Tue 21:00 UTC |
| Mercer County Board roster (the county's own board table — party, term, home town, chairman) + the redistricting tripwire: the same run re-reads the district composition from the Clerk's results feed at il-mercer.pollresults.net and fails if it stops matching the shipped dissolve | update-mercer-county-board-roster.yml | Wed 20:00 UTC |
| Edgar County Board roster (edgarcountyillinois.COM board page — the .gov does not carry it) + the redistricting tripwire: the same run re-reads the district composition from the Clerk's results feed at il-edgar.pollresults.net and fails if it stops matching the shipped dissolve | update-edgar-county-board-roster.yml | Thu 21:00 UTC |
| Franklin County Board roster (the county's own members page, read as a GRID by cell coordinate — a flattened text read pairs every role with the wrong member, and its Address column is every member's home) + the redistricting tripwire: the same run re-checks the county's live precinct count and each numbered board contest's precinct count against the shipped dissolve, and fails if either moves | update-franklin-county-board-roster.yml | Fri 19:00 UTC |
| Clinton County Board roster (the county's own board page — 15 members with phone, e-mail and term year, Chairman and Vice Chairman badged by name match) + the redistricting tripwire: the same run re-checks the county's live precinct count and each numbered board contest's precinct count against the shipped dissolve | update-clinton-county-board-roster.yml | Sat 19:00 UTC |
| Adams County Board roster (certified canvasses on platinumelectionresults.com; also the backfill tripwire for the unread November 2022 canvass) | update-adams-county-board-roster.yml | Sat 20:00 UTC |
| Macoupin County Board roster (the County Clerk's elected-officials directory, read from the service its page fills itself from) + a redraw tripwire re-reading the county's own district layer by key and area | update-macoupin-county-board-roster.yml | Thu 10:00 UTC |
| Knox County Board roster (roll call of the county's own board minutes on its Revize CMS; its member page is a 403) | update-knox-county-board-roster.yml | Sun 20:00 UTC |
| Warren County Board roster (the county's own board page — 15 members with committees, term year, phone and e-mail, Chairman of the Board badged; home addresses seen and dropped) + the strongest drift check in the fleet: the same run re-reads the precinct-map legend the shipped boundary was dissolved from | update-warren-county-board-roster.yml | Sun 19:00 UTC |
| Daily watchdog over every roster refresh: reports the ones that are FAILING or have gone longer than their own cadence without a successful run, on one standing issue. Exists because a builder that refuses the write opens no PR and files nothing — the shipped roster silently freezes at its last good run | roster-health.yml | daily 23:00 UTC |
| Hancock County Board roster (county board page scrape) | update-hancock-county-board-roster.yml | Fri 20:00 UTC |
| Richland County Board roster (the county's own board page) + the redistricting tripwire: the same run re-reads the board-district and voting-precinct layers from the county's own GIS at richlandil.wthgis.com and fails if either stops matching the shipped dissolve | update-richland-county-board-roster.yml | Mon 21:00 UTC |
| Jackson County Board roster (the county's own board page, grouped by district heading, plus each member's staff-directory entry for the e-mail); the builder fails if the page's grouping and the directory disagree about a member's district | update-jackson-county-board-roster.yml | Tue 22:00 UTC |
| Douglas County Board roster, assembled from the county's annual Yearbook (people and e-mail) and its certified election summary reports (district numbers), joined on surname; the builder fails if the two county documents stop naming the same seven people | update-douglas-county-board-roster.yml | Wed 21:00 UTC |
| Vermilion County Board roster (27 members across 9 districts) from the county's own members page, fetched with the intermediate certificate its server omits supplied by AIA and pinned by hash | update-vermilion-county-board-roster.yml | Thu 22:00 UTC |
| Wayne County Board roster (14 members across 7 districts) from the county's own board page + the redistricting tripwire: the same run re-reads the per-district precinct list from that page and fails if it stops matching the shipped dissolve (District 7 checked as the known page/canvass subset, since the page never names Fairfield 1 or Fairfield 2) | update-wayne-county-board-roster.yml | Fri 21:00 UTC |
| Clay County Board roster (14 members across 14 lettered districts, 14/14 with a phone) from the county's own members page + the redistricting tripwire: the same run re-reads the county-board page's Districts section letter by letter and fails if it stops matching the shipped composition (the village-limits line splitting Clay City between districts A and B came from the Clerk's e-mail, and no page re-read can trip a line the page never draws) | update-clay-county-board-roster.yml | Sat 21:00 UTC |
| Boone County's five park and library districts' office and board. The County Clerk's Year Book supplies the office — address, telephone, fax, website and agency head — for all five; every BOARD comes from that body's own published page, because the booklet is an annual snapshot and is short of what two of the five bodies publish (it names four Belvidere Park District commissioners where the district's own page names all five; Ida Public Library's own page lists eight trustees and the word 'Open', which the booklet cannot show). Where both name the same person the booklet's term-expiry year rides along. The builder refuses to write unless the roster's keys are exactly the districts the two boundary files draw | update-boone-district-officials.yml | Wed 22:00 UTC |
| Peoria County's fire, park and library districts' officers, office and contact details, read from each district's OWN Annual Financial Report filing with the Illinois Comptroller — the county publishes a district name and a website and no officer at all. The filed TITLE decides which list a person joins, because the Chief Executive Officer slot is not reliably an elected officer, and every record carries the fiscal year it was filed for since an AFR is a one-year snapshot. Paced at one request a second against a state site, two pages per district: the landing page for the year that district last filed, then that year's contact block. The builder's floors sit under the measured 18 of 25 districts, so one district going quiet cannot freeze the rest | update-peoria-district-officials.yml | Sun 21:00 UTC |
| Logan County's seven park districts' officers, office and contact details, read from each district's OWN Annual Financial Report filing with the Illinois Comptroller — the Tri-County Regional Planning Commission draws the boundary and carries a name and nothing else, and neither the commission nor the county publishes a trustee. The same route Peoria's job uses, scheduled an hour later so the two are not competing for the same paced state site. ALL SEVEN FILE, two of them under another county because a cross-county district files once (Armington Community in Tazewell, San Jose in Mason), so the join is an explicit table of unit codes rather than a name match — a Warehouse search is a substring match and 'Armington' also returns seven FARMINGTON units. Every row is verified against the Warehouse's own label and county before it is read. The builder's floors sit under the measured seven, so one district going quiet cannot freeze the rest | update-logan-district-officials.yml | Sun 22:00 UTC |
| Woodford County's 25 filing fire, park and library districts' officers, office and contact details, read from each district's OWN Annual Financial Report filing with the Illinois Comptroller — the boundaries are dissolved from the county's parcel fabric and carry a district name and a tax code and nothing else, and the county publishes no officer. The same route Peoria's and Logan's jobs use, scheduled clear of both (Peoria Sun 21:00, Logan Sun 22:00) so the three are not competing for the same paced state site, and after Woodford's own county-board roster at Mon 16:00. EIGHT OF THE 25 FILE UNDER ANOTHER COUNTY because a cross-county district files once — six fire and two library districts serving slivers of Woodford from McLean, Tazewell and LaSalle — so the join is an explicit table of unit codes rather than a name match, verified against the Warehouse's own label, type and county on every run, four requests covering all 25. The 26th, Minonk City Library, is a municipal library filing inside the City of Minonk and has no unit of its own. The builder's floors sit under the measured 25, so one district going quiet cannot freeze the rest | update-woodford-district-officials.yml | Mon 22:00 UTC |
| Grundy County's 20 filing fire, park and library districts' officers, office and contact details, read from each district's OWN Annual Financial Report filing with the Illinois Comptroller — the boundaries are dissolved from the county's parcel fabric and carry a district name and a tax code and nothing else. The county's own Appointed Local Public Entity Report names nine of these bodies and no individual at all, which is why a second publisher was needed. The same route Peoria's, Logan's and Woodford's jobs use, scheduled clear of all three (Peoria Sun 21:00, Logan Sun 22:00, Woodford Mon 22:00) so the four are not competing for the same paced state site, and clear of Grundy's own county-board roster at Wed 17:00. TEN OF THE 21 FILE UNDER ANOTHER COUNTY because a cross-county district files once — five fire, four library and one park district filing from LaSalle, Will and Livingston — so the join is an explicit table of unit codes rather than a name match, verified against the Warehouse's own label, type and county on every run, four requests covering all 21. The 21st, Mazon Fire, has a unit and no readable report this year and ships the run one appears. The builder's floors sit under the measured 20, so one district going quiet cannot freeze the rest | update-grundy-district-officials.yml | Fri 22:00 UTC |
| Kankakee County's 25 filing fire, park and library districts' officers, office and contact details, read from each district's OWN Annual Financial Report filing with the Illinois Comptroller — the county's taxing-district service declares telephone, website and email on every row and populates none of them (0 of 17 fire, 0 of 4 park, 0 of 8 library). The same route Peoria's, Logan's, Woodford's and Grundy's jobs use, scheduled clear of all four (Peoria Sun 21:00, Logan Sun 22:00, Woodford Mon 22:00, Grundy Fri 22:00) so the five are not competing for the same paced state site. IT ALSO READS THE COUNTY'S LIVE SERVICE, because Kankakee's boundaries are fetched at render time rather than committed, so the join key has to come from the same layer the app will read; a name in one and not the other fails the run. Three of the 27 units file under Livingston, Will or Iroquois because a cross-county district files once. The builder's floors sit under the measured 25, so one district going quiet cannot freeze the rest | update-kankakee-district-officials.yml | Sat 22:00 UTC |
| The officers, office and contact details of the 197 district-governed Illinois public libraries that file, read from each library's own Annual Financial Report filing with the Illinois Comptroller. The statewide layer's boundary comes from the Illinois Broadband Office and carries a library's name, how it is governed and no contact of any kind, so all 550 cards named a library and nobody. One job for 72 counties, because the layer clips each library's service area to the county: a district spanning counties draws a card in every county it touches while filing once, so the index is statewide and 198 filings stamp 340 cards. The same route Peoria's, Logan's, Woodford's, Grundy's and Kankakee's jobs use, scheduled clear of all five (Peoria Sun 21:00, Logan Sun 22:00, Woodford Mon 22:00, Grundy Fri 22:00, Kankakee Sat 22:00) so the six are not competing for the same paced state site. It is the longest of the six by an order of magnitude — roughly 480 paced requests, because it indexes all 102 counties and then reads two pages per library — so it takes the one hour band no other weekly job occupies. A partial index is the failure it must never ship: a county missing from it unresolves every library filed there, which reads on the card exactly like a library that files nothing, so each county is retried and the run is abandoned rather than completed short. The builder's floors sit under the measured 197, and it re-measures the card count against the shipped boundary files rather than trusting the scraper, because a library renamed in the boundary layer costs its cards and no key at all | update-il-library-district-officials.yml | Sun 12:00 UTC |
| The office address, telephone, website and administrator of 365 Illinois public libraries, read from L2, the directory Chicago Public Library, RAILS and Illinois Heartland run together. It is the second publisher for the statewide library layer: the Comptroller filings name the boards of the 197 libraries that file, and this names where the other 173 are, fills the address or telephone the filings leave out, names one administrator per library and links each library's own site. It answers no trustee — the directory's staff lists and its annual certifications are both behind a sign-in, which is not worked around. Roughly 410 paced requests: two to TIGERweb for the Illinois place list that splits the address cell, 41 pages of the rendered directory listing, then one page per matched library, because the listing table carries neither the website nor the administrator. It reads the RENDERED page, because librarylearning.org's robots.txt closes the REST endpoint a Drupal directory of this shape usually serves the same rows from (Disallow: /restapi/*). Scheduled Monday 11:00 UTC, after Sunday 12:00's library-officials run, because this scraper reads that job's output to decide which fields the filings already answer. The builder's five floors each sit about 14% under their measured value (365 libraries, 244 addresses, 223 telephones, 335 websites, 286 administrators), so one library renamed in either publisher cannot freeze the rest | update-il-library-contacts.yml | Mon 11:00 UTC |
| The officers, office and contact details of the fire and park districts of twenty counties, read from each district's own Annual Financial Report filing with the Illinois Comptroller. The app draws fire protection districts in 26 counties and park districts in 18; Peoria, Logan, Woodford, Grundy and Kankakee already name their officers through their own AFR jobs and Boone's park districts come from the county's directory, and everywhere else the card gave a name, a county and nobody. This job covers 29 county/layer pairs: ten whose boundaries ship as GeoJSON here, and nineteen it reads from the county's own ArcGIS service at scrape time because that is where il/index.html reads them at render time; a name in one and not the other fails the run. Roughly 900 paced requests: nineteen service queries for the card names, a Warehouse search per county to verify every unit code in the table still names the district it claims, then two pages per unit. The same route and pacing as the six other AFR jobs and scheduled clear of all of them (library Sun 12:00, Peoria Sun 21:00, Logan Sun 22:00, Woodford Mon 22:00, Grundy Fri 22:00, Kankakee Sat 22:00). The builder's floors sit under the measured 485 cards, and it re-measures every key against the card names the scraper recorded rather than going back to the county services, because its --check runs in CI where an unrelated county outage must not fail the build | update-il-special-district-officials.yml | Mon 12:00 UTC |
| Cook County's 17 commissioners and its Board President, from the county's own GIS people table — the same rows il/index.html already renders on the Cook board card. THE APP DOES NOT READ WHAT THIS WRITES: the card fetches that table live on every Cook click, so the roster lands in il/data/source rather than data/app, where validate_index.py would rightly refuse a file index.html does not reference. What it is for is il/county-board/cook.html, which returned 404 until 2026-09-15 — the second most populous county in the United States, with the Illinois hub's own prose naming it — because build_county_pages.py enumerates whatever the roster FILES carry and Cook had none. TWO OTHER SOURCES WERE MEASURED AND REJECTED: the county website publishes no machine-readable members list (its /board-of-commissioners page is a hub of meetings, /service/board-commissioners 404s), and the open-data portal's "Cook County Commissioner Information" seats a member who died in 2018 and gives "next_election: 2018". The GIS table is maintained — District 8's row names the 2025 appointee and was edited four days later — but ONE COLUMN IN IT IS NOT: lastElectedDate says 2018-11-06 for twelve of the seventeen, all of whom were on the 2022 ballot, so no term or election date is carried at all. The per-member office is the SECOND address, because the first is the board's own County Building suite repeated on fourteen rows. One request. The builder gates on exactly 17 districts, a named member in each, a Board President, and floors of 12 e-mails, 12 phones and 9 district offices under a measured 14/15/12 | update-cook-county-board-roster.yml | Wed 23:00 UTC |
| Chicago's 50 alderpeople, from the City's own Socrata dataset htai-wnw4, with each ward office's address, phone and e-mail. THE APP DOES NOT READ WHAT THIS WRITES: il/index.html calls that dataset live on first toggle of the ward layer, so the card is never a week behind the City. This job exists so the 50 names appear in the served bytes of il/ward.html — measured 2026-09-15, not one of them appeared in any page a crawler, an AI client or a reader with JavaScript off could read, which is the absence build_county_pages.py closed for 2,650 county seats and left open for the city. The roster therefore lands in il/data/source rather than data/app, where validate_index.py would rightly refuse a file the app never fetches, and the job regenerates the page in the same run because build_officeholder_tables.py --check fails a workflow that does not. One request, after robots.txt and the host's stated 1s crawl delay. The builder gates on EXACTLY 50 wards rather than a floor — Chicago elects one alderperson per ward and that number cannot move — with field floors of 45 e-mails, 45 phones and 45 offices under a measured 50 of each | update-chicago-ward-roster.yml | Tue 23:00 UTC |
| The Supreme and Appellate Court justices of each of the five Illinois judicial districts, read from illinoiscourts.gov — the seven Supreme Court justices with the district each was elected from and the Chief Justice badged, then each district's appellate branch: its sitting justices each linked to their own page, the courthouse address, the telephone and the Clerk of the court. ONE JOB ANSWERS FOR BOTH COURTS because one set of districts serves both: Const. art. VI, sec. 2 divides the State into five Judicial Districts "for the selection of Supreme and Appellate Court Judges", 705 ILCS 25/1(a) puts a branch of the appellate court in each of them "as such districts are determined by law" and defines none of its own, and 705 ILCS 23 (which IS PA 102-0011, the act the shipped shapefile comes from) determines them county by county. Three paced page reads plus one form postback — the directory is ASP.NET WebForms, so its full roster needs a POST carrying __VIEWSTATE rather than a query string. IT IS ALSO THE REDISTRICTING TRIPWIRE for a layer nobody watches: the builder carries 705 ILCS 23's county lists and refuses to write unless the counties the court's own district pages name are exactly those 102, in those districts, so a redraw or a page rewrite fails the run instead of quietly moving a card | update-il-court-justices.yml | Wed 12:00 UTC |
| The SERVICE PROVIDER AGENCY of Chicago's Special Service Areas — the non-profit the city contracts with to run each corridor, with its office address, telephone and site. An SSA elects nobody, so this is the only people source the card has, and the city's boundary dataset carries none of it. The source is ONE HAND-MAINTAINED HTML PAGE reached through Business Assistance rather than the department's landing page, which is why this runs weekly and why the scraper enumerates its traps instead of trusting its shape — above all a PHANTOM BLOCK, where an expired area's notice bolds its replacement's name and a bold-keyed parser invents an empty provider for a live area. Two paced requests. All 58 areas have a provider; every area's Mayor-appointed commission stays recorded in gap chicago-ssa-service-providers | update-chicago-ssa-providers.yml | Tue 11:00 UTC |
| The Chicago Board of Education's twenty sub-district members and its city-wide President, from the Board's own member index at cpsboe.org. THE FILE IT REBUILDS WAS HAND-CURATED AND HAD DRIFTED: on 2026-09-23 District 9a shipped the bare string VACANT where the Board names its Vice President, and District 10b named a member the Board's own news says resigned in March 2026 — while the card's provenance note claimed no machine-readable roster is published for this board. Twenty-two paced requests: the index, then each member's own bio page, which is the second witness the builder requires to agree on name, office and district before it writes, and which carries the cps.edu address. The join onto the map is read off the shipped geometry's own sub-district labels, so a re-districting fails the run rather than filing a member under a neighbour's district | update-chicago-school-board-roster.yml | Thu 11:00 UTC |
| A board of trustees read off each library's OWN website, for the statewide library layer's cards no Annual Financial Report answers for. Only a district-governed library files an AFR, so Sunday 12:00's job reaches the districts and nobody else — a municipal library is covered by its city's or village's report and a township library files as or within a township, whose board is not the library's board — and L2's staff lists and annual certifications are both behind a sign-in that is not worked around. The library itself is the only publisher left. THE POOL IS DERIVED on every run from the shipped boundary files, the filings roster and the directory: every card that names no board and links a site, so a library that starts filing leaves the pool by itself. robots.txt is read through scripts/robots_policy.py as the client that fetches, before each host's first request, with a stated Crawl-delay honoured per host; a refusal is recorded as a refusal and nothing is fetched from that host. Three hops at most per library — the home page, a link on it naming a board, and only where the home page names none at most two same-origin About pages, because a home page with no board link is not a library that publishes no board — around 300 paced requests across 170-odd hosts. NOTHING IS GUESSED: a library ships only where its page yields a board between three and twelve names and the page's own words say the board is that library's, and five hosts carry more than one library, one of them contracting its service to a neighbour. The parser's --selftest runs offline before a single host is asked for a page, and in CI. Monday 10:00 UTC, three hours ahead of the roster wave and clear of the two other library jobs | update-il-library-trustees.yml | Mon 10:00 UTC |
| Where each layer's shapes come from, fleet-wide (browser probe -> layer-sources.json + the endpoint inventory's table); PR only when a result changes | update-layer-sources.yml | Sun 09:17 UTC |
| Re-mirror the Census TIGERweb layers the apps draw from vector tiles, fleet-wide (scripts/mirror_tiger_tiles.py -> the archives + tiger-mirror.json); rebuilds and gates only a layer whose fetched set changed, moves the cache name of each app whose archive it replaced, and opens a PR only then | update-tiger-tiles.yml | Sun 10:41 UTC |
Back to the map Sources & data layers Common questions Privacy Why this exists