Skip to content

feat(spp_demo): curated PHL geodata + spp_demo_phl_luzon demo module (re-land from #76)#277

Open
gonzalesedwin1123 wants to merge 7 commits into
19.0from
reland/phl-demo
Open

feat(spp_demo): curated PHL geodata + spp_demo_phl_luzon demo module (re-land from #76)#277
gonzalesedwin1123 wants to merge 7 commits into
19.0from
reland/phl-demo

Conversation

@gonzalesedwin1123

@gonzalesedwin1123 gonzalesedwin1123 commented Jul 2, 2026

Copy link
Copy Markdown
Member

Re-lands the PHL demo-data portion of reverted PR #76 (revert: #271). Everything listed is contained in THIS PR's diff.

Summary

  • spp_demo: curated PHL shapes/areas (phl_curated.geojson, areas.xml) and demo generator updates.
  • New spp_demo_phl_luzon module: Luzon administrative areas (regions/provinces/municipalities as spp.area records with polygon shapes), population weights, area loader (depends on spp_demo's loader changes).
  • scripts/prepare_phl_geodata.py: the script that generates the curated geodata.

Added on top of #76 (not in the original)

  • gemini-code-assist review findings, all applied: the Luzon area loader batch-fetches areas by code (removing a per-feature N+1 search over ~700 features); the streamed geodata download is wrapped in a context manager; usage docs list the pandas/openpyxl deps required for population weights (plus a ruff line-length wrap).
  • Module docs brought to repo standard: spp_demo_phl_luzon's description moved from the non-standard static/description/DESCRIPTION.md to readme/DESCRIPTION.md, initial readme/HISTORY.md added, and the module's first generated README.rst/index.html committed (accepted by CI's generator).
  • Version bump + HISTORY entry for spp_demo; README rendered from the updated fragment.

Good to know

Verification

  • ./spp t spp_demo: 121 passed, 0 failed
  • ./spp t spp_demo_phl_luzon: 17 passed, 0 failed
  • codecov: patch pass
  • All gemini-code-assist threads resolved with fixing-commit references
  • Deep audit: no post-feat: geofence-based geographic targeting for programs #76 mainline commit touches any file here; deltas vs the pre-revert state are exactly the review fixes listed above

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds a geodata preparation script (prepare_phl_geodata.py) and updates the spp_demo module with refreshed Philippine geodata using PSA/HDX p-codes. It also introduces a new companion module, spp_demo_phl_luzon, which provides Luzon-scale demo areas, population weights, and an area loader. The review feedback highlights three key improvements: optimizing an N+1 query pattern in the Luzon area loader's shape-loading method, wrapping the streaming HTTP request in a context manager to prevent connection leaks, and updating the script's documented dependencies to include pandas and openpyxl so that population data processing does not silently fail.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread spp_demo_phl_luzon/models/area_loader.py
Comment thread scripts/prepare_phl_geodata.py Outdated
Comment thread scripts/prepare_phl_geodata.py Outdated
@codecov

codecov Bot commented Jul 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.11111% with 20 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.92%. Comparing base (bf61488) to head (464d709).
⚠️ Report is 9 commits behind head on 19.0.

Files with missing lines Patch % Lines
spp_demo_phl_luzon/models/area_loader.py 82.17% 18 Missing ⚠️
spp_demo_phl_luzon/__manifest__.py 0.00% 1 Missing ⚠️
spp_demo_phl_luzon/models/population_weights.py 97.43% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             19.0     #277      +/-   ##
==========================================
- Coverage   74.86%   68.92%   -5.94%     
==========================================
  Files        1093      163     -930     
  Lines       63718    14248   -49470     
==========================================
- Hits        47701     9821   -37880     
+ Misses      16017     4427   -11590     
Flag Coverage Δ
endpoint_route_handler ?
fastapi ?
spp_aggregation ?
spp_alerts ?
spp_analytics ?
spp_api_v2 ?
spp_api_v2_change_request ?
spp_api_v2_cycles ?
spp_api_v2_data ?
spp_api_v2_entitlements ?
spp_api_v2_gis ?
spp_api_v2_products ?
spp_api_v2_programs ?
spp_api_v2_service_points ?
spp_api_v2_simulation ?
spp_api_v2_vocabulary ?
spp_approval ?
spp_area ?
spp_area_hdx ?
spp_attachment_av_scan ?
spp_audit ?
spp_audit_programs ?
spp_banking ?
spp_base_common 90.26% <ø> (ø)
spp_base_setting ?
spp_case_base ?
spp_case_cel ?
spp_case_demo 94.34% <ø> (ø)
spp_case_entitlements ?
spp_case_graduation ?
spp_case_programs ?
spp_case_registry ?
spp_case_session ?
spp_cel_domain ?
spp_cel_event ?
spp_cel_registry_search ?
spp_cel_vocabulary ?
spp_change_request_v2 ?
spp_claim_169 ?
spp_cr_type_assign_program ?
spp_cr_types_advanced ?
spp_cr_types_base ?
spp_dci ?
spp_dci_client ?
spp_dci_client_dr ?
spp_dci_client_ibr ?
spp_dci_client_sr ?
spp_dci_compliance ?
spp_dci_demo 69.23% <ø> (ø)
spp_dci_indicators ?
spp_dci_server ?
spp_dci_server_social ?
spp_demo 73.63% <ø> (+0.08%) ⬆️
spp_demo_phl_luzon 86.11% <86.11%> (-1.49%) ⬇️
spp_disability_registry ?
spp_drims ?
spp_drims_sl ?
spp_drims_sl_demo ?
spp_encryption ?
spp_farmer_registry ?
spp_farmer_registry_cr ?
spp_farmer_registry_demo 60.97% <ø> (+8.23%) ⬆️
spp_farmer_registry_vocabularies ?
spp_gis ?
spp_gis_indicators ?
spp_gis_report ?
spp_graduation ?
spp_grm ?
spp_grm_case_link ?
spp_grm_demo 81.16% <ø> (+1.29%) ⬆️
spp_hazard ?
spp_hazard_programs ?
spp_hxl_area ?
spp_import_match ?
spp_indicator ?
spp_irrigation ?
spp_land_record ?
spp_metric ?
spp_metric_service ?
spp_metrics_core ?
spp_metrics_services ?
spp_mis_demo_v2 70.44% <ø> (+2.51%) ⬆️
spp_oauth ?
spp_program_geofence ?
spp_programs 65.27% <ø> (ø)
spp_registrant_gis ?
spp_registry 86.83% <ø> (ø)
spp_registry_group_hierarchy ?
spp_scoring ?
spp_scoring_programs ?
spp_security 66.66% <ø> (ø)
spp_service_points ?
spp_simulation ?
spp_starter_disability_registry ?
spp_starter_farmer_registry ?
spp_starter_social_registry ?
spp_starter_sp_mis ?
spp_statistic ?
spp_storage_backend ?
spp_studio ?
spp_studio_change_requests ?

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
spp_demo/__manifest__.py 0.00% <ø> (ø)
spp_demo/models/demo_data_generator.py 70.81% <ø> (+0.12%) ⬆️
spp_demo_phl_luzon/__init__.py 100.00% <100.00%> (ø)
spp_demo_phl_luzon/models/__init__.py 100.00% <100.00%> (ø)
spp_demo_phl_luzon/__manifest__.py 0.00% <0.00%> (ø)
spp_demo_phl_luzon/models/population_weights.py 97.43% <97.43%> (ø)
spp_demo_phl_luzon/models/area_loader.py 82.17% <82.17%> (-1.87%) ⬇️

... and 934 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

… module (from #76)

Re-lands the PHL demo data portion of reverted PR #76: curated PHL
geojson shapes and areas, demo data generator and area loader test
updates, the prepare_phl_geodata.py preparation script, and the new
spp_demo_phl_luzon module (Luzon demo areas + population weights).

Files restored verbatim from the pre-revert merged state (8bf9a3a).
spp_demo bumped to 19.0.2.1.0 with a HISTORY entry.
- area_loader: batch-fetch areas by code instead of per-feature search (N+1
  over ~700 Luzon features).
- prepare_phl_geodata.py: close streamed download via context manager; usage
  docs include pandas/openpyxl needed for population weights.
@gonzalesedwin1123

Copy link
Copy Markdown
Member Author

All three gemini-code-assist findings applied (commit 91c9e06): batched area lookup replacing the per-feature N+1 search, streamed download wrapped in a context manager, and usage docs now include pandas/openpyxl. ./spp t spp_demo_phl_luzon: 17/0.

…initial HISTORY

readme/DESCRIPTION.md is the repo-standard fragment location (the generator
builds README.rst from it); static/description/DESCRIPTION.md was never
picked up. README will be aligned to CI's renderer output in a follow-up
commit.
Fragments are plain bullet lists (no RST tables), so local rendering matches
CI's pinned generator; CI's generated-files check will confirm.
@gonzalesedwin1123 gonzalesedwin1123 marked this pull request as ready for review July 6, 2026 07:19
@kneckinator

Copy link
Copy Markdown
Contributor

Code review: PR #277 — curated PHL geodata + spp_demo_phl_luzon

Reviewed the full gh pr diff plus surrounding code on the 19.0 base. Data files were spot-checked and validated programmatically rather than read line-by-line.

Overview

  • spp_demo: refreshes data/countries/phl/areas.xml (+1017/-200) and data/shapes/phl_curated.geojson (170 features) to PSA/HDX p-code-based codes and external IDs (e.g. Quezon City is now code=PH1307404, xmlid area_phl_ph1307404). One-line docstring touch-up in demo_data_generator.py; test expectations in tests/test_demo_area_loader.py updated to the new codes/hierarchy; version bump 19.0.2.0.0 -> 19.0.2.1.0 + HISTORY entry; README/index regenerated.
  • New spp_demo_phl_luzon: 821 spp.area records (data/areas_luzon.xml), 771-municipality population_weights.csv, data/shapes/phl_luzon.geojson (821 features, 2.1 MB), two TransientModel loaders (area_loader.py, population_weights.py), security CSV, tests, readme fragments, manifest.
  • scripts/prepare_phl_geodata.py: dev-time generator (uv-run, downloads from HDX) that produces all of the above.

Data files validated: both GeoJSON files parse as valid FeatureCollections; both XML files are well-formed; areas_luzon.xml has 821 records matching 821 GeoJSON features; all 813 parent_id refs resolve within the file (0 dangling); area_type_id refs resolve to the spp_demo.area_kind_phl_* records defined in spp_demo/data/countries/phl/area_kinds.xml; CSV is 771 rows + header, matching the test assertion.


Confirmed defects

1. [Medium — downstream regression] spp_mis_demo_v2 story-area assignments break for the Philippines.
Base 19.0 areas.xml defines named external IDs (area_phl_calamba, area_phl_quezon_city, area_phl_makati, area_phl_manila, area_phl_pasig, area_phl_taguig, area_phl_bacoor, area_phl_antipolo, area_phl_san_pablo, area_phl_santa_rosa, area_phl_dasmarinas, area_phl_qc_commonwealth, area_phl_makati_poblacion, area_phl_calamba_real). This PR removes all of them, replacing with p-code IDs (area_phl_ph1307404, …). spp_mis_demo_v2/models/mis_demo_generator.py STORY_AREA_MAP (lines ~3873+) still references the old named IDs for the fil_PH locale, and that file is not in this PR's diff. At the consumption site (line ~4046):

area = self.env.ref(area_xmlid, raise_if_not_found=False)
if not area:
    _logger.warning("[spp.mis.demo] Area %s not found for story %s", area_xmlid, story_id)
    continue

So it won't crash, but all 14 fil_PH story registrants silently lose their intended municipality assignment (a warning per story). This defeats the purpose of the new geodata for the PHL demo and is directly relevant because the misluzon profile bundles spp_mis_demo_v2,spp_demo_phl_luzon (spp:137). The re-land is incomplete: it should either preserve the named external IDs as aliases in areas.xml, or update STORY_AREA_MAP to the new p-code IDs. (tgo/lka entries are unaffected since only the PHL areas file changed.) This is the one item I'd block on.

2. [Medium — completeness / inaccurate docs] The Luzon loaders are never invoked by any runtime path.
spp_demo_phl_luzon has no post_init_hook, areas_luzon.xml/population_weights.csv are (correctly) only in oca_data_manual, and nothing calls load_luzon_areas(), get_weights(), or get_weights_by_area_id() outside the module's own tests — demo_data_generator.py only ever calls the base spp.demo.area.loader, and the misluzon CLI profile merely installs the module. Consequently, installing spp_demo_phl_luzon does not populate Luzon areas or apply population weights. readme/DESCRIPTION.md ("Regions, provinces, and municipalities of Luzon loaded as spp.area records on install") is therefore inaccurate. Please confirm whether the wiring is intended for a follow-up PR; if so, the DESCRIPTION should be softened.

Things to verify manually

3. [Low–Medium] Uninstall side-effect of _link_existing_areas. It creates ir.model.data rows owned by spp_demo_phl_luzon that point at spp.area records created by spp_demo (PH04/PH13 overlaps). Uninstalling spp_demo_phl_luzon could cascade-delete those base spp_demo areas. Worth a quick install→uninstall check.

4. [Low] shapely dependency. area_loader._load_shapes uses shapely but the manifest's external_dependencies doesn't declare it (it's guarded by try/except and the geo_polygon field check, so it degrades gracefully). Also from shapely.geometry import shape is imported inside the per-feature loop rather than at module top — minor.


What's clean / done well

  • Data integrity: all XML/GeoJSON/CSV valid and mutually consistent (counts and refs check out as noted above).
  • Security & performance in the loaders: lxml parser hardened against XXE (resolve_entities=False, no_network=True); shapes loader batch-fetches areas by code (no N+1 over ~800 features); population CSV cached at class level; idempotent re-load via pre-linked ir.model.data.
  • Manifest completeness: license: LGPL-3, author, category, summary, development_status, correct depends: ["spp_demo"]; sensible security/ir.model.access.csv (read-only for users, no unlink). oca_data_manual used appropriately for runtime-loaded files.
  • Versioning/changelog consistency: spp_demo 19.0.2.1.0 matches its HISTORY top entry; spp_demo_phl_luzon 19.0.1.0.0 matches its HISTORY.
  • Licensing/attribution: HDX COD-AB / PSA / NAMRIA under CC BY-IGO is attributed in the manifest comment, DESCRIPTION.md, the XML file headers, and the generator script. Redistributing CC BY data alongside LGPL-3 code is fine — the data license and the code license are independent, and attribution is present. No conflict.
  • Tests: the model-level tests are thorough in isolation — loader idempotency, overlapping-base-area handling, GIS-absent path, weight parsing/caching/skip-invalid/area-id mapping. (Note: no test covers the spp_mis_demo_v2 integration in defect deps(actions): Bump docker/build-push-action from 5 to 6 #1, which is why the regression slips through green.)
  • Repo hygiene: ~2.7 MB of geodata added, dominated by phl_luzon.geojson (2.1 MB). It's already simplified (~500 m tolerance) and the generator is dev-time only (not in install/CI). Acceptable for a demo module, though it's permanent in git history. The large net deletion count is mostly the previous verbose curated GeoJSON being replaced by the simplified one — a net improvement.

Bottom line: the new module and data are well-constructed and internally consistent. The one thing I'd resolve before merge is defect #1 (the spp_mis_demo_v2 STORY_AREA_MAP p-code mismatch), with defect #2 (loaders/DESCRIPTION not matching actual wiring) a close second.

@kneckinator

Copy link
Copy Markdown
Contributor

The spp_mis_demo_v2 downstream breakage flagged in the review above is now fixed in #295 (draft).

This PR renames the PHL area external IDs to curated PSGC p-codes, but spp_mis_demo_v2's STORY_AREA_MAP still referenced the old named IDs (area_phl_calamba, area_phl_quezon_city, …) — resolved with raise_if_not_found=False, so PHL story registrants silently lost their area assignment. #295 remaps them to the new p-codes.

Merge order: #295 depends on this PR (and #280), so it must merge after both and be rebased onto the result.

…oaders

- test_population_weights: patch file_path at its imported location
  (odoo.addons.spp_demo_phl_luzon.models.population_weights.file_path)
  so the stub actually takes effect instead of being a no-op.
- area_loader._load_shapes: hoist the shapely import above the feature
  loop and bail early on ImportError, avoiding one swallowed ImportError
  (and log warning) per feature when shapely is absent.
- population_weights.get_weights: extract pcode inside the try/except so a
  malformed row skips gracefully instead of aborting the whole load.

@kneckinator kneckinator left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code review

Reviewed the ~1,000 lines of hand-written Python (the bulk of the diff is generated GeoJSON/XML data). Overall this is a clean re-land — the _link_existing_areas pre-link strategy neatly solves the overlapping-code problem, XXE hardening on the lxml parse is a nice touch, the N+1 fixes are correctly applied, and test coverage is genuinely good (idempotency, overlap, caching, invalid-row skipping).

Four findings below. 1–3 are now addressed in 464d709 (pushed to this branch); #4 is informational.

1. Ineffective mock patch in test_population_weights.py — ✅ fixed in 464d709

population_weights.py does from odoo.tools.misc import file_path and calls the bare name, so patching odoo.tools.misc.file_path did not rebind the imported reference — the stub was a no-op and the tests only passed because open was mocked and the real CSV path happened to exist. Repointed the patch to odoo.addons.spp_demo_phl_luzon.models.population_weights.file_path.

2. shapely import inside the per-feature loop (area_loader.py::_load_shapes) — ✅ fixed in 464d709

The from shapely.geometry import shape was inside the loop. If shapely is genuinely missing, the ImportError fires once per feature (~700×) and is swallowed by the per-feature except, flooding the log. Hoisted the import above the loop with an early return 0 on ImportError. (No manifest change needed: shapes only load when geo_polygon exists, which requires spp_gis, and spp_gis already declares shapely.)

3. Unwrapped row["pcode"] in get_weights (population_weights.py) — ✅ fixed in 464d709

A missing/renamed pcode column raised an uncaught KeyError that aborted the whole load, whereas a bad population value was skipped gracefully — an asymmetry next to the defensive handling. Moved pcode extraction inside the try (added AttributeError for good measure) so a malformed row skips gracefully.

4. Two full parses of areas_luzon.xml per load (informational — not changed)

_link_existing_areas parses the XML with lxml to extract (xml_id, code) pairs, then convert_file parses it again. Acceptable for a one-shot demo loader; noting it only for completeness, no change recommended.

Risk assessment

  • Correctness: the programmatic-load design is the sharp edge and it's handled well and tested (overlap + idempotency). No blocking issues.
  • Security: XXE mitigated; the transient loader's create/write grant to base.group_user only touches bundled spp.area codes — acceptable for demo tooling.
  • Performance: N+1s already addressed.

Verdict: Approve. Findings were minor; 1–3 are fixed. Heads-up per the PR body — merged #278's misluzon profile soft-depends on this, so ./spp start --demo misluzon stays broken until this lands.

Note: local pre-commit couldn't run the semgrep hook (Python 3.14 / protobuf incompatibility in the cached env); all other hooks pass. CI's semgrep will cover it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants