File size: 10,925 Bytes
0d0008e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
"""Tests for the comprehensive admin analytics layer (informal-market science +
platform aggregation). Pure math is tested directly; _collect_platform_stats is
tested against a fake Firestore. Run: python test_admin_analytics.py"""
import os, sys, math
os.environ.setdefault("FIREBASE", "{}")
from unittest import mock

sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))

import analytics

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 approx(desc, got, want, tol=1e-6):
    global PASS, FAIL
    ok = got is not None and abs(got - want) <= tol
    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)

print("== Gini coefficient ==")
check(" empty β†’ None", analytics.gini_coefficient([]), None)
check(" all zero β†’ None", analytics.gini_coefficient([0, 0, 0]), None)
check(" perfect equality β†’ 0", analytics.gini_coefficient([5, 5, 5, 5]), 0.0)
# One trader has everything β†’ approaches (n-1)/n
approx(" total concentration", analytics.gini_coefficient([0, 0, 0, 100]), 0.75, tol=0.001)
g = analytics.gini_coefficient([1, 2, 3, 4, 5])
check_true(" mild inequality in (0,1)", 0 < g < 1)

print("== HHI concentration ==")
check(" monopoly", analytics.hhi_concentration({"a": 100}), 1.0)
approx(" 4 equal β†’ 0.25", analytics.hhi_concentration({"a": 1, "b": 1, "c": 1, "d": 1}), 0.25, tol=1e-9)
check(" empty β†’ None", analytics.hhi_concentration({}), None)

print("== top_share / median ==")
check(" top-10% of 10 equal earners = 10%", analytics.top_share([10]*10, 0.10), 10.0)
approx(" top earner dominates", analytics.top_share([1, 1, 1, 97], 0.25), 97.0, tol=0.5)
check(" median odd", analytics.median([3, 1, 2]), 2.0)
check(" median even", analytics.median([1, 2, 3, 4]), 2.5)

print("== price dispersion (law of one price) ==")
disp = analytics.price_dispersion({
    "tomato": [1.00, 1.00, 1.00],       # no dispersion
    "dovi":   [2.0, 4.0, 6.0],          # high dispersion
    "rare":   [5.0],                    # only 1 trader β†’ excluded
})
check(" items compared (>=2 traders)", disp["itemsCompared"], 2)
check(" most dispersed first", disp["mostDispersed"][0]["item"], "dovi")
check(" tomato zero CV", [d for d in disp["mostDispersed"] if d["item"] == "tomato"][0]["cv"], 0.0)
check(" dovi spread%", disp["mostDispersed"][0]["spreadPct"], 100.0)

print("== scan_transactions ==")
txns = [
    {"transaction_type": "sale", "created_at": "2026-07-01T10:00:00Z",
     "details": {"customer_credit": 5.0}},
    {"transaction_type": "sale", "created_at": "2026-07-02T10:00:00Z", "details": {}},
    {"transaction_type": "stock_in", "created_at": "2026-07-02T09:00:00Z", "details": {}},
    {"transaction_type": "expense", "created_at": "2026-07-03T09:00:00Z", "details": {}},
]
scan = analytics.scan_transactions(txns)
check(" type mix sale", scan["byType"]["sale"], 2)
check(" type mix stock_in", scan["byType"]["stock_in"], 1)
check(" total", scan["total"], 4)
check(" sale count", scan["saleCount"], 2)
check(" credit sale count", scan["creditSaleCount"], 1)
check(" credit value", scan["creditSalesValue"], 5.0)
check(" last activity", scan["lastActivity"][:10], "2026-07-03")
check(" daily buckets", scan["daily"]["2026-07-02"], 2)

print("== model_health accuracy-by-modality ==")
ex = [
    {"modality": "audio", "verdict": "confirmed", "task": "intent_parse"},
    {"modality": "audio", "verdict": "rejected", "task": "intent_parse"},
    {"modality": "text",  "verdict": "confirmed", "task": "intent_parse"},
    {"modality": "text",  "verdict": "confirmed", "task": "intent_parse"},
]
mh = analytics.model_health(ex)
check(" audio 50%", mh["accuracyByModality"]["audio"], 50.0)
check(" text 100%", mh["accuracyByModality"]["text"], 100.0)
check(" overall confirmRate", mh["confirmRate"], 75.0)

print("== report prompt + fallback ==")
prompt = analytics.build_admin_report_prompt({"platform": {"totalUsers": 3}}, "market", "2026-07-04T00:00:00Z")
check_true(" market focus present", "law of one price" in prompt)
check_true(" stats embedded", "totalUsers" in prompt)
fb = analytics.fallback_admin_report("full", "2026-07-04T00:00:00Z")
check(" fallback flags aiError", fb["aiError"], True)
check(" fallback keeps type", fb["reportType"], "full")


# ── _collect_platform_stats against a fake Firestore ─────────────────────────
print("== _collect_platform_stats (fake Firestore) ==")

class _Doc:
    def __init__(self, data, _id="x"): self._d = data; self.id = _id
    @property
    def exists(self): return self._d is not None
    def to_dict(self): return dict(self._d) if self._d else None

class _Col:
    def __init__(self, docs=None, kv=None): self._docs = docs or []; self._kv = kv or {}
    def stream(self):
        if self._kv:
            return [_Doc(v, k) for k, v in self._kv.items()]
        return [_Doc(d, d.get("_id", "d")) for d in self._docs]
    def where(self, *a, **k): return self
    def limit(self, n): return self
    def order_by(self, *a, **k): return self

class _UserDocRef:
    def __init__(self, subs): self.subs = subs
    def collection(self, name): return _Col(kv={} if name not in self.subs else None,
                                            docs=self.subs.get(name, []))

