Skip to content

Calibration boxes log

Ground-truth calibration boxes

OPEN, live log (as of 2026-08-11)

This is the running log of calibration-area mapping, and it lags the current state. It stops at 21 calibration areas; there are now 23, all Rule-1 complete -- see Calibration quadrats and results/calibration_quadrats.csv for current per-area status. The atlas totals and the est_mwp_rc figure quoted here were superseded several times after they were written; current headline numbers are on Capacity.

Small, hand-verified areas where all rooftop PV has been mapped from high-resolution imagery (not just OSM's usual partial coverage) -- fetched fresh via earthpv overpass-labels --bbox rather than the Overture snapshot, since a just-finished mapping pass won't be in Overture for weeks/months. Unlike the country-wide mapped_frac used by capacity_calibration/configs/calibration/<aoi>_candidate_precision.yaml (which only measures precision -- is an existing candidate real -- because it can only check candidates that exist), a fully-mapped box is small enough to check recall too: every real installation is known, so a candidate-free patch of the box is a genuine miss, not just "unmapped."

Box 1 -- Lahore, 1km x 1km around (31.4633307, 74.4045096) -- 2026-07-22

Superseded 2026-08-05

This 1 km² square was replaced by a hand-drawn 6.61 km² boundary that fully contains it (lahore_calib_6p61km2) -- see Box 1 replaced below. Everything in this entry and the two that follow it describes the retired square, whose files are at data/labels/retired/. The counts below (8 installations) are also the original stale snapshot, corrected further down the same day.

bbox 74.399244,31.458839,74.409775,31.467822 (data/labels/retired/lahore_calib_1km_overpass_solar.parquet).

Ground truth: 8 rooftop installations, all clustered in one corner of the box (74.409-74.410, 31.467-31.468), 314-577 m2 each -- reads as one small residential/ commercial development where each unit got its own rooftop array, not 8 independent sites.

Our candidates (pk16085) in/near the box: exactly 1, a 2702 m2 / confidence 0.49 / rank_score 0.39 rooftop candidate ~335m from the box center. Checked against a widened 2km search: the closest any candidate gets to any of the 8 real installations is 877m -- not a geometry-offset artifact, a genuine miss.

Result: 0/8 recall, 1 likely false positive. - The model missed all 8 real installations. Each is well below this project's ≥1000 m2 recall-first design target (README/CLAUDE.md) -- consistent with, and now a direct empirical confirmation of, the known operating point rather than a new bug. Doesn't mean small arrays are unrecoverable, just that they're outside what this checkpoint was tuned to prioritize. - The one candidate found in the box is nowhere near any of the 8 confirmed installations. Since this box's OSM mapping is asserted complete, a real PV array at that location would already be in the fetch -- it isn't, so this candidate is very likely a false positive. One useful confirmed-FP data point for the 1k-5k bucket (currently p_real=0.149 in the interim-mapped-only calibration table).

Update (2026-07-23): this box now feeds the calibration pipeline, not just this doc. earthpv calibrate-candidates gained --calibration-box <parquet> (repeatable): it pools a box's per-bin (installations, matched-by-any-candidate) counts directly into the same recall_reference denominator the country snapshot already builds, before the existing Beta-posterior machinery runs -- so a quadrat's TRUE-recall evidence (every installation known, unlike the snapshot) moves the recall estimate and its credible interval by exactly as much as its sample size supports, automatically, no separate code path. Re-ran for Pakistan with this box pooled in:

earthpv calibrate-candidates --aoi pakistan --pred-dir data/predictions_pk16085 \
    --glint-sample data/glint/pakistan_candidate_glint_sample.csv \
    --calibration-box data/labels/lahore_calib_1km_overpass_solar.parquet
bin recall before recall after 90% CI before 90% CI after
100-500 m² 96/164 = 58.5% 96/170 = 56.5% [52.0, 64.5] [50.1, 62.4]
500-1k m² 270/367 = 73.6% 270/369 = 73.2% [69.7, 77.2] [69.3, 76.8]

National est_mwp_rc moved +0.001% -- negligible, and correctly so: n=8 against a ~2,800-installation country snapshot genuinely can't move a country-scale estimate much, exactly as this doc originally predicted. What changed is that the pipeline now HAS a mechanism ready to matter as more of the mapping protocol's planned 25-35 quadrats land (docs/calibration-mapping-protocol.md) -- each new box is one more --calibration-box flag, no code changes needed. The precision side (mapped_frac/p_real) is genuinely unaffected here (no candidate sits near these 8 features, confirmed above), by design: --calibration-box only ever feeds recall, the same separation capacity_calibration.py already draws between precision and recall evidence.

One qualitative flag worth carrying forward even though n=8 can't prove it: this box's TRUE recall (0/8) sits well below the country-snapshot recall in the same size bins (58-74%) -- consistent with the snapshot itself being an incomplete, and possibly biased, sample of real installations (a mapper is more likely to have mapped exactly the installations a model also finds easiest to detect). More quadrats will tell us whether that gap is real or n=8 noise.

Direct density-method validation against Box 1 (2026-07-23)

Location confirmed by reverse-geocode: DHA Phase V, Lahore -- exactly the mapping protocol's stratum 1 ("affluent planned housing... highest rooftop-PV adoption"). Boundary saved as data/labels/lahore_calib_1km_boundary.geojson.

Beyond the candidate-level recall check above, this checks density.py's actual per-pixel and per-building output against the same 8 installations (true total 3,145.8 m²), pulling the raw probability raster (cell 0135_0077) and buildings.geoparquet directly -- the same artifacts density ships, not a re-derived approximation.

At the 8 true installation footprints, the model's raw pixel probability is exactly 0.000 -- at every one, not just below threshold. This is a stronger and more concerning finding than "recall is low": it means pv_area_exp (the metric whose entire premise is integrating sub-threshold signal) has nothing to integrate here -- these installations aren't faintly visible and miscalibrated, they produce no model activation at all. All three building-level metrics are therefore zero at every one of the 8 true locations: pv_area_det = pv_area_cal = pv_area_exp = 0.

Meanwhile the box's only signal is a false positive, elsewhere. One candidate (2,701.9 m², rooftop, confidence 0.49) sits 877 m from the nearest real installation -- density.py correctly attributes it to 9 nearby (wrong) buildings:

metric total in the false-positive cluster true total (8 real installations)
pv_area_det 2,281.0 m² 3,145.8 m²
pv_area_cal 340.6 m² --
pv_area_exp 1,241.4 m² --

The trap this exposes: naively summing "PV area estimated in this box" gives ~1,200-2,300 m² -- deceptively close to the true 3,145.8 m², entirely by coincidence. Every square metre of it is misattributed; the aggregate number would look roughly right while being 100% spatially wrong. A cell/region-aggregate sanity check (as the density stage's grid/region layers necessarily are) cannot catch this; only a fully-mapped, building-level ground truth like this box can.

Implication for est_mwp_rc (recall-corrected estimator): the recall correction (dividing calibrated candidate area by a size-bin's country-average recall, ~56-73% for 100-1k m²) is a population-level correction -- it is only unbiased in expectation across many neighbourhoods whose true recall averages out to the country figure. This box is a direct counterexample at the neighbourhood scale: its true recall is 0%, not 56-73%, so est_mwp_rc for a query scoped to just this box (or a similar single neighbourhood) would still be far too low -- recall-correction repairs the national total, it does not make any single building- or neighbourhood-level number trustworthy. Worth stating explicitly wherever est_mwp_rc is surfaced at sub-national granularity.

Net read on this one box: 0/8 recall, confirmed at both the candidate-polygon level (above) and now the raw-probability level (this section) -- the model is currently blind, not just imprecise, in the exact stratum (affluent planned housing, 300-600 m² rooftop arrays) the mapping protocol calls the highest-adoption one. That's the single most useful thing a first calibration box could have told us.

Correction (2026-07-23, later same day): the 8-feature snapshot was stale AND flawed

Prompted by a direct "have you pulled the newest labels for this?" check -- good catch, because the answer was no, and it mattered. Re-fetching this exact bbox live via earthpv overpass-labels --bbox 74.399244,31.458839,74.409775,31.467822 --iso3 PAK returned 1,021 features (52,188.9 m² true PV area), not 8 (3,145.8 m²). Comparing geometries: 2 of the original 8 (both exactly 314 m², same OSM way IDs) are unchanged; the other 6 have been replaced by clusters of much smaller polygons 3-20 m away (areas now 8-95 m² instead of 314-577 m²) -- i.e. the original mapping pass traced whole roofs for those 6, not individual panels, exactly the "Common failure mode" the protocol's Rule 1 warns about, and a second, more careful pass has since fixed it. The section above and the earlier calibration-table update both used the stale, partly-wrong file -- corrected below. New size distribution: 882 installations <100 m², 138 in 100-500 m², 1 in 1k-5k m², none larger. Everything above this point in the file describes the superseded 8-feature analysis; treat the numbers below as current.

Recall, recomputed on the corrected 1,021-installation ground truth: - Within 100 m of any candidate: 40/1,021 (3.9%) -- all 40 attributable to the SAME one candidate (2,701.9 m², rooftop, confidence 0.49), which sits in/next to a dense cluster of small installations, not scattered across the box. - Literal polygon intersection: only 2/1,021 (0.2%). - Raw pixel probability at the true footprints: nonzero for exactly 2/1,021 (the same 2 the candidate literally overlaps) -- 1,019/1,021 (99.8%) installations still show exactly 0.000 probability. The core finding survives correction, just more starkly: near-total blindness, not literal-zero-of-everything. pv_area_exp recovers 571.7 of 52,188.9 m² true (1.1%) -- nonzero this time, but still capturing almost none of the true signal. - This reverses the earlier "false positive, 877 m from nearest install" read on the one candidate: against the corrected ground truth its nearest real installation is 0.0 m away (it literally overlaps one). The candidate is better read as a coarse, unresolved detection of a genuine dense small-array cluster -- the model correctly flagged that something PV-related is happening there, it just can't resolve the ~40 individual panels into separate polygons. Different lesson than "spurious FP elsewhere in the box": this is a resolution failure on a real signal, not hallucination.

Calibration table impact (--calibration-box, corrected file) -- now genuinely consequential, unlike the first (stale) pooling:

bin recall before any box after stale 8-feature box after corrected 1,021-feature box
<100 m² 0/142 (0%) 0/142 (0%, box had none this size) 34/1,024 (3.3%)
100-500 m² 96/164 (58.5%) 96/170 (56.5%) 101/302 (33.4%)
1k-5k m² 1152/1296 1152/1296 1153/1297 (negligible)

The <100 and 100-500 bins moved for real this time -- n=882 and n=138 from one quadrat are not negligible next to the country snapshot's own n=142/n=164 there. National est_mwp_rc: 18,309.5 → 18,312.0 MWp (+0.014%, still small -- these bins hold little of the country's total capacity -- but the interval widened more meaningfully, 90% CI [17,065, 21,318] → [17,021, 21,400]).

Absolute totals here are superseded

The est_mwp_rc figures above are from the pre-2026-07-26 conversion, which applied the rooftop kWp/m² constant to ground-mount site area and put no bound on candidate polygon size. The current national total is roughly a third of the number quoted here (see Capacity density). The relative effect this section measures -- which is what it is about -- is unaffected: the bins in question still hold little of the country's capacity, and the interval still widens.

Lesson for the mapping protocol itself: a "done" quadrat should be spot-checked against a fresh Overpass pull before it feeds any calibration, even hours after completion -- mapping is iterative (the two-mapper completeness pass is designed to add exactly this kind of correction), and a calibration mechanism now exists that will silently encode whatever was cached at pull time as ground truth.


Box 2 -- Faisalabad, 1km x 1km around (31.4976169, 73.0523711) -- 2026-07-24

bbox 73.047103,31.493125,73.057639,31.502108 (data/labels/faisalabad_calib_1km_overpass_solar.parquet, boundary data/labels/faisalabad_calib_1km_boundary.geojson). Reverse-geocode: Punjab Small Industries Estate, Faisalabad Sadar Tehsil -- the mapping protocol's stratum 6 (industrial zone), which names Faisalabad as an example location directly.

Status: NOT a Rule-1-verified quadrat. This is a live OSM pull, not an exhaustively-mapped completeness pass -- no second-mapper declaration, no imagery-date record. Documented here as a useful interim data point; do not fold into calibrate-candidates --calibration-box until it's been through the same completeness process as Box 1.

Ground truth (as currently mapped): 53 installations, all tagged plant:source=solar (deliberate ground-mount/captive-plant tagging, not an ambiguous-generator fallback), 63,501 m² total. Sequential OSM way IDs (1498181449–1498181511) -- one mapping pass. Size range 193–4,722 m² (median 922 m², mean 1,198 m²) -- squarely in the model's designed ≥500 m² strength zone, unlike Box 1's sub-100 m² residential cluster.

Candidate recall (pk16085): 53/53 (100%) within 100 m, 34/53 (64.2%) literal intersection -- a sharp contrast with Box 1's 0.2%/3.9%. Consistent with size: these installations sit well inside the range the model was tuned for. One thing worth a second look later: several nearby candidates are much larger than any single true installation here (up to 64,997 m² and 58,697 m², vs. a 4,722 m² true max) -- plausibly the model merging a dense cluster of adjacent ground arrays into fewer, larger candidate polygons rather than resolving them individually; not confirmed, just flagged.

Box 3 -- Multan, 1km x 1km around (30.1262242, 71.3829068) -- 2026-07-24

Superseded 2026-08-05

This 1 km² square was replaced by a hand-drawn 3.92 km² boundary that fully contains it (multan_calib_3p92km2) -- see Box 3 replaced below. Files retired to data/labels/retired/. Everything below describes the retired square.

bbox 71.377714,30.121733,71.3881,30.130716 (boundary data/labels/multan_calib_1km_boundary.geojson; no labels parquet -- see below). Reverse-geocode: Multan Industrial Estate, Thati Lal, Multan Sadar Tehsil -- also stratum 6 (industrial). Sits inside the pakistan AOI's val_tiles holdout region (configs/aoi.yaml, the Multan cluster used for the model's own validation split).

Live Overpass pull returned zero generator:source=solar/plant:source=solar features. This is explicitly not usable as a "0 installations, 0 recall" ground-truth point: per Rule 1 of docs/calibration-mapping-protocol.md, a quadrat with no completeness declaration is indistinguishable between "genuinely no PV here" and "nobody has mapped this area in OSM yet" -- treating an unmapped area as a confirmed negative is exactly the failure mode the protocol calls out as worse than leaving a quadrat out entirely. Flagged as an open mapping task (industrial zone, Multan, overlapping the model's own val split -- a high-value quadrat to complete), not a result.

Both boxes' honest status: neither has been through a human high-res-imagery completeness pass. Box 2's plant-tagged, sequential-ID mapping looks like a deliberate, consistent single pass (a good sign, same pattern the corrected Lahore mapping eventually showed) but that is circumstantial, not a substitute for the protocol's actual two-mapper sign-off. Treat both as candidates for the mapping team's queue, not finished quadrats, until that happens.

Box 4 -- Sundar Industrial Estate, Lahore, 1km x 1km around (31.2861646, 74.1720942) -- 2026-07-24

Superseded 2026-08-05

This square was replaced by a hand-drawn 4.34 km² boundary that fully contains it (sundar_calib_4p34km2); files retired to data/labels/retired/. See the 2026-08-05 batch. Everything below describes the retired square.

bbox 74.166838,31.281673,74.17735,31.290656 (data/labels/sundar_calib_1km_overpass_solar.parquet, boundary data/labels/sundar_calib_1km_boundary.geojson). Reverse-geocode: Sundar Industrial Estate, Raiwind Tehsil, Lahore District -- stratum 6 (industrial) again, a third industrial box alongside Faisalabad and Multan.

Status: NOT a Rule-1-verified quadrat (same caveat as Boxes ⅔ -- live pull only, no completeness declaration).

Ground truth (as currently mapped): 38 installations, 72,561 m² total, mixed tagging (20 plant/18 generator, 23 ground/15 rooftop by placement) and non-sequential OSM way IDs spanning several ID ranges -- unlike Faisalabad's single contiguous pass, this looks like several separate mapping sessions over time. Size range 76.7–6,579.7 m² (median 1,658 m²) -- again squarely in the model's designed strength zone.

Candidate recall (pk16085): 33/38 (86.8%) within 100 m, 26/38 (68.4%) literal intersection. The 5 misses are all at or below 999 m² (76.7, 103.8, 254.4, 722.8, 999.4 m²) -- consistent with the model's known size-dependent recall falloff, not a surprise. A third data point reinforcing the same pattern as Box 2: this model performs reasonably well once installations clear roughly the 1,000 m² mark, regardless of industrial vs. residential context; the failure mode found in Box 1 is specifically about very small (<500 m²) arrays, not industrial siting per se.


Box 5 -- SITE Karachi, 1km x 1km around (24.9070005, 66.9941461) -- 2026-07-24

bbox 66.989194,24.902509,66.999098,24.911492 (data/labels/site_karachi_calib_1km_overpass_solar.parquet, boundary data/labels/site_karachi_calib_1km_boundary.geojson). Reverse-geocode: Sindh Industrial Trading Estate (SITE), Rashid Abad, SITE Town, Kemari District, Karachi -- stratum 6 (industrial), and the mapping protocol's explicit Karachi example. First non-Punjab box.

Ground truth (as currently mapped): 67 installations (53 rooftop/14 ground), 115,843 m² total, median 1,110 m².

Candidate recall (pk16085): 67/67 (100%) within 100 m, 60/67 (89.6%) literal intersection.


Rule-1-complete quadrats (owner-mapped), 2026-07-29

Three quadrats added directly as Rule-1 complete, following the karachi_coast_calib_700m precedent: mapped and declared complete by the repository owner rather than pulled live and left as an open task like Boxes 2-5 above. As with karachi_coast_calib_700m, "Rule-1 complete" here means the mapper's own completeness declaration per docs/calibration-mapping-protocol.md; there is no separately recorded independent second-mapper sweep for any of the four owner-mapped boxes, karachi_coast included. Each boundary was recorded before this doc entry was written, per the protocol's "record the rectangle... before mapping starts."

Box 6 -- Sialkot Old City, 1km x 1km around (32.503855, 74.5422037) -- 2026-07-29

bbox 74.536883,32.499346,74.547524,32.508364 (data/labels/sialkot_calib_1km_overpass_solar.parquet, boundary data/labels/sialkot_calib_1km_boundary.geojson). Reverse-geocode: Puran Nagar / Old City, Sialkot Tehsil, Sialkot District, Punjab -- stratum 2 (dense older urban / informal settlement), the protocol's least-represented stratum among prior boxes.

Ground truth: 182 installations (177 rooftop / 5 ground), 15,055.8 m² total, median 63.7 m², max 646.8 m². 148/182 (81.3%) below 100 m², none at or above 1,000 m² -- a second sub-floor test ground alongside karachi_coast_calib_700m, this time in dense inner-city fabric rather than affluent planned housing.

Box 7 -- Sheikh Maltoon Town, Mardan, 1km x 1km around (34.189388, 72.0253755) -- 2026-07-29

bbox 72.019951,34.18488,72.0308,34.193896 (data/labels/mardan_calib_1km_overpass_solar.parquet, boundary data/labels/mardan_calib_1km_boundary.geojson). Reverse-geocode: Sheikh Maltoon Town, Bypass Road, Mardan Tehsil, Mardan District, Khyber Pakhtunkhwa -- a planned residential scheme in a peri-urban tehsil town, so it sits between the protocol's stratum 1 and stratum 3 examples rather than cleanly inside either. First Khyber Pakhtunkhwa quadrat, and the first outside Punjab/Sindh.

Ground truth: 794 installations, all rooftop, 20,701.2 m² total, median 21.7 m², max 221.5 m². 784/794 (98.7%) below 100 m², none at or above 500 m² -- the smallest median installation size of any registered quadrat, and by far the largest installation count for a 1 km² box so far.

Box 8 -- Quetta City, 1km x 1km around (30.1915156, 67.015288) -- 2026-07-29

bbox 67.010096,30.187005,67.02048,30.196026 (data/labels/quetta_calib_1km_overpass_solar.parquet, boundary data/labels/quetta_calib_1km_boundary.geojson). Reverse-geocode: Brahimzai, Abdul Sattar Road, Quetta City Tehsil, Quetta District, Balochistan -- stratum 5 (arid / bare-land settlement). First Balochistan quadrat -- notable because plausibility.py's ground-mount:rooftop check already flags Balochistan as structurally suspect (see docs/methods/density.md), so a trustworthy rooftop-only ground truth here is high-value independent of the sub-floor question.

Ground truth: 73 installations, all rooftop, 12,238.3 m² total, median 103.9 m², max 1,856.6 m². Wider size spread than Boxes 6/7 (35/73 below 100 m², 3/73 at or above 1,000 m²) -- not purely a sub-floor test ground, unlike the other two new boxes.

Box 9 -- Peshawar, 1km x 1km around (34.0199854, 71.5505752) -- 2026-07-30

bbox 71.545162,34.015478,71.555989,34.024493 (data/labels/peshawar_calib_1km_overpass_solar.parquet, boundary data/labels/peshawar_calib_1km_boundary.geojson, geodesic area 999,999.998 m² by construction -- built as a precise 1 km square via pyproj.Geod.fwd, not drawn by eye). Reverse-geocode against the density stage's own admin polygons: Peshawar district, Khyber Pakhtunkhwa -- the first Peshawar quadrat, and the city was already flagged as a priority target in docs/methods/density.md's sub-400 m² region-suggestion table (one of the top building-density cities nationally, alongside Karachi/Lahore/Faisalabad/ Islamabad, none of which had a Peshawar-specific quadrat yet).

Status: NOT a Rule-1-verified quadrat, same caveat as Boxes 2–5. This is a live Overpass pull at a center coordinate the user supplied, not an exhaustively-mapped completeness pass by a human against high-res imagery -- no mapper name, no second-pass countersign, no imagery-date record. Its boundary and building geometry are exact; its completeness is unverified, so absence of a mapped installation anywhere inside it does NOT mean absence of PV. Usable as an interim data point and as a roofclf training quadrat (the same way Boxes 2–5 already are), not as a source of trustworthy negatives, until it goes through the two-mapper sign-off in docs/calibration-mapping-protocol.md.

Re-pulled twice more, 2026-07-30, same day, after the user added missing OSM labels each time: 290 → 353 (+63) → 360 (+7) installations. Every snapshot preserved, none overwritten: bare peshawar_calib_1km_overpass_solar.parquet (pull 1, 290), dated ..._20260730.parquet (pull 2, 353), ..._20260730_v2.parquet (pull 3, 360, current -- _newest_solar/_newest_overpass_path both pick it, verified). This is the "OSM is iteratively completed, re-pull after mapping" pattern the protocol expects, not a sign this box is done -- still not Rule-1 without a completeness declaration and second-mapper pass; the shrinking increment (+63, then +7) is a reasonable, but not sufficient, signal that the mapping is converging.

Ground truth (current pull): 360 installations, 356 rooftop / 4 ground, 28,955.1 m² total, median 71.5 m², mean 80.4 m², max 555.8 m². 358/360 (99.4%) below the 400 m² detection floor, 265/360 (73.6%) below 100 m² -- the largest single haul of sub-floor installations of any quadrat registered so far (previous densest was Mardan at 794 installations but a larger box; Peshawar's 360-in-1 km² is a higher areal density). packing_density (nn_median_m) measures 15.7 m -- tightly packed, in the same "informal/residential" cluster as Karachi coastal (16.8 m), Quetta (16.8 m; coincidentally close) and Sialkot (18.8 m), not the industrial estates' 44–52 m spacing. Geometries are simple way polygons tagged generator:source=solar/location=roof, consistent with an ordinary building-by-building OSM mapping pass rather than a bulk import.

Box 10 -- Peshawar East, 1km x 1km around (34.0242579, 71.5600512) -- 2026-07-30 -- WITHDRAWN 2026-08-05

Withdrawn 2026-08-05 as wrong -- no longer a quadrat

Removed from the project at the owner's instruction. Its files are retired to data/labels/retired/peshawar_east_calib_1km_* (kept, not deleted: data/ is gitignored and there is no other copy), it is gone from results/calibration_quadrats.csv, the JOSM validation layer and atlas.py::CALIBRATION_BOXES, and roofclf.discover_quadrats no longer finds it. Removal is also what resolved the un-deduplicated overlap described below -- no deduplication code was ever written, so the pre-creation overlap check in scripts/new_calibration_quadrat.py is still the only guard against a repeat. The entry is kept for the record; everything in it is history, not current state.

bbox 71.554638,34.019750,71.565465,34.028766 (data/labels/retired/peshawar_east_calib_1km_overpass_solar.parquet, boundary data/labels/retired/peshawar_east_calib_1km_boundary.geojson, geodesic area 999,999.998 m²). User-suggested center, ~995 m from Box 9's center -- checked for overlap before creation (per the protocol note added to this doc): the two 1 km boxes share a corner, 6.56% of this box's area. Added anyway on the user's confirmation, as adjacent Peshawar coverage rather than a duplicate.

The overlap matters more than its area share suggests. 42 of this box's 131 installations (32.1%) -- nearly a third -- sit inside that 6.56%-of-area shared corner, i.e. the corner is far denser with PV than the rest of either box. Consequence: if peshawar_calib_1km and peshawar_east_calib_1km are both pooled into roofclf training/LOQO without deduplication, those ~42 installations (and their host buildings) are double-counted -- present in both quadrats' building tables, which breaks the leave-one-quadrat-out independence assumption (holding out one no longer removes all of that ground truth from training, since the other still carries the overlap). Not yet deduplicated -- roofclf.building_table/discover_quadrats has no overlap-aware filtering today. Whoever next runs earthpv roof-classifier with both Peshawar quadrats present should either clip one box's buildings to exclude the shared corner, or treat this as a known limitation of the resulting fold statistics for these two quadrats specifically.

Status: NOT Rule-1 verified, same caveat as every other non-owner-mapped box.

Ground truth: 131 installations, 127 rooftop / 4 ground, 7,572.2 m² total, median 44.9 m², 100% below the 400 m² floor -- entirely sub-floor, smaller median than even Box 9. packing_density (nn_median_m) 17.3 m, same tightly-packed cluster. base_rate 3.7% (126/3,382 buildings) -- notably lower than Box 9's 16.5% despite being 995 m away and sharing a corner; this box's own building count (3,382) is also 60% higher than Box 9's (2,111) over a similarly-sized area, so the two boxes are not drawn from the same population despite being adjacent and in the same city -- a useful, concrete illustration of why base_rate must be read per quadrat, never pooled, even at this fine a spatial grain.

Box 11 -- Rahim Yar Khan District, 1km x 1km around (28.4255547, 70.2779961) -- 2026-07-31

bbox 70.2728926,28.4210431,70.2830996,28.4300663 (data/labels/rahim_yar_khan_calib_1km_overpass_solar.parquet, boundary data/labels/rahim_yar_khan_calib_1km_boundary.geojson, geodesic area 999,999.999 m² by construction -- pyproj.Geod.fwd, not drawn by eye). User-supplied center. Checked for overlap against all 10 existing boxes before creation (per the protocol note added after the Peshawar pair): no intersection with any existing quadrat.

Location: Rahim Yar Khan District, Punjab (near Sadiqabad) -- approximate. Identified from the coordinates alone (southern Punjab, close to the Sindh border); the exact settlement is not confirmed against the density stage's admin polygons the way Box 9 was, so treat "Rahim Yar Khan District" as a district-level placement, not a verified town name. stratum in the boundary file is left as "unclassified pending mapper review," same as every other non-owner-mapped box at creation.

Status: NOT a Rule-1-verified quadrat, same caveat as every other live-pull box. A single Overpass pull at a user-supplied center, no completeness declaration, no second-mapper sign-off. Usable as a roofclf training quadrat, not as a source of trustworthy negatives.

Ground truth: 204 installations, 183 rooftop / 21 ground, 19,803.9 m² total, median 35.4 m². 97.1% below the 400 m² detection floor, 88.7% below 100 m² -- solidly in the small/informal-residential regime this project's sub-400 m² work is most short on quadrats for.

Exact base rate resolved 2026-08-04: 10.3% (214 PV buildings / 2,068). The estimate recorded here on 2026-07-31 was an approximate 8.7% (183 rooftop installations against a 2,099-building live VIDA fetch), pending the composite/VIDA join roofclf.building_table performs. That join has now been run, and this box is in results/calibration_quadrats.csv and docs/methods/calibration-quadrats.md's overview table.


Box 12 -- Peshawar West, 1.5km x 1.5km around (33.9905887, 71.4261494) -- 2026-08-04

Superseded 2026-08-05

This square was replaced by a hand-drawn 4.39 km² boundary that fully contains it (peshawar_west_calib_4p39km2); files retired to data/labels/retired/. See the 2026-08-05 batch. Everything below describes the retired square, including its "largest quadrat in the set" claim, which the replacement and the Lahore box both overtook.

bbox 71.418032,33.983827,71.434267,33.997350 (data/labels/retired/peshawar_west_calib_1500m_overpass_solar.parquet, boundary data/labels/retired/peshawar_west_calib_1500m_boundary.geojson, geodesic area 2,249,999.991 m² by construction). User-supplied center. The largest quadrat in the set at 2.25 km² -- the first that is neither 1 km² nor Karachi coastal's 0.49 km², which is exactly why the naming convention is size-agnostic (*_calib_*_boundary.geojson; the stem is _calib_1500m, not _calib_1km).

Created with scripts/new_calibration_quadrat.py, added the same day so this stops being a hand-built artifact: it builds the square via pyproj.Geod.fwd, runs the overlap check before writing anything and refuses to continue on a hit without --allow-overlap, then pulls OSM solar and prints the profile below.

Overlap check: clear against all 12 existing quadrats. Nearest is Box 9 (Peshawar) at 11.95 km centre-to-centre and Box 10 at 12.92 km, so unlike the Peshawar/Peshawar East pair this box adds no deduplication debt. Reverse-geocoded against the density stage's own admin polygons: Peshawar district, Khyber Pakhtunkhwa -- the third Peshawar quadrat.

Status: NOT a Rule-1-verified quadrat, same caveat as every live-pull box. A single Overpass pull at a supplied center, no completeness declaration, no second-mapper sign-off, no imagery-date record. Usable as a roofclf training quadrat, not as a source of trustworthy negatives.

Ground truth: 163 installations, 143 rooftop / 20 ground, 147,502.4 m² total, median 416.8 m², mean 904.9 m², max 10,271.1 m². base_rate 10.8% (415 PV buildings / 3,845), nn_median_m 34.0 m.

This box is unlike the other two Peshawar quadrats, and that is its value. Boxes 9 and 10 are 99.4% and 100% sub-floor with medians of 71.5 and 44.9 m²; this one is 48.5% sub-floor by count but only 7.7% sub-floor by area -- 92.3% of its mapped PV area sits in installations at or above the 400 m² detection floor, 108,174 m² of it in 38 installations of 1,000 m² or more:

size bucket installations mapped area
0-100 m² 36 1,854 m²
100-250 m² 27 4,201 m²
250-400 m² 16 5,367 m²
400-1,000 m² 46 27,906 m²
>= 1,000 m² 38 108,174 m²

So this is the first Peshawar quadrat that can test the segmentation model rather than only the sub-400 m² instruments -- the other two have essentially no in-floor population to score against. Its nn_median_m of 34.0 m also falls in what was an empty band between the "informal/residential" (7-19 m) and "industrial" (44-52 m) clusters, which is why docs/methods/calibration-quadrats.md's packing section no longer describes that split as having nothing in between.

Two things to read carefully. n_pv_buildings (415) exceeds n_installations (163), unlike every previous box -- not an error: building_table flags a footprint by overlap share, and installations this large span several VIDA polygons each. And 28 of the 163 installations (17%) do not land on any VIDA footprint at all, consistent with the 20 ground-mounted ones plus a few unmapped roofs.

A silent Overpass failure mode was measured on this box and is worth knowing. When overpass-api.de returns 504 and _run_query fails over, a mirror can answer with zero elements instead of an error: two consecutive pulls of this exact bbox returned 0, then 167. build_overpass_labels does raise on an empty result, so nothing empty was written, but from a single attempt "this box has no PV" and "the endpoint lied" are indistinguishable. new_calibration_quadrat.py therefore treats an empty response as retryable (--retries, default 4) rather than as truth -- never register a 0-installation quadrat from one attempt.


Box 1 replaced -- Lahore DHA Phase 5, hand-drawn 6.61 km2 -- 2026-08-05

Box 1's 1 km x 1 km square was replaced by a boundary drawn in JOSM and supplied as data/labels/calibration_boundaries/DH5.geojson: lahore_calib_6p61km2, 6.61 km2, 18 vertices, bbox 74.392709,31.450724,74.432030,31.477134. It is the first non-square quadrat in the set and the largest by a wide margin (2.9x Box 12). The retired square and both its Overpass pulls are kept at data/labels/retired/lahore_calib_1km_* -- not deleted, since data/ is gitignored and there is no other copy.

The new boundary fully contains the old one (0.0 m2 of the old square falls outside it), which makes the replacement checkable rather than a matter of trust. Both checks pass:

  • Nothing was lost. All 1,014 installations the old box held are present in the new pull, matched by OSM id -- 0 absent. (The old core's count rose 1,014 -> 1,031 in the new pull, i.e. mapping continued there since 2026-07-25.)
  • The extension is mapped to a comparable standard, so this is an extension and not a dilution with unmapped ground: 1,034 installations/km2 inside the old core against 831/km2 across the added 5.61 km2. A fringe that had merely never been mapped would show a fraction of that.

Profile: 5,688 installations inside the boundary, 13,500 VIDA buildings, base rate 25.4% (was 30.1% on the square), median installation 29.0 m2, 99.0% below the 400 m2 floor, packing distance 6.8 m -- the tightest of any quadrat, and now by far the largest sub-400 m2 ground-truth population in the project (5,631 sub-floor installations against 5,688 for all twelve other quadrats combined). Still NOT Rule-1 complete: no completeness declaration has been made for the added area, so its negatives remain untrustworthy exactly as before.

A second, worse Overpass truncation mode was measured here -- read this before trusting any pull. The Box 12 note above covers an endpoint answering with zero elements; build_overpass_labels raises on empty, so nothing empty is written. This box hit the non-empty version: a mirror returned HTTP 200, valid JSON, no remark, and a partial element list. The first registration pull wrote 68 installations for a box whose correct answer is ~5,700 -- and it would have been accepted as ground truth, in a box known to contain 1,034 mapped installations, if the containment invariant had not been checked. Four consecutive raw queries of the same bbox then returned 5,983 / 5,983 / 5,983 / 72, so the failure is intermittent and a single query is untrustworthy in either direction. Two guards were added:

  • overpass._run_query now rejects any response carrying a top-level remark (Overpass's own "timed out"/"out of memory" signal) and fails over to the next mirror instead of returning a truncated element list as data (OverpassTruncated).
  • new_calibration_quadrat.py cross-checks every pull against confirm_element_count, which takes the maximum over three independent queries of the same bbox -- the max, because truncation only ever loses elements, so the largest answer is the best available lower bound on the truth -- and retries the pull when the written count falls below 98% of it.

Neither guard would have caught this from the count alone had the box been new: what actually caught it was the invariant that a boundary containing a known box cannot hold fewer installations than it. Prefer replacing a quadrat by extension for that reason.


Box 3 replaced -- Multan Industrial Estate, hand-drawn 3.92 km2 -- 2026-08-05

Box 3's 1 km x 1 km square was replaced by a boundary supplied as data/labels/calibration_boundaries/multan_industrial.geojson: multan_calib_3p92km2, 3.92 km2 (3.94x the old box), bbox 71.370492,30.117067,71.392508,30.134698. The retired square and its pulls are kept at data/labels/retired/multan_calib_1km_*.

Containment checked before registering, as with every replacement so far: the new boundary fully contains the old one (0.0 m2 outside it), and all 40 installations the old box held are present in the new pull -- 0 lost. The pull itself was clean on the first attempt (166 features written, 166 confirmed by an independent query, no truncation).

Profile: 164 installations, 3,419 VIDA buildings, base rate 8.1% (was 8.6% on the square), median installation 605.5 m2, 37.8% below the 400 m2 floor (was 26.7%) -- sub-floor share rose because the extension reaches beyond the estate's core large arrays. Packing distance 35.2 m, in the same sparse industrial-estate range as Multan always was. The added 2.92 km2 is mapped at 34 installations/km2 against the old core's 64 -- about half the density, the same signature Sundar showed on 2026-08-05: consistent with extending past an industrial estate into surrounding, less array-dense ground rather than a mapping gap, but the two are not distinguishable without a completeness sweep.

This is the first extension registered after all seventeen quadrats were declared Rule-1 (2026-08-05, see the mapping protocol), which is why Rule-1 was initially withheld here. That declaration was for the boundaries that existed at the time it was made; a boundary the owner has not yet looked at does not inherit it just by sharing a name with one that did. The owner then explicitly declared Rule-1 for the extended area the same day, so multan_calib_3p92km2 now reads rule1_complete: yes in results/calibration_quadrats.csv and status rule1 in atlas.py::CALIBRATION_BOXES, matching the other sixteen. The general rule for the next extension is unchanged: Rule-1 must be re-asserted for new ground, never inferred from a predecessor's.


Visual verification pass (2026-07-24) -- what this is and is NOT

Prompted by a direct request to bring all boxes to Rule-1-verified status. Important scope note, stated plainly: this pass does not achieve that. Rule 1 (docs/calibration-mapping-protocol.md) requires every visible panel traced to polygon precision AND an independent second human mapper's sign-off, with a dated completeness declaration. What follows is a single systematic visual pass by Claude against live Esri World Imagery (fetched via the public ArcGIS World_Imagery/MapServer/export REST endpoint, no API key, capture date not exposed by this endpoint so recorded as "unknown" per the protocol's own allowance), reading each exported image directly. That is real, substantive evidence -- a genuine plausibility/completeness check against imagery, not just trust in existing OSM tags -- but it is one AI pass, not two independent human mappers, and it produces approximate location/plausibility judgments, not precise digitized polygons. None of these boxes should be described as "Rule 1 verified" on this basis. Treat this as a strong prioritization signal for the actual mapping team, not a substitute for their sign-off.

Method: fetched a ~1024x1024–2048x2048 export per box (~0.35–0.5 m/pixel -- enough to resolve individual rooftop panel-grid texture, confirmed by cross-checking specific claimed installations' exact centroids against the image), plus quadrant crops and targeted zooms on the largest claimed features per box.

Findings, box by box:

  • Box 1 (Lahore DHA): Confirmed. A close crop of the known-dense NE corner shows dozens of distinct small dark-panel-grid rooftops scattered through the residential blocks -- visually consistent with the corrected 1,021-installation dataset (already established via the OSM re-pull earlier this session).
  • Box 2 (Faisalabad): Not confirmed -- a real discrepancy. The single largest claimed installation (osm-way/1498181472, 4,722 m², tagged plant:source=solar) is absent from the imagery at its exact stated centroid (31.499789, 73.051969) -- ordinary small rooftops, no large dark array. The general area also doesn't show the kind of obvious large ground-mount arrays the claimed size distribution (median 922 m², all 53 tagged plant) would predict, unlike the unambiguous large arrays visible in Boxes 3-5. Recommendation: do not trust this box's labels without independent re-verification -- possible bad tagging/import, not necessarily "the model is wrong."
  • Box 3 (Multan): Confirmed absent from OSM, confirmed present in reality -- the single most important finding of this pass. All four quadrants show multiple large, unambiguous rooftop solar arrays with clear panel-row texture (one in the SE quadrant alone is easily several thousand m², with crisp visible panel rows). Roughly a dozen-plus plausible installations visible total. This resolves last turn's stated ambiguity definitively: not a solar-free estate, just an unmapped one. Highest-value quadrat for the mapping team to complete next -- real signal is sitting there unmapped, and it sits inside the model's own val_tiles holdout.
  • Box 4 (Sundar): Confirmed. Roughly 10-15 large, clearly grid-textured rooftop arrays visible across the estate; the three largest claimed installations checked against their exact centroids correspond to real visible arrays in the same cluster of buildings.
  • Box 5 (SITE Karachi): Confirmed, most visually dense of all five boxes -- dozens of clear rooftop arrays visible across nearly every block, strongly supporting (if anything, suggesting the true count could be even higher than) the claimed 67.

Net effect on box status: - Boxes 1, 4, 5: visual pass materially increases confidence but does not constitute Rule-1 sign-off. Still recommend NOT pooling ⅘ into --calibration-box without an actual human completeness pass -- the Faisalabad case below is exactly why that caution matters. - Box 2 (Faisalabad): actively flagged as suspect, not just unverified -- recommend a human mapper re-examine these specific 53 features before using them for anything. - Box 3 (Multan): status changed from "ambiguous, open task" to "confirmed high-value open task" -- there is real, visible, substantial PV here that needs mapping.


Glint-method validation against Box 1's full ground truth (2026-07-24)

Direct empirical check of earthpv.glint's own spike-detection/orientation-consistency method against the corrected 1,021-installation Lahore ground truth -- a genuinely different question from the model-blindness finding above: does the independent physics-based corroborator (not the trained segmentation model) find anything here? Reused glint.tile_scene_series_batch (all 1,021 targets sit in one 1-degree tile group, so one shared STAC search) and glint_validation.analyze_point unchanged, split into 7 chunks of ~150 targets each (fresh search + token per chunk) after the tile-batched country-scale bug (docs/issues/glint-tile-batched-coverage.md) bit an un-chunked first attempt at this exact box (every target came back with an identical, truncated 55-scene count). Chunking fixed it: scene counts came back in 3 clean, systematic tiers (136/95/87 scenes, each shared by a large uniform group -- sub-area band-availability differences, not random loss) rather than one suspicious uniform low number.

Result: 48/1,021 (4.7%) showed at least one spike; 0/1,021 (0.0%) reached the n_consistent >= 2 validation bar, against a country-wide reference of 2.5% (<100 m²) and 8.8% (100-500 m²) validated from the 500-target study. Even generously discounting for the reduced scene count here (87-136 valid scenes vs. the original study's typical ~130-150 -- a real methodological difference, not nothing), a clean 0/1,021 against an expectation of roughly 22-34 lands far outside what reduced sample size alone would explain.

This is consistent with, not contradicting, the earlier model-blindness finding -- two independent detection channels (the trained segmentation model, and the physics-based glint corroborator) both show near-total failure on this specific stratum. A plausible physical reason specific to glint: glint.py's own documented caveat is that ~30% of confirmed real installations show zero spikes over 2 years because their actual tilt/azimuth doesn't happen to bisect the sun/sensor at the fixed ~10:30 overpass geometry -- a per-installation orientation lottery nationally. But this box is one affluent planned-housing development (DHA Phase V, stratum 1) where many roofs likely share a similar pitch/orientation convention by construction standard -- if that shared convention happens to be glint-unfavorable, it could plausibly apply to nearly the whole quadrat at once, rather than the ~30% national miss rate being independently rolled per installation. Not confirmed (would need actual roof-orientation data to check), but a coherent explanation for why a whole quadrat could read near-zero even though the technique has real, if modest, power nationally.

Practical implication: glint corroboration should not be expected to help recover capacity in this specific stratum/quadrat type (small, uniform, closely-packed residential rooftop arrays) -- both the model and the independent physics check are weak here. Data: data/glint/calib_box/lahore_calib_box_glint_summary.csv, lahore_calib_box_stats_by_size.csv.


Two ground-mount solar-farm calibration areas (2026-08-06)

Every box above is rooftop/urban -- ground-mount (site-area, 0.07 kWp/m² conversion) has had zero dedicated calibration ground truth anywhere in this project, despite being roughly half of the >= 400 m² capacity estimate and the component check-density's worst plausibility failures (Balochistan, KP, Gilgit-Baltistan) concentrate in. Two known, well-mapped Pakistani utility-scale farms, using their own OSM boundary rather than a drawn/geodesic shape:

  • sukkur_solar_farm_gmcalib_5p93km2 -- the combined "Helios Power (Pvt.) Limited (Phase 3), Meridian Energy (Pvt.) Ltd (Phase 1), HND Energy (Pvt.) Limited (Phase 2), Scatec Sukkur solar farm" complex (osm-way/1374632672, plant:output:electricity 50 MW), Sukkur district, Sindh. Plant footprint 2.265 km², boundary buffered +400 m to 5.93 km² for surrounding-terrain false-positive testing.
  • quaid_e_azam_solar_park_gmcalib_14p07km2 -- Quaid-e-Azam Solar Park (osm-relation/11789995, operator QA Solar Power Ltd, 400 MW), Bahawalpur district, Punjab. Buffered +400 m from 6.65 km² (the relation's own area) to 14.07 km².

Both named ..._gmcalib_..., not ..._calib_..., so roofclf.discover_quadrats()'s glob (*_calib_*_boundary.geojson) does not pick them up -- confirmed (still 18 quadrats discovered after adding these). Mixing ground-mount PV into roofclf's rooftop-classifier training population would contradict the placement separation this project enforces everywhere else.

Both raw OSM pulls needed a fix before use. Each site turned out to have nested, overlapping OSM mapping at multiple levels -- an outer envelope (oddly tagged generator:source=solar at both sites rather than plant:source=solar) drawn over pre-existing finer per-phase/per-block mapping, with no tags distinguishing the levels from each other. Naively summing the raw pull's area_m2 triple-counts the same ground: 8.63 km² raw vs. 2.61 km² dissolved at Sukkur (21 overlapping elements -> 1), 22.20 km² raw vs. 8.90 km² dissolved at QASP (6 overlapping elements -> 1). Both quadrats' *_overpass_solar.parquet now hold one dissolved-footprint row rather than the raw pull.

Checking both sites against the current candidates.parquet surfaced a real, previously undocumented pipeline bug, since fixed: replace_with_osm_geometry now keeps only the closest match per OSM feature via groupby(...).idxmin(), and labels.dissolve_overlapping merges nested plant/generator ways before any capacity computation sees them. Short version of the original bug: at Quaid-e-Azam Solar Park, two different model-detected candidates each independently matched their own nearest OSM feature via postprocess.replace_with_osm_geometry -- one to the outer envelope (8,904,839 m²), one to a member way 100% contained inside it (1,745,036 m²) -- and both survive as separate rows in candidates.parquet, so this one site's ~8.90 km² footprint is currently double-counted to ~10.65 km² (+20%) in any capacity estimate built from this snapshot. At Sukkur the opposite failure shows instead: the one nearby candidate (44,948 m², never OSM-replaced) undercounts the true 2,606,013 m² footprint by 58x -- a second, independent confirmation of this project's established "segmentation badly underestimates ground-mount" finding, beyond Quaid-e-Azam Solar Park's own count-zero cell (the evidence-atlas mwp_best floor fix, same session).

Not yet folded into any capacity number or plausibility check -- these two quadrats exist so far only as ground truth for a future targeted evaluation of the segmentation model's own ground-mount recall/area accuracy (the actually-novel thing solar-farm calibration areas can measure that rooftop quadrats cannot), and as the source of the duplicate-match finding above. Not Rule-1 complete in the usual sense (no human completeness pass over the surrounding buffer for false positives yet) -- the two sites' own footprints are corroborated by two independently-drawn OSM outlines converging on the same shape, which is not nothing, but is not a substitute for that pass.


Box 13 -- Hasal, ~1km x 1km around (29.7161176, 72.5512755) -- 2026-08-10

A drawn boundary, not a geodesic square (--geojson data/labels/calibration_boundaries/ hasal.geojson, a hand-drawn ~1km x 1km rectangle exported from JOSM, feature name hasal_1x1), registered via scripts/new_calibration_quadrat.py. Geodesic area 996,572.5 m² (0.9966 km²), stem hasal_calib_1p00km2.

Overlap check: clear against all 18 existing quadrats. Nearest is multan_ calib_3p92km2 at 121.74 km, so no deduplication debt. Reverse-geocoded against the density stage's own admin polygons: Bahawalpur District, Punjab.

Overpass fetch hit a genuine outage, not a truncation. At creation time all three mirrors (overpass-api.de, overpass.kumi.systems, overpass.openstreetmap.ru) were returning 504s or connection timeouts; the first build_overpass_labels call itself succeeded (328 solar elements returned cleanly), but the SECOND, independent confirming query new_calibration_quadrat.py runs specifically to guard against silent partial responses could not complete against any mirror. The script does not treat an unavailable confirming check as a failure (an unreachable checker must not read as "fine" but also must not block registration) -- it wrote the pull with pull_unverified: True recorded on the boundary parquet. 328 features over a ~1 km² box is not the shape of a truncated response, but this should be re-run once Overpass recovers to get an actual independent count to check against.

Ground truth: 328 installations, 318 rooftop / 10 ground, 16,082.9 m² total, median 31.4 m², mean 49.0 m², max 1,056.3 m². 99.7% of installations (327/328) sit below the 400 m² detection floor, 93.9% below 100 m² -- one of the most sub-floor-dominated quadrats registered so far, close to Sialkot/Rahim Yar Khan/Mardan/Sukkur territory. nn_median_m 18.5 m, the tightly-packed informal/residential cluster.

Status: Rule-1 complete, per the repository owner's explicit declaration the same day the box was created (2026-08-10) -- recorded in results/calibration_quadrats.csv (rule1_complete: True). Same standing caveat as every other quadrat in this file: this is an owner-attested completeness declaration, not an independently second-mapper-verified one, and (per the Rule-1-is-epoch-relative warning earlier in this file / docs/methods/ calibration-quadrats.md) imagery_layer/imagery_date remain unrecorded for this box too, so the gap between the mapping imagery's epoch and the Sentinel-2 composite's is unmeasured here as everywhere else.

Folded into roofclf the same day. earthpv roof-classifier was re-fit on all 19 quadrats: hasal contributes 4,378 buildings, 444 with PV (base_rate 10.14%), AUC 0.8048 (mid-pack -- above mardan 0.760 and sukkur 0.788, below most others). Its rate_ratio (roofclf's own predicted/true adoption-rate ratio) is 0.461 -- roofclf predicts only 4.67% adoption against Hasal's true 10.14%, more than 2x under-prediction, the same failure shape as Box 11 (Rahim Yar Khan, 0.304-0.332 across refits). That keeps Hasal just outside select_calibrated_quadrats's [0.5, 2.0] band, so it does not enter the trusted-13 set that drives the domain-restricted sub-400 m² precision/coverage-ratio fit (sub400_capacity.py) -- it only widens the 19-quadrat pool behind the pooled LOQO threshold/AUC fit, which moved the deployment threshold from 0.2405 to 0.2441 and median_fold_auc from 0.8824 to 0.8757. This is a rate-mismatch, not a low-quality addition -- Hasal's own discrimination (AUC 0.805) is unremarkable in the good sense; roofclf simply under-counts adoption there, exactly as it does at Rahim Yar Khan.


Ground-mount validation against the promoted production checkpoint (2026-08-10)

The "future targeted evaluation" the two ground-mount boxes above were registered for but never got was run this session: scripts/validate_groundmount_quadrats.py scores v3_combined_india (the checkpoint behind data/predictions/pakistan/prob/ and candidates.parquet -- confirmed the production checkpoint, see CLAUDE.md's "Which segmentation checkpoint" note) against both sites two ways -- pixel-level (raw probability raster: scale = probability-integral / true area, and pixel AUC separating mapped-PV pixels from background) and candidate-level (what density.py's capacity numbers actually consume, matched within 200 m of the box). Output: results/ groundmount_quadrat_validation.csv.

The model itself is excellent at both sites -- new evidence, measured cleanly for the first time:

site true area pixel scale pixel AUC mean prob on PV / bg
Quaid-e-Azam Solar Park 8,904,839 m² 1.011 0.9168 0.834 / 0.050
Sukkur solar farm 2,606,013 m² 0.935 0.9552 0.885 / 0.024

But almost none of that reaches the published capacity number, and the reason is more specific than "segmentation underestimates ground-mount" -- and partially corrects the 2026-08-06 entry above. That entry measured the RAW candidate-area sum at Quaid-e-Azam Solar Park (10,779,834 m², a "+20% inflation") without checking whether the inflated area survives density.py's own capacity aggregation. It does not: both contributing candidates -- the outer-envelope match (8,904,839 m², osm_matched_id=osm-way/1530316244) and the nested member-way match (1,745,036 m², osm_matched_id=596123516), confirmed still geometrically overlapping and still unfixed -- exceed postprocess. MAX_CANDIDATE_M2 (100,000 m²) and are excluded from every capacity sum density.py computes. Only one small 13,086 m² fragment survives that filter.

site candidate area, all near (old framing) candidate area, capacity-relevant (oversize excluded, matches density.py) capacity-relevant scale
Quaid-e-Azam Solar Park 10,779,834 m² (scale 1.211, "+20%") 13,086 m² 0.0015
Sukkur solar farm 44,948 m² (scale 0.017) 44,948 m² (unaffected -- not oversize) 0.0172

So both sites are near-total misses in the number that actually reaches the atlas -- ~680x undercount at Quaid-e-Azam Solar Park, ~58x at Sukkur -- via two different mechanisms now clearly separated from each other and from the model's own (good) output:

  • Quaid-e-Azam Solar Park: correct candidates form, then get discarded. Both oversize candidates have geometry_source == "osm" -- they are the exact, human-mapped OSM footprint, not a model-polygonized blob. MAX_CANDIDATE_M2 exists to guard against polygonize_chips merging an unconstrained false-positive sheet (dry riverbed, salt flat) into one multi-km² blob with confidence 1.0 by construction -- it was never meant to also catch a verified real installation whose true footprint happens to be large, but currently cannot tell the two cases apart and drops both.
  • Sukkur: root-caused the same session -- candidates.parquet is STALE at this site, not a polygonize_chips failure. The box spans exactly 2 composite cells (0081_0036, 0081_0037, both dated 2026-07-16); candidates.parquet is dated 2026-07-29, 13 days later, so staleness was not the first guess. But re-running postprocess.polygonize_chips directly against those same two current rasters at the standard threshold (0.3) produces one 2,467,076 m² polygon -- 94.7% of the true footprint, matching the pixel-level scale (0.935) almost exactly, and confirming the segmentation+polygonize mechanism works fine here once given current data. The 44,948 m² fragment sitting in candidates.parquet today does not reflect these rasters at all -- it is stale, from an earlier inference/postprocess pass at this location that was never refreshed, the same failure shape already on record elsewhere in this project (candidates.parquet predating the 2026-07-29 OSM-geometry replacement is the closest precedent). The national OSM pull (pakistan_overpass_solar.parquet) does have 21 overlapping elements near this site (largest 2,526,454 m²) -- the same un-dissolved nested-mapping problem the quadrat's own targeted pull needed a manual dissolve to fix, unfixed at the national source -- so a fresh postprocess run's OSM-geometry-replacement step would find a match, but which of the 21 raw elements it latches onto (nearest by centroid, not necessarily the best single footprint) is not yet checked.

Not yet fixed. No change has been made to postprocess.py or density.py; see the next section for what a fix would need to look like for each mechanism.

What a fix would need to look like

Quaid-e-Azam Solar Park's mechanism has a targeted, low-risk fix candidate: exempt geometry_source == "osm" candidates from the oversize capacity exclusion. An OSM-matched candidate's geometry is not something polygonize_chips invented -- a human mapper drew it, so it cannot be the unconstrained-false-positive-sheet failure mode MAX_CANDIDATE_M2 exists to catch, regardless of its area. Concretely this would mean density.py's oversize filter (currently one unconditional area_m2 > MAX_CANDIDATE_M2 test) needs to become (area_m2 > MAX_CANDIDATE_M2) & (geometry_source != "osm"), or equivalent. Measured nationally this session: 149 oversize candidates exist (60.72 km² total), of which 28 are geometry_source == "osm" (18.32 km², every one placement == "no_building") -- real, human-verified ground-mount footprints currently excluded from every capacity number purely for being large. A pairwise geometric-overlap check among those 28 finds 4 overlapping pairs: 3 are exact duplicates (same osm_matched_id, identical area -- a trivial dedup, drop one of each pair) and 1 is the QASP nested pair above (drop the smaller, contained member). Deduped total: 15.22 km², which at the ground-mount site-area constant (DEFAULT_KWP_PER_M2_LAND = 0.07) implies roughly 1,065 MWp of currently-excluded, OSM-verified ground-mount capacity nationally -- the size of the fix, before touching Sukkur-style staleness at all. Any implementation needs the same 4-pair dedup this measurement already required, not just the exemption itself.

Sukkur needed a different fix: refresh candidates.parquet, not change any filter. Its mechanism, root-caused above, is stale candidates, not a polygonize_chips or oversize-filter problem -- the segmentation+polygonize pipeline already produces an accurate large polygon (94.7% of true area) when run against current rasters. The fix here is operational (re-run postprocess nationally so every cell's candidates reflect the current rasters, addressing the same staleness class already documented for candidates.parquet elsewhere in this project), and once refreshed, Sukkur's new large candidate would need the SAME OSM-exemption fix as QASP to actually reach capacity, since it too would land well above MAX_CANDIDATE_M2. Whether other cells nationally carry the same kind of stale-relative-to-current-rasters candidate is an open question this session did not check beyond these two sites.

Correction and full fix, same day (2026-08-10, later): Sukkur's root cause was mis-diagnosed above

The staleness explanation above does not survive a direct check: re-running postprocess against Sukkur's own current rasters (no candidate-population change at all) reproduces the identical 44,948 m² fragment, byte-for-byte. The real mechanism is the one flagged as unchecked at the end of the entry above -- the national OSM pull's 21 overlapping, un-dissolved elements near Sukkur, and postprocess. replace_with_osm_geometry's nearest-match latching onto whichever small fragment happens to be closest to a given candidate's polygon, not the site's real combined footprint. candidates.parquet was never stale; the OSM reference it matches against was un-dissolved.

Fixed at the source, not by exempting or patching around it. labels. dissolve_overlapping (new function) merges geometrically-overlapping polygons within the same placement group into one feature per connected cluster, recomputing area geodesically on the union, before ANY matching happens. export. load_mapped_reference_attrs (feeds postprocess.replace_with_osm_geometry) and atlas.build_evidence_atlas's own OSM hand-mapped sum both now dissolve first. Measured nationally: ground-mount OSM area 55.95 -> 42.32 km² (-24.4%, the QASP generator/plant nesting), rooftop 6.21 -> 6.08 km² (-2.1%).

That fix, on its own, made a second problem WORSE, not better -- caught before publishing, not after. A bigger, dissolved reference polygon sits within max_distance_m of more nearby candidates than the smaller fragments it replaced, so MORE candidates independently matched (and each fully inherited) the SAME real installation's area: duplicate osm_matched_id groups went 16 -> ... a naive "keep the closest" dedup attempt still left 13 unresolved, because ties at dist_m == 0.0 (a candidate whose polygon directly overlaps the OSM feature) are not resolved by an equality test against the group minimum -- both tied rows pass it. Fixed with pandas.groupby(...).idxmin(), which breaks a tie by position instead of leaving all of it "the minimum": re-verified on the full national candidate set, 0 duplicate osm_matched_id groups remain. Every candidate that matched an already-claimed feature keeps its OWN original, model-polygonized geometry (not dropped -- it may be a real, separately-detected patch of the same large site, which is signal for the human-reviewed leads product even though only the closest match should carry the site's authoritative OSM footprint for capacity).

The oversize-exemption fix from the "What a fix would need to look like" section above was also implemented, as density.capacity_relevant_candidates (a refactor of the uncommitted _dedup_osm_oversize diff already in density.py at the start of this session): geometry_source == "osm" candidates are exempted from MAX_CANDIDATE_M2, after deduping overlapping OSM-oversize candidates via dissolve_overlapping itself (replacing the original keep-largest-drop-rest logic, which would have silently lost whichever part of a smaller, non-fully-nested member sits outside the larger one it was compared against). atlas.build_evidence_atlas now calls the SAME function for its OSM-matched/unmatched split, so an installation matched only by a candidate this run excludes from capacity is correctly treated as "still unmatched," not double-subtracted for nothing (see this doc's earlier "1,704 MWp" measurement, from the 2026-08-10 pipeline-review session, now closed by this shared function).

The land constant was recalibrated against these same two boxes, the first external nameplate-capacity anchors this project has had for it: capacity_calibration.DEFAULT_KWP_PER_M2_LAND moved from a GCR-assumption-derived 0.07 to 0.05 (geometric mean of QASP's 400 MW / 8,904,839 m² dissolved footprint = 0.0449 and Sukkur's confirmed 150 MW combined complex / 2,606,013 m² = 0.0576 -- the OSM tag on Sukkur's matched way names only one of its three 50 MW phases, so 50 MW alone would have been a 3x undercount here). See capacity_calibration.py's updated constant comment for the full derivation.

National re-run of postprocess with every fix above (dissolve, dedup, capacity_relevant_candidates): ground-mount candidate area 122.22 -> 123.81 km² (+1.3%, net of Sukkur-style undercounts being fixed and QASP-style duplicate-match inflation being fixed roughly cancelling) -- the small net change is not a sign nothing happened; it is two large, real, opposite-signed corrections landing close to where the naive number already was, for the first time for the right reason instead of by coincidence of two uncorrected errors.

Placement-split calibration, national density --force, and a new (different)

check-density failure -- resolved same day, 2026-08-10/11

capacity_calibration.derive_placement_tables (new) fits separate rooftop/ground mapped-fraction and recall tables instead of one pooled set of area bins -- pooling let ground-mount borrow rooftop's much higher OSM corroboration in the same size bin (measured: ~1% of surviving ground candidates sit within 100 m of any OSM feature nationally, vs ~14% for rooftop). Ground bins fall back to p_unmapped = 0.0 ("interim-mapped-only-by-placement", an honest floor) rather than inheriting the pooled glint-derived value, since the existing glint sample predates three candidate-population regenerations and cannot be reliably re-attributed by placement. Re-derived the calibration table against the fully refreshed 19-quadrat set (15,465 pooled installations, up from the single-box 3,832 the previously-published table used -- see "redownload all calibration areas" below), then ran density --force (2h18m, 0 cell failures, fingerprint written) followed by a cheap non---force re-run to pick up the marginal refinement from the last 3 quadrats' refresh (moved the total by <0.1%). National est_mwp_rc: 5,077.9 -> 4,051.9 MWp (-20.2%): rooftop 2,229.9 -> 2,916.3 MWp (+30.8%), ground-mount 2,848.0 -> 1,135.6 MWp (-60.1%) -- exactly the direction predicted going in (pooling had been dragging rooftop's own p_real down toward ground's, and ground's up toward rooftop's, in every shared bin).

check-density now PASSES the exact check this fix targeted, and fails a different one. KP's ground:rooftop ratio (the ratio the 2026-07-30 KP/Balochistan investigation in this file could not root-cause past "confirmed genuine, not a further bug") moved 3.35-8x -> 0.49x; Balochistan's moved 3.90-18x -> 2.01x -- both comfortably inside the 3.0/5.0 warn/fail band. But 3 regions now fail the OTHER plausibility check, single-cell concentration (top_cell_share > 0.25): Khyber Pakhtunkhwa (cell 0105_0098, 42%), Balochistan (0060_0013, 31%), Islamabad Capital Territory (0120_0099, 68%). This did not newly appear -- it was always mechanically implied once the ground-mount over-inflation that used to dominate these regions' totals was removed: shrinking a wrong, inflated denominator elsewhere in a region necessarily raises the visible concentration share of whatever legitimate signal remains, even though that signal did not itself change.

Checked, not just asserted: all three flagged cells are the calibration quadrats' own cities, not an artifact. 0105_0098 and 0060_0013 are Peshawar and Quetta respectively (peshawar_calib_1km/peshawar_west_calib_4p39km2 and quetta_calib_1km in results/calibration_quadrats.csv) -- KP's and Balochistan's own provincial capitals, and by far their largest cities in provinces that are otherwise sparse (231 of KP's 733 cells and 148 of Balochistan's 850 carry any capacity at all, and the gap to the SECOND-largest cell is 4-9x in both). 0120_0099 is Islamabad's own urban core, in a federal territory of only 10 cells total -- one city dominating a ten-cell administrative unit built around exactly that city is not a plausibility failure, it is what the geography actually is. This is the roofclf ge400 domain-replacement mechanism (CLAUDE.md's "roofclf now replaces segmentation's own rooftop estimate") doing exactly what it was built to do -- concentrate a real, calibrated per-building estimate in the cells that have calibration ground truth -- made newly visible, not newly wrong, once ground-mount stopped drowning it out.

Not fixed in code -- a policy question left to the project owner, not a bug this session found or should silently paper over. plausibility.MAX_CELL_SHARE (0.25) was never tuned against a province this small or this urban-concentrated; whether it should gain a size-aware threshold, a per-region exemption (matching Gilgit-Baltistan's existing RATIO_CHECK_EXEMPT_REGIONS precedent for check 1), or simply stay failing with this documented explanation attached is a threshold-setting decision, not a correctness one. Full check-density output as of this run:

region mwp_roof mwp_ground nonroof_ratio top_cell top_cell_share status
Khyber Pakhtunkhwa 240.3 116.8 0.49 0105_0098 (Peshawar) 0.42 fail (concentration)
Balochistan 25.2 50.6 2.01 0060_0013 (Quetta) 0.31 fail (concentration)
Islamabad Capital Territory 182.0 5.5 0.03 0120_0099 0.68 fail (concentration)
Azad Kashmir 0.0 7.1 7052 -- 0.12 suspect (below floor)
Punjab 1,861.5 658.4 0.35 0133_0075 0.11 ok
Sindh 605.4 249.1 0.41 0065_0011 0.19 ok
Gilgit-Baltistan 0.0 41.6 41584 -- 0.13 ok (ratio check exempted)

Published anyway, with this table and explanation, matching the project's own established precedent (the 2026-07-30 KP/Balochistan ratio finding was accepted as "confirmed genuine" and published unresolved in the same sense) -- the alternative, holding back a validated, materially-more-correct national number over a heuristic threshold two calibration-quadrat cities were always going to trip once the number they used to hide inside was fixed, would be worse than the documented failure.

All calibration areas re-downloaded, at the owner's request, 2026-08-10/11

All 21 registered quadrats (19 _calib_ + the 2 _gmcalib_ ground-mount boxes) were re-pulled from live Overpass via a new scripts/refresh_calibration_areas.py, writing dated <stem>_overpass_solar_<date>.parquet files that never overwrite the pull they supersede (roofclf._newest_solar picks up the newest automatically). Overpass was under heavy load throughout (repeated 429/504s across all three mirrors), so the run took several hours and was interrupted once by an unrelated process kill; it resumed cleanly because the script skips any quadrat whose dated file for the day already exists, re-fetching only what was still missing.

The two ground-mount boxes produced the most dramatic-looking change: Quaid-e-Azam Solar Park went from 1 mapped installation to 6, Sukkur solar farm from 1 to 21 -- OSM had gained more granular way-level mapping of both sites since the last pull. This did not change the land-constant calibration: labels.dissolve_overlapping merges the newly-fragmented ways back into one footprint per site, and the dissolved area came out byte-identical to the single-feature pull it replaced (8,904,838.5 m² / 2,606,012.6 m² respectively) -- direct confirmation that the dissolve fix is robust to exactly the kind of re-mapping that motivated re-pulling everything in the first place. The other 17 quadrats' installation counts moved by single digits to low tens of percent (e.g. Sukkur's rooftop/mixed quadrat 1105 -> 1115, Sundar 132 -> 134), consistent with ongoing incremental OSM mapping rather than any systematic gap.

Box 14 -- Bahawalnagar Rural, hand-drawn ~4.00 km2 around (29.5002, 72.8716) -- 2026-08-13

Proposed the same session as a deliberately-rural extension candidate for widening density.CALIBRATED_BLDG_DENSITY_KM2's floor (141.00 bldg/km2 at the time, set by Khairpur Rural): a plain geodesic square at (29.50, 72.87), side 2000 m, pre-checked against national VIDA buildings and measured 49.5 bldg/km2, comfortably below the floor, following the pattern that worked for Muzaffargarh Rural Wide and Khairpur Rural (mix farmland with a small settlement; do not trace tightly around a village's own built-up extent, which is what sank Muzaffargarh Rural 1km and Malok).

The owner re-drew the boundary in JOSM, shifted east, mapped every installation inside it, and exported the result to data/labels/calibration_boundaries/bahawalnagar_rural_calib_2km_boundary.geojson (same filename as the original proposal, overwritten). Reprocessed via scripts/new_calibration_quadrat.py --name bahawalnagar_rural --geojson data/labels/calibration_boundaries/bahawalnagar_rural_calib_2km_boundary.geojson (drawn mode, since the shifted boundary is no longer the exact square the original --lat/--lon/--side-m command produced). Drawn mode tags a quadrat by its measured geodesic area, not by side length, so the stem changed to bahawalnagar_rural_calib_4p00km2 -- the original bahawalnagar_rural_calib_2km boundary/parquet were deleted as superseded (the geojson was already gone, evidently removed as part of the same edit; the orphaned parquet was cleaned up here). Confirmed clear of all 23 other quadrats (nearest is Hasal, 39.19 km) and correctly resolves to Bahawalnagar District, Punjab via the admin lookup.

Live Overpass pull: 9 installations, cross-confirmed (a second query also saw 9, so the pull is not a silent partial response). 6 rooftop / 3 ground, 100% below the 400 m² floor (max 67.3 m², median 26.9 m²), packing nn_median_m 74.8 m -- sparser than every quadrat in the table except Muzaffargarh Rural (292.6 m), consistent with genuinely rural, sparsely-built farmland rather than a village core. The owner declared it Rule-1 complete (every installation inside the boundary mapped).

Own building density, measured directly against national VIDA (independent of any roofclf run): 494 buildings / 4.00 km2 = 123.5 bldg/km2. Lower than the pre-check's 49.5 because the owner's shift east landed on somewhat denser ground than the original square, but still below the current 141.00 floor -- so, per the project's own rule, this quadrat does qualify to lower it further, just by a smaller margin than originally hoped. Not yet done: CALIBRATED_BLDG_DENSITY_KM2 is a hand-maintained constant (density.py), and lowering it without re-running the national roofclf-score-national/sub400-capacity/ge400-roof-capacity pass would describe a domain wider than the one the currently-published capacity figures actually cover -- exactly the kind of drift _size_distribution_data's residual check exists to catch elsewhere. Added to results/calibration_quadrats.csv (n_installations, median_install_m2, frac_sub400 populated from the pull directly; n_buildings/ n_pv_buildings/base_rate/nn_median_m left blank, matching Sanghar's precedent, since both need a roofclf fold table that includes this quadrat) and to Calibration quadrats's "Known gap" count. Folding it into an actual roofclf refit and deciding whether to widen the domain constant are follow-up steps, not done here.

Box 15 -- Nasirabad Rural, geodesic square 4.00 km2 around (28.65, 68.15) -- 2026-08-13 -- first confirmed-zero Rule-1 quadrat

Suggested the same session as a further rural-extension candidate, same recipe as Bahawalnagar Rural: pre-checked against national VIDA buildings before mapping, measured 48.5 bldg/km2 (194 buildings in 4 km2), below the 123.5 floor Bahawalnagar Rural had just set. Also the first quadrat drawn from Balochistan's rural interior rather than its one existing quadrat (Quetta, urban, anchors the upper end of the density range) -- Nasirabad District, on the flat, canal-irrigated Kacchi plain, distinct from the mountainous arid terrain that covers most of the province. Confirmed clear of all 25 existing quadrats via scripts/new_calibration_quadrat.py --dry-run (nearest: Sukkur, 123.9 km) before anything was written.

The owner mapped the box and found zero installations -- confirmed as a genuine completeness pass, not the Muzaffargarh Rural Wide mistake. This project has been burned by exactly this scenario once already: Muzaffargarh Rural Wide's original "0 installations" was based on 8 independent Overpass queries all returning zero, which established "0 OSM-mapped installations as of that pull," not "mapping is complete" -- the owner later swept the actual imagery by eye and found real, unmapped PV. Before registering this box the same way, the owner was asked directly whether "I could not find a single PV installation" meant a visual sweep of the high-res background imagery or just an OSM/Overpass check, and confirmed a visual sweep of the whole box -- the actual Rule-1 claim, not the OSM-data claim that misled the Muzaffargarh declaration. The live Overpass pull independently agrees: 6 confirmed zero-element responses across ~10 minutes and at least 2 mirrors (the pipeline's own 4-retry loop plus 2 additional manual confirming queries, one of which hit rate-limiting/timeouts on the other two mirrors after repeated hammering -- expected, not a data quality signal), comparable to the 8-zero bar that (wrongly) satisfied the Muzaffargarh case, but this time backed by an actual completeness declaration rather than resting on the Overpass result alone.

build_overpass_labels refuses to accept an empty pull automatically by design (raises after retries exhausted, exactly to force this kind of manual confirmation rather than silently writing an empty quadrat) -- so nasirabad_rural_calib_2km_overpass_solar.parquet was written by hand, schema-matched to an existing quadrat's pull (same column dtypes and CRS), 0 rows. Registered Rule-1 complete in results/calibration_quadrats.csv on the strength of the owner's declaration.

Checked against the codebase before registering: nothing breaks on a zero-positive quadrat. roofclf.auc() is a hand-rolled rank-based implementation (not sklearn's roc_auc_score) that explicitly returns NaN when either class is empty (if n1 == 0 or n0 == 0: return nan) rather than raising -- the same degradation Khairpur Rural's near-undefined AUC (3 installations) already exercises, just complete this time. rate_ratio floors its denominator (max(base_rate, 1e-9)) so a zero base rate produces a very large finite number rather than a crash, which will trivially fail select_calibrated_quadrats's [0.5, 2.0] trust band -- the same mechanical exclusion Hasal, Rahim Yar Khan, and Bahawalnagar Rural already sit outside for unrelated reasons, not special-cased zero-detection logic, but sufficient. build_calibration_quadrats_csv.py already guards n_installations=0 explicitly (if len(a) else 0.0/np.nan). Not yet folded into a roofclf refit -- n_buildings/base_rate/nn_median_m are blank in results/calibration_quadrats.csv pending one, same as Sanghar and Bahawalnagar Rural.

Box 16 -- Tank Rural, geodesic square 4.00 km2 around (32.20, 70.30) -- 2026-08-13

The second of the two candidates suggested the same session as Nasirabad Rural, in Tank District, southern Khyber Pakhtunkhwa -- KP's first rural-extension quadrat; its three existing quadrats (Peshawar x2, Mardan) all sit in the dense Peshawar valley, which measured well above the domain floor everywhere tried before this spot. Own density (measured directly against VIDA, independent of any OSM pull) 55.75 bldg/km2 (223 buildings in 4 km2) -- below the 123.5 floor Bahawalnagar Rural had set, though not as low as Nasirabad Rural's 48.5, so it does not move the floor further on its own (see the density-domain update below). Confirmed clear of all 26 existing quadrats via scripts/new_calibration_quadrat.py --dry-run (nearest: Muzaffargarh Rural, 192.3 km) before anything was written.

The owner mapped the box and reported finding real installations this time -- 10 installations, cross-confirmed (a second independent query also saw 10, despite two of the three Overpass mirrors timing out during the confirming pass; the third succeeded, which is what the cross-check is for). 7 rooftop / 3 ground-mount, 100% below the 400 m2 floor (max 285.6 m2, median 26.0 m2), packing nn_median_m 52.2 m -- denser installation spacing than Nasirabad's (vacuously undefined, zero installations) or Bahawalnagar's (74.8 m), consistent with a genuinely mixed rather than uniformly sparse rural regime. Registered Rule-1 complete on the owner's declaration. Not yet folded into a roofclf refit -- n_buildings/base_rate/nn_median_m are blank in results/calibration_quadrats.csv pending one, same as every quadrat added this session.

Density domain widened again, 2026-08-13: Nasirabad Rural's 48.5 bldg/km2 is the new floor

Both Box 15 and Box 16 were registered before a refit, so neither had been folded into density.CALIBRATED_BLDG_DENSITY_KM2 yet (deliberately deferred at the time -- see Box 15's closing note). Folding both into one refit + national rescoring pass is also the right moment to apply the domain widening: Nasirabad Rural's own density, 48.5 bldg/km2, is now the minimum across every Rule-1-complete quadrat, lower than the 123.5 floor Bahawalnagar Rural set. The floor moves 123.5 -> 48.5 bldg/km2; Tank Rural (55.75 bldg/km2) sits inside the new range but does not itself move the boundary, since Nasirabad's is lower. See docs/methods/density.md for the updated domain cell/building counts once the national rescoring pass this widening motivates has run.

Box 17 -- Kalat Rural, geodesic square 9.01 km2 around (28.792663, 66.615940) -- 2026-08-17

One of four low-density sites proposed this session to widen density.CALIBRATED_BLDG_DENSITY_KM2's floor below 48.5 bldg/km2, where 1,506 of 4,463 national cells (4.03M buildings) still sit outside the domain, and where roofclf-AND-SPPI still flags 14,038 buildings / 75.3 MWp with no calibration under it (the component dropped from the atlas 2026-08-15). The four were sited one per major province on a density ladder, each a 3 km geodesic square: Dera Ghazi Khan Rural (11.8 bldg/km2, the intended floor-setter), Waziristan Rural (17.2), Kalat Rural (25.9) and Jamshoro Rural (30.9). 3 km rather than the protocol's 2 km because at these densities a 2 km box holds around 50 buildings; all four together hold 772, fewer than Faisalabad's 1 km2 box.

Only Kalat became a quadrat. The other three were swept, came back empty, and were discarded rather than registered as confirmed zeros, because the owner reports the JOSM reference imagery over all three as very old. That is the correct call and it is worth recording precisely, because the empty result looked like a strong finding before the imagery question was asked. What the model claims in those three boxes:

proposed box bldg/km2 buildings roofclf-AND-SPPI flagged claimed segmentation candidates found by sweep
Dera Ghazi Khan Rural 11.8 106 9 19.1 kWp 0 0
Waziristan Rural 16.9 152 61 417.7 kWp 0 0
Jamshoro Rural 30.9 278 13 38.6 kWp 0 0

83 flagged buildings and 475.4 kWp with nothing under them reads like a direct falsification of the out-of-domain AND gate, and it is not one: Rule-1 is epoch-relative, so against imagery older than the Sentinel-2 composite an empty sweep cannot separate "the gate is firing on bare ground" from "the panels went up after the picture was taken". Registering the three as confirmed zeros would have pushed the domain floor to 11.8 bldg/km2 (2,957 -> 4,266 cells, 66.3% -> 95.6%; 94.67% -> 99.74% of national buildings) and dragged the sparse density band's coverage ratio toward zero on evidence that does not support it -- the same mistake, in the same direction, as Muzaffargarh Rural Wide's original zero (see the correction above), caught before it entered a refit rather than after.

This is the third time stale reference imagery has blocked validation of the out-of-domain population, and the first time it has blocked purpose-drawn quadrats rather than randomly drawn review cells. It is the reason the out-of-domain AND gate was dropped from the atlas on 2026-08-15 and the reason it stays dropped. Their coordinates are recorded here rather than kept as files, so the sites can be re-drawn if the imagery over any of them refreshes: Dera Ghazi Khan Rural (29.331582, 69.684320), Waziristan Rural (32.736105, 69.572562), Jamshoro Rural (25.658722, 67.775397), all 3 km geodesic squares. The gating question for any of them is imagery date, not mapping effort, which makes populating imagery_layer/imagery_date (never yet filled for any quadrat, see Calibration imagery dating) the prerequisite rather than a nicety.

The owner moved the box roughly 8 km south-southeast before mapping, deliberately, to bring more ground-mount PV inside it, and this changed what the quadrat measures. Own density (VIDA, representative_point().within(boundary), the same rule roofclf.building_table uses) 46.5 bldg/km2 (419 buildings in 9.0113 km2), against 25.9 at the proposed location. It is still below the 48.5 floor, but only by 1.7, so it does not meaningfully widen the domain: floor 48.5 -> 46.5 moves coverage from 2,957 to 2,997 cells (66.3% -> 67.2%) and from 94.67% to 94.94% of national buildings, against the 3,609 cells (80.9%, 98.04%) the proposed location would have reached. The box sits inside cell 0057_0050, whose own average is 18.98 bldg/km2, so it is a dense settlement pocket inside a sparse cell -- the exact pattern docs/methods/calibration-quadrats.md warns about, where a boundary traced around built-up extent reads far denser than the countryside it sits in. Confirmed clear of all 27 existing quadrats (nearest: Nasirabad Rural, 150.71 km).

The first JOSM export carried a second feature, a 1,975 m2 power=generator way whose centroid was 8.23 km outside the boundary, left over from the proposed location. roofclf.load_boundary unions every feature in the file, so registering it as-is would have produced a two-part MultiPolygon boundary: the square plus a 0.002 km2 fleck 8 km away, which is then what every downstream stage masks on. The owner removed it and the file now loads as a single valid polygon. Worth remembering as a general hazard of the drawn-boundary path: --geojson accepts any file JOSM writes, and a working layer usually holds more than the boundary.

52 installations, cross-confirmed (a second independent query also saw 54 raw features, 52 inside the boundary). Registered Rule-1 complete on the owner's declaration ("I have mapped all in JOSM I could find"), which is a visual sweep, not an Overpass check -- the distinction that made Muzaffargarh Rural Wide's original zero wrong (Box 15).

The profile is unlike any other rural quadrat in the set, and the reason is the move:

Kalat Rural Bahawalnagar Rural Tank Rural
installations 52 9 10
median install 429.3 m2 26.9 m2 26.0 m2
below the 400 m2 floor 40.4% 100% 100%
placement 45 ground / 7 rooftop -- 3 ground / 7 rooftop
packing nn_median_m 47.0 m 74.8 m 52.2 m

Mapped PV totals 28,412 m2 against 18,919 m2 of VIDA roof area for the whole quadrat: there is 1.5x more PV here than there is roof, and 22,064 m2 of it is ground-mount at or above the 400 m2 segmentation floor.

This makes the box unusable as a rooftop calibration quadrat in its current form, and the reason is a real gap in building_table, not in the mapping. parcel_pv_area's rule 3 skips any installation at or above YARD_MAX_INSTALLATION_M2 (400 m2), because above that floor ground-mount is segmentation's instrument and the atlas already counts it. The roof term has no such guard: it intersects every mapped PV polygon with every footprint, whatever its placement or size. Measured on this quadrat:

PV group installations buildings labelled has-PV roof-intersection area
ground, >= 400 m2 29 69 3,506.9 m2
ground, < 400 m2 16 9 264.6 m2
rooftop, >= 400 m2 2 5 540.4 m2
rooftop, < 400 m2 5 6 479.5 m2
total 52 89 of 419 (21.24%) 4,791.4 m2

69 of the 89 buildings (78%) would be labelled has-PV solely because a >= 400 m2 ground array clips them, on 3,824 m2 of roof whose median footprint is 29 m2 -- sheds and animal pens standing inside a ground-mount site, not hosts. The resulting 21.24% base rate would read higher than Mardan's (16.0%) in a 46.5 bldg/km2 rural box. Because the sparse density band's coverage ratio is extrapolated across millions of rural buildings, folding this quadrat in as-is would inflate national sub-400 rooftop capacity and double-count against segmentation's own est_mwp_rc_ground, which already prices those same 29 arrays. In every earlier quadrat this was harmless because ground-mount above the floor was rare; here it is 78% of the mapped PV area, which is precisely what the move was for.

Status: registered, Rule-1, and deliberately not folded into a roofclf refit. n_buildings/base_rate/nn_median_m are blank in results/calibration_quadrats.csv pending one, and roofclf.discover_quadrats globs data/labels/*_calib_*_boundary.geojson, so the next earthpv roof-classifier run picks this box up automatically -- extend parcel_pv_area's rule 3 to the roof term first, or exclude this stem, or the numbers above enter the fit silently. The open item is in docs/open-questions.md.

What the quadrat is good for, and it is not a small thing: 29 mapped ground-mount arrays between 400 m2 and 1,998 m2 in a rural setting. Outside the two solar-farm boxes (Quaid-e-Azam, Sukkur, both utility scale) this project has almost no ground truth at that size, and docs/issues/small-ground-mount-instrument.md is open for exactly that reason.

Box 18 -- three peri-urban screens (Attock, Layyah, Lodhran), geodesic squares 4.00 km2 each -- 2026-08-19

Six candidate boxes were screened the same way as Box 17's three rejects: centroids of national grid cells (~100 km2) whose measured building density falls in the 100-800 bldg/km2 calibration gap, each a coarse regional pointer rather than a verified settlement (data/labels/candidate_quadrats/, six *_periurban_calib_2km_candidate files, all stratum: "CANDIDATE -- not yet mapped, not a real quadrat"). The owner checked imagery recency over all six before mapping anything -- exactly the gate Box 17 says to check before drawing, not after. Only three had usable imagery: Attock, Layyah and Lodhran. Mardan (Khyber Pakhtunkhwa), Shikarpur (Sindh) and Sialkot District (Punjab, a different site from the existing sialkot_calib_1km quadrat) were dropped for stale/unusable imagery and were never registered -- their candidate files remain in data/labels/candidate_quadrats/ as a record of what was screened out and why, not as pending work.

The three survivors were registered as-is via scripts/new_calibration_quadrat.py (--geojson, --size-tag 2km), with no nudge onto a more specific settlement inside the box -- the candidate geometry the owner copied into data/labels/calibration_boundaries/ (the staging "boundary layer") is byte-identical to the original screening square. Confirmed clear against all 30 existing quadrats. Own building density (VIDA, representative_point().within(boundary), the same rule roofclf.building_table uses, not the coarser source-grid-cell average the candidate files carry):

quadrat own density (bldg/km2) source-grid density district installations median install m² % sub-400 m² placement packing nn_median_m
attock_periurban_calib_2km 894.75 549.29 Attock, Punjab 54 18.4 100.0% 27 ground / 27 rooftop 44.2 m
lodhran_periurban_calib_2km 237.25 249.96 Lodhran, Punjab 36 21.8 100.0% 8 ground / 28 rooftop 29.6 m
layyah_periurban_calib_2km 146.00 149.99 Layyah, Punjab 17 96.0 100.0% 12 ground / 5 rooftop 296.6 m

All three own densities sit comfortably inside the current calibrated domain (density.CALIBRATED_BLDG_DENSITY_KM2 = 48.5-5,258.00 bldg/km2) -- none of them widens it. Their value is filling out the middle of that range with a peri-urban regime distinct from both the industrial estates and the sparse rural extensions added in Box 14-17: all-sub-400 m² populations (unlike Kalat Rural), but at three different packing regimes -- Attock and Lodhran tightly packed (29.6-44.2 m, close to Lahore/Mardan's band), Layyah sparse (296.6 m, close to Muzaffargarh Rural's).

Update 2026-08-20: the owner declared all three Rule-1 complete, and they were folded into a fresh roofclf refit the same day (data/roofclf_refit_box18_20260820_v2.log, 30 quadrats -- every discoverable quadrat except kalat_rural_calib_3km, which stays excluded per its own entry above until building_table's roof term gets a placement/size guard). results/calibration_quadrats.csv now carries n_buildings/base_rate/nn_median_m for all three (scripts/build_calibration_quadrats_csv.py --folds data/roofclf/folds.csv) and rule1_complete=True. Fold results: Attock auc in the refit's folds.csv (56/3579 buildings have PV, 1.6%, rate_ratio 3.71), Lodhran (32/949, 3.4%, ratio 0.82), Layyah (15/584, 2.6%, ratio 0.87) -- all inside the historical 0.2-5x rate_ratio span, no fold collapsed. Median fold AUC moved 0.8735 -> 0.8574 (27 -> 30 quadrats), a small, expected give from added landscape diversity rather than a fit failure. National rescoring (roofclf-score-national --force, needed because the model itself changed) was launched the same day as unit earthpv-roofclf-score-box18, completed in ~2.5h (4,470 cells, 75.7M buildings scored). sub400-capacity/ge400-roof-capacity/atlas re-run followed the same day: sub-400 central 9,201.7 -> 8,922.7 MWp, sub-400 AND-gate 2,647.0 -> 2,515.3 MWp, the

= 400 m² roofclf rooftop replacement 7,405.0 -> 6,747.3 MWp, Best estimate 19,745.9 -> 18,826.7 MWp (90% CI 16,022-24,358), Verified (floor) 5,856.8 -> 5,725.1 MWp. All three components moved down together, consistent with the lower median fold AUC rather than any one quadrat dominating. The pre-refit model/tables are kept at data/roofclf_PRE_box18_20260820/ and data/roofclf_national_with_sppi/pakistan/ density_PRE_box18_20260820/ for comparison. Random-cell validation (20 cells, seed 20260820) was generated (results/pakistan_roofclf_validation/) and logged to results/roofclf_random_validation_log.csv but not yet reviewed -- same open backlog as the 2026-08-17 batch.

~~Status: registered, all three explicitly NOT Rule-1 ... Do not fold into a fit or count on their negatives until the owner declares a completeness pass for each, same as every other quadrat in this log.~~ (superseded above, 2026-08-20)

Box 19 -- eight candidate screens targeting the coverage-ratio fit's own density gap, geodesic squares 4.00 km2 each -- 2026-08-20 (proposed, unscreened)

Not registered, not mapped, no imagery check done -- these are candidate pointers only, same status as Box 18's original six before three were kept. Generated in response to roofclf calibration density mismatch's finding that the coverage-ratio/area-recall fit's sparsest supporting quadrat sits at 124 bldg/km2 (bahawalnagar_rural_calib_4p00km2) while 13.5% of in-domain buildings nationally (building-weighted median ~89 bldg/km2) sit sparser than that -- i.e. this targets the fit's own density support directly, not the domain floor (density. CALIBRATED_BLDG_DENSITY_KM2, unrelated and not what these would move).

Method: data/roofclf/national_cell_density.parquet (national 0.1° grid, cell/ n_buildings/density) filtered to the 50-125 bldg/km2 gap band, spatial-joined to geoBoundaries ADM1/ADM2 for province/district, filtered to >=20 km from every existing quadrat center (genuine new coverage, not redundant with an existing box), and to the four settled provinces (Punjab, Sindh, Khyber Pakhtunkhwa, Balochistan -- Azad Kashmir and Gilgit-Baltistan excluded, no existing quadrats there and GB is already ratio-check-exempt for an unrelated reason). Former-FATA/tribal KP districts (Waziristan, Mohmand, Bajaur, Khyber, Kurram, Orakzai) were explicitly excluded from the KP picks -- Waziristan specifically already failed Box 17's imagery-recency screen at a different (much sparser) density target, so it was not worth re-proposing here. Two candidates per province, spanning the low (~54-65 bldg/km2) and high (~102-125 bldg/km2) ends of the gap:

candidate province district source-grid density (bldg/km2)
rajanpur_gap_calib_2km Punjab Rajanpur 54.4
kasur_gap_calib_2km Punjab Kasur 124.4
khairpur_gap_calib_2km Sindh Khairpur 55.3
jacobabad_gap_calib_2km Sindh Jacobabad 107.9
deraismailkhan_gap_calib_2km Khyber Pakhtunkhwa Dera Ismail Khan 63.2
haripur_gap_calib_2km Khyber Pakhtunkhwa Haripur 102.1
pishin_gap_calib_2km Balochistan Pishin 53.8
mastung_gap_calib_2km Balochistan Mastung 118.8

Confirmed clear (0% geometric overlap, nearest existing quadrat 46-53 km away) against every existing quadrat via scripts/new_calibration_quadrat.py's own check_overlap. Files: data/labels/candidate_quadrats/*_gap_calib_2km_candidate.geojson (individually) and density_gap_candidates_combined.geojson (all eight). No automated imagery-recency check exists anywhere in this codebase (docs/issues/calibration-imagery-dating.md is still open, and results/calibration_quadrats.csv's imagery_layer/imagery_date columns are empty for every quadrat) -- each candidate still needs the same manual JOSM/Esri Wayback/ Google Earth Pro historical-imagery check that dropped three of Box 18's six, before scripts/new_calibration_quadrat.py --geojson ... --lat ... --lon ... turns any of these into a real quadrat. source_grid_cell/source_grid_density_bldg_km2 are the screening grid cell's average, not the quadrat's own density once actually drawn and mapped -- Box 18 measured its three survivors' own densities at 894.75/237.25/146.00 against source-grid values of 549.29/249.96/149.99, so expect these eight to move similarly once mapped.