Source document

docs/cities/philly/06_enrollment_capacity_utilization.md

Served verbatim from the project repository. Internal working document conventions apply: documents may reference file paths, branch names, and findings-ledger anchors from the repo.

06 — Philadelphia enrollment, capacity and utilization

Question asked: the same one answered for NYC in docs/design/10_nyc_enrollment_capacity_utilization.md — does Philadelphia have a Miami-style capacity/utilization surface, and what would it take to display it?

Answer: yes. Philadelphia publishes per-site capacity, October enrollment and a utilization score and its own close / co-locate / modernize recommendation for every site. It is the only one of the three cities to publish such a list: Miami has to hand-build its decision list from board PDFs, and NYC does not publish one. It joins to PhillySchool.ulcsCode with no crosswalk. It also has the shortest history of the three, one snapshot (SY2024-25), not a panel, and it is published only through a Qlik Sense dashboard, not as a file.

Philadelphia has the largest under-half-full share of the three districts measured: 24.7% of 215 district sites under 50% in SY2024-25, against 21.2% of Miami-Dade's 316 district-run regular schools and 13.3% of NYC's 1,702 schools in the same school year. Its median site (67.1%) is level with Miami-Dade's median school (66.7%) and 13 points below NYC's (80%). That figure is on the district's own published denominator, SDP site capacity (the app's older, 2017-labelled capacity is described in §6.5).

The figures below are reproduced by probe/facilities.py (python docs/cities/philly/probe/facilities.py), live against the app, and by scripts/analysis/philly-capacity-packet.ts from the committed extract. The §4.1–4.3 site-grain figures come from the packet; §6.3 and §6.5 are printed by the probe, and the packet carries the §6.5 comparison as denominator_sensitivity; the §3.3 building counts are in the packet's building_extract block and in the fetcher's dry run (python -m pipeline_philly.fetch_facilities --dry-run). The §3.2 load checks are printed by the loader (npx tsx scripts/loaders/philly/facilities.ts --dry-run, or a full load). Three sets of §3.2 figures are one-off measurements that no committed script reprints: the comparison with the dashboard's per-school Site Utilization (MAP) measure, the NCES comparison of the loaded school figures, and the distribution of those figures.

1. The source

The School District of Philadelphia's Facilities Data Dashboard, behind philasd.org/fpp, published as part of Accelerating Opportunity: Facilities Master Plan. It is a Qlik Sense app:

hostdashboards.philasd.org
app id2d47abaf-02e1-4671-adba-ea661203eda5
endpointwss://dashboards.philasd.org/embed/app/engineData (Qlik Engine JSON API)
authanonymous — the engine returns mustAuthenticate: false
gotchathe load balancer returns 403 without a browser User-Agent, before the engine sees the request. That header is the only trick.

The app carries 341 fields. The ones this track needs:

