AI search for messy marketplace queries.
ScoutLocal needed search that could understand vague queries like “vibey coffee spot” without losing real marketplace facts such as location, hours, and merchant data.
CASE STUDY AT A GLANCE
- Hybrid semantic + SQL search for messy queries
- Geospatial fetching designed to reduce wasted map requests
- Polymorphic schema + optimistic UI patterns
- Clerk identity integration for merchant role separation
Reuse stored vectors in PostgreSQL instead of generating an embedding for every repeated query.
A cache miss calls the embedding service, making the external request explicit in the search path.
If semantic matching is not confident enough, the system falls back to structured SQL search.
SaaS Product Teams
Needing better search/recommendations without hiring a full-time specialist or rebuilding the product from scratch.
Marketplaces
Dealing with overlapping entities (merchants, events) and heavy read-traffic.
Scaling Startups
Trying to scale past MVP without letting technical debt in search crush your velocity.
Natural-Language Intent Meets SQL Fact Grounding
The Scenario: ScoutLocal's marketplace needed to accommodate "vague" human searches (e.g., "vibey coffee spot for reading") rather than strict SQL category dropdowns.
PostgreSQL Vector Search & Strict Fallback
We built a Hybrid Search Engine. The pipeline extracts live merchant data, embeds semantic intent via Azure OpenAI (`text-embedding-3-small`) on ingestion, and computes cosine similarity directly in PostgreSQL via a PL/pgSQL function.
Implementation Patterns
Below are the core engineering patterns used to keep vectors and entities in sync, avoiding "ghost" records and race conditions.
1. Backend: Atomic Integrity
// 1. Reserve Entity ID (Atomic & Fast)
const record = await prisma.embedding.create({ data: { status: 'PENDING', content: text } });
// 2. External Vector Gen (Network I/O Outside DB Lock)
const response = await openai.embeddings.create({ input: text });
const vector = response.data[0].embedding;
// 3. Save Vector (Separate Fast Write)
await prisma.embedding.update({
where: { id: record.id },
data: { status: 'READY', vector }
});
2. Frontend: Optimistic UI Pattern
const toggleSave = useCallback(async (id) => {
// 1. Immediate UI Update
setSavedIds((prev) => new Set(prev).add(id));
try {
await api.post(`/user/saved/${id}`);
} catch (err) {
// 2. Self-Healing Rollback
setSavedIds((prev) => { ... });
toast.error("Sync failed");
}
}, []);
3. Identity & Auth Guard
export async function POST(req: Request) {
const { userId } = auth(); // Clerk Identity
if (!userId) return new Response("Unauthorized", { status: 401 });
// Proceed with secure search/sync
// Merchant isolation is enforced by userId
}
Want this kind of search or product build?
MVP Build Path
If you are building a product with search, user accounts, data workflows, and launch constraints, start with the MVP path.
- Product scope and user path
- Data model and search requirements
Search or Workflow Build
$4,000 - $8,000 / Fixed
A focused 2-4 week build for one search, routing, reporting, dashboard, or automation system wired into your existing tools.
- One critical workflow mapped
- Focused changes implemented
- Full code handoff & docs