transcript.md
↑ ContentsState Planner chat · https://stateplanner.com
- State: Ohio (OH)
- Started: November 1, 2026, 7:12 PM UTC
- Runtime: 49 sec
- Status: completed
Prompt
Which Ohio Counties Have the Worst Local Road Pavement?
Model: Anthropic: Claude Opus 5.5 (anthropic/claude-opus-5.5, via openrouter)
Response
Local road pavement condition by county (ODOT PCR, local system)
I used ODOT's PCR (Local) table (oh_pcr_local). It holds the Pavement Condition Rating, a 0–100 score where lower means more distress, for about 12,000 miles of county, municipal and township roads in all 88 counties. I ranked counties two ways:
- Average PCR weighted by mileage, so long segments count more than short ones.
- Share of rated miles below PCR 55. On ODOT's scale that is "poor" or worse.
Statewide, the mileage-weighted average is 82.1, and 4.7% of rated miles are below 55.

Counties with the lowest average local-road PCR
| County | Rated miles | Avg PCR | % of miles PCR < 55 |
|---|---|---|---|
| Vinton | 38 | 70.4 | 0.0 |
| Noble | 12 | 70.4 | 40.0 |
| Belmont | 116 | 73.2 | 19.1 |
| Harrison | 46 | 73.9 | 16.1 |
| Pike | 38 | 74.8 | 7.0 |
| Cuyahoga | 795 | 75.3 | 10.9 |
| Knox | 33 | 75.6 | 18.3 |
| Ross | 110 | 75.7 | 11.8 |
| Hamilton | 628 | 75.8 | 7.8 |
| Licking | 215 | 76.0 | 7.5 |
| Sandusky | 88 | 76.1 | 6.5 |
| Lawrence | 98 | 76.2 | 12.7 |
Counties with at least 50 rated miles, by share below PCR 55: Belmont 19.1%, Jackson 13.5%, Lawrence 12.7%, Summit 12.0%, Ross 11.8%, Lucas 11.5%, Cuyahoga 10.9%, Hamilton 7.8%.
What stands out
- Belmont is low on both measures, with enough mileage (116 mi) to be a reliable reading. Nearby Harrison and Noble also score low, but they have few rated miles.
- Among the large urban counties, Cuyahoga (75.3) and Hamilton (75.8) have the lowest averages. Together they have about 1,400 rated miles. Lucas and Summit have average scores near the middle, but more than 11% of their miles are below 55.
- Some rankings rest on very few miles. Noble has 12 rated miles, Knox 33, and Vinton and Pike 38 each. One or two segments can move these counties a lot. Vinton's low average comes from many middling scores rather than any segment below 55.
Map legend
- County fill: mileage-weighted average PCR.
- Dark red: under 75
- Orange: 75–77.9
- Light pink: 78–80.9
- Light blue: 81–83.9
- Dark blue: 84 and above
- Dark purple lines: the 959 local segments rated below 55.
- Click a county for its rated miles, share below 55 and rating dates. Click a segment for its route, city or township, PCR, roughness (IRI) and rating date.
Caveats
- Not every local road is rated. The table covers about 12,000 miles, mostly federal-aid-eligible routes (functional classes 2–7) rather than every residential street. How much is rated varies widely by county, and so does how reliable each county's figure is.
- Ratings range from October 2020 to February 2026, so counties are not all measured at the same time. A county rated early may have repaved since.
- The threshold and weighting are my choices. Lengths come from county log points, and "below 55" is my cut-off. Using below 65 instead, Noble, Harrison, Belmont and Ross have the highest shares (34–53%).
I can also compare counties by road owner (county, city or township roads), rank cities or townships, or show the state-highway PCR beside these.
Exploratory Queries
Query 1 — Understand year coverage, rated mileage and counties in local PCR data.
SELECT pcr_year,
count(*) n,
count(DISTINCT county_cd) counties,
round(sum(ctl_end - ctl_begin)::numeric) miles,
round(avg(pcr_nbr), 1) avg_pcr,
count(*) FILTER (
WHERE pcr_nbr IS NULL
) nulls
FROM oh_pcr_local
GROUP BY 1
ORDER BY 1
| pcr_year | n | counties | miles | avg_pcr | nulls |
|---|---|---|---|---|---|
| 2026 | 16316 | 88 | 12011 | 80.9 | 0 |
Query 2 — Check jurisdiction, route type and direction mix of rated local segments.
SELECT jurisdiction,
route_type,
direction_cd,
count(*) n,
round(sum(ctl_end - ctl_begin)::numeric) miles,
round(avg(pcr_nbr), 1) avg_pcr
FROM oh_pcr_local
GROUP BY 1,
2,
3
ORDER BY n DESC
LIMIT 20
| jurisdiction | route_type | direction_cd | n | miles | avg_pcr |
|---|---|---|---|---|---|
| C | CR | UP | 8156 | 7903 | 83.0 |
| M | MR | UP | 6469 | 3052 | 78.0 |
| T | TR | UP | 1317 | 786 | 82.8 |
| M | MR | DOWN | 200 | 137 | 79.5 |
| C | CR | DOWN | 161 | 128 | 82.8 |
| T | TR | DOWN | 13 | 6 | 88.0 |
Query 3 — Rank counties by mileage-weighted local-road PCR and share of miles rated below 55.
WITH s AS (
SELECT county_cd,
greatest(ctl_end - ctl_begin, 0) mi,
pcr_nbr
FROM oh_pcr_local
)
SELECT c.county,
s.county_cd,
round(sum(mi)::numeric, 0) miles,
count(*) segs,
round((sum(mi * pcr_nbr) / nullif(sum(mi), 0))::numeric, 1) wpcr,
round(
(
100 * sum(mi) FILTER (
WHERE pcr_nbr < 55
) / sum(mi)
)::numeric,
1
) pct_lt55,
round(
(
100 * sum(mi) FILTER (
WHERE pcr_nbr < 65
) / sum(mi)
)::numeric,
1
) pct_lt65
FROM s
JOIN oh_county c USING (county_cd)
GROUP BY 1,
2
ORDER BY wpcr
LIMIT 20
| county | county_cd | miles | segs | wpcr | pct_lt55 | pct_lt65 |
|---|---|---|---|---|---|---|
| VINTON | VIN | 38 | 19 | 70.4 | 6.6 | |
| NOBLE | NOB | 12 | 8 | 70.4 | 40.0 | 53.2 |
| BELMONT | BEL | 116 | 158 | 73.2 | 19.1 | 34.4 |
| HARRISON | HAS | 46 | 23 | 73.9 | 16.1 | 39.4 |
| PIKE | PIK | 38 | 33 | 74.8 | 7.0 | 17.8 |
| CUYAHOGA | CUY | 795 | 1202 | 75.3 | 10.9 | 25.7 |
| KNOX | KNO | 33 | 41 | 75.6 | 18.3 | 26.0 |
| ROSS | ROS | 110 | 107 | 75.7 | 11.8 | 33.2 |
| HAMILTON | HAM | 628 | 1198 | 75.8 | 7.8 | 25.4 |
| LICKING | LIC | 215 | 286 | 76.0 | 7.5 | 21.7 |
| SANDUSKY | SAN | 88 | 125 | 76.1 | 6.5 | 24.9 |
| LAWRENCE | LAW | 98 | 95 | 76.2 | 12.7 | 17.9 |
| FRANKLIN | FRA | 998 | 1570 | 78.0 | 5.5 | 19.2 |
| JEFFERSON | JEF | 111 | 164 | 78.2 | 7.3 | 21.0 |
| LUCAS | LUC | 447 | 728 | 78.9 | 11.5 | 21.9 |
| PAULDING | PAU | 41 | 28 | 79.4 | 6.7 | 11.9 |
| JACKSON | JAC | 74 | 92 | 80.1 | 13.5 | 14.6 |
| LAKE | LAK | 159 | 212 | 80.3 | 6.0 | 22.1 |
| FULTON | FUL | 117 | 103 | 80.6 | 2.4 | 11.1 |
| BUTLER | BUT | 273 | 418 | 80.6 | 5.2 | 18.3 |
Query 4 — Check rating dates, functional classes and statewide totals for context.
SELECT min(rated_date)::date mind,
max(rated_date)::date maxd,
round(
(
sum(greatest(ctl_end - ctl_begin, 0) * pcr_nbr) / sum(greatest(ctl_end - ctl_begin, 0))
)::numeric,
1
) wpcr,
round(
(
100 * sum(greatest(ctl_end - ctl_begin, 0)) FILTER (
WHERE pcr_nbr < 55
) / sum(greatest(ctl_end - ctl_begin, 0))
)::numeric,
1
) pct55,
string_agg(DISTINCT functional_class, ',') fcs,
min(pcr_nbr),
max(pcr_nbr)
FROM oh_pcr_local
| mind | maxd | wpcr | pct55 | fcs | min | max |
|---|---|---|---|---|---|---|
| 2020-10-14T00:00:00.000Z | 2026-02-19T00:00:00.000Z | 82.1 | 4.7 | 2,3,4,5,6,7 | 0 | 100 |
Query 5 — Rank counties with at least 50 rated miles by share of miles below PCR 55.
WITH s AS (
SELECT county_cd,
greatest(ctl_end - ctl_begin, 0) mi,
pcr_nbr
FROM oh_pcr_local
)
SELECT c.county,
round(sum(mi)::numeric) miles,
round((sum(mi * pcr_nbr) / sum(mi))::numeric, 1) wpcr,
round(
(
100 * coalesce(
sum(mi) FILTER (
WHERE pcr_nbr < 55
),
0
) / sum(mi)
)::numeric,
1
) p55
FROM s
JOIN oh_county c USING (county_cd)
GROUP BY 1
HAVING sum(mi) >= 50
ORDER BY p55 DESC
LIMIT 10
| county | miles | wpcr | p55 |
|---|---|---|---|
| BELMONT | 116 | 73.2 | 19.1 |
| JACKSON | 74 | 80.1 | 13.5 |
| LAWRENCE | 98 | 76.2 | 12.7 |
| SUMMIT | 490 | 81.5 | 12.0 |
| ROSS | 110 | 75.7 | 11.8 |
| LUCAS | 447 | 78.9 | 11.5 |
| CUYAHOGA | 795 | 75.3 | 10.9 |
| HAMILTON | 628 | 75.8 | 7.8 |
| LICKING | 215 | 76.0 | 7.5 |
| JEFFERSON | 111 | 78.2 | 7.3 |
Map Layer Queries
Average local-road PCR (mileage-weighted) — colored by "bin": Avg PCR under 75 (#b2182b), 75 – 77.9 (#ef8a62), 78 – 80.9 (#fddbc7), 81 – 83.9 (#92c5de), 84 and above (#2166ac)
WITH s AS (
SELECT county_cd,
greatest(ctl_end - ctl_begin, 0) mi,
pcr_nbr,
rated_date
FROM oh_pcr_local
),
a AS (
SELECT county_cd,
sum(mi) miles,
count(*) segs,
sum(mi * pcr_nbr) / nullif(sum(mi), 0) wpcr,
100 * coalesce(
sum(mi) FILTER (
WHERE pcr_nbr < 55
),
0
) / nullif(sum(mi), 0) p55,
min(rated_date)::date d0,
max(rated_date)::date d1
FROM s
GROUP BY 1
)
SELECT c.geom,
initcap(c.county) AS "County",
round(a.wpcr::numeric, 1) AS "Avg PCR(mile-weighted)",
round(a.p55::numeric, 1) AS "% of rated miles PCR < 55",
round(a.miles::numeric) AS "Rated miles",
a.segs AS "Rated segments",
a.d0 || ' to ' || a.d1 AS "Rating dates",
CASE
WHEN a.wpcr < 75 THEN 'a'
WHEN a.wpcr < 78 THEN 'b'
WHEN a.wpcr < 81 THEN 'c'
WHEN a.wpcr < 84 THEN 'd'
ELSE 'e'
END AS bin
FROM oh_county c
JOIN a USING (county_cd)
Picture: maps/turn-1-local-pcr-county.png
Local segments rated PCR under 55
SELECT geom,
county_cd AS "County code",
route_type || ' ' || route_nbr || coalesce(route_suffix, '') AS "Route",
coalesce(city_name, township_name) AS "City / township",
pcr_nbr AS "PCR",
avg_iri AS "Avg IRI",
rated_date::date AS "Rated",
round((ctl_end - ctl_begin)::numeric, 2) AS "Miles"
FROM oh_pcr_local
WHERE pcr_nbr < 55
Picture: maps/turn-1-local-pcr-county.png
Used Data Sets
oh_county— last synced 2026-09-27 · 88 rowsoh_pcr_local— last synced 2026-11-01 · 16,316 rows