fieldwhat it is
ULCS CodeSDP's canonical school code — already PhillySchool.ulcsCode
id_site_eos, site_namesite identity — the grain capacity is reported at. The id is the join key; the name is not unique (Lewis Cassidy's is on two sites)
id_bldg_eos, bldg_name, Building Use Typebuilding identity — a different grain (§3.3)
Site Capacitythe denominator
Site Enrollment, Site Pre-K Allotment, Site Enroll w/ Allotmentthe numerator and its parts
Site Utilization, Site Utilization Range Label (NEW)the published score and the district's own bands
Program Changethe district's published recommendation per site
Project Recommendationthe prose version, naming years and receiving schools
FCI Score, total_replacement_value_curbuilding condition (building grain)
BLDGFCIan older condition index, in the app's BuildingsOld table beside BLDGCAPACITY (gaps §8)
BLDGCAPACITYan older capacity figure, per building, which the app labels 2017 ("OLD"); no figure here uses it (§6.5)
gross_square_footage, lot_size, year_built, floor_countphysical plant (building grain)
sch_bldg_score_avg, program_alignment_score, Neighborhood Vulnerability Scorethe plan's other three scoring dimensions

That is a wider facilities surface than either NYC or Miami publishes — condition, replacement value, floor area and program fit alongside capacity.

The district's own definition and cut points

The dashboard states it: "The Capacity/Utilization Score is calculated by dividing the total Enrollment (from the October 1 enrollment snapshot) at the school sites by the total Capacity of the school sites." Its published bands are <50%, >=50–74%, >=75–99%, >=100–104%, >=105% — measured cut points, not ones we chose.

2. Vintage — the binding constraint

NEGATIVE RESULT, and it changes what can be built. The app exposes a School Year field with two values (2016-2017, 2024-2025), which looks like a two-point panel. It is not one. School Year lives on the student/catchment table; the facilities table is cross-joined to it, so every site's capacity appears under both labels with identical numbers:

  • distinct (ULCS, site) carrying a capacity: 342
  • of those, present under more than one School Year label: 335
  • of those, with any of capacity / enrollment / utilization differing between labels: 0

So the year dimension carries no information about capacity. Philadelphia publishes one vintage, SY2024-25. There is no decomposition of the NYC kind (§4.3 of the NYC doc: enrollment effect vs capacity effect) because there is no second point to decompose against. The app does keep a separate, older per-building capacity it labels 2017, and a SY2016-17 enrollment count beside it (§6.5); whether that pair is usable as a second vintage is unmeasured (gaps §4, §5).

Coverage of that vintage, after deduplicating to one row per (ULCS, site):

sectorsiteswith capacitywith enrollmentwith utilization
District249219249219
Charter22222222
Alternative34183418
Pre-K80787578

Charter coverage is thin — 22 sites, so charters in their own buildings are largely absent, the same limitation NYC's Blue Book has.

3. Three things that will break a naive loader

3.1 The identifier join is free; the grain join is not

ULCS Code is already the primary key of PhillySchool. No crosswalk, no derivation, no ambiguity — the opposite of NYC, where the Blue Book's org_id needs an explicit rule, and four of its org codes map to two DBNs each across NYC's directory and Snapshot years (NYC doc §3.1).

But school and site are many-to-many, as in NYC. The extract's 428 rows resolve to 328 distinct sites and 350 ULCS codes: 32 schools sit on more than one site, and 91 sites host more than one program — a district school plus its Pre-K program, or a district school plus a co-located charter. John Turner (site S238) carries three: the district school, Motivation High School, and KIPP West Philadelphia.

Among district sites with a capacity, 80 of 215 (37%) host more than one program — the direct analogue of NYC's 42% of buildings holding more than one organization.

3.2 A multi-site school's enrollment is counted once

32 schools occupy more than one site. On those, the dashboard repeats the whole school's enrollment on every site row while splitting capacity per site. (NYC's Blue Book splits both, so NYC sums both; the two cities differ on enrollment only.) A site's enrollment is the full October enrollment of every programme linked to it, so a school linked to several sites carries its whole enrollment on each. Of the 32 multi-site schools carrying an enrollment on more than one site, 29 carry an identical enrollment on every one. The other three (3850, 4140, 7060) each have one site shared with other programmes, whose figure is the total of everything on it: S172 reads 3,644 = 3,467 (Northeast High) + 177 (3850); S109 reads 542 = 307 (4140) + 147 + 88 (two One Bright Ray programmes); S173 reads 1,198 = 1,148 (7060) + 25 + 25 (two continuation academies).

A school's figure is therefore built in three steps, in this order:

  1. Shared sites first. A school with any site that also hosts another directory school gets no school figure. A site shared only with a Pre-K programme the Master School List does not carry is attributed to the school, and its allotted seats count in the school's numerator.
  2. Enrollment is the school's own October enrollment, counted once, plus the Pre-K seats allotted across its sites, summed.
  3. Capacity is summed across its sites; utilization is enrollment over capacity.

