plugin JID Search: Update cutoff ratio + dedup:
- `RATIO_CUTOFF` has been adapted to avoid returning approximate matches. - In case of duplicate entries (e.g., rooms returned by the local search are also returned by the external one), we keep only the item with the most metadata. - Exclude `None` values in `jid_search` bridge method.
This commit is contained in:
parent
1a96c32c45
commit
de6c050db2
1 changed files with 37 additions and 2 deletions
|
|
@ -45,7 +45,8 @@ PLUGIN_INFO = {
|
|||
C.PI_HANDLER: "no",
|
||||
C.PI_DESCRIPTION: _("""Search for XMPP entities"""),
|
||||
}
|
||||
RATIO_CUTOFF = 0.6
|
||||
# Minimal ratio for fuzzy matching
|
||||
RATIO_CUTOFF = 0.75
|
||||
# Used when a search term matches a substring of a relevant data.
|
||||
PARTIAL_MATCH_RATIO = 0.8
|
||||
MAX_CACHE_SIZE = 10
|
||||
|
|
@ -99,6 +100,24 @@ class SearchItems(RootModel):
|
|||
self.root.sort(key=key, reverse=reverse) # type: ignore
|
||||
|
||||
|
||||
def _data_rank(item: SearchItem) -> tuple[bool, bool, bool, bool]:
|
||||
"""Rank a search item by its metadata completeness
|
||||
|
||||
We can have duplicate items when allowing external search (local MUC can be referenced
|
||||
globally). To choose the item to keep, we check the one with most metadata, and chose
|
||||
the local one in case of equivalent data.
|
||||
|
||||
@param item: The item to rank.
|
||||
@return: A tuple to use for comparaison.
|
||||
"""
|
||||
return (
|
||||
getattr(item, "nusers", None) is not None,
|
||||
bool(getattr(item, "description", None)),
|
||||
bool(getattr(item, "language", None)),
|
||||
getattr(item, "local", False),
|
||||
)
|
||||
|
||||
|
||||
class Options(BaseModel):
|
||||
entities: bool = Field(
|
||||
default=False, description="Search for entities for direct chat"
|
||||
|
|
@ -137,7 +156,9 @@ class JidSearch:
|
|||
client = self.host.get_client(profile)
|
||||
options = Options.model_validate_json(options_s)
|
||||
d = defer.ensureDeferred(self.search(client, search_term, options))
|
||||
d.addCallback(lambda search_items: search_items.model_dump_json())
|
||||
d.addCallback(
|
||||
lambda search_items: search_items.model_dump_json(exclude_none=True)
|
||||
)
|
||||
return d
|
||||
|
||||
async def search(
|
||||
|
|
@ -217,6 +238,20 @@ class JidSearch:
|
|||
)
|
||||
)
|
||||
|
||||
# Deduplicate by entity, keeping the most informative item (the global directory
|
||||
# often provides more data)
|
||||
by_entity = {}
|
||||
for item in matches:
|
||||
existing = by_entity.get(item.entity)
|
||||
if existing is None:
|
||||
by_entity[item.entity] = item
|
||||
elif _data_rank(item) > _data_rank(existing):
|
||||
# the entity keeps the best match score of its duplicates
|
||||
item.exact_match = item.exact_match or existing.exact_match
|
||||
item.relevance = max(item.relevance or 0, existing.relevance or 0) or None
|
||||
by_entity[item.entity] = item
|
||||
matches = SearchItems(list(by_entity.values()))
|
||||
|
||||
matches.sort(
|
||||
key=lambda item: (
|
||||
item.exact_match,
|
||||
|
|
|
|||
Loading…
Reference in a new issue