Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

43 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

PPS Data Explorer

Live site: ppsdata.info

A data-driven explorer for the 74 Portland Public Schools (PPS) elementary, K-8, middle, and alternative schools that the district plans to narrow to a closure shortlist in November 2026.

The dashboard gathers enrollment, building, demographic, academic, and neighborhood-housing data for every in-scope school.

Live site

web/index.html is a single-page static dashboard (vanilla JS + Plotly.js + Leaflet). It reads from web/data.json, which is generated by the pipeline below.

Serve locally:

python3 -m http.server -d web 8000
# open http://localhost:8000

What's in the dashboard

  • Map of every in-scope school (click to enable scroll-zoom).
  • Trends chart showing 7-year enrollment change (2018 → 2025) with per-school toggles.
  • Utilization ranking (enrollment vs. 2021 LRFP functional capacity).
  • Long-term sustainability ranking (7-year enrollment change 2018 → 2025).
  • 10-year enrollment forecast ranking — per-school projected 2034-35 enrollment from PSU Population Research Center (Table 5.5, medium scenario), with low/high scenario bands derived by scaling each school's medium by the district-and-grade-band Low/Medium and High/Medium ratios.
  • Housing growth forecast ranking — projected new residential units within each school's catchment by ~2035 (Metro BLI 2045 grid, area-weighted to catchment polygons).
  • Seismic ranking (remaining retrofit cost per campus from Holmes 2024, colored by funding status; URM buildings flagged).
  • Transportation ranking (distance to nearest same-level school for the 15 lowest-enrollment schools).
  • Ratios ranking (students per teacher FTE, Fall 2023 CCD).
  • DLI & focus-option section with strand-vs-non-strand headcounts and a collapsible "why this matters" note.
  • Scatter plots: enrollment vs. utilization, math/avg proficiency vs. poverty, enrollment vs. % BIPOC, chronic absenteeism vs. proficiency, support-staff FTE equity, 7-year change vs. current size, students-per-teacher vs. enrollment.
  • Sortable table of all 74 schools with column descriptions and source citations; CSV export of the filtered rows.
  • Charter reference table of the 5 PPS-sponsored charters (Emerson, Kairos PDX, Le Monde, Portland Arthur Academy, Portland Village). Kept separate from the in-scope 74 since PPS can't close a charter through its consolidation process. Shows only the state/federal measures that cover charters (enrollment, demographics, OSAS proficiency, attendance, Title I); building, seismic, capacity, ventilation, and forecast columns are omitted because no source publishes them for charters.
  • Methodology section documenting every source with vintage.
  • Feeder trees on a standalone page at /feeders.html — elementary → middle → high school feeder chains, one tree per high school (Mermaid diagrams).

Ranking charts render vertically on desktop and swap to horizontal bars on narrow screens (<700 px) so every school label stays legible.

Data sources

