Sampling, global locations, and UI improvements - #30
Conversation
Round creation is now a synchronous DB insert with optional per-area auto-sampling via CampaignAreaSamplingWorkflow. The CreateRoundModal shows campaign area checkboxes with per-area sample counts instead of adaptive sampling parameters. Coverage pixels are created automatically when computing pixels for a campaign area, so they appear in the Predicted Coverage table immediately. Fix JSON parsing in sampling activity to handle extra R function output. Rename round filter "All Rounds" to "Sampled Only" for clarity and default to showing all pixels unfiltered.
Rename "Sampled Only" back to "All Rounds" - selecting any round inherently means sampled. Default round filter to "All Rounds" when round data exists, empty (show everything) otherwise.
…cache - Resolve data source by name prefix when frontend sends string instead of UUID - Fix enrichment activity to query pixels via pixel_area join (not campaign_id column) - Skip already-enriched pixels to avoid redundant processing - Update cached_population from pixel_metadata after enrichment completes - Use pixel_metadata as population source in cache computation queries
After pixels are adaptively sampled, optionally select up to N buildings per pixel using the same adaptive sampling service. Buildings share the level-18 quadkey with their pixel. Default: 5 in CreateRoundModal, 0 (disabled) if not specified. New activity sample_buildings_within_pixels groups locations by quadkey, calls sampling service for pixels with more buildings than the limit, and assigns selected buildings to the round via the coverage table.
Lift viewport state (lng/lat/zoom) to LocationsPage so it persists when MapView unmounts/remounts during map mode toggle. Previously, switching modes reset the map to the initial bounds.
- Add buildings_per_pixel option to cluster sampling - Show existing building counts in AddCampaignAreaModal - Preserve map viewport when toggling map mode - Fix TacticalMultiSelect styling - Optimize pixels_by_campaign migration
Locations are now global entities (like pixels and population) that associate with campaigns through spatial overlap via quadkey/pixel_area joins. The coverage table retains campaign_id for campaign-specific data. - Add migration script to drop campaign_id, update constraints, recreate tile functions and materialized view - Update all routes and activities to use spatial joins instead of campaign_id filters on locations - Update ON CONFLICT clauses to match new coverage unique constraint - Update building count queries to use pixel_area joins - Update duplicate detection to work globally by external_id
…on=0 for coverage
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8bbcaf265b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| LIMIT %s OFFSET %s | ||
| """, (campaign_id, batch_size, offset)) | ||
| """, (campaign_id, metadata_field_name, batch_size, offset)) |
There was a problem hiding this comment.
Remove OFFSET pagination from mutating enrichment query
In enrich_area_pixels, the loop pages unenriched pixels with LIMIT/OFFSET while each batch write updates pixel_metadata so those rows no longer match pm.metadata IS NULL OR NOT pm.metadata ? .... Because the filtered result set shrinks after every batch, incrementing offset skips remaining rows (for example, after enriching the first 500 rows, the next query starts at offset 500 of the now-smaller set), so many pixels are never processed even though total_pixels counted them initially.
Useful? React with 👍 / 👎.
| round_data = cursor.fetchone() | ||
| round_id = str(round_data[0]) | ||
|
|
||
| conn.commit() |
There was a problem hiding this comment.
Keep round creation atomic with workflow startup failures
This handler commits the new round before starting area workflows, but still returns a 500 if run_async(start_area_workflows()) fails; in that failure path, except rolls back an already-committed transaction, so clients see "failed to create round" even though the round exists and retries can create duplicates. This produces inconsistent state whenever Temporal is unavailable or one workflow start errors.
Useful? React with 👍 / 👎.
| SELECT id FROM locations WHERE id = %s | ||
| """, (uploaded_location_id,)) |
There was a problem hiding this comment.
Scope ID-based visit matching to the target campaign
The bulk visit import now accepts any existing locations.id without verifying campaign membership, so a CSV containing a valid UUID from another campaign will attach visits to that foreign location under the current campaign. This cross-campaign linkage bypasses the area-scoped proximity check and can corrupt campaign-level visit/coverage data.
Useful? React with 👍 / 👎.
Summary
Global locations: Locations (buildings) are now global entities that associate with campaigns through spatial overlap (quadkey/pixel_area joins) rather than a direct
campaign_idforeign key. Buildings imported by one campaign are automatically available to other campaigns covering the same area — no re-import needed. Thecoveragetable retainscampaign_idfor campaign-specific predictions.Building sampling: Added
buildings_per_pixeloption to round creation. After pixels are adaptively sampled, buildings within each selected pixel are sampled (default: 5 per pixel). Sampled buildings appear highlighted on the map and in the Predicted Coverage table.Round creation simplification: Round creation is now a synchronous DB insert with optional per-area auto-sampling. Coverage pixels are created automatically when computing pixels for a campaign area.
Adaptive sampling improvements: Batch SQL for round assignment, persist sampling status on campaign areas, optimize by removing GeoJSON/sf overhead, fix border pixel handling.
Pixel enrichment fixes: Resolve data sources by name prefix, query pixels via pixel_area joins, skip already-enriched pixels, update cached population from pixel metadata.
UI improvements: Preserve map viewport when toggling map mode, reduce building fill opacity to 0.1 so outlines are prominent, add sampled/pixel count column to coverage table, move pixel quadkey label to top-left, add category colors to campaign areas, fix TacticalMultiSelect styling.
Tile performance: PostgreSQL tuning migration for spatial queries, optimize pixels_by_campaign tile function, fix spatial index usage.
Test plan