#!/usr/bin/env python3 """ Fix /api/clients handler to: 1. Filter out entities (no Surname = entity/SMSF/trust/company) 2. Only show person records (have Surname populated) 3. Deduplicate Group 1/2/3 entries (keep Group 1, strip suffix) 4. Sort alphabetically by surname """ import re SERVER_JS = "/root/wealthsim-server/server.js" with open(SERVER_JS, "r") as f: content = f.read() # The current handler (after previous fix) uses clientArray and BasicInfo mapping # Find and replace the simplifiedClients mapping block old_mapping = """ const simplifiedClients = clientArray.map(client => { const bi = client.BasicInfo || {}; const cd = client.ContactDetail || {}; return { ADLClientID: bi.ADLClientID || client.ADLClientID, Name: `${bi.FirstName || client.FirstName || ''} ${bi.Surname || client.LastName || ''}`.trim(), DOB: bi.DateOFBirth || client.DOB || '', Email: cd.EmailAddress || client.Email || '', PartnerName: null }; });""" new_mapping = """ // Filter: only person records (have a non-empty Surname) const personRecords = clientArray.filter(client => { const bi = client.BasicInfo || {}; const surname = (bi.Surname || '').trim(); return surname.length > 0; }); // Deduplicate Group 1/2/3 entries: keep lowest group number, strip suffix const seen = new Map(); const groupRegex = /\\s*\\(Group\\s*\\d+\\)\\s*$/i; for (const client of personRecords) { const bi = client.BasicInfo || {}; const firstName = (bi.FirstName || '').trim(); const rawSurname = (bi.Surname || '').trim(); const cleanSurname = rawSurname.replace(groupRegex, '').trim(); const dedupeKey = `${firstName.toLowerCase()}|${cleanSurname.toLowerCase()}`; if (!seen.has(dedupeKey)) { seen.set(dedupeKey, { client, cleanSurname }); } } // Build simplified client list sorted by surname then first name const simplifiedClients = Array.from(seen.values()) .sort((a, b) => { const surnameCompare = a.cleanSurname.toLowerCase().localeCompare(b.cleanSurname.toLowerCase()); if (surnameCompare !== 0) return surnameCompare; const aFirst = (a.client.BasicInfo || {}).FirstName || ''; const bFirst = (b.client.BasicInfo || {}).FirstName || ''; return aFirst.toLowerCase().localeCompare(bFirst.toLowerCase()); }) .map(({ client, cleanSurname }) => { const bi = client.BasicInfo || {}; const cd = client.ContactDetail || {}; return { ADLClientID: bi.ADLClientID, Name: `${(bi.FirstName || '').trim()} ${cleanSurname}`.trim(), DOB: bi.DateOFBirth || '', Email: cd.EmailAddress || '', Gender: bi.Gender || '', PartnerName: null }; });""" if old_mapping in content: content = content.replace(old_mapping, new_mapping) print("SUCCESS: Replaced client mapping with person-only filter + dedup + sort") else: print("ERROR: Could not find the expected mapping block to replace") print("Attempting line-by-line search...") # Show what's around 'simplifiedClients' for debugging lines = content.split('\n') for i, line in enumerate(lines): if 'simplifiedClients' in line and 'clientArray' in line: print(f" Found at line {i+1}: {line.strip()}") exit(1) with open(SERVER_JS, "w") as f: f.write(content) print("File saved. Verifying...") with open(SERVER_JS, "r") as f: updated = f.read() checks = [ ("personRecords" in updated, "personRecords filter present"), ("groupRegex" in updated, "Group dedup regex present"), ("cleanSurname" in updated, "Clean surname logic present"), (".sort(" in updated, "Alphabetical sort present"), ("bi.Gender" in updated, "Gender field included"), ] all_ok = True for check, label in checks: status = "OK" if check else "FAIL" print(f" [{status}] {label}") if not check: all_ok = False if all_ok: print("VERIFIED: All checks passed") else: print("WARNING: Some checks failed")