smart-w / scripts /test_july_report_fixes.py
initiumzim
July field report: AI-primary command routing + canonical price registry (ADR 0018)
ac25a64
Raw
History Blame Contribute Delete
9.05 kB
# 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
@property
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)