Skip to content

Commit

Permalink
Rework ranking
Browse files Browse the repository at this point in the history
  • Loading branch information
Tschuppi81 committed Jul 16, 2024
1 parent 3beab01 commit da09357
Show file tree
Hide file tree
Showing 5 changed files with 55 additions and 49 deletions.
94 changes: 51 additions & 43 deletions src/onegov/org/models/search.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from datetime import datetime
from operator import attrgetter

from elasticsearch_dsl.function import SF
Expand All @@ -7,8 +6,6 @@
from elasticsearch_dsl.query import MatchPhrase
from elasticsearch_dsl.query import MultiMatch
from functools import cached_property

from pytz import utc
from sqlalchemy import func

from onegov.core.collection import Pagination, _M
Expand All @@ -33,7 +30,7 @@ class Search(Pagination[_M]):
def __init__(self, request: 'OrgRequest', query: str, page: int) -> None:
super().__init__(page)
self.request = request
self.query = query
self.web_search = query

Check warning on line 33 in src/onegov/org/models/search.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/org/models/search.py#L33

Added line #L33 was not covered by tests

@cached_property
def available_documents(self) -> int:
Expand All @@ -46,13 +43,13 @@ def explain(self) -> bool:

@property
def q(self) -> str:
return self.query
return self.web_search

Check warning on line 46 in src/onegov/org/models/search.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/org/models/search.py#L46

Added line #L46 was not covered by tests

def __eq__(self, other: object) -> bool:
return (
isinstance(other, self.__class__)
and self.page == other.page
and self.query == other.query
and self.web_search == other.web_search
)

if TYPE_CHECKING:
Expand All @@ -67,11 +64,11 @@ def page_index(self) -> int:
return self.page

def page_by_index(self, index: int) -> 'Search[_M]':
return Search(self.request, self.query, index)
return Search(self.request, self.web_search, index)

Check warning on line 67 in src/onegov/org/models/search.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/org/models/search.py#L67

Added line #L67 was not covered by tests

@cached_property
def batch(self) -> 'Response | None': # type:ignore[override]
if not self.query:
if not self.web_search:

Check warning on line 71 in src/onegov/org/models/search.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/org/models/search.py#L71

Added line #L71 was not covered by tests
return None

