Skip to content

Commit fc20f30

Browse files
authored
Search: Add rank modifier to search index
TYPE: Feature LINK: None
1 parent c81f8b0 commit fc20f30

10 files changed

Lines changed: 44 additions & 18 deletions

File tree

src/onegov/file/models/file.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ class SearchableFile(ORMSearchable):
111111
"""
112112

113113
fts_public = True
114+
fts_rank_modifier = 0.1
114115
fts_title_property = 'name'
115116
fts_properties = {
116117
'name': {'type': 'text', 'weight': 'A'},

src/onegov/org/models/meeting.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class Meeting(
3838

3939
fts_type_title = _('Meetings')
4040
fts_public = True
41+
fts_rank_modifier = 0.1
4142
fts_title_property = 'display_name'
4243
fts_properties = {
4344
'title_text': {'type': 'text', 'weight': 'A'},

src/onegov/org/models/meeting_item.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class MeetingItem(Base, ORMSearchable):
2323

2424
fts_type_title = _('Agenda')
2525
fts_public = True
26+
fts_rank_modifier = 0.1
2627
fts_title_property = 'title'
2728
fts_properties = {
2829
'title': {'type': 'text', 'weight': 'A'},

src/onegov/org/models/search.py

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from markupsafe import Markup
1212
from operator import itemgetter
1313
from sedate import align_date_to_day, as_datetime, replace_timezone, utcnow
14-
from sqlalchemy import case, func, inspect, type_coerce
14+
from sqlalchemy import func, inspect, type_coerce
1515
from sqlalchemy_utils import escape_like
1616

1717

@@ -329,22 +329,8 @@ def generic_search(self) -> Query[SearchIndex]:
329329
math.log(1e-6)
330330
)
331331
)
332-
# HACK: We may want to add some fts_rank_modifier property
333-
# to searchable models instead and add a column to
334-
# the index for that, that would allow us to weigh
335-
# results per type more effectively. Currently files
336-
# are he most egregious offender though, so we just
337-
# hardcode this into the query.
338-
* case(
339-
(SearchIndex.owner_tablename == 'files', 0.1),
340-
# NOTE: Tickets may be excluded entirely in the
341-
# future but for now we'll de-prioritize them
342-
(SearchIndex.owner_tablename == 'tickets', 0.2),
343-
# de-priorize RIS meeting documents
344-
(SearchIndex.owner_tablename == 'par_meetings', 0.1),
345-
(SearchIndex.owner_tablename == 'par_meeting_items', 0.1),
346-
else_=1.0
347-
)
332+
# per-type rank weight (Searchable::fts_rank_modifier)
333+
* SearchIndex.rank_modifier
348334
).desc().label('rank')
349335
)
350336
return self.apply_common_filters(query)

src/onegov/search/indexer.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ class IndexTask(TypedDict):
3737
public: bool
3838
suggestion: list[str]
3939
tags: list[str]
40+
rank_modifier: float
4041
last_change: datetime | None
4142
publication_start: datetime | None
4243
publication_end: datetime | None
@@ -175,6 +176,7 @@ def index(
175176
'_public': task['public'],
176177
'_access': task.get('access', 'public'),
177178
'_last_change': task['last_change'],
179+
'_rank_modifier': task['rank_modifier'],
178180
'_tags': _tags,
179181
'_suggestion': task['suggestion'],
180182
'_publication_start':
@@ -259,6 +261,7 @@ def index(
259261
SearchIndex.public: bindparam('_public'),
260262
SearchIndex.access: bindparam('_access'),
261263
SearchIndex.last_change: bindparam('_last_change'),
264+
SearchIndex.rank_modifier: bindparam('_rank_modifier'),
262265
SearchIndex._tags:
263266
bindparam('_tags', type_=ARRAY(Text)),
264267
SearchIndex.suggestion: bindparam('_suggestion'),
@@ -281,6 +284,7 @@ def index(
281284
'public': stmt.excluded.public,
282285
'access': stmt.excluded.access,
283286
'last_change': stmt.excluded.last_change,
287+
'rank_modifier': stmt.excluded.rank_modifier,
284288
'tags': stmt.excluded.tags,
285289
'suggestion': stmt.excluded.suggestion,
286290
'title_vector': stmt.excluded.title_vector,
@@ -631,6 +635,7 @@ def index_task(
631635
'public': obj.fts_public,
632636
'suggestion': [],
633637
'tags': obj.fts_tags or [],
638+
'rank_modifier': obj.fts_rank_modifier,
634639
'last_change': obj.fts_last_change,
635640
'publication_start': obj.fts_publication_start,
636641
'publication_end': obj.fts_publication_end,

src/onegov/search/mixins.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,12 @@ def fts_tags(self) -> list[str] | None:
164164
""" A list of tags associated with this content. """
165165
return None
166166

167+
@property
168+
def fts_rank_modifier(self) -> float:
169+
""" Multiplies this entry's search rank. 1.0 is neutral; a lower
170+
value de-prioritizes this type in mixed search results. """
171+
return 1.0
172+
167173

168174
class ORMSearchable(Searchable):
169175
""" Extends the default :class:`Searchable` class with sensible defaults

