diff --git a/packages/api/src/db/queries.ts b/packages/api/src/db/queries.ts index c507ce9..9f4ca92 100644 --- a/packages/api/src/db/queries.ts +++ b/packages/api/src/db/queries.ts @@ -28,7 +28,7 @@ export async function searchTransceivers(params: SearchParams) { let idx = 1; if (params.q) { - conditions.push(`search_vector @@ plainto_tsquery('english', $${idx})`); + conditions.push(`(search_vector @@ plainto_tsquery('english', $${idx}) OR t.part_number ILIKE '%' || $${idx} || '%' OR t.standard_name ILIKE '%' || $${idx} || '%')`); values.push(params.q); idx++; } @@ -98,8 +98,8 @@ export async function searchTransceivers(params: SearchParams) { // Add relevance ranking when full-text search is used const orderBy = params.q - ? `ORDER BY ts_rank(search_vector, plainto_tsquery('english', $1)) DESC` - : `ORDER BY speed_gbps DESC, reach_meters ASC`; + ? `ORDER BY (t.part_number ILIKE $1) DESC, ts_rank(search_vector, plainto_tsquery('english', $1)) DESC, fully_verified DESC NULLS LAST, has_image DESC NULLS LAST` + : `ORDER BY fully_verified DESC NULLS LAST, has_image DESC NULLS LAST, speed_gbps DESC NULLS LAST, reach_meters ASC NULLS LAST`; const query = ` SELECT t.*, v.name as vendor_name diff --git a/packages/api/src/routes/search.ts b/packages/api/src/routes/search.ts index 3a1caf6..741340a 100644 --- a/packages/api/src/routes/search.ts +++ b/packages/api/src/routes/search.ts @@ -8,6 +8,7 @@ */ import { Router, Request, Response } from "express"; import { semanticSearch, getCollectionInfo, CollectionName } from "../embeddings/client"; +import { searchTransceivers } from "../db/queries"; export const searchRouter = Router(); @@ -43,11 +44,20 @@ searchRouter.get("/", async (req: Request, res: Response) => { } try { - const results = await semanticSearch(collection, query, limit); + let results: any[]; + let usedFallback = false; + if (collection === "product_embeddings") { + const fts = await searchTransceivers({ q: query, limit }); + results = (((fts as any).data) || []).map((t: any) => ({ id: t.id, score: 0.5, payload: t })); + usedFallback = true; + } else { + results = await semanticSearch(collection, query, limit); + } res.json({ success: true, query, collection, + fallback: usedFallback ? "fts" : undefined, results: results.map((r) => ({ id: r.id, score: Math.round(r.score * 1000) / 1000, @@ -56,6 +66,14 @@ searchRouter.get("/", async (req: Request, res: Response) => { count: results.length, }); } catch (err) { + if (collection === "product_embeddings") { + try { + const fts = await searchTransceivers({ q: query, limit }); + const results = (((fts as any).data) || []).map((t: any) => ({ id: t.id, score: 0.5, ...t })); + res.json({ success: true, query, collection, fallback: "fts", results, count: results.length }); + return; + } catch (e2) { /* fall through */ } + } res.status(503).json({ success: false, error: "Vector search unavailable",