diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index fe15ca9..a83144e 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -304,6 +304,30 @@ { "title": "Участок 10 соток ИЖС в с. Вешки" } ``` +## Поиск (Search) + +Два complementary endpoint'а для поиска объявлений: + +### Semantic Search — `POST /api/v1/search/similar` +- **Технология:** pgvector (768d embeddings) + cosine similarity (HNSW index) +- **Поток:** Query → Ollama `nomic-embed-text:latest` → `embedding <=> query_vector` в PostgreSQL +- **Преимущества:** Находит "близкие по смыслу" объявления даже при других словах +- **Недостатки:** Требует Ollama call per query (~200-500ms) + +### Full-Text Search — `POST /api/v1/search/fulltext` +- **Технология:** PostgreSQL tsvector + tsquery + GIN index +- **Конфигурация:** `ukrainian` (копия `simple` + `ukrainian_stop` словарь) +- **Стоп-слова:** ~120 украинских слов из `scripts/ukrainian.stop` +- **Преимущества:** Быстрый (< 50ms), точный keyword match +- **Недостатки:** Нет стемминга (`квартира` ≠ `квартири`). См. [[REVIEW_FOLLOWUP.md]] пункт 15. + +### Hybrid Search (идея для будущего) +``` +1. FTS pre-filter: tsquery находит кандидатов (top-100) +2. Vector re-rank: cosine similarity среди кандидатов (top-10) +3. Комбинированный score: α * rank_score + β * similarity_score +``` + ## Масштабируемость Текущая архитектура рассчитана на **среднюю нагрузку** (синхронная обработка в HTTP-запросе). diff --git a/docs/REVIEW_FOLLOWUP.md b/docs/REVIEW_FOLLOWUP.md index 80eee58..67871c6 100644 --- a/docs/REVIEW_FOLLOWUP.md +++ b/docs/REVIEW_FOLLOWUP.md @@ -45,6 +45,36 @@ --- +## 🟡 FTS / Search — текущие ограничения + +### 15. Полноценный украинский стемминг в PostgreSQL FTS +**Проблема:** Текущая `ukrainian` text-search конфигурация — это копия `simple` + `ukrainian_stop` словарь. Он фильтрует стоп-слова, но **не стеммит** (не сводит `квартира` → `квартир` → `квартири` к общей лемме). Для полноценного морфологического анализа нужен `hunspell` шаблон, но пакет `postgresql-16` в Debian-образе `pgvector/pgvector:pg16` не включает `hunspell` text-search template. + +**Решения (в порядке приоритета):** + +1. **Elasticsearch / OpenSearch** (рекомендуется) + - Ukrainian analyzer из коробки (Lucene Ukrainian analyzer) + - Комбинировать с pgvector: FTS в ES, vectors в PostgreSQL + - Hybrid search через `rank_score` из ES + `similarity_score` из pgvector + +2. **Пересборка PostgreSQL с `--with-libhunspell`** + - Нужен Alpine-based образ (где есть `postgresql-hunspell` пакет) + - Или сборка из исходников с флагом `--with-libhunspell` + - Потом подключить `uk_UA.dic` / `uk_UA.aff` как `TEMPLATE = hunspell` + +3. **`pg_trgm` как fallback** + - `CREATE INDEX ... USING gin (title gin_trgm_ops)` + - Поиск по схожести строк (расстояние Левенштейна) + - Не заменяет стемминг, но улучшает recall для опечаток + +4. **Snowball stemmer для украинского** + - Нет официального snowball-алгоритма для украинского + - Можно попробовать русский snowball (несовершенно, но лучше nothing) + +**Текущий workaround:** `plainto_tsquery('ukrainian', query)` использует `simple` парсер (токенизация + lowercase) + украинские стоп-слова. Для поиска `київ квартира` это работает, но `квартири` не найдёт `квартиру`. + +--- + ## 🟢 Nice to Have (Phase 8 или позже) ### 7. Строгий Pydantic schema для `payload` diff --git a/src/vmk_data_collector/api/v1/router_properties.py b/src/vmk_data_collector/api/v1/router_properties.py index 07bdc65..b9401d0 100644 --- a/src/vmk_data_collector/api/v1/router_properties.py +++ b/src/vmk_data_collector/api/v1/router_properties.py @@ -289,8 +289,8 @@ ) -> FulltextSearchResponse: """Full-text (keyword) search over property listings. - Uses PostgreSQL tsvector with the ``simple`` text-search configuration, - which is language-agnostic and works well for mixed ru/ua content. + Uses PostgreSQL tsvector with the ``ukrainian`` text-search + configuration (stop-words filtering from ``scripts/ukrainian.stop``). Results are ranked by relevance. """ logger.info(