◆ trust · how rankings work · phase 12.25
No retailer pays to rank higher.
Every other AU comparison engine sorts by what advertisers pay. We don’t. This page shows the exact two-stage sort we ship, the inputs, and which inputs we never read at rank-time. Source lives in apps/web/src/lib/search/run-search.ts and the hybrid_search SQL migration.
◆ pledge 01
No paid placement
Ever. Retailers cannot buy slots, accent badges, or sort tweaks. The same algorithm runs for every product.
◆ pledge 02
No retailer bias
Two retailers with identical total_aud rank identically. Even Amazon does not get a thumb on the scale.
◆ pledge 03
No personalisation
You see the same ranking your agent sees. No A/B test re-orders results based on who is asking.
01Stage 1 — Product ranking— hybrid_search RPC · text + embedding
When a query comes in, the matcher computes a hybrid_score per candidate product using four signals — three text-based (token overlap, trigram, brand similarity) and one semantic (embedding distance). The top-N candidates by hybrid_score become the result list, in score order.
apps/web/src/lib/search/run-search.ts · hybrid_score formula
hybrid_score =
(emb_dist != null && trgm_sim > 0)
? (1.0 - emb_dist / 2.0) * 0.7 + trgm_sim * 0.3
: (emb_dist != null)
? (1.0 - emb_dist / 2.0)
: trgm_sim
+ token_overlap_pct * 0.30 // plurality-aware (Phase 12.22)02Stage 2 — Offer ranking— cheapest total first, deterministic
Within each matched product, offers are sorted cheapest total ascending where total_aud = price + shipping. The cheapest in-stock offer becomes best_offer. No retailer-specific weighting; no commission-aware re-rank; no “preferred partner” bypass.
apps/web/src/lib/search/run-search.ts:478
// Materialise top-N offers in cheapest-first order. The first is best_offer.
const sortedOffers = [...offers]
.filter((o) => o.in_stock && !isStale(o.last_seen_at))
.sort((a, b) => a.total_aud - b.total_aud);
const bestOffer = sortedOffers[0];
03What goes into the rank— auditable
| input | source | used? | notes |
|---|
| token overlap (query ↔ title/brand/category) | pg_trgm SQL | ◆ yes | plurality-aware; weighted 30% of hybrid_score |
| trigram similarity (full string fuzz) | pg_trgm SQL | ◆ yes | survives typos like "samsong" → Samsung |
| brand similarity | pg_trgm SQL | ◆ yes | precision floor for brand-explicit queries |
| embedding distance (Voyage AI · 512-dim) | pgvector | ◆ yes | 70% of hybrid_score when both lanes hit |
| price (AUD list) per offer | retailer feed (illustrative seed today) | ◆ yes | GST-inclusive where applicable |
| shipping to default postcode 2000 | feed + retailer calculator | ◆ yes | override per request when live feeds land |
| in-stock flag | feed | ◆ yes | out-of-stock offers are filtered, not penalised |
| affiliate commission % | CF / Amazon / EPN portals | ✕ never | we never read this at rank-time. Tested. |
| retailer relationship | manual | ✕ never | no preferred-partner tier exists |
| user history / agent identity | — | ✕ never | we do not track per-user behaviour or agent identity |
04Why we can credibly promise no paid placement
- The code is in plain sight. Apart from the SQL migration, every rank decision is in TypeScript at the path above.
- The match_confidence field is exposed. Every response includes the four sub-scores (token_overlap, trgm, brand_sim, embedding distance) so an agent can audit why a result ranked where it did.
- The catalogue is read-only at rank time. Commission rates are stored in a separate table not joined into the RPC’s query plan. There is no syntactic path for commission % to influence ranking.
- The MCP wire shows everything. Agents see prices, shipping, and the cheapest_first sort directly. If we cheated, the agent would surface the inconsistency.
◆Want to verify? Run the L2 corpus locally: GS_BASE=https://gridscoot.vercel.app python3 scripts/test-protocol/02-recall.py. Today: 54/54 (100%) recall · 17/17 brand-explicit accuracy.