Retire lp_profiles + LP Tracker; repoint Dashboard committed to the grid (v0.1.0:78)
The fundraising grid + email capture is the canonical system of record. lp_profiles was a superseded single-fund model with no reachable create/edit path, and the LP Tracker page was already orphaned (no nav entry + a redirect bouncing it to the grid). - Remove /api/lp-profiles* endpoints + handlers, the unused lp-breakdown report, the contact-dossier LP section, the demo-seed LP block, and (frontend) the LPTrackerPage component + its lp-tracker->fundraising-grid redirect. - Dashboard "Total Committed" now sums fundraising_investors.total_invested (graveyarded investors excluded) instead of the orphaned lp_profiles table, which read ~$0. "Total Funded" dropped: the grid tracks commitments, not a funded amount, and the frontend never rendered it. - Leave the empty lp_profiles table/index, the contact-delete soft-delete cascade, and the --reset-all-data clear in place (never-hard-delete). - Tests: add test_dashboard_report.py; update test_soft_delete_reads.py. 21/21 green.
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
#!/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()
|
||||
Reference in New Issue
Block a user