From f2376580397170855a3aaf8bb64399bc87e7fa2f Mon Sep 17 00:00:00 2001 From: Itz-Agasta Date: Thu, 9 Jul 2026 11:49:04 +0530 Subject: [PATCH] Keep the object class/type in the result category field Revert the per-category match tracking; report the row's own class/type as before and collapse the POI query into a single indexed lookup. Re-skip the club=scout scenario: a merged placex row cannot surface a non-winning searched category under this model.(we will fix it soon) --- .../search/db_searches/near_search.py | 3 - .../search/db_searches/poi_search.py | 78 +++++++++---------- test/bdd/features/api/search/queries.feature | 4 + 3 files changed, 40 insertions(+), 45 deletions(-) diff --git a/src/nominatim_api/search/db_searches/near_search.py b/src/nominatim_api/search/db_searches/near_search.py index e75efc30..0e49d082 100644 --- a/src/nominatim_api/search/db_searches/near_search.py +++ b/src/nominatim_api/search/db_searches/near_search.py @@ -120,9 +120,6 @@ class NearSearch(base.AbstractSearch): 'countries': details.countries} for row in await conn.execute(sql, bind_params): result = nres.create_from_placex_row(row, nres.SearchResult) - # A matched placex row may carry several categories; report the - # one that was searched for rather than its class/type. - result.category = category result.accuracy = self.penalty + penalty result.bbox = Bbox.from_wkb(row.bbox) results.append(result) diff --git a/src/nominatim_api/search/db_searches/poi_search.py b/src/nominatim_api/search/db_searches/poi_search.py index 74db2e73..1d8671b1 100644 --- a/src/nominatim_api/search/db_searches/poi_search.py +++ b/src/nominatim_api/search/db_searches/poi_search.py @@ -12,7 +12,7 @@ import sqlalchemy as sa from . import base from ..db_search_fields import SearchData from ... import results as nres -from ...typing import SaBind, SaRow, SaSelect +from ...typing import SaBind, SaRow from ...sql.sqlalchemy_types import Geometry from ...connection import SearchConnection from ...types import SearchDetails, Bbox @@ -46,64 +46,58 @@ class PoiSearch(base.AbstractSearch): t = conn.t.placex - # Rows tagged with the category they were matched on. A placex row may - # carry several categories, so the matched one is not necessarily its - # class/type and must be tracked explicitly. - rows: list[tuple[SaRow, tuple[str, str]]] = [] + rows: list[SaRow] = [] + + category_match = sa.or_(*(base.category_filter(t, *category) + for category in self.qualifiers.values)) if details.near and details.near_radius is not None and details.near_radius < 0.2: # simply search in placex table - def _base_query() -> SaSelect: - return base.select_placex(t) \ - .add_columns((-t.c.centroid.ST_Distance(NEAR_PARAM)) - .label('importance'))\ - .where(t.c.linked_place_id == None) \ - .where(t.c.geometry.within_distance(NEAR_PARAM, NEAR_RADIUS_PARAM)) \ - .order_by(t.c.centroid.ST_Distance(NEAR_PARAM)) \ - .limit(LIMIT_PARAM) - - for category in self.qualifiers.values: - sql = _base_query().where(base.category_filter(t, *category)) + sql = base.select_placex(t) \ + .add_columns((-t.c.centroid.ST_Distance(NEAR_PARAM)) + .label('importance'))\ + .where(t.c.linked_place_id == None) \ + .where(t.c.geometry.within_distance(NEAR_PARAM, NEAR_RADIUS_PARAM)) \ + .where(category_match) \ + .order_by(t.c.centroid.ST_Distance(NEAR_PARAM)) \ + .limit(LIMIT_PARAM) - if self.countries: - sql = sql.where(t.c.country_code.in_(self.countries.values)) + if self.countries: + sql = sql.where(t.c.country_code.in_(self.countries.values)) - if details.viewbox is not None and details.bounded_viewbox: - sql = sql.where(t.c.geometry.intersects(VIEWBOX_PARAM)) + if details.viewbox is not None and details.bounded_viewbox: + sql = sql.where(t.c.geometry.intersects(VIEWBOX_PARAM)) - if details.excluded: - sql = sql.where(base.exclude_places(t)) + if details.excluded: + sql = sql.where(base.exclude_places(t)) - rows.extend((r, category) for r in await conn.execute(sql, bind_params)) + rows.extend(await conn.execute(sql, bind_params)) else: # use the categories column, backed by the ltree GiST index - for category in self.qualifiers.values: - sql = base.select_placex(t)\ - .add_columns(t.c.importance)\ - .where(base.category_filter(t, *category)) + sql = base.select_placex(t)\ + .add_columns(t.c.importance)\ + .where(category_match) - if details.viewbox is not None and details.bounded_viewbox: - sql = sql.where(t.c.geometry.intersects(VIEWBOX_PARAM)) + if details.viewbox is not None and details.bounded_viewbox: + sql = sql.where(t.c.geometry.intersects(VIEWBOX_PARAM)) - if details.near and details.near_radius is not None: - sql = sql.order_by(t.c.centroid.ST_Distance(NEAR_PARAM))\ - .where(t.c.geometry.within_distance(NEAR_PARAM, - NEAR_RADIUS_PARAM)) + if details.near and details.near_radius is not None: + sql = sql.order_by(t.c.centroid.ST_Distance(NEAR_PARAM))\ + .where(t.c.geometry.within_distance(NEAR_PARAM, NEAR_RADIUS_PARAM)) - if self.countries: - sql = sql.where(t.c.country_code.in_(self.countries.values)) + if self.countries: + sql = sql.where(t.c.country_code.in_(self.countries.values)) - if details.excluded: - sql = sql.where(base.exclude_places(t)) + if details.excluded: + sql = sql.where(base.exclude_places(t)) - sql = sql.limit(LIMIT_PARAM) - rows.extend((r, category) for r in await conn.execute(sql, bind_params)) + sql = sql.limit(LIMIT_PARAM) + rows.extend(await conn.execute(sql, bind_params)) results = nres.SearchResults() - for row, category in rows: + for row in rows: result = nres.create_from_placex_row(row, nres.SearchResult) - result.category = category - result.accuracy = self.penalty + self.qualifiers.get_penalty(category) + result.accuracy = self.penalty + self.qualifiers.get_penalty((row.class_, row.type)) result.bbox = Bbox.from_wkb(row.bbox) results.append(result) diff --git a/test/bdd/features/api/search/queries.feature b/test/bdd/features/api/search/queries.feature index 697b0a78..9321954f 100644 --- a/test/bdd/features/api/search/queries.feature +++ b/test/bdd/features/api/search/queries.feature @@ -93,6 +93,10 @@ Feature: Search queries | category | type | address+country | | amenity | restaurant | Liechtenstein | + @skip + # FIXME: the matched placex row carries several categories (club=scout and + # amenity=...); result.category reports its main class/type (amenity), so the + # searched category is not surfaced. Needs a decision on how to report it. Scenario: Search with key-value amenity When geocoding "[club=scout] Vaduz" Then all results contain -- 2.47.3