search = self.request.app.es_search_by_request(
Expand All @@ -81,7 +78,7 @@ def batch(self) -> 'Response | None': # type:ignore[override]

# queries need to be cut at some point to make sure we're not
# pushing the elasticsearch cluster to the brink
query = self.query[:self.max_query_length]
query = self.web_search[:self.max_query_length]

Check warning on line 81 in src/onegov/org/models/search.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/org/models/search.py#L81

Added line #L81 was not covered by tests

if query.startswith('#'):
search = self.hashtag_search(search, query)
Expand Down Expand Up @@ -170,7 +167,7 @@ def subset_count(self) -> int:

def suggestions(self) -> tuple[str, ...]:
return tuple(self.request.app.es_suggestions_by_request(
self.request, self.query
self.request, self.web_search
))


Expand All @@ -189,7 +186,7 @@ class SearchPostgres(Pagination):

def __init__(self, request: 'OrgRequest', query: str, page: int):
self.request = request
self.query = query
self.web_search = query
self.page = page # page index

Check warning on line 190 in src/onegov/org/models/search.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/org/models/search.py#L188-L190

Added lines #L188 - L190 were not covered by tests

self.nbr_of_docs = 0
Expand All @@ -209,12 +206,12 @@ def available_results(self) -> int:

@property
def q(self) -> str:
return self.query
return self.web_search

Check warning on line 209 in src/onegov/org/models/search.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/org/models/search.py#L209

Added line #L209 was not covered by tests

def __eq__(self, other: object) -> bool:
if not isinstance(other, SearchPostgres):
return NotImplemented
return self.page == other.page and self.query == other.query
return self.page == other.page and self.web_search == other.web_search

Check warning on line 214 in src/onegov/org/models/search.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/org/models/search.py#L212-L214

Added lines #L212 - L214 were not covered by tests

def subset(self):
return self.batch

Check warning on line 217 in src/onegov/org/models/search.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/org/models/search.py#L217

Added line #L217 was not covered by tests
Expand All @@ -224,14 +221,14 @@ def page_index(self) -> int:
return self.page

Check warning on line 221 in src/onegov/org/models/search.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/org/models/search.py#L221

Added line #L221 was not covered by tests

def page_by_index(self, index: int):
return SearchPostgres(self.request, self.query, index)
return SearchPostgres(self.request, self.web_search, index)

Check warning on line 224 in src/onegov/org/models/search.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/org/models/search.py#L224

Added line #L224 was not covered by tests

@cached_property
def batch(self):
if not self.query:
if not self.web_search:
return None

Check warning on line 229 in src/onegov/org/models/search.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/org/models/search.py#L228-L229

Added lines #L228 - L229 were not covered by tests

if self.query.startswith('#'):
if self.web_search.startswith('#'):
results = self.hashtag_search()

Check warning on line 232 in src/onegov/org/models/search.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/org/models/search.py#L231-L232

Added lines #L231 - L232 were not covered by tests
else:
results = self.generic_search()

Check warning on line 234 in src/onegov/org/models/search.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/org/models/search.py#L234

Added line #L234 was not covered by tests
Expand All @@ -257,53 +254,64 @@ def load_batch_results(self):
sorted_events = sorted(events, key=lambda e: e.latest_occurrence.start)
return sorted_events + non_events

Check warning on line 255 in src/onegov/org/models/search.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/org/models/search.py#L251-L255

Added lines #L251 - L255 were not covered by tests

def _create_weighted_vector(self, model, language='simple'):
# for now weight the first field with 'A', the rest with 'B'
weighted_vector = [

Check warning on line 259 in src/onegov/org/models/search.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/org/models/search.py#L259

Added line #L259 was not covered by tests
func.setweight(
func.to_tsvector(
language,
getattr(model, field, '')
),
weight
)
for field, weight in zip(model.es_properties.keys(), 'ABBBBBBBBBB')
if not field.startswith('es_') # TODO: rename to fts_
]

# combine all weighted vectors
if weighted_vector:
combined_vector = weighted_vector[0]
for vector in weighted_vector[1:]:
combined_vector = combined_vector.op('||')(vector)

Check warning on line 275 in src/onegov/org/models/search.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/org/models/search.py#L272-L275

Added lines #L272 - L275 were not covered by tests
else:
combined_vector = func.to_tsvector(language, '')

Check warning on line 277 in src/onegov/org/models/search.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/org/models/search.py#L277

Added line #L277 was not covered by tests

return combined_vector

Check warning on line 279 in src/onegov/org/models/search.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/org/models/search.py#L279

Added line #L279 was not covered by tests

def generic_search(self):
doc_count = 0
results = []

language = locale_mapping(self.request.locale)
ts_query = func.websearch_to_tsquery(language, self.web_search)

Check warning on line 285 in src/onegov/org/models/search.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/org/models/search.py#L282-L285

Added lines #L282 - L285 were not covered by tests

for base in self.request.app.session_manager.bases:
for model in searchable_sqlalchemy_models(base):
if model.es_public or self.request.is_logged_in:
query = self.request.session.query(model)

Check warning on line 290 in src/onegov/org/models/search.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/org/models/search.py#L287-L290

Added lines #L287 - L290 were not covered by tests

if query.count():
doc_count += query.count()
query = query.filter(
model.fts_idx.op('@@')(func.websearch_to_tsquery(
language, self.query))
vector = self._create_weighted_vector(model, language)
rank_expression = func.ts_rank(

Check warning on line 295 in src/onegov/org/models/search.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/org/models/search.py#L292-L295

Added lines #L292 - L295 were not covered by tests
vector,
ts_query,
0 # normalization, ignore document length
)
query = query.order_by(
func.ts_rank_cd(
model.fts_idx,
func.websearch_to_tsquery(
language,
self.query)
)
query = query.filter(

Check warning on line 300 in src/onegov/org/models/search.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/org/models/search.py#L300

Added line #L300 was not covered by tests
model.fts_idx.op('@@')(ts_query)
)
results.extend(query.all())
query = query.order_by(rank_expression.desc())
res = query.all()
results.extend(res)

Check warning on line 305 in src/onegov/org/models/search.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/org/models/search.py#L303-L305

Added lines #L303 - L305 were not covered by tests

self.nbr_of_docs = doc_count
self.nbr_of_results = len(results)

Check warning on line 308 in src/onegov/org/models/search.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/org/models/search.py#L307-L308

Added lines #L307 - L308 were not covered by tests

# remove duplicates
results = list(set(results))

# sort items after ts_score, modified and created. If no timestamp
# is available, use default time
default_time = utc.localize(
datetime.datetime(1970, 1, 1))
return sorted(
results,
key=lambda k: (
k.get('ts_score', 10),
k.get('modified') or default_time,
k.get('created') or default_time,
),
reverse=False)
return tuple(set(results))

Check warning on line 311 in src/onegov/org/models/search.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/org/models/search.py#L311

Added line #L311 was not covered by tests

def hashtag_search(self):
q = self.query.lstrip('#')
q = self.web_search.lstrip('#')
results = []

Check warning on line 315 in src/onegov/org/models/search.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/org/models/search.py#L314-L315

Added lines #L314 - L315 were not covered by tests

for model in searchable_sqlalchemy_models(Base):

Check warning on line 317 in src/onegov/org/models/search.py

View check run for this annotation

Codecov / codecov/patch

src/onegov/org/models/search.py#L317

Added line #L317 was not covered by tests
Expand Down
1 change: 0 additions & 1 deletion src/onegov/people/models/person.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ class Person(Base, ContentMixin, TimestampMixin, ORMSearchable,
def es_suggestion(self) -> tuple[str, ...]:
return (self.title, f'{self.first_name} {self.last_name}')

# @property
@hybrid_property
def title(self) -> str:
""" Returns the Eastern-ordered name. """
Expand Down
4 changes: 2 additions & 2 deletions src/onegov/town6/templates/search.pt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<form class="searchbox" action="${request.link(model).split('?')[0]}" method="GET" data-typeahead="on" data-typeahead-source="${layout.suggestions_url}" data-typeahead-target="${layout.search_url}">
<label>${searchlabel}</label>
<div class="input-group">
<input class="input-group-field" id="search" data-typeahead-subject type="search" name="q" value="${model.query}" autocomplete="off" autocorrect="off" required autofocus />
<input class="input-group-field" id="search" data-typeahead-subject type="search" name="q" value="${model.web_search}" autocomplete="off" autocorrect="off" required autofocus />
<div class="input-group-button">
<button type="submit" class="button" aria-label="Search" i18n:attributes="aria-label">
<i class="fa fa-fw fa-search"></i>
Expand All @@ -29,7 +29,7 @@
</div>
</div>

<tal:b condition="connection and model.query">
<tal:b condition="connection and model.web_search">
<h2 i18n:translate>${resultslabel}</h2>
<div class="grid-x">
<div class="cell medium-8 small-12">
Expand Down
4 changes: 2 additions & 2 deletions src/onegov/town6/templates/search_postgres.pt
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<form class="searchbox" action="${request.link(model).split('?')[0]}" method="GET" data-typeahead="on" data-typeahead-source="${layout.suggestions_url}" data-typeahead-target="${layout.search_url}">
<label>${searchlabel}</label>
<div class="input-group">
<input class="input-group-field" id="search" data-typeahead-subject type="search" name="q" value="${model.query}" autocomplete="off" autocorrect="off" required autofocus />
<input class="input-group-field" id="search" data-typeahead-subject type="search" name="q" value="${model.web_search}" autocomplete="off" autocorrect="off" required autofocus />
<div class="input-group-button">
<button type="submit" class="button" aria-label="Search" i18n:attributes="aria-label">
<i class="fa fa-fw fa-search"></i>
Expand All @@ -30,7 +30,7 @@
</div>
</div>

<tal:b condition="connection and model.query">
<tal:b condition="connection and model.web_search">
<h2 i18n:translate>${resultslabel}</h2>
<div class="grid-x">
<div class="cell medium-8 small-12">
Expand Down
1 change: 0 additions & 1 deletion src/onegov/user/models/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ class User(Base, TimestampMixin, ORMSearchable):
def es_suggestion(self) -> tuple[str, str]:
return (self.realname or self.username, self.username)

# @property
@hybrid_property
def userprofile(self) -> list[str]:
if not self.data:
Expand Down

0 comments on commit da09357

Please sign in to comment.