96 Commits

Author SHA1 Message Date
ea90b95cdc lookup endpoint updated 2026-09-30 16:13:25 +05:30
fb859ecda1 health score 2026-09-29 22:40:36 +05:30
f9fb405974 nutrition field 2026-09-29 17:27:48 +05:30
b46902f51b auto mail generation 2026-09-29 16:53:21 +05:30
090e9c0c2f shifts 2026-09-25 16:18:53 +05:30
276e12beb9 login fix 2026-09-25 10:36:38 +05:30
299871b820 shifts 2026-09-24 15:51:47 +05:30
697b77f8c1 agent 2026-09-23 17:26:13 +05:30
24339a8b51 Merge origin/main: keep the substring rule, read its tie
main had moved on with retrieval work validated against real queries —
minTokenHits (the word match needs two thirds of the label, not all of
it), separator folding so "Parle G"/"Parle-G"/"ParleG" all reach Parle-G,
the floor at 0.50 after "Paracetamol" came back as "Paneer Makhni 500ml"
at 0.304, and ties broken on cosine distance instead of name. All of that
is kept exactly as it was.

The conflict was in textScore: this branch replaced the substring rule
with a coverage formula to stop a bare brand name resolving to one
arbitrary product. That is the wrong half to change. The substring rule
scores every product of a brand 0.95 IDENTICALLY, and that tie is not the
bug — it is the signal. isAmbiguous reads it, so the branch's coverage
rewrite is dropped and the ambiguity layer alone does the work:

  "britannia" → all 258 rows tie at 0.95 → ambiguous: true + candidates
  "Parle G"   → folding and the single-character token still land it
  a real name → runner-up far behind → match, unchanged

Dropped with it: scanSpecificEnough, the per-hit text score, and the
proportional confirmation bonus — the flat +0.10 is back. Simpler, and it
leaves main's tuning untouched.

TestTextScoreRewardsSpecificityNotJustOverlap tested the removed formula
and is replaced by TestABrandNameScoresItsProductsIdentically, which
guards the tie itself: a formula that broke it on name length or word
count would bring the bug back.

Docs carry both rationales, and now say plainly that confidence stays
high on the ambiguous path — gate on `ambiguous`, never on `confidence`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-23 11:05:03 +05:30
01bc89ab77 Ask instead of guessing when a label fits several products
`"britannia"` is a substring of all 258 Britannia product names, and
textScore returned 0.95 for any product whose name contained the label.
So every one of them tied, the tie broke alphabetically, and the customer
was shown one arbitrary biscuit with "confidence": 0.95 and a price. Lens
hands back a bare wordmark often — it is usually the biggest thing printed
on a packet — so this was the common case, not an edge one. Found via the
example request in the mobile team's own proposal.

Scoring now asks both questions. A hit carries `score` (ranks) and `text`
(how specifically the label names THIS product: the harmonic mean of how
much of the label the product explains and how much of the product's name
the label explains, pack sizes dropped from both sides). A brand name
scores its products ~0.33 equally instead of 0.95 arbitrarily. The
"vector and text agree" bonus is now proportional to the text score, so a
weak match can no longer inflate a whole brand.

isAmbiguous reads that: the leader is a guess if anything is level with it
(margin) or if the label names no one product (specificity), and then the
response carries `ambiguous: true` with `candidates` — distinct products,
not pack sizes, at most ten, each marked with whether one of the
customer's stores has it in stock, available ones first. `match` is nil
and `stores` empty on that path: no price for a product nobody chose.
Erring towards asking is deliberate — a tap versus the wrong biscuit.

To act on a pick, /lookup now accepts `brand` + `catalogueid` instead of a
label and skips recognition entirely (also serves deep links and re-order).
New: ScanRepository.CatalogueRef, resolving via the brand tables discovered
from information_schema, never a name built from the request.

Also: scratch/cataloguedims now reports every vector column, not just
`embedding` — which is how we learned the catalogue also carries
img_vector(1024), filled on 1885 of 2124 rows. SCAN_TO_ORDER.md records
why that column stays unread for now and what would change it, alongside
why the app is not asked to compute vectors on the phone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-23 10:56:02 +05:30
692c10e553 partner list 2026-09-17 11:04:37 +05:30
2f1501883b e2e changes 2026-09-16 17:09:04 +05:30
c06b029cb2 text search 2026-09-16 12:17:39 +05:30
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
1633617dc4 Stop reporting a failed login lookup as "Invalid Email"
GetUserByAuthname / GetUserByContactNo / GetUserLogin discarded the
Scan error, so a database that could not answer — down, pool exhausted,
or booted without its config (2026-07-20) — came back as uid 0 and every
user was told their email was wrong.

