Small fixes

This commit is contained in:
Andras Schmelczer 2026-06-14 14:52:44 +01:00
parent 54fbcb1ea6
commit 083f8a982e
24 changed files with 1505 additions and 79 deletions

View file

@ -3,9 +3,12 @@ import json
import polars as pl
from pipeline.transform.transform_poi import (
BUS_STOP_DEDUP_RADIUS_M,
_load_ofsted_ratings,
_school_icon_category_expr,
_significant_tokens,
osm_groceries_colocated_with_geolytix,
osm_stops_near_naptan,
transform,
transform_grocery_retail_points,
)
@ -71,6 +74,61 @@ def test_osm_groceries_colocated_with_geolytix_handles_empty_inputs():
assert osm_groceries_colocated_with_geolytix(osm, empty_glx) == []
def test_significant_tokens_keeps_ms_short_brand():
# "M&S" strips to "ms" (len 2); without the short-brand allowlist it would
# tokenise to set() and never dedupe against the GEOLYTIX "M&S" chain.
assert _significant_tokens("M&S") == {"ms"}
assert _significant_tokens("M&S Simply Food") == {"ms", "simply", "food"}
# Other sub-3-char tokens stay excluded (allowlist must remain minimal).
assert _significant_tokens("Co") == set()
def test_osm_groceries_colocated_with_geolytix_dedupes_m_and_s():
# GEOLYTIX brand "M&S" must now match a colocated OSM "M&S Simply Food".
geolytix = pl.DataFrame({"category": ["M&S"], "lat": [53.0], "lng": [-1.5]})
osm = pl.DataFrame(
{
"id": ["ms-dup"],
"name": ["M&S Simply Food"],
"lat": [53.00001],
"lng": [-1.5],
}
)
assert osm_groceries_colocated_with_geolytix(osm, geolytix, radius_m=50.0) == [
"ms-dup"
]
def test_osm_groceries_colocated_default_radius_catches_60m_offset():
# OSM and GEOLYTIX routinely place the same store 50-100m apart. The widened
# 100m default catches a ~60m same-brand duplicate that the old 50m missed.
geolytix = pl.DataFrame({"category": ["Tesco"], "lat": [53.0], "lng": [-1.5]})
osm = pl.DataFrame(
# ~60 m north of the GEOLYTIX point.
{"id": ["dup"], "name": ["Tesco Express"], "lat": [53.00054], "lng": [-1.5]}
)
assert osm_groceries_colocated_with_geolytix(osm, geolytix) == ["dup"]
assert osm_groceries_colocated_with_geolytix(osm, geolytix, radius_m=50.0) == []
def test_osm_stops_near_naptan_drops_within_radius_keeps_far():
# A ~60m-offset OSM platform duplicates the NaPTAN stop (dropped at the 100m
# default); a genuine opposite-direction stop ~160m away is kept.
naptan = pl.DataFrame({"id": ["n1"], "lat": [51.5000], "lng": [-0.1000]})
osm = pl.DataFrame(
{
"id": ["dup", "far"],
# ~60 m and ~160 m north of the NaPTAN stop.
"lat": [51.50054, 51.50144],
"lng": [-0.1000, -0.1000],
}
)
assert BUS_STOP_DEDUP_RADIUS_M == 100.0
assert osm_stops_near_naptan(osm, naptan) == ["dup"]
# At the old 50m radius the duplicate survived (the bug this fix closes).
assert osm_stops_near_naptan(osm, naptan, radius_m=50.0) == []
def _write_boundary(tmp_path):
"""A FeatureCollection whose single feature covers the London-area test
coords used by the transform() fixtures, so in_england_mask keeps them."""
@ -505,6 +563,32 @@ def test_osm_supermarkets_dropped(tmp_path):
assert convenience.height == 1
def test_transform_relabels_osm_iceland_to_brand(tmp_path):
# OSM tags Iceland as shop/frozen_food -> "Deli & Specialty". A genuine
# Iceland store GEOLYTIX lacks (away from any GEOLYTIX point) must survive
# AND be relabelled to the "Iceland" brand, not ship mislabelled as a deli.
raw = pl.DataFrame(
{
"id": ["ice1"],
"name": ["Iceland"],
"category": ["shop/frozen_food"],
"lat": [51.40],
"lng": [-0.05],
}
)
inputs = _write_transform_inputs(tmp_path, raw)
out = transform(**inputs).collect()
row = out.filter(pl.col("name") == "Iceland")
assert row.height == 1
assert row["group"][0] == "Groceries"
assert row["category"][0] == "Iceland"
assert row["icon_category"][0] == "Iceland"
# No deli pin remains for the relabelled store.
assert out.filter(pl.col("category") == "Deli & Specialty").height == 0
def test_transform_grocery_dedup_drops_only_grocery_aspect(tmp_path):
# The _write_transform_inputs fixture seeds 5 GEOLYTIX "Tesco" points at
# (51.52, -0.14). An OSM object colocated there carrying "Tesco" in its name