A naive sum or maximum gives a different answer. Summing enrollment multiplies a school by its site count: Walter Saul (ULCS 6040) has four site rows of 416 that sum to 1,664 (NCES counts 417); Overbrook (4020) has three of 441 that sum to 1,323 (NCES counts 440). Taking the largest enrollment gives the rule's answer only after step 1; on the three shared sites above it would take 3,644 for 3850, 542 for 4140 and 1,198 for 7060. The dashboard's own per-school measure, Site Utilization (MAP), follows this rule: its numerator and capacity equal the rule's for all 23 multi-site District schools that get a school figure (a one-off comparison against that measure pulled per ULCS Code from the app).

The loader (schoolFacts in scripts/loaders/philly/_facilities.ts) applies the rule, and three checks guard it, each of which aborts the whole load. Checks (v) and (vi) are Philadelphia additions to the (i)–(iv) validation taxonomy of 05_validation_spec.md:

  • check (v) — on every site, enrollment with allotment equals enrollment plus the Pre-K allotment (285 of 285 sites);
  • check (vi) — a school whose capacity sits on exactly one of its sites reproduces that site's published utilization within 0.005 (220 of 220 schools on a full load; 156 of 156 on the dry run, which runs over every link in the extract because it cannot read the directory);
  • a refusal if any school's own sites disagree on enrollment (0 do).

Check (iv), the taxonomy's computed-value check, recomputes every published site utilization as (enrollment + Pre-K allotment) ÷ capacity (§6.1, §8). It compares figures site by site and cannot see a roll-up error; check (vi) compares at school grain. Against the NCES 2024 enrollment count, joined through the Master School List as in §6.2, the loaded school figures correlate at r 0.9993, median difference 2 students, over 222 schools; on the 23 multi-site schools alone the median difference is 1 student (one-off measurements against the database).

Across the 223 schools with a school figure (§7), the median utilization is 68.1%; 51 schools are under 50% and 26 over 100%, with a maximum of 198.6%; summed enrollment over summed capacity is 68.6% (a one-off query against the loaded values). This is a school universe; the §4.1 distribution is over 215 district sites.

Three school figures may have a short denominator. Each school has a linked site with no or zero published capacity that holds classroom buildings:

  • Olney Elementary (7400): S314 holds two auxiliary classroom buildings, W403 (11,000 sq ft) and W442 (area not published), and publishes no site capacity. School figure 99.4%.
  • The Workshop School (8560): S264, Daroff, is W338, 85,080 sq ft, with a building capacity of 795 on the app's 2017 basis (§6.5) and a blank site capacity. School figure 75.4%.
  • Walter Saul (6040): S212, the farm, holds classroom buildings W268 (1,200 sq ft) and W273 (1,900 sq ft) and publishes a site capacity of 0. School figure 50.7%.

If those buildings hold seats, the three figures overstate utilization. They are flagged here, not adjusted: the dashboard publishes no positive capacity for those sites.

3.3 There are three grains, and mixing them corrupts the data

Capacity, enrollment and utilization are per site. Floor area, year built, condition index, use type and the older capacity figure (§6.5) are per building — and 47 of the 285 sites with a building hold more than one. John Bartram High (ULCS 1010) has a Garage (16,000 sq ft) and a Main Classroom Building (270,000) on its site and a Fieldhouse on a separate athletic-complex site; the 8,996 sq ft Bartram Annex sits on a site of its own and hosts a different programme (ULCS 3810).

Adding a building dimension to the site cube silently changes its grain: a Bartram row then reads building_name = "Bartram, John Site 1 - Garage 1", gross_sqft = 16000 as if that were the school. Pulling Only([gross_square_footage]) at site grain is the mirror error — Qlik returns null when a site has more than one building, so a statistic on it silently runs on the simplest sites only (§6.3).

