← back to Rentv
PR intel: CRM name/title hygiene tool (byline + trailing-punct cleanup)
1663154b6f494832bd80c1a8920462933ce30e47 · 2026-08-05 14:38:03 -0700 · Steve
src/pr/tools/cleanup-names.js strips byline prefixes (By/With/Photos by) and
trailing punctuation on titles via audited PUT (recomputes normalized_name +
re-classifies). Dry-run default. First apply: 92 fixes across 2490 contacts.
Files touched
A src/pr/tools/cleanup-names.js
Diff
commit 1663154b6f494832bd80c1a8920462933ce30e47
Author: Steve <steve@designerwallcoverings.com>
Date: Wed Aug 5 14:38:03 2026 -0700
PR intel: CRM name/title hygiene tool (byline + trailing-punct cleanup)
src/pr/tools/cleanup-names.js strips byline prefixes (By/With/Photos by) and
trailing punctuation on titles via audited PUT (recomputes normalized_name +
re-classifies). Dry-run default. First apply: 92 fixes across 2490 contacts.
---
src/pr/tools/cleanup-names.js | 63 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 63 insertions(+)
diff --git a/src/pr/tools/cleanup-names.js b/src/pr/tools/cleanup-names.js
new file mode 100644
index 00000000..94644531
--- /dev/null
+++ b/src/pr/tools/cleanup-names.js
@@ -0,0 +1,63 @@
+'use strict';
+// CRM name/title hygiene — strips byline prefixes ("By Peter Fabris" → "Peter Fabris")
+// and trailing punctuation on titles ("Editor-in-Chief:" → "Editor-in-Chief"), through
+// the audited PUT /api/pr/people/:id (which recomputes normalized_name + re-classifies
+// the title). Dry-run by default; pass --apply to write. Idempotent.
+//
+// Usage: node src/pr/tools/cleanup-names.js [--apply]
+// Env: PR_API (default prod), PR_AUTH (user:pass)
+const API = process.env.PR_API || 'https://rentv.agentabrams.com';
+const AUTH = 'Basic ' + Buffer.from(process.env.PR_AUTH || 'admin:DW2024!').toString('base64');
+const APPLY = process.argv.includes('--apply');
+
+async function api(p, opts = {}) {
+ const r = await fetch(API + '/api/pr' + p, {
+ ...opts,
+ headers: { Authorization: AUTH, 'Content-Type': 'application/json', ...(opts.headers || {}) },
+ body: opts.body ? JSON.stringify(opts.body) : undefined,
+ });
+ if (!r.ok) throw new Error(p + ' → ' + r.status + ': ' + (await r.text()).slice(0, 120));
+ return r.json();
+}
+
+const BYLINE = /^(by|with|photos?\s+by|words?\s+by|story\s+by|reporting\s+by|edited\s+by)\s+/i;
+function cleanName(n) {
+ let s = String(n || '').replace(/\s+/g, ' ').trim();
+ s = s.replace(BYLINE, '');
+ s = s.replace(/[,:;|]+$/, '').trim();
+ return s;
+}
+function cleanTitle(t) {
+ if (t == null) return t;
+ let s = String(t).replace(/\s+/g, ' ').trim();
+ s = s.replace(/[-–—:;,\s]+$/, '').replace(/^[-–—:;,\s]+/, '').trim();
+ return s || null;
+}
+
+async function main() {
+ let offset = 0, scanned = 0, nameFix = 0, titleFix = 0, applied = 0;
+ const samples = [];
+ for (;;) {
+ const page = await api(`/people?limit=500&offset=${offset}`);
+ const rows = page.rows || page.people || [];
+ if (!rows.length) break;
+ for (const p of rows) {
+ scanned++;
+ const patch = {};
+ const nn = cleanName(p.full_name);
+ if (nn && nn !== p.full_name) { patch.full_name = nn; nameFix++; }
+ const nt = cleanTitle(p.exact_title);
+ if ((p.exact_title || null) !== nt) { patch.exact_title = nt; titleFix++; }
+ if (!Object.keys(patch).length) continue;
+ if (samples.length < 12) samples.push(`${p.full_name} | ${p.exact_title || ''} ⇒ ${patch.full_name || p.full_name} | ${patch.exact_title !== undefined ? patch.exact_title : p.exact_title || ''}`);
+ if (APPLY) { try { await api('/people/' + p.id, { method: 'PUT', body: patch }); applied++; } catch (e) { /* skip */ } }
+ }
+ offset += rows.length;
+ if (rows.length < 500) break;
+ }
+ console.log(`[cleanup-names] scanned ${scanned} · name-fixes ${nameFix} · title-fixes ${titleFix} · ${APPLY ? 'APPLIED ' + applied : 'DRY-RUN (pass --apply to write)'}`);
+ console.log('samples:\n ' + samples.join('\n '));
+ process.exit(0);
+}
+
+main().catch((e) => { console.error('fatal:', e.message); process.exit(1); });
← 1fd2ec07 Cody-gate: posts public-status now published|live in lockste
·
back to Rentv
·
closings: buyer/seller/broker are now clickable → filter the 279335d4 →