#!/usr/bin/env python3 """Regression test for the dashboard KPI repoint + lp_profiles retirement (2026-06-16). "Total Committed" used to SUM lp_profiles.commitment_amount — an orphaned table with no reachable input path, so the dashboard read ~$0 while the real commitments lived in the fundraising grid. It now sums fundraising_investors.total_invested (the canonical grid rollup) with graveyarded (written-off) investors excluded, "Total Funded" is dropped (the grid has no funded-vs-committed concept), and the /api/lp-profiles* + lp-breakdown endpoints are gone. This boots the REAL server against a temp DB, seeds two grid investors (one live, one graveyarded), and asserts: total_committed reflects the live grid rollup only, the metrics no longer carry a total_funded key, and the retired routes 404. Synthetic only. Run: cd backend && python3 test_dashboard_report.py """ import http.client import json import os import sqlite3 import sys import tempfile import threading from http.server import ThreadingHTTPServer _DATA = tempfile.mkdtemp() os.environ["CRM_DATA_DIR"] = _DATA os.environ["CRM_DB_PATH"] = os.path.join(_DATA, "crm.db") sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import server # noqa: E402 FAILS = [] def check(cond, msg): print((" PASS " if cond else " FAIL ") + msg) if not cond: FAILS.append(msg) class _Quiet(server.CRMHandler): def log_message(self, *a): pass def _get(port, path, token): conn = http.client.HTTPConnection("127.0.0.1", port, timeout=10) conn.request("GET", path, headers={"Authorization": "Bearer " + token}) resp = conn.getresponse() body = resp.read().decode("utf-8", "replace") conn.close() data = None if body: try: data = json.loads(body) except ValueError: pass return resp.status, data def seed(): c = sqlite3.connect(os.environ["CRM_DB_PATH"]) c.execute("INSERT INTO users (id,username,email,password_hash,full_name,role,is_active) " "VALUES ('u1','grant','grant@ten31.example','x','Grant','admin',1)") # live investor committed 3,000,000; graveyarded investor committed 500,000 (must be excluded) c.execute("INSERT INTO fundraising_investors (id,investor_name,source_row_id,total_invested,graveyard) " "VALUES ('fiLive','Harbor LP','rowLive',3000000,0)") c.execute("INSERT INTO fundraising_investors (id,investor_name,source_row_id,total_invested,graveyard) " "VALUES ('fiDead','Passed LP','rowDead',500000,1)") c.commit() c.close() def main(): server.init_db() seed() token = server.create_token("u1", "grant", "admin") httpd = ThreadingHTTPServer(("127.0.0.1", 0), _Quiet) port = httpd.server_address[1] threading.Thread(target=httpd.serve_forever, daemon=True).start() try: print("\n[dashboard total_committed comes from the grid, graveyard excluded]") st, dash = _get(port, "/api/reports/dashboard", token) check(st == 200, f"GET dashboard -> 200 (got {st})") metrics = (dash or {}).get("data", {}).get("metrics", {}) check(metrics.get("total_committed") == 3000000, f"total_committed = live grid rollup only (3,000,000; got {metrics.get('total_committed')})") check("total_funded" not in metrics, f"total_funded key dropped from metrics (got keys {sorted(metrics)})") print("\n[retired lp_profiles endpoints 404]") for path in ("/api/lp-profiles", "/api/lp-profiles/anything", "/api/reports/lp-breakdown"): st, _ = _get(port, path, token) check(st == 404, f"GET {path} -> 404 (got {st})") finally: httpd.shutdown() print() if FAILS: print(f"FAILED ({len(FAILS)}):") for f in FAILS: print(f" - {f}") sys.exit(1) print("ALL PASS (dashboard KPI repoint + lp_profiles retirement)") if __name__ == "__main__": main()