The extract therefore writes two tables, sdp_facilities_sites.csv (428 rows) and sdp_facilities_buildings.csv (502 rows), joined on site_id. Both row counts are larger than the thing they count: the sites cube repeats a site once per programme (428 rows → 328 sites) and the buildings cube does the same one grain down (502 rows → 399 distinct buildings, 94 ids repeating up to four times; 362 of them sit on 285 sites, and the other 37 ids carry nothing but the id). Dedupe both before counting or summing anything.

The building cube repeats because ULCS Code is a field of the app's Buildings table, which holds one record per building per programme. BLDGCAPACITY sits in a separate table keyed by building, so the app's own Sum([BLDGCAPACITY]) per site counts each building once; gross_square_footage sits in the per-programme table, so even the app's own Sum of it counts a two-programme building twice. Summing rows rather than buildings would inflate 79 of the 236 sites carrying a BLDGCAPACITY and 77 of the 213 sites in the floor-area comparison (§6.3). The committed provenance record (data/cities/philly/sources/sdp_facilities_dashboard/2026-09-22/provenance.json) says in its grain note that the tables join on site_name; they join on site_id, because a site name is not unique (§1).

4. What the data says

4.1 Distribution, SY2024-25

Counted on distinct sites, not on (site, program) rows — a site hosting a school plus its Pre-K program publishes the same figures twice, and counting rows would weight it double:

universenp10p25medianp75p90<50%<33%>100%Σ/Σ
District sites215375167.18510624.7%7.9%13.5%68.4%

By level:

levelnmedian<50%>100%Σ/Σ
Elementary4672.26.5%21.7%78.0%
Elementary-Middle (K-8)10665.527.4%9.4%68.2%
Middle1245.850.0%16.7%57.1%
High4664.828.3%10.9%65.9%

Two further levels, Middle-High (n = 4) and Elementary-Middle-High (n = 1), are left out of this table as too small to summarize; they are in the packet's distribution.by_school_level and on the page, and bring the rows to 215.

The emptiness is concentrated in the middle grades. Straight elementary schools are the fullest group and a fifth are over capacity; standalone middle schools are the emptiest group in the district — half of the twelve are under 50% — and K-8 and high schools sit between. Twelve is a small group, so the middle-school figure is indicative rather than settled.

Three split campuses are counted as six sites. Bache-Martin (2210: S016 and S148), Bayard Taylor (7440: S234 and S278) and Hancock (8770: S100 and S130) each have classroom capacity on two sites of their own. SDP publishes a utilization for each site, and because the dashboard repeats a school's whole enrollment on every one of its sites (§3.2), each of those six figures divides the whole school by one site's capacity: Hancock reads 194.6% on S100 and 256.4% on S130, against 110.6% for the school. The figures here keep SDP's published per-site basis, so the enrollment total counts 1,871 students twice. Merged into one unit per school (enrollment once, Pre-K seats and capacity summed):

universenmedian<50%<33%>100%Σ/Σ<66% Close lift
District sites, as published21567.124.7%7.9%13.5%68.4%7.47× on 104 sites (p = 0.0012)
Split campuses merged21266.625.0%8.0%11.8%67.4%7.13× on 105 units (p = 0.0015)

All six sites are recommended Modernize, so no Close count changes. The packet carries this as distribution.split_campus_sensitivity.

4.2 Against the other two cities

nmedian<50%<⅓>100%overallvintage
Philadelphia (district sites, 2024-25)21567.1%24.7%7.9%13.5%68.4%1 snapshot
Miami-Dade (district-run regular schools, 2024-25)31666.7%21.2%4.7%7.9% (≥100)69.7%1 snapshot + FISH LOS series
NYC (schools, 2024-25)1,70280%13.3%3.6%26.2%83.1%12 school years; latest 2025-26: 78%, 15.5%