Source What it provides Vintage
Oregon ODE Fall Membership Enrollment by school (2024-25, 2025-26), race/ethnicity 2025-26
Oregon OSAS (PAGR) ELA & math state-test proficiency 2023-24, 2024-25
Oregon ODE At-A-Glance source file Regular attenders %, experienced-teacher %, teacher retention %, class size 2024-25
NCES Common Core of Data Free/reduced lunch counts, geocoded addresses, 2018-2023 enrollment history, 2023 teacher FTE (for students-per-teacher ratios) 2018-2023
US Dept of Ed CRDC English learners, IDEA/SPED, chronic absenteeism (2020); counselor / social worker / psychologist / nurse FTE, OSS suspension instances, post-COVID chronic absenteeism (2021) 2020, 2021 (both COVID-era)
KPFF Seismic Report 2009 Year built, square footage, construction type 2009
Holmes Engineering 2024 PPS Seismic Assessment Per-campus ROM retrofit cost (all ~80 sites), URM-only partial-retrofit cost, URM classification 2024
PPS Long-Range Facility Plan 2021 (Vol 1) Functional capacity per school (classrooms × station size minus set-asides, with PPS-defined utilization rates). Used for the building-utilization metric. 2021
PPS Bond page Seismic retrofit status 2025
PPS Indoor Air Quality Per-room airflow tests (Amerseco + Neudorfer Engineers, NEBB-certified) — ACH_e medians, % rooms below Lancet 3/6 ACH, MERV-13 filter status 2021
Oregon Affordable Housing Inventory (OAHI) Existing + in-development subsidized housing 2024
Metro RLIS Supplementary regional housing 2024
Portland BDS / PortlandMaps Residential building permits (2022+) 2022 – present
City of Portland — School Boundaries PPS attendance polygons (ArcGIS FeatureServer) 2024-25
PPS Dual Language Immersion + focus-option pages Per-school DLI languages (5) + K-8 focus options (Arts, Environmental, STEAM, TAG, Alt) 2026-04
PPS Language Immersion Enrollment Report Per-school DLI-strand vs. non-strand headcounts (SIS Synergy, annual PDF) 2025-26
Metro 2045 Distributed Forecast (Ord. 21-1457) Regional growth context (city/county only) 2021
Portland Building Land Inventory — Metro BLI 2045 Housing Allocation Grid-cell projected new residential units by ~2035 under current zoning (area-weighted to each school's catchment) 2024
PSU Population Research Center — PPS Enrollment Forecasts 2025-26 to 2034-35 Per-school 10-year enrollment forecast (Table 5.5, medium scenario); district-and-grade low/high scenarios (Tables 5.3, 5.4) used to derive per-school scenario bands July 2025

Project structure

data/
  pps_schools.csv         master table (88 rows; 74 in-scope + 9 high schools + 5 PPS-sponsored charters)
  raw/                    source CSVs, JSONs, XLSXs, PDFs, GeoJSONs
scripts/
  fetch_bds_permits.py          → data/raw/portland_bds_permits.csv
  fetch_affordable_pipeline.py  → data/raw/portland_affordable_pipeline.csv
  fetch_boundaries.py           → data/raw/pps_boundaries_{level}.geojson
  fetch_crdc_2021.py            → data/raw/pps_crdc_2021_agg.json
  fetch_enrollment_history.py   → data/raw/pps_ccd_enrollment_history.json
  fetch_ode_aag.py              → data/raw/ode_aag_schools_2425.csv
  fetch_dli_report.py           → data/raw/pps_immersion_details_2526.{pdf,json}
  fetch_metro_bli.py            → data/raw/metro_bli_housing_allocation.geojson
  fetch_pps_enrollment_forecast.py → data/raw/pps_enrollment_forecast_2025.pdf
  parse_pps_enrollment_forecast.py → data/raw/pps_enrollment_forecast.csv (Table 5.5 per-school)
  parse_lrfp_capacity.py        → data/raw/pps_functional_capacity_2021.json
  fetch_pps_airflow.py          → data/raw/pps_airflow_pdfs/*.pdf + pps_airflow_index.json
  parse_pps_airflow.py          → data/raw/pps_airflow_stats.json
  parse_holmes_costs.py         → data/raw/pps_holmes_2024_costs.json
  boundary_join.py              shared BoundaryIndex (point-in-polygon w/ haversine fallback)
  build_master.py               assemble data/pps_schools.csv
  merge_housing.py              + affordable_units / pipeline_* columns
  merge_permits.py              + permit columns
  merge_bli_forecast.py         + bli_forecast_units_within_catchment (area-weighted from BLI grid)
  merge_pps_enrollment_forecast.py + enrollment_forecast_2025_26..2034_35, _2034_35_low/high, pct_change_10yr (PRC medium + derived scenario bands)
  export_web.py                 → web/data.json (filters to the 74 in-scope schools)
web/
  index.html              single-page dashboard
  feeders.html            standalone elem→middle→HS feeder-tree diagrams (Mermaid)
  data.json               dashboard payload (generated)

Reproducing the pipeline

Requires Python 3.11+ and the packages in pandas, requests, openpyxl, shapely, pdfplumber.

python3 -m venv .venv
source .venv/bin/activate
pip install pandas requests openpyxl shapely pdfplumber

# 1. Fetch raw sources (network):
python scripts/fetch_bds_permits.py
python scripts/fetch_affordable_pipeline.py
python scripts/fetch_boundaries.py
python scripts/fetch_crdc_2021.py
python scripts/fetch_enrollment_history.py
python scripts/fetch_ode_aag.py
python scripts/fetch_dli_report.py
python scripts/fetch_metro_bli.py      # Metro BLI 2045 housing-allocation grid (~30 MB GeoJSON)
python scripts/fetch_pps_enrollment_forecast.py  # PRC 10-year enrollment forecast PDF
python scripts/parse_pps_enrollment_forecast.py  # Table 5.5 per-school → CSV
python scripts/parse_lrfp_capacity.py  # reads data/raw/LRFP_Vol1_2021.pdf (checked in)
python scripts/fetch_pps_airflow.py    # ~270 MB of per-building airflow PDFs
python scripts/parse_pps_airflow.py    # ~6 min: pdfplumber table extraction
python scripts/parse_holmes_costs.py   # reads data/raw/holmes_2024_seismic.pdf (download separately, see below)

# 2. Build master CSV:
python scripts/build_master.py

# 3. Spatial joins + per-school forecasts into the master:
python scripts/merge_housing.py
python scripts/merge_permits.py
python scripts/merge_bli_forecast.py
python scripts/merge_pps_enrollment_forecast.py

# 4. Export dashboard payload:
python scripts/export_web.py

Each step is idempotent; rerun freely as sources refresh.

Spatial-join methodology

Every "nearby" metric (affordable units, pipeline family units, residential permits, BLI forecast units) is aggregated over the school's PPS attendance polygon rather than a 1-mile circle. Schools without a published catchment (ACCESS Academy, focus-option programs, some alternatives) fall back to a 1-mile haversine radius. boundary_join.BoundaryIndex handles the lookup and the name-alias table between ODE and the City's boundary feed.

The BLI 2045 forecast grid is attributed by area-weighted overlap: for each grid cell intersecting a catchment, the fraction intersection.area / cell.area of that cell's Forecast_Units_Prop contributes to the school's total. See scripts/merge_bli_forecast.py.

Caveats

  • Academic performance is included for context but is notably absent from the criteria PPS has discussed publicly.
  • CRDC (LEP, SPED, chronic absenteeism) is from 2020 — a COVID-suppressed reporting year. Percentages are CRDC counts ÷ current enrollment.
  • Square footage is from a 2009 inventory and may predate bond-funded expansions.
  • Building permits ≠ completions. permits_units_within_1mi_since_2022 counts approved units from 2022-01-01 forward, not occupied units.
  • Forecasts are not commitments. The PRC 10-year enrollment forecast encodes July-2025 assumptions (net migration, K/1 capture rate, birth rates). PRC reports district-level accuracy only — 2024 district MAPE was 1.65% at one year, but Kindergarten alone was 8.4% off that year, and pandemic-era multi-year forecasts missed by 8-20% at 4-5 years. No per-school error analysis is published; Low/High bands in this dashboard are derived (PRC only publishes them at the district-and-grade level).
  • Ventilation data is from 2021 and reflects ASHRAE Total Effective ACH with HVAC only; portable HEPA units deployed in response to COVID are excluded. Some older buildings genuinely have rooms with near-zero mechanical ventilation (no rooftop supply, operable windows only).
  • High schools are out of scope. PPS's announced closure process covers elementary / K-8 / middle / alternative only. The master CSV keeps high schools for reference but export_web.py filters them out.

Excluded from this repository

  • .venv/ — Python virtualenv, regenerate locally.
  • data/raw/LRFP_Vol2_2021.pdf (134 MB) — PPS Long-Range Facility Plan Vol. 2, available on bond.pps.net.
  • data/raw/holmes_2024_seismic.pdf (51 MB) — Holmes 2024 PPS Seismic Assessment, available on Google Drive.
  • data/raw/metro/ (336 MB) — Metro 2045 forecast .ppkx + extracted GDB, available on oregonmetro.gov.
  • data/raw/metro_bli_housing_allocation.geojson (~30 MB) — Metro BLI 2045 housing-allocation grid, regenerated via python scripts/fetch_metro_bli.py.

Credits

Built by Alex Meub with help from Claude Code.

License

Released under the MIT License. Copyright © 2026 Alex Meub. The code is freely reusable; data compiled in this repository comes from public sources cited in the Data sources table and remains subject to the terms of its original publishers.

About

PPS Data Explorer is a visualization of data from Portland Public schools that may factor in to closure decisions

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages