feat: playground & restroom amenity POI types (#418)#426
Conversation
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add Playground and Restroom as map POI types so parents can find parks with good amenities, seed real locations from OpenStreetMap, and exclude these amenity types from news/events collection. - migration 066: playground + restroom icon types (name-classified, no activity fallbacks so a park that merely offers a playground keeps its own icon); pois.osm_id provenance/idempotency column; icons.default_hidden so amenities sit in the legend but start toggled off (avoid clutter); news_collection_excluded_types setting (default playground,restroom). - OSM import: backend/data/osm/amenities.json (240 features inside park boundaries: 201 restrooms, 39 playgrounds) + idempotent importer (import-osm-amenities.js) upserting point POIs by osm_id, named by containing park. - collection skip: backend/utils/poiClassify.js + newsService selection drops POIs whose type is excluded (name classification) or whose sole activity is a dedicated amenity. - frontend: default_hidden types start unchecked; OSM (ODbL) attribution on the satellite base layer. - two amenity icons (playground.svg, restroom.svg); poiClassify unit tests. Closes #418 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Gatehouse review dispositionFixed (doc, 1 commit):
Justified — false positives:
Justified — no change:
|
There was a problem hiding this comment.
Code Review
This pull request introduces "Playground" and "Restroom" as first-class map POI types, seeds them using a committed OpenStreetMap snapshot, and excludes these amenity types from news/events collection to save API budget. Feedback was provided to optimize the backend POI classifier by caching compiled regular expressions in matchesWholeWord to prevent performance degradation during batch processing.
| function matchesWholeWord(text, keyword) { | ||
| const escaped = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); | ||
| return new RegExp(`\\b${escaped}\\b`, 'i').test(text); | ||
| } |
There was a problem hiding this comment.
Compiling a new RegExp object on every call to matchesWholeWord inside nested loops for every POI can lead to significant performance degradation during batch processing of hundreds or thousands of POIs. Caching the compiled regular expressions by keyword avoids redundant compilation overhead.
| function matchesWholeWord(text, keyword) { | |
| const escaped = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); | |
| return new RegExp(`\\b${escaped}\\b`, 'i').test(text); | |
| } | |
| const regexCache = new Map(); | |
| function matchesWholeWord(text, keyword) { | |
| let regex = regexCache.get(keyword); | |
| if (!regex) { | |
| const escaped = keyword.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\$&'); | |
| regex = new RegExp('\\\\b' + escaped + '\\\\b', 'i'); | |
| regexCache.set(keyword, regex); | |
| } | |
| return regex.test(text); | |
| } |
Summary
Adds Playground and Restroom as map POI types so parents can find parks with good amenities, seeds real locations from OpenStreetMap, and excludes these amenity types from news/events collection.
playground+restroomicon rows. Name-classified with no activity fallbacks, so a full park that merely lists "Playground" among its activities keeps its own icon. Lowestsort_orderso the explicitrestroom/playgroundkeyword beats park-name keywords (e.g.mill→historic).icons.default_hidden: amenities appear in the legend but start toggled off to avoid clutter; users opt in.pois.osm_id: provenance + idempotency key (unique partial index).backend/data/osm/amenities.json— 240 features insideboundary_type='park'polygons (201 restrooms, 39 playgrounds). Idempotent importerimport-osm-amenities.jsupserts point POIs byosm_id, named from the OSM tag or"<Park> Restroom/Playground".news_collection_excluded_types(default["playground","restroom"]);backend/utils/poiClassify.js+ the two selection helpers drop excluded types (by name classification or sole-activity match). Verified 0 amenities leak; real parks still collect.No pois-schema change beyond the additive
osm_idcolumn;066is idempotent.Closes #418
Deploy steps (beyond image pull + restart)
066_add_amenity_poi_types.sqlruns automatically on container init.node /app/migrations/import-osm-amenities.js.Test plan
./run.sh build(frontend compiled via reload rebuild)🤖 Generated with Claude Code