All three rows are SY2024-25, read from the three committed packets (the Philadelphia packet's cross_city block). The under-a-third cuts are <33 (Philadelphia), <33.33 (Miami-Dade) and <33.3 on whole percents (NYC); Miami-Dade publishes only ≥100 for the top cut. Philadelphia's per-site basis counts three split campuses twice (§4.1).

Miami-Dade's terms are defined in its own doc (docs/cities/miami/06_enrollment_capacity_closures.md). FISH is the Florida Inventory of School Houses, the state's facility inventory; Miami-Dade's denominator is its permanent capacity. The FISH LOS series is FLDOE's annual FISH Level of Service report per district, fetched for 2022-23 to 2024-25.

Philadelphia has the largest under-50% tail of the three — a quarter of its district sites, 3.5 points above Miami-Dade and 11.4 above NYC — and more than twice NYC's share under a third (7.9% against 3.6%). Its median sits with Miami-Dade's (67.1% against 66.7%), about 13 points below NYC's (80%).

Three caveats apply wherever this comparison appears:

  1. Different denominators. Philadelphia's capacity is SDP site capacity, Miami's is permanent FISH capacity, NYC's is SCA target capacity. NYC's own two published capacity definitions differ enough to move its under-50% share by 2.4 points (NYC doc §4.4, on 2013-14), about a third of the 7.9-point NYC–Miami-Dade gap in 2024-25 (13.3% against 21.2%).
  2. All three numerators include Pre-K, on different bases. Philadelphia adds the Pre-K seats allotted to a site's early-childhood programmes to its K-12 October enrollment; NYC's Blue Book enrollment is the audited register including 3-K and Pre-K students; Miami-Dade's is the October (FLDOE Survey 2) headcount, PK-12. Philadelphia counts Pre-K seats where NYC and Miami-Dade count Pre-K children. The denominators differ on Pre-K as well: NYC's target capacity gives each Pre-K room up to 18 seats (36 half-day) — the lower of that and the room's area ÷ 35 sq ft — with the PK Cap column restating that share rather than adding to it; Philadelphia's site capacity is assigned to Pre-K space too (its five Pre-K-only sites carry 60–221 seats); whether Miami-Dade's FISH capacity counts Pre-K stations is not established. Pre-K is 2.2% of Philadelphia's district-site numerator (215 sites; §6.1 shows the published figure is built this way); in NYC, 3-K and Pre-K together are 3.5% of the school-grain numerator (Pre-K alone 2.5%; 1,702 schools, 2024-25).
  3. Different universes. Philadelphia's figure is district sites; Miami's is 316 district-run regular schools, including K-8, 6-12 and K-12; NYC's is the 1,702 organizations in DOE buildings whose org code resolves to exactly one DBN, co-located charters (about 110) and District 75 included.

4.3 Low utilization tracks the district's own closure recommendation

This is the analysis Miami's plan designs and NYC cannot run: the district published its recommendation for every site, so the reference already exists.

On the 215 distinct district sites, the base rate of a Close recommendation is 7.4%:

cut pointflaggedclose raterestliftFisher p
utilization < 33%1729.4%5.6%5.29×0.0043
utilization < 50%5318.9%3.7%5.09×0.00093
utilization < 66%10413.5%1.8%7.47×0.0012
utilization < 75%14010.7%1.3%8.04×0.013

Not one site at or above 100% utilization is recommended for closure. Size separates more strongly than utilization: on the same 215 sites, enrollment (with allotted Pre-K seats) under 300 carries a 10.72× lift — 12 of 47 sites (25.5%) recommended for closure against 4 of 168 (2.4%), p = 3.2 × 10⁻⁶ — so the same caveat as NYC applies: utilization adds information, but school size is the stronger single predictor.

Ablation. Within the 91 sites under 400 students, <50% utilization still carries a 3.14× lift (9 of 38 against 4 of 53, p = 0.037); within the 124 sites of 400+, no site at or above 66% is recommended for closure, so <66% carries an unbounded lift (3 of 38, 7.9%, against 0 of 86, p = 0.027). Utilization is not a restatement of size — but as in both other cities, size is the stronger single predictor (enrollment < 300: 10.72×). The packet derives these counts on distinct sites (decision_lift.size, decision_lift.ablation); a site hosting two district schools enters once.

What this is and is not. Both sides come from the same publication, so this measures how closely the district's decision tracks its own stated criterion — not whether an outside observer could have predicted the list. That is a weaker claim than Miami's (where the closure list and the enrollment predictor come from different publishers), and the page says so. It is still the only one of the three cities where the question can be asked against a published decision list.

5. Where Philadelphia sits against NYC and Miami

PhiladelphiaNYCMiami-Dade
utilization published?yes, per siteyes, per school and buildingno — must be derived, three numerators
history1 snapshot12 school years, 2013-14 → 2025-26 (2020-21 never published)FISH LOS 2022-23→2024-25 + GIS + ABC book
machine-readable?Qlik Engine API, anonymousplain Socrata JSON/CSVPDF tables, two-parser gate
join to our schoolsfree (ULCS Code is the PK)org-code rule; 4 ambiguous codes skipped; 99.6–99.7%MSID/CCD/GIS join on the 4-digit number
closure/decision listpublished, machine-readablenone publichand-built from board PDFs
building conditionFCI, replacement value, per systempartial (room inventory)not verified
co-location37% of district sites42% of buildingsrare

In the Miami-Dade column, the ABC book is M-DCPS's Attendance Zone Recommendations document, which gives capacity blocks for the schools it targets; MSID is FLDOE's Master School ID directory; GIS is the county's Public School layer (Miami-Dade doc §1 and §7).

No city leads on every row. NYC has the history and the clean file; Philadelphia has the decision list, the condition data and the free join; Miami-Dade has a closure process of its own: nine schools closed or consolidated for 2026-27, and 27 more flagged for reassessment or review in August 2026, with a decision targeted for December 2026 (Miami-Dade doc §0).

6. Verification against other sources

6.1 Computed values

The published Site Utilization equals (K-12 enrollment + allotted Pre-K seats) ÷ site capacity on all 337 (site, programme) rows carrying a utilization (245 distinct sites), maximum absolute difference 0.0000. Enrollment alone matches 192 rows (57%) — exactly the 192 whose allotment is zero, so that is the share of rows without Pre-K, not independent evidence. The dashboard's stated definition — "the total number of students enrolled (and allotted seats for PreK) at the school sites divided by school site capacity" — is the one it computes. Pre-K enters as allotted seats, not enrolled children.

6.2 Enrollment, against an independent publisher

SDP → PDE → NCES is a different chain from the dashboard. Joined through the ULCS Code → NCES Code crosswalk in SDP's own Master School List (the source this repo already registers as sdp_master_school_list_2024-25), not by name:

  • dashboard schools 345, joined to CCD 241
  • median absolute difference 2 students, 68% within 2% of school size, 76% within 5%, r = 0.9718

The dashboard's October 1 count and CCD's membership are not the same count date, so exact agreement is not expected. This is the only independent publisher available for Philadelphia, and it confirms the numerator at magnitude and coverage. The probe's roll-up takes the largest enrollment across all of a school's sites, shared ones included, so a school on a shared site carries the whole site's total here. The loader's school figures set those schools aside (§3.2) and correlate with the NCES 2024 count at r 0.9993, median difference 2 students, over 222 schools.

6.3 Capacity, against floor area

Classroom-use building area summed per site, each building counted once (§3.3), against site capacity:

measurenmedian sq ft per seatR² of capacity on area
Philadelphiagross area of classroom-use buildings, each counted once213122.0 (IQR 99.7–147.5)0.721
NYCinstructional room area, Pre-K rooms excluded as registered¹1,33132.6 (IQR 28.8–36.6)0.922

¹ With Pre-K full- and half-day rooms added (2025-26): median 34.2 sq ft per seat, R² 0.918 (NYC doc §5.2).

Each building has to be counted once. The building extract and the app's own Sum([gross_square_footage]) repeat a building once per programme on its site, which would inflate 77 of these 213 sites; Only([gross_square_footage]) at site grain returns null wherever a site has more than one building, which would leave only the 182 simplest.

The two cities' levels are not comparable — Philadelphia publishes gross building area (structure, circulation, gyms, offices and all), NYC's comes from a room-by-room inventory filtered to teaching space. Only the R² compares, and Philadelphia's capacity tracks floor area less tightly: 0.721 against NYC's 0.922, and some of that may reflect the coarser numerator — gross area carries gyms, corridors and offices that NYC's room filter removes. It is a within-publisher consistency check, not an independent confirmation of the capacity figure.

6.4 What is not verified

  • No second publisher states Philadelphia school capacity. PDE does not publish building capacity at school level; NCES does not publish capacity at all. §6.3 is a within-publisher consistency check (R² 0.721), not an independent one. The app's older, 2017-labelled per-building figure (§6.5) is a different year's capacity, not a check on the 2024-25 one.
  • No historical capacity in a usable form, so no trend can be verified (§2). The 2017-labelled figure and its SY2016-17 enrollment count (§6.5) are unmeasured as a vintage.
  • The figures shown under the app's School Year label 2016-17 are the 2024-25 figures under a second label — read without this check, the dashboard shows a flat trend that does not exist. (That is the School Year field; the app's separate "OLD" measures are a different thing, §6.5.)

