Skip to content

Commit b981f26

Browse files
committed
Add test for ancestor-cascade pcode bug in population weighting
The existing test for boundary-id population resolution only used level-4 boundaries with a single non-null adm4_pcode column, so it passed regardless of whether the code picked the first non-null adm{n}_pcode column or the one matching the boundary's own level. Real admin boundaries below level 1 cascade ancestor pcodes onto the same row (a level-3 upazila also carries its division's adm1_pcode and district's adm2_pcode), so the new test uses that shape and verifies population weighting resolves the boundary's own pcode rather than an ancestor's.
1 parent 47b8c05 commit b981f26

1 file changed

Lines changed: 39 additions & 0 deletions

File tree

truecover-backend/tests/test_cluster_sampling_generalization.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,45 @@ def test_population_weighting_by_boundary_id_with_own_pcode_uses_pcode_populatio
231231

232232
assert picks[heavy_id] > picks[light_id]
233233

234+
def test_population_for_identifier_uses_own_level_pcode_not_ancestor_pcode(self, db_conn, monkeypatch):
235+
# A level-3 upazila row carries its own adm3_pcode alongside its
236+
# ancestors' adm1_pcode (division) and adm2_pcode (district), which
237+
# are cascaded onto every descendant row - this is how real BD
238+
# admin_boundaries data looks, not the "only one adm{n}_pcode set"
239+
# shape the other tests here use. Resolving "first non-null column"
240+
# would grab the division's adm1_pcode and sum the whole division's
241+
# population instead of just this upazila's.
242+
from temporal.activities import cluster_sampling
243+
from temporal.activities.cluster_sampling import _population_for_identifier
244+
245+
monkeypatch.setattr(cluster_sampling, 'get_db_connection', lambda: db_conn)
246+
monkeypatch.setattr(cluster_sampling, 'return_db_connection', lambda conn: None)
247+
248+
cursor = db_conn.cursor()
249+
cursor.execute("""
250+
INSERT INTO admin_boundaries (name, iso3, level, adm1_pcode, adm2_pcode, adm3_pcode, boundary_type)
251+
VALUES ('Test Upazila SCPW Cascade', 'BD', 3, 'BDSCPWDIV', 'BDSCPWDIST', 'BDSCPWUPZ', 'upazila')
252+
RETURNING id
253+
""")
254+
upazila_id = str(cursor.fetchone()[0])
255+
256+
# Division-level pixel: matches the ancestor adm1_pcode cascaded
257+
# onto the upazila row, with a large population.
258+
cursor.execute("""
259+
INSERT INTO pixels (quadkey, geometry, latitude, longitude, level, adm1_pcode, population)
260+
VALUES ('test_scpw_cascade_division', ST_GeomFromText('POLYGON((96 29, 96.01 29, 96.01 29.01, 96 29.01, 96 29))', 4326), 29, 96, 18, 'BDSCPWDIV', 10000000)
261+
""")
262+
# Upazila's own-level pixel: matches adm3_pcode, with a small,
263+
# distinct population.
264+
cursor.execute("""
265+
INSERT INTO pixels (quadkey, geometry, latitude, longitude, level, adm3_pcode, population)
266+
VALUES ('test_scpw_cascade_upazila', ST_GeomFromText('POLYGON((97 30, 97.01 30, 97.01 30.01, 97 30.01, 97 30))', 4326), 30, 97, 18, 'BDSCPWUPZ', 5000)
267+
""")
268+
269+
population = _population_for_identifier(cursor, upazila_id)
270+
271+
assert population == 5000.0
272+
234273
def test_boundary_id_with_more_pixels_is_weighted_higher(self, db_conn, monkeypatch):
235274
from temporal.activities import cluster_sampling
236275
from temporal.activities.cluster_sampling import select_clusters

0 commit comments

Comments
 (0)