src/onegov/search/search_index.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ class SearchIndex(Base, UTCPublicationMixin):
6565
#: Suggestions for search functionality (Searchable::fts_suggestion)
6666
suggestion: Mapped[list[str] | None] = mapped_column(ARRAY(String))
6767

68+
#: Rank multiplier for this entry (Searchable::fts_rank_modifier)
69+
rank_modifier: Mapped[float] = mapped_column(default=1.0)
70+
6871
#: Postgres full-text search (fts) index (title)
6972
title_vector: Mapped[str] = mapped_column(TSVECTOR)
7073

src/onegov/search/upgrade.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from onegov.core.upgrade import upgrade_task, UpgradeContext
99
from onegov.search.utils import searchable_sqlalchemy_models
10-
from sqlalchemy import inspect, text, Column, String
10+
from sqlalchemy import inspect, text, Column, Float, String
1111
from sqlalchemy.dialects.postgresql import ARRAY, TSVECTOR
1212

1313

@@ -98,3 +98,18 @@ def split_title_and_data_tsvector_columns(context: UpgradeContext) -> None:
9898
columns=['data_vector'],
9999
postgresql_using='gin'
100100
)
101+
102+
103+
@upgrade_task('Add rank_modifier to search_index')
104+
def add_search_index_rank_modifier(context: UpgradeContext) -> None:
105+
if not context.has_table('search_index'):
106+
return
107+
if context.has_column('search_index', 'rank_modifier'):
108+
return
109+
context.operations.add_column(
110+
'search_index',
111+
Column(
112+
'rank_modifier', Float, nullable=False, server_default='1.0'
113+
)
114+
)
115+
# existing files/tickets rows correct themselves on next reindex

src/onegov/ticket/models/ticket.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ class Ticket(Base, TimestampMixin, ORMSearchable):
182182
# so we just manually specify it for now.
183183
fts_type_title = TranslationString('Tickets', domain='onegov.org')
184184
fts_public = False
185+
fts_rank_modifier = 0.2
185186
fts_title_property = 'number'
186187
fts_properties = {
187188
'number': {'type': 'text', 'weight': 'A'},

tests/onegov/search/test_indexer.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ def fts_tags(self) -> list[str]:
107107
'public': True,
108108
'suggestion': ['About'],
109109
'tags': ['aboutus', 'company'],
110+
'rank_modifier': 1.0,
110111
'publication_start': None,
111112
'publication_end': None,
112113
'last_change': creation_date,
@@ -148,6 +149,7 @@ def fts_tags(self) -> list[str]:
148149
'public': True,
149150
'suggestion': ['About'],
150151
'tags': ['aboutus', 'company'],
152+
'rank_modifier': 1.0,
151153
'publication_start': None,
152154
'publication_end': None,
153155
'last_change': creation_date,
@@ -292,6 +294,7 @@ def test_indexer_process(
292294
'publication_end': None,
293295
'suggestion': [],
294296
'tags': [],
297+
'rank_modifier': 1.0,
295298
'language': 'en',
296299
'title': 'Go ahead and jump',
297300
'properties': {'title': 'Go ahead and jump'},
@@ -339,6 +342,7 @@ def test_indexer_process_mid_transaction(
339342
'language': 'en',
340343
'suggestion': ['John Doe'],
341344
'tags': [],
345+
'rank_modifier': 1.0,
342346
'access': 'public',
343347
'public': True,
344348
'publication_start': None,
@@ -358,6 +362,7 @@ def test_indexer_process_mid_transaction(
358362
'language': 'en',
359363
'suggestion': ['Jane Doe'],
360364
'tags': [],
365+
'rank_modifier': 1.0,
361366
'access': 'public',
362367
'public': True,
363368
'publication_start': None,
@@ -379,6 +384,7 @@ def test_indexer_process_mid_transaction(
379384
'language': 'en',
380385
'suggestion': ['Paul Atishon', 'Atishon Paul'],
381386
'tags': [],
387+
'rank_modifier': 1.0,
382388
'access': 'public',
383389
'public': True,
384390
'publication_start': None,
@@ -418,6 +424,7 @@ def test_tags(
418424
'language': 'en',
419425
'suggestion': [],
420426
'tags': ['foo', 'BAR', 'baz'],
427+
'rank_modifier': 1.0,
421428
'access': 'public',
422429
'public': True,
423430
'publication_start': None,

0 commit comments

Comments
 (0)