feat(osm): Phase 6 — proximity materialized views + refresh cron

* `mv_park_nearest_poi` — for each IndustrialPark, the 3 nearest POI of
  six priority categories (HOSPITAL/BANK/GAS/BUS/METRO/POLICE) within
  5km. Refreshed weekly. Pre-aggregated 6,513 rows from the live
  catalog so the KCN sidebar can render in <50ms instead of running
  ST_Distance for every page hit.
* `mv_poi_density_by_province` — count of POI per (province, category)
  for analytics heatmaps.
* `OsmSyncService.refreshMaterializedViews()` calls
  `REFRESH MATERIALIZED VIEW CONCURRENTLY` so reads aren't blocked.
* New cron entry `weeklyRefreshViews` (Sun 04:00 ICT) and admin
  endpoint `POST /admin/osm/refresh-views` for on-demand refresh.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ho Ngoc Hai
2026-05-01 12:44:57 +07:00
parent a9770a5f93
commit 884a8d2a63
4 changed files with 80 additions and 0 deletions

View File

@@ -0,0 +1,39 @@
-- Phase 6: cached proximity scores for fast filter / sort.
-- Refreshed by `OsmRefreshViewsCron` (Sun 03:00 ICT) and on admin-trigger.
-- For each industrial park, the 3 nearest POI of each priority category.
CREATE MATERIALIZED VIEW IF NOT EXISTS "mv_park_nearest_poi" AS
SELECT
p.id AS park_id,
poi.category::text AS category,
poi.id AS poi_id,
poi.name AS poi_name,
ROUND(ST_Distance(p.location::geography, poi.location::geography)::numeric, 0)::int AS distance_m,
ROW_NUMBER() OVER (
PARTITION BY p.id, poi.category
ORDER BY ST_Distance(p.location::geography, poi.location::geography)
) AS rank
FROM "IndustrialPark" p
CROSS JOIN LATERAL (
SELECT id, name, category, location
FROM "Poi"
WHERE "isPublic" = true
AND category::text IN ('HOSPITAL','BANK','GAS_STATION','BUS_STATION','METRO_STATION','POLICE')
AND ST_DWithin(location::geography, p.location::geography, 5000)
) poi;
CREATE INDEX IF NOT EXISTS "mv_park_nearest_poi_park_cat" ON "mv_park_nearest_poi"(park_id, category, rank);
CREATE UNIQUE INDEX IF NOT EXISTS "mv_park_nearest_poi_uq" ON "mv_park_nearest_poi"(park_id, category, poi_id);
-- Per-province POI density (count by category) for analytics heatmaps.
CREATE MATERIALIZED VIEW IF NOT EXISTS "mv_poi_density_by_province" AS
SELECT
"provinceCode",
category::text AS category,
COUNT(*)::int AS poi_count
FROM "Poi"
WHERE "isPublic" = true
AND "provinceCode" IS NOT NULL
GROUP BY "provinceCode", category;
CREATE INDEX IF NOT EXISTS "mv_poi_density_by_province_idx" ON "mv_poi_density_by_province"("provinceCode", category);