6.5 An older, 2017-labelled capacity figure

The app also carries BLDGCAPACITY, a capacity per building, in its BuildingsOld table. Its master measure "Current Capacity (OLD)" is sum(BLDGCAPACITY); the app labels its OLD view 2017 and pairs the figure, in "Site Utilization (OLD)", with a SY2016-17 enrollment count. The latest year built on the table's 268 capacity records is 2011. The app documents nothing more about how it was measured, and no figure in this document uses it.

The extract repeats a building once per programme on its site (§3.3), so a per-site sum has to count each building once, as the app's own Sum([BLDGCAPACITY]) does. On the 207 district sites carrying both figures:

site capacityBLDGCAPACITY, each building once
seats169,551164,931 (2.7% fewer)
2024-25 utilization, median67.1%61.8%
share under 50%25.1%31.4%

Site by site the two differ: building over site capacity runs 0.754 at p10, 0.989 at the median and 1.301 at p90, and 56 of the 207 differ by more than a quarter (outside 0.8–1.25×). The utilization rows divide 2024-25 enrollment by a 2017-labelled capacity, which pairs two different years. A further 19 BLDGCAPACITY records (11,536 seats) are linked to no current building.

7. Metrics and schema

Two tables hold the site grain (§3.3), the figures a school page lists beside the school's own:

  • PhillySite (philly_sites, 328 sites) — keyed on id_site_eos (S238), carrying the site's name, capacity, Pre-K allotment, enrollment with allotment and utilization as published. These are SITE facts, not school facts.
  • PhillySiteProgram (philly_site_programs) — the join table (siteId, ulcsCode). School and site are many-to-many (§3.1), so a foreign key on either side would drop rows. Links to a ULCS code the Master School List does not carry are skipped; on the 2024-25 extract those are Pre-K programmes.

