From c670846cbd13f644477997a38b7d2a392a50f285 Mon Sep 17 00:00:00 2001 From: Agent Zero Date: Sun, 19 Jul 2026 21:32:24 +0200 Subject: [PATCH] Update unified search providers for unified contacts model (company queries contacts with type=company) --- .../builtins/unified_search/embedding.py | 2 +- app/plugins/builtins/unified_search/jobs.py | 6 +-- .../providers/company_provider.py | 39 ++++++++++-------- .../providers/contact_provider.py | 41 ++++++++++--------- app/plugins/builtins/unified_search/routes.py | 2 +- 5 files changed, 50 insertions(+), 40 deletions(-) diff --git a/app/plugins/builtins/unified_search/embedding.py b/app/plugins/builtins/unified_search/embedding.py index ca8a69f..b25fdad 100644 --- a/app/plugins/builtins/unified_search/embedding.py +++ b/app/plugins/builtins/unified_search/embedding.py @@ -181,7 +181,7 @@ async def index_entity( table_map = { "contact": "contacts", - "company": "companies", + "company": "contacts", "mail": "mails", "file": "files", "event": "calendar_entries", diff --git a/app/plugins/builtins/unified_search/jobs.py b/app/plugins/builtins/unified_search/jobs.py index 7549c5c..fc91807 100644 --- a/app/plugins/builtins/unified_search/jobs.py +++ b/app/plugins/builtins/unified_search/jobs.py @@ -131,7 +131,7 @@ async def index_company(ctx: dict[str, Any], company_id: str) -> None: try: eid = _parse_id(company_id) result = await db.execute( - text("SELECT tenant_id FROM companies WHERE id = :cid"), + text("SELECT tenant_id FROM contacts WHERE id = :cid"), {"cid": eid}, ) row = result.mappings().first() @@ -170,7 +170,7 @@ async def reindex(ctx: dict[str, Any], entity_type: str) -> None: table_map = { "contact": "contacts", - "company": "companies", + "company": "contacts", "mail": "mails", "file": "files", "event": "calendar_entries", @@ -216,7 +216,7 @@ async def embedding_batch(ctx: dict[str, Any]) -> None: table_map = { "contact": "contacts", - "company": "companies", + "company": "contacts", "mail": "mails", "file": "files", "event": "calendar_entries", diff --git a/app/plugins/builtins/unified_search/providers/company_provider.py b/app/plugins/builtins/unified_search/providers/company_provider.py index 9bea152..8d87612 100644 --- a/app/plugins/builtins/unified_search/providers/company_provider.py +++ b/app/plugins/builtins/unified_search/providers/company_provider.py @@ -1,4 +1,4 @@ -"""Company search provider.""" +"""Company search provider — queries contacts with type='company'.""" from __future__ import annotations @@ -13,7 +13,7 @@ logger = logging.getLogger(__name__) class CompanySearchProvider: - """Search provider for Company entities.""" + """Search provider for Company entities (contacts with type='company').""" entity_type = "company" @@ -24,13 +24,14 @@ class CompanySearchProvider: tenant_id: uuid.UUID, limit: int, ) -> list[dict[str, Any]]: - """Full-text search on companies.search_tsv.""" + """Full-text search on contacts.search_tsv where type='company'.""" sql = text( """ SELECT c.*, ts_rank(c.search_tsv, to_tsquery('pg_catalog.german', :q)) AS rank - FROM companies c + FROM contacts c WHERE c.tenant_id = :tid AND c.deleted_at IS NULL + AND c.type = 'company' AND c.search_tsv @@ to_tsquery('pg_catalog.german', :q) ORDER BY rank DESC LIMIT :lim @@ -50,13 +51,14 @@ class CompanySearchProvider: tenant_id: uuid.UUID, limit: int, ) -> list[dict[str, Any]]: - """Semantic search on companies.embedding.""" + """Semantic search on contacts.embedding where type='company'.""" sql = text( """ SELECT c.*, 1 - (c.embedding <=> cast(:emb AS vector)) AS score - FROM companies c + FROM contacts c WHERE c.tenant_id = :tid AND c.deleted_at IS NULL + AND c.type = 'company' AND c.embedding IS NOT NULL ORDER BY c.embedding <=> cast(:emb AS vector) LIMIT :lim @@ -78,9 +80,10 @@ class CompanySearchProvider: """Get text for embedding generation.""" sql = text( """ - SELECT name, description, industry - FROM companies - WHERE id = :eid AND tenant_id = :tid + SELECT displayname, name, email_1, email_2, phone_1, website, + mailing_city, tags, vat_code + FROM contacts + WHERE id = :eid AND tenant_id = :tid AND type = 'company' """ ) result = await db.execute(sql, {"eid": entity_id, "tid": tenant_id}) @@ -88,27 +91,31 @@ class CompanySearchProvider: if not row: return "" parts = [ + row.get("displayname", ""), row.get("name", ""), - row.get("description", ""), - row.get("industry", ""), + row.get("email_1", ""), + row.get("phone_1", ""), + row.get("website", ""), + row.get("mailing_city", ""), + row.get("tags", ""), ] return " ".join(str(p) for p in parts if p) def to_search_result(self, entity: object) -> dict[str, Any]: """Convert company to search result dict.""" if isinstance(entity, dict): - name = entity.get("name", "") - description = entity.get("description", "") or "" + name = entity.get("name", "") or entity.get("displayname", "") + email = entity.get("email_1", "") or "" entity_id = str(entity.get("id", "")) else: - name = getattr(entity, "name", "") - description = getattr(entity, "description", "") or "" + name = getattr(entity, "name", "") or getattr(entity, "displayname", "") + email = getattr(entity, "email_1", "") or "" entity_id = str(getattr(entity, "id", "")) return { "entity_type": self.entity_type, "entity_id": entity_id, "title": name, - "snippet": description[:200], + "snippet": email[:200], "score": 0.0, "data": {}, } diff --git a/app/plugins/builtins/unified_search/providers/contact_provider.py b/app/plugins/builtins/unified_search/providers/contact_provider.py index b891f9a..ea416d9 100644 --- a/app/plugins/builtins/unified_search/providers/contact_provider.py +++ b/app/plugins/builtins/unified_search/providers/contact_provider.py @@ -1,4 +1,4 @@ -"""Contact search provider.""" +"""Contact search provider — works with unified contacts table.""" from __future__ import annotations @@ -9,13 +9,11 @@ from typing import Any from sqlalchemy import text from sqlalchemy.ext.asyncio import AsyncSession -from app.models.contact import Contact - logger = logging.getLogger(__name__) class ContactSearchProvider: - """Search provider for Contact entities.""" + """Search provider for Contact entities (all types).""" entity_type = "contact" @@ -80,7 +78,8 @@ class ContactSearchProvider: """Get text for embedding generation.""" sql = text( """ - SELECT first_name, last_name, email, phone, mobile, notes + SELECT displayname, name, firstname, surname, email_1, email_2, + phone_1, phone_2, mailing_city, tags, contact_warning FROM contacts WHERE id = :eid AND tenant_id = :tid """ @@ -90,32 +89,36 @@ class ContactSearchProvider: if not row: return "" parts = [ - row.get("first_name", ""), - row.get("last_name", ""), - row.get("email", ""), - row.get("phone", ""), - row.get("mobile", ""), - row.get("notes", ""), + row.get("displayname", ""), + row.get("name", ""), + row.get("firstname", ""), + row.get("surname", ""), + row.get("email_1", ""), + row.get("email_2", ""), + row.get("phone_1", ""), + row.get("phone_2", ""), + row.get("mailing_city", ""), + row.get("tags", ""), ] return " ".join(str(p) for p in parts if p) def to_search_result(self, entity: object) -> dict[str, Any]: """Convert contact to search result dict.""" if isinstance(entity, dict): - first = entity.get("first_name", "") - last = entity.get("last_name", "") - email = entity.get("email", "") + displayname = entity.get("displayname", "") + email = entity.get("email_1", "") entity_id = str(entity.get("id", "")) + contact_type = entity.get("type", "") else: - first = getattr(entity, "first_name", "") - last = getattr(entity, "last_name", "") - email = getattr(entity, "email", "") + displayname = getattr(entity, "displayname", "") + email = getattr(entity, "email_1", "") entity_id = str(getattr(entity, "id", "")) + contact_type = getattr(entity, "type", "") return { "entity_type": self.entity_type, "entity_id": entity_id, - "title": f"{first} {last}".strip(), + "title": displayname, "snippet": email or "", "score": 0.0, - "data": {}, + "data": {"type": contact_type}, } diff --git a/app/plugins/builtins/unified_search/routes.py b/app/plugins/builtins/unified_search/routes.py index c49baec..fa2c4e5 100644 --- a/app/plugins/builtins/unified_search/routes.py +++ b/app/plugins/builtins/unified_search/routes.py @@ -265,7 +265,7 @@ async def search_stats( stats: dict = {} tables = { "contacts": "embedding", - "companies": "embedding", + "contacts": "embedding", "mails": "embedding", "files": "embedding", "calendar_entries": "embedding",