electric-horses-infra/stacks/eh-search/app/search/suggest.py
Benjamin Weinlich b3813ed6ac feat(stacks/eh-search): add site-search FastAPI service
Mirrors /opt/ai-apps/eh-search/ on the server, including the full
FastAPI app (intent routing, FTS+fuzzy+substring hybrid, multi-source
federation across vehicles + blog + brands + pages + static + tag
bridge), SQL schema (Postgres materialized view with german_unaccent
text search, pg_trgm for fuzzy), Dockerfile and compose.

Sanitized the hardcoded password in sql/01_init.sql — replaced with
REPLACE_ME_BEFORE_APPLYING placeholder since this repo is public.

The eh-search service binds only on the private network (10.0.0.8:8200)
and is reachable only via Pegasus nginx proxy at /api/search.

Refs OP#1094 OP#1105 OP#1112 OP#1116 OP#1117
2026-04-11 22:19:39 +02:00

50 lines
1.5 KiB
Python

"""Autocomplete / Suggest endpoint (Typ 11)."""
from app import db
from app.schemas import SuggestItem
async def top_brands(limit: int = 10) -> list[SuggestItem]:
rows = await db.fetch(
"SELECT brand, count(*) AS cnt FROM search_vehicles "
"WHERE brand IS NOT NULL GROUP BY brand ORDER BY cnt DESC LIMIT $1",
limit,
)
return [
SuggestItem(text=r["brand"], type="brand", count=r["cnt"])
for r in rows
]
async def prefix_suggest(prefix: str, limit: int = 8) -> list[SuggestItem]:
"""Brand + model prefix matching, case-insensitive."""
pattern = f"{prefix.lower()}%"
# Brands first
brand_rows = await db.fetch(
"SELECT brand, count(*) AS cnt FROM search_vehicles "
"WHERE LOWER(brand) LIKE $1 GROUP BY brand ORDER BY cnt DESC LIMIT $2",
pattern,
limit,
)
items = [
SuggestItem(text=r["brand"], type="brand", count=r["cnt"])
for r in brand_rows
]
# Then models if room
remaining = limit - len(items)
if remaining > 0:
model_rows = await db.fetch(
"SELECT DISTINCT brand || ' ' || model AS text, count(*) AS cnt "
"FROM search_vehicles "
"WHERE model IS NOT NULL AND LOWER(model) LIKE $1 "
"GROUP BY brand, model ORDER BY cnt DESC LIMIT $2",
pattern,
remaining,
)
items.extend(
SuggestItem(text=r["text"], type="model", count=r["cnt"])
for r in model_rows
)
return items