The rest of the extract stays in the committed CSVs, which the analysis packet reads, and is not stored: the whole building grain (floor area, condition, BLDGCAPACITY; §3.3, §6.3, §6.5), and six site-grain fields that no page reads from the database — sector, school level, K-12 enrollment, the utilization band, and the district's Program Change and Project Recommendation (§4.3). The gap register, docs/capacity-utilization-gaps.md §10, lists them and how to add them.

The metric keys sit in philly_metric_definitions with subdomain Facilities and direction NEUTRAL; the subdomain keeps them out of the percentile batch, so they carry no percentile. There are four, all at school grain, with values for the 223 directory schools that get a school figure (39 on a shared site get none), all SY2024-25:

metric_keyunitvaluesnotes
site_capacityCOUNT223 schoolssummed across a school's own sites (§3.2)
site_enrollmentCOUNT223 schoolsOctober 1 enrollment counted once, plus the Pre-K seats allotted across the school's sites (§3.2)
prek_allotmentCOUNT223 schoolsallotted seats, inside the utilization numerator; summed across a school's sites
utilization_pctPERCENT223 schoolssite_enrollment ÷ site_capacity; published per site as (enrollment + Pre-K) ÷ capacity

The plan's other three scoring dimensions (§1) are not loaded.

8. What is built

