Compare commits

...
Author SHA1 Message Date
Josh Hawkins d149de038a cover events/search in sub label filter test 2026-11-12 07:41:00 -06:00
Josh Hawkins a3e1966013 fix sub label filter for non-ascii names
The sub_labels filter lowercased the requested name in Python but the stored value with SQLite's LOWER(), which only folds ASCII. Cyrillic and other non-ASCII names never matched, so filtering by a face name returned no events. Both sides are now lowered in SQLite, which keeps ASCII matching case-insensitive and non-ASCII matching exact.
2026-11-12 07:34:25 -06:00
2 changed files with 48 additions and 22 deletions
+14 -22
View File
@@ -199,18 +199,14 @@ def events(
sub_label_clauses.append(Event.sub_label.is_null())
for label in filtered_sub_labels:
lowered = label.lower()
sub_label_clauses.append(
fn.LOWER(Event.sub_label.cast("text")) == lowered
) # include exact matches (case-insensitive)
# SQLite LOWER() only folds ASCII, so lower both sides in SQL to
# keep non-ASCII names matching exactly
stored = fn.LOWER(Event.sub_label.cast("text"))
sub_label_clauses.append(stored == fn.LOWER(label))
# include this label when part of a list (LIKE is case-insensitive in sqlite for ASCII)
sub_label_clauses.append(
fn.LOWER(Event.sub_label.cast("text")) % f"*{lowered},*"
)
sub_label_clauses.append(
fn.LOWER(Event.sub_label.cast("text")) % f"*, {lowered}*"
)
# include this label when part of a list
sub_label_clauses.append(stored % fn.LOWER(f"*{label},*"))
sub_label_clauses.append(stored % fn.LOWER(f"*, {label}*"))
sub_label_clause = reduce(operator.or_, sub_label_clauses)
clauses.append(sub_label_clause)
@@ -633,18 +629,14 @@ def events_search(
sub_label_clauses.append(Event.sub_label.is_null())
for label in filtered_sub_labels:
lowered = label.lower()
sub_label_clauses.append(
fn.LOWER(Event.sub_label.cast("text")) == lowered
) # include exact matches (case-insensitive)
# SQLite LOWER() only folds ASCII, so lower both sides in SQL to
# keep non-ASCII names matching exactly
stored = fn.LOWER(Event.sub_label.cast("text"))
sub_label_clauses.append(stored == fn.LOWER(label))
# include this label when part of a list (LIKE is case-insensitive in sqlite for ASCII)
sub_label_clauses.append(
fn.LOWER(Event.sub_label.cast("text")) % f"*{lowered},*"
)
sub_label_clauses.append(
fn.LOWER(Event.sub_label.cast("text")) % f"*, {lowered}*"
)
# include this label when part of a list
sub_label_clauses.append(stored % fn.LOWER(f"*{label},*"))
sub_label_clauses.append(stored % fn.LOWER(f"*, {label}*"))
event_filters.append(reduce(operator.or_, sub_label_clauses))
+34
View File
@@ -485,6 +485,40 @@ class TestHttpApp(BaseTestHttp):
assert sub_labels
assert sub_labels == [sub_label]
def test_get_event_list_filter_sub_labels(self):
sub_labels = {
"1.cyrillic": "Кузнецова Ира",
"2.joined": "Ира, Bob",
"3.ascii": "Alice",
"4.none": None,
}
mock_embeddings = Mock()
mock_embeddings.search_thumbnail.return_value = [(id, 0.1) for id in sub_labels]
self.app.frigate_config.semantic_search.enabled = True
self.app.embeddings = mock_embeddings
with AuthTestClient(self.app) as client:
for id, sub_label in sub_labels.items():
super().insert_mock_event(id)
Event.update(sub_label=sub_label).where(Event.id == id).execute()
search_params = {"search_type": "similarity", "event_id": "1.cyrillic"}
for path, params in (("/events", {}), ("/events/search", search_params)):
def ids(query: str) -> set[str]:
events = client.get(
path, params={**params, "sub_labels": query}
).json()
return {event["id"] for event in events}
assert ids("Кузнецова Ира") == {"1.cyrillic"}
assert ids("Ира") == {"2.joined"}
assert ids("bob") == {"2.joined"}
assert ids("ALICE") == {"3.ascii"}
assert ids("None") == {"4.none"}
####################################################################################################################
################################### GET /metrics Endpoint #########################################################
####################################################################################################################