Files
backend_fiesta/utils/geo_test.go
Suriyakumarvijayanayagam 72907dae74 Scan-to-order: label from the customer's camera to "buy it here"
POST /v1/mob/scan/lookup   label + customer → catalogue match, sizes, and
                           every registered store that sells it with live
                           stock, in-stock first / nearest first, one
                           recommended
POST /v1/mob/scan/confirm  chosen store + size + qty → re-read the ledger;
                           ok, or the next-nearest store with enough of the
                           same product
GET  /v1/mob/scan/stores   registered stores nearest first

Recognition is pgvector cosine search over every brand_* table (each
with its own index, merged) plus a word match that settles near-ties
and works alone when no model is configured. The embedder is chosen by
EMBEDDING_PROVIDER (OpenAI-compatible or Gemini) and must be the model
that indexed the catalogue: verified 2026-09-15 as all-MiniLM-L6-v2 over
search_query, served by the cluster's Ollama as `all-minilm`; the first
search refuses a width mismatch by name.

Customer, stores and catalogue are read concurrently under a 5 s cap; a
slow model degrades to a text answer. Vectors and ranked hits are cached
in Redis and in-process; live stock never is. Availability uses the same
rules as the customer catalogue (approve, publishedat, ledger balance,
outlet price else retail). No stock reservation: confirm re-reads.

scratch/cataloguedims reports the catalogue's embedding width and fill.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 17:04:34 +05:30

66 lines
1.7 KiB
Go

package utils
import (
"testing"
"time"
)
func TestParseLatLng(t *testing.T) {
if _, _, ok := ParseLatLng("", ""); ok {
t.Error("blank must not parse")
}
if _, _, ok := ParseLatLng("0", "0"); ok {
t.Error("0,0 is an empty map picker, not a place")
}
if _, _, ok := ParseLatLng("91", "10"); ok {
t.Error("out of range")
}
lat, lng, ok := ParseLatLng(" 11.0168 ", "76.9558")
if !ok || lat != 11.0168 || lng != 76.9558 {
t.Errorf("got %v %v %v", lat, lng, ok)
}
}
func TestHaversineKm(t *testing.T) {
// Coimbatore railway station to Peelamedu, roughly 8 km.
d := HaversineKm(11.0018, 76.9660, 11.0290, 77.0290)
if d < 7 || d > 9 {
t.Errorf("got %.2f km", d)
}
if HaversineKm(1, 1, 1, 1) != 0 {
t.Error("same point")
}
}
func TestOpenNow(t *testing.T) {
at := func(h, m int) time.Time { return time.Date(2026, 9, 15, h, m, 0, 0, time.UTC) }
if !OpenNow("09:00", "21:00", at(12, 0)) || OpenNow("09:00", "21:00", at(22, 0)) {
t.Error("plain hours")
}
if !OpenNow("20:00", "02:00", at(1, 0)) || OpenNow("20:00", "02:00", at(12, 0)) {
t.Error("past midnight")
}
if !OpenNow("9:00 AM", "9:30 PM", at(21, 0)) || OpenNow("9:00 AM", "9:30 PM", at(21, 45)) {
t.Error("12-hour clock")
}
if !OpenNow("", "", at(3, 0)) || !OpenNow("always", "", at(3, 0)) {
t.Error("unknown hours mean open")
}
}
func TestSearchTokens(t *testing.T) {
got := SearchTokens("Milk Bikis 100g, Britannia (2 x 50gm)")
want := []string{"milk", "bikis", "britannia"}
if len(got) != len(want) {
t.Fatalf("got %v want %v", got, want)
}
for i := range want {
if got[i] != want[i] {
t.Fatalf("got %v want %v", got, want)
}
}
if len(SearchTokens("500ml 1kg 2")) != 0 {
t.Error("pack sizes alone are not searchable")
}
}