One lookup, GetUserLogin, now returns an error; sql.ErrNoRows is "not
found" and anything else reaches the service, which answers 500 "Login
is temporarily unavailable" and logs the cause. 409 "Invalid Email" is
unchanged for a genuine no-match: the console reads that exact shape as
"not registered". NULL password/role columns scan through sql.Null* so
they do not become 500s.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-15 17:04:33 +05:30
1b2cc77063 merchant login app 2026-09-11 11:23:07 +05:30
8cc567c89b daily merchant app login fix 2026-09-11 11:10:25 +05:30
e7577fe0cf rider partner 2026-09-09 15:44:27 +05:30
d9cad2bbe4 Timing 2026-09-08 15:41:31 +05:30
7d9e628fd0 app sbucategories 2026-09-08 11:49:34 +05:30
e87f09c4f2 console categories 2026-09-07 17:53:35 +05:30
c699a400c3 product categories 2026-09-07 17:28:38 +05:30
f1b3a5eba4 deliveries 2026-09-05 15:02:23 +05:30
593b11f1b8 rider creation 2026-09-04 16:02:47 +05:30
e51ad615ed assign 2026-09-04 11:32:12 +05:30
fb5d7da81f deliveries 2026-09-03 19:12:03 +05:30
0d17f46fcf pricing in app 2026-09-03 15:44:09 +05:30
da0e9d987e variants as a single product 2026-09-03 10:43:05 +05:30
e45da9f7df variants units 2026-09-02 16:01:24 +05:30
09efc5403f orders 2026-09-02 15:37:59 +05:30
68871cb043 product variant id 2026-09-02 13:11:01 +05:30
c0d39e550d changes 2026-09-02 10:49:46 +05:30
6b27448ff9 role id 2026-09-01 13:43:05 +05:30
485f31239d guide changes 2026-09-01 12:01:43 +05:30
d8cfe2e87b store filter 2026-08-31 16:39:20 +05:30
bc10b589cd fix on stocks on store catalogue 2026-08-31 16:26:06 +05:30
d55f101834 fix on shelf 2026-08-31 15:51:36 +05:30
4bce5ac854 product on shelf 2026-08-31 14:59:48 +05:30
ffc66462fc changes on agent 2026-08-31 12:33:10 +05:30
76554d26e5 bugs on variant id 2026-08-29 16:45:47 +05:30
c5109bf216 catalogue images 2026-08-29 11:29:15 +05:30
fb4fcaee66 catalogue brands 2026-08-29 10:58:01 +05:30
7c5be9b5cf bugs fixed 2026-08-28 18:19:52 +05:30
40500f936a variant and price 2026-08-28 16:54:55 +05:30
6c1ad472f2 changes according to the test 2026-08-28 13:19:13 +05:30
c31f3aca27 changes with app variant 2026-08-28 13:02:03 +05:30
46da26c452 changes 2026-08-28 11:56:57 +05:30
dc9c049bb9 live stock update 2026-08-27 11:17:17 +05:30
Suriya
ca846f9cd6 require a mobile number when creating a till account
A till signs in with a mobile number and a PIN, but createposuser still
accepted an account without a number. Such an account cannot reach the
sign-in screen at all, and the failure surfaces at a counter in front of
a queue rather than at the point of creation. Enforced in CreatePosUser,
so both doors are covered: POST /pos/users from the terminal and
createposuser from the console share that path.

Two checks, not one. normalisePosPhone answers ("", nil) rather than an
error for a value holding no digits, so "abc" would have passed an
emptiness check, then been written as a blank and skipped the uniqueness
check below it — which is the hole this closes.

Scope is new accounts only. The column stays nullable and UpdatePosUser
still reads an empty contactno as "leave alone", so the accounts that
predate the number keep working through the backfill and cannot have
theirs cleared. The PIN stays optional at creation.

Also in this change:

- docs: correct both phone-login handovers, which claimed creation
  already required a number. The sequencing note in the PIN handover
  said step 2 was a backfill that could never be finished; it now is
  one, and POS_LOGIN.md says which half of the pair creation enforces.

- docs: remove credentials from the examples. POS_PHONE_LOGIN_HANDOVER
  carried a real-looking back-office pair and a generated till password,
  and POS_LOGIN.md a second one.

- posController.Staff: the comment justified scoping by token because
  "the answer carries PINs". It has not since the PIN left the wire. The
  scoping is still right for a different reason, which the comment now
  gives.

- scratch/posstaffsetup: takes both mobile numbers as arguments and
  refuses to run without them. Generating stand-ins would have produced
  exactly what this change prevents. Validated before the database is
  opened, in plan mode too, so a dry run cannot print a plan that apply
  would reject halfway through and leave half a shop set up.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 17:26:41 +05:30
d566ca5591 pos login with the ph number and pin 2026-08-12 17:10:27 +05:30