class _DB:
    def __init__(self, users, ledgers, examples):
        self.users = users; self.ledgers = ledgers; self.examples = examples
    def collection(self, name):
        if name == "users":
            outer = self
            class _UsersCol(_Col):
                def __init__(s): super().__init__(docs=outer.users)
                def document(s, pid):
                    return _UserDocRef(outer.ledgers.get(pid, {}))
            return _UsersCol()
        if name == "organizations":
            return _Col(docs=[{"_id": "o1"}])
        if name == "distillation_examples":
            return _Col(docs=self.examples)
        if name == "admin_reports":
            return _Col(docs=[])
        return _Col()

users = [
    {"_id": "u1", "email": "a@x.com", "displayName": "Rutendo", "phoneStatus": "approved",
     "phone": "+263771000001", "defaultCurrency": "USD", "createdAt": "2026-06-01T00:00:00Z"},
    {"_id": "u2", "email": "b@x.com", "displayName": "Chipo", "phoneStatus": "approved",
     "phone": "+263771000002", "defaultCurrency": "USD", "createdAt": "2026-06-02T00:00:00Z"},
    {"_id": "u3", "email": "c@x.com", "displayName": "Admin", "isAdmin": True,
     "phoneStatus": "pending", "createdAt": "2026-06-03T00:00:00Z"},
]
from datetime import datetime, timezone, timedelta
recent = (datetime.now(timezone.utc) - timedelta(days=1)).isoformat()
ledgers = {
    "263771000001": {
        "transactions": [
            {"transaction_type": "sale", "created_at": recent,
             "details": {"currency": "USD", "amount": 100.0,
                         "items": [{"item": "dovi", "quantity": 25, "price_per_unit": 4.0}]}},
            {"transaction_type": "sale", "created_at": recent,
             "details": {"currency": "USD", "amount": 20.0, "customer_credit": 20.0,
                         "items": [{"item": "tomato", "quantity": 20, "price_per_unit": 1.0}]}},
        ],
        "stock_batches": [{"name": "dovi", "quantity_remaining": 100, "cost_each": 2.0,
                           "price_each": 4.0, "stocked_at": "2026-06-01"}],
        "customers": [{"name": "Tariro", "receivable": 20.0}],
        "item_prices": [{"name": "dovi", "price": 4.0, "on_sale": False},
                        {"name": "tomato", "price": 1.0, "on_sale": False}],
        "services": [{"_id": "s1", "name": "braiding"}],
    },
    "263771000002": {
        "transactions": [
            {"transaction_type": "sale", "created_at": recent,
             "details": {"currency": "USD", "amount": 10.0,
                         "items": [{"item": "dovi", "quantity": 5, "price_per_unit": 2.0}]}},
        ],
        "stock_batches": [{"name": "dovi", "quantity_remaining": 10, "cost_each": 1.5,
                           "price_each": 2.0, "stocked_at": "2026-06-05"}],
        "customers": [],
        "item_prices": [{"name": "dovi", "price": 2.0, "on_sale": False}],
        "services": [],
    },
}
examples = [{"modality": "audio", "verdict": "confirmed", "task": "asr"},
            {"modality": "text", "verdict": "confirmed", "task": "intent_parse"}]

fake_db = _DB(users, ledgers, examples)
with mock.patch.object(analytics, "money_opt", analytics.money_opt):  # no-op, keep ref
    import importlib
    with mock.patch.dict(os.environ, {"FIREBASE": "{}"}):
        # import main with firebase/genai mocked
        with mock.patch("firebase_admin.credentials.Certificate", return_value=mock.MagicMock()), \
             mock.patch("firebase_admin.initialize_app", return_value=mock.MagicMock()), \
             mock.patch("firebase_admin.firestore.client", return_value=fake_db):
            import main
            main.db = fake_db  # ensure the module global points at the fake

stats = main._collect_platform_stats()
print("  platform:", stats["platform"])
print("  economy.byCurrency:", stats["economy"]["byCurrency"])
print("  creditEconomy:", stats["informalMarketScience"]["creditEconomy"])
print("  inequality:", stats["informalMarketScience"]["traderInequality"])
print("  priceDiscovery:", stats["informalMarketScience"]["priceDiscovery"])
check(" approved traders", stats["platform"]["approvedTraders"], 2)
check(" pending", stats["platform"]["pendingApproval"], 1)
check(" admins", stats["platform"]["admins"], 1)
check(" active 7d (recent txns)", stats["platform"]["activeTraders7d"], 2)
check(" USD sales summed", stats["economy"]["byCurrency"]["USD"]["sales"], 130.0)
check(" txn mix sale count", stats["economy"]["transactionMix"]["sale"], 3)
check(" receivables", stats["informalMarketScience"]["creditEconomy"]["receivables"], 20.0)
check_true(" credit-sale share computed",
           stats["informalMarketScience"]["creditEconomy"]["creditSaleShareOfSalesTxns"] is not None)
check_true(" gini computed", stats["informalMarketScience"]["traderInequality"]["giniRevenue"] is not None)
# dovi priced 4.0 (u1) vs 2.0 (u2) β†’ dispersion tracked
disp_items = [d["item"] for d in stats["informalMarketScience"]["priceDiscovery"]["mostDispersed"]]
check(" dovi price dispersion tracked", "dovi" in disp_items, True)
check(" services counted", stats["informalMarketScience"]["serviceEconomy"]["registeredServices"], 1)
check(" model health wired", stats["modelHealth"]["totalCaptured"], 2)
check(" stock retail > cost", stats["economy"]["stockValueRetail"] > stats["economy"]["stockValueCost"], True)

print(f"\n{'='*40}\nTOTAL: {PASS} passed, {FAIL} failed")
sys.exit(1 if FAIL else 0)