initiumzim
July field report: AI-primary command routing + canonical price registry (ADR 0018)
ac25a64 | # Regression tests for the July 2026 field report (recurrences + new findings): | |
| # single-source price registry, AI-routed command types, spoken numbers, service | |
| # dedupe. See ADR 0018. Run with: python scripts/test_july_report_fixes.py | |
| import os, sys | |
| os.environ.setdefault("GEMINI_API_KEY", "fake-key-for-tests") | |
| os.environ.setdefault("FIREBASE", "{}") | |
| os.environ.setdefault("aai_key", "fake") | |
| os.environ.setdefault("whatsapp_token", "fake") | |
| os.environ.setdefault("phone_number_id", "0000") | |
| from unittest import mock | |
| sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) | |
| import firebase_admin | |
| from firebase_admin import credentials, firestore | |
| with mock.patch.object(credentials, "Certificate", return_value=mock.MagicMock()), \ | |
| mock.patch.object(firebase_admin, "initialize_app", return_value=mock.MagicMock()), \ | |
| mock.patch.object(firestore, "client", return_value=mock.MagicMock()): | |
| import main | |
| import utility | |
| PASS = FAIL = 0 | |
| def check(desc, got, want): | |
| global PASS, FAIL | |
| ok = got == want | |
| PASS += ok; FAIL += (not ok) | |
| print(f"PASS {desc}" if ok else f"FAIL {desc}\n got={got!r} want={want!r}") | |
| def check_true(desc, got): check(desc, bool(got), True) | |
| def check_false(desc, got): check(desc, bool(got), False) | |
| # ── Minimal fake Firestore (enough for the price/service paths) ──────────────── | |
| class _FDoc: | |
| def __init__(self, data): self._d = data | |
| def exists(self): return self._d is not None | |
| def to_dict(self): return dict(self._d) if self._d else None | |
| class _FDocRef: | |
| def __init__(self, store, key): self.store, self.key = store, key | |
| def get(self): return _FDoc(self.store.get(self.key)) | |
| def set(self, data, merge=False): | |
| cur = dict(self.store.get(self.key) or {}) if merge else {} | |
| cur.update(data); self.store[self.key] = cur | |
| def update(self, data): self.set(data, merge=True) | |
| def delete(self): self.store.pop(self.key, None) | |
| class _FListRef: | |
| def __init__(self, coll, idx): self.coll, self.idx = coll, idx | |
| def update(self, data): self.coll.rows[self.idx].update(data) | |
| class _FListDoc(_FDoc): | |
| def __init__(self, data, coll, idx): | |
| super().__init__(data); self.reference = _FListRef(coll, idx) | |
| class _FListColl: | |
| def __init__(self, rows): self.rows = rows; self._keep = None | |
| def where(self, f, op, v): | |
| sub = _FListColl(self.rows) | |
| sub._keep = [i for i, r in enumerate(self.rows) if r.get(f) == v] | |
| return sub | |
| def get(self): | |
| keep = self._keep if self._keep is not None else range(len(self.rows)) | |
| return [_FListDoc(self.rows[i], self, i) for i in keep] | |
| def limit(self, n): return self | |
| def order_by(self, *a, **k): return self | |
| class _FKvColl: | |
| def __init__(self, store): self.store = store | |
| def document(self, key): return _FDocRef(self.store, key) | |
| def get(self): return [_FDoc(v) for v in self.store.values()] | |
| def limit(self, n): return self | |
| class _FUser: | |
| def __init__(self, colls): self.colls = colls | |
| def collection(self, name): return self.colls[name] | |
| def get(self): return _FDoc({"currency": "USD"}) | |
| class _FDb: | |
| def __init__(self, colls): self.user = _FUser(colls) | |
| def collection(self, name): | |
| user = self.user | |
| class _U: | |
| def document(self, _): return user | |
| return _U() | |
| def make_db(batches=None, item_prices=None, services=None, overrides=None): | |
| return _FDb({ | |
| "stock_batches": _FListColl(batches or []), | |
| "item_prices": _FKvColl(item_prices if item_prices is not None else {}), | |
| "services": _FKvColl(services if services is not None else {}), | |
| "price_overrides": _FListColl(overrides or []), | |
| "transactions": _FListColl([]), | |
| }) | |
| print("== July report §3: registry price beats stale batch price ==") | |
| ip = {} | |
| fdb = make_db( | |
| batches=[{"name": "dovi", "quantity_remaining": 0, "price_each": 2.56, "stocked_at": "2026-05-01"}, | |
| {"name": "dovi", "quantity_remaining": 270, "price_each": 2.56, "stocked_at": "2026-06-01"}], | |
| item_prices=ip) | |
| utility.set_item_price(fdb, "m", "Dovi", 4.00, currency="USD", source="test") | |
| check(" registry write", ip["dovi"]["price"], 4.0) | |
| check(" new price ends promo", ip["dovi"]["on_sale"], False) | |
| check(" lookup uses registry", utility._lookup_item_price(fdb, "m", "dovi"), 4.0) | |
| print("== July §3: sale resolves the UPDATED price (Chido atenga 50 Dovi) ==") | |
| txns = [{"intent": "create", "transaction_type": "sale", | |
| "details": {"items": [{"item": "dovi", "quantity": 50, "unit": "each"}], | |
| "customer": "Chido"}}] | |
| utility.resolve_sale_prices(fdb, "m", txns, raw_text="Chido atenga 50 Dovi") | |
| check(" sale ppu = 4.00", txns[0]["details"]["items"][0].get("price_per_unit"), 4.0) | |
| check(" sale total = 200", txns[0]["details"].get("amount"), 200.0) | |
| print("== July §3 step 4: 'move' discounts from the NEW price ==") | |
| msg = utility.mark_item_on_sale(fdb, "m", "dovi") | |
| check_true(" 20% off 4.00 = 3.20", "3.20" in msg) | |
| check(" registry promo", ip["dovi"]["sale_price"], 3.2) | |
| check_true(" registry on_sale", ip["dovi"]["on_sale"]) | |
| check(" promo lookup", utility._lookup_promo_price(fdb, "m", "dovi"), 3.2) | |
| txns2 = [{"intent": "create", "transaction_type": "sale", | |
| "details": {"items": [{"item": "dovi", "quantity": 10, "unit": "each"}]}}] | |
| utility.resolve_sale_prices(fdb, "m", txns2, raw_text="sold 10 dovi") | |
| check(" sale uses promo", txns2[0]["details"]["items"][0].get("price_per_unit"), 3.2) | |
| print("== _sorted_batches: active + newest first ==") | |
| sb = utility._sorted_batches([ | |
| {"quantity_remaining": 0, "stocked_at": "2026-06-30", "price_each": 9}, | |
| {"quantity_remaining": 5, "stocked_at": "2026-05-01", "price_each": 1}, | |
| {"quantity_remaining": 7, "stocked_at": "2026-06-01", "price_each": 2}]) | |
| check(" first is active+newest", sb[0]["price_each"], 2) | |
| check(" depleted last", sb[-1]["price_each"], 9) | |
| print("== July §8: service dedupe (no re-onboarding) ==") | |
| svc = {"knotless_short": {"name": "knotless short", "price": 25.0}} | |
| sdb = make_db(services=svc) | |
| r = utility._register_service(sdb, "m", {"service": "Knotless Short", "currency": "USD"}) | |
| check_true(" states existing price", "already one of your services at USD25.00" in r[1]) | |
| check_false(" no onboarding restart", "What do you usually charge" in r[1]) | |
| r2 = utility._register_service(sdb, "m", {"service": "Knotless Short", "amount": 30, "currency": "USD"}) | |
| check_true(" price update path", "Updated" in r2[1] and "30.00" in r2[1]) | |
| check(" service price updated", svc["knotless_short"]["price"], 30.0) | |
| print("== July §2/§8: price reply hits a known service ==") | |
| prices_msg = main._apply_price_replies(sdb, "m", {"knotless short": 28.0}) | |
| check_true(" service price reply", "(service)" in prices_msg and "28.00" in prices_msg) | |
| check(" stored", svc["knotless_short"]["price"], 28.0) | |
| print("== July §7: spoken numbers ==") | |
| nsn = utility.normalise_spoken_numbers | |
| check(" eight oranges", nsn("I sold eight oranges"), "I sold 8 oranges") | |
| check(" eight(100)", nsn("I sold eight(100) bananas"), "I sold 100 bananas") | |
| check(" twenty five", nsn("stocked twenty five bread"), "stocked 25 bread") | |
| check(" ninety-nine", nsn("ninety-nine oranges"), "99 oranges") | |
| check(" two hundred", nsn("sold two hundred tomatoes"), "sold 200 tomatoes") | |
| check(" a hundred", nsn("a hundred eggs"), "100 eggs") | |
| check(" digits untouched", nsn("sold 40 tomatoes to Chipo"), "sold 40 tomatoes to Chipo") | |
| check(" item-name strip", utility.normalise_item_name("eight oranges"), "orange") | |
| print("== AI-routed command types via process_intent ==") | |
| with mock.patch("firebase_admin.firestore.client", return_value=fdb): | |
| out = utility.process_intent([{"intent": "create", "transaction_type": "set_price", | |
| "details": {"items": [{"item": "dovi", "price_per_unit": 5.0}], | |
| "currency": "USD"}}], "m") | |
| check_true(" set_price saves", "Prices saved" in str(out) and "5.00" in str(out)) | |
| check(" registry updated", ip["dovi"]["price"], 5.0) | |
| check(" set_price ends promo", ip["dovi"]["on_sale"], False) | |
| out2 = utility.process_intent([{"intent": "create", "transaction_type": "mark_on_sale", | |
| "details": {"item": "dovi"}}], "m") | |
| check_true(" mark_on_sale runs", "on sale" in str(out2) and "4.00" in str(out2)) | |
| check(" promo from new base 5.00", ip["dovi"]["sale_price"], 4.0) | |
| print("== debt_query type fills the query ==") | |
| with mock.patch.object(utility, "_read_transactions", | |
| side_effect=lambda db, m, det: det.get("query")), \ | |
| mock.patch("firebase_admin.firestore.client", return_value=fdb): | |
| out = utility.process_intent([{"intent": "read", "transaction_type": "debt_query", | |
| "details": {"direction": "i_owe"}}], "m") | |
| check(" debt_query mapped", out, "who do i owe") | |
| print(f"\n{'='*40}\nTOTAL: {PASS} passed, {FAIL} failed") | |
| sys.exit(1 if FAIL else 0) | |