PieceWhat exists
Sourcesdp_facilities_dashboard in sources.yaml, with the app id, the field list and the User-Agent gotcha. python -m pipeline_philly.fetch_facilities (make philly-facilities-fetch) pulls the extract and commits it as two CSVs plus provenance
Tablesphilly_sites, philly_site_programs (§7), created by scripts/sql/capacity-schema.sql
Loaderscripts/loaders/philly/facilities.ts (make philly-facilities-load), which reads the committed site CSV, never the network, and applies the §3.2 roll-up; its hash pin covers both CSVs
Load checks(iv) every published utilization recomputed, 245 of 245 sites within 0.005 (max difference 0.0000); (v) and (vi) and the refusal on conflicting enrollments (§3.2). Each aborts the load; a failure means the extract changed shape. The loader also refuses a CSV pair whose hash differs from the one pinned beside its school year, since the dashboard publishes no year of its own, and a reload removes the school figures and site links that the new extract no longer produces
Independent checkenrollment against NCES CCD (§6.2), in the probe
Packet and pagesscripts/analysis/philly-capacity-packet.ts → data/analysis/philly-enrollment-capacity.json, rendered at /philly/methodology/analysis/enrollment-capacity; each school page shows the school's figure, or its sites' figures where it shares a site

Philadelphia is the cheapest of the three cities to load: the join is free and the computed-value check holds on every site, and the decision list the packet reads comes structured in the same extract. The cost is that the page shows a cross-section only, until a historical capacity vintage turns up (§9).

9. Open items and caveats

  • One vintage. Until a historical capacity source turns up, no Philadelphia trend figure is possible. Candidates not yet checked: the app's own 2017-labelled BLDGCAPACITY and SY2016-17 enrollment count (§6.5; unmeasured — check the enrollment against CCD 2016-17 first), the 2017 Comprehensive School Planning Review, the longitudinal school list, and PICA / City Controller reports. Checked and ruled out: SDP's Facilities Condition Assessment archive, which is condition only (gaps §4).
  • Qlik as the only access path. There is no CSV, no Socrata dataset, no file on the SDP open-data CDN — the SDP open-data catalog has no facilities category at all. The fetcher depends on an undocumented engine API and an app id read out of a JavaScript bundle. Both could change without notice, so the pulled extract is committed, and the fetcher refuses to write a short table rather than silently serve a truncated one.
  • Anonymous access is a courtesy, not a contract. It works today; the 403-without-User-Agent behaviour shows there is already a filter in front of it.
  • Charter coverage is 22 sites. Any district-wide utilization figure is a district-sites figure.
  • Three school figures may have a short denominator (§3.2): 7400, 8560 and 6040 hold classroom buildings on sites with no positive published capacity. They are flagged in this document, not adjusted, and not flagged on the school pages.
  • Pre-K is in the numerator as allotted seats (§4.2, §6.1). NYC and Miami-Dade count Pre-K too, as enrolled children (NYC also 3-K), so the three numerators are not on one basis; name it wherever the cities appear together.
  • The decision-list analysis is same-publisher (§4.3) and is labelled as measuring adherence to a stated criterion, not out-of-sample prediction.