[object Object]

← back to Rentv

pr-intelligence: c7 structural name rule — trailing role/category noun ⇒ title not name (kills 'Utilities Field Inspector' class without word-chasing)

14d950ab551d95c58dbf038115b53ab534e4eddd · 2026-07-30 22:26:21 -0700 · Steve Abrams

Files touched

Diff

commit 14d950ab551d95c58dbf038115b53ab534e4eddd
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Jul 30 22:26:21 2026 -0700

    pr-intelligence: c7 structural name rule — trailing role/category noun ⇒ title not name (kills 'Utilities Field Inspector' class without word-chasing)
---
 src/pr/lib/normalize.js | 23 ++++++++++++++++++++---
 1 file changed, 20 insertions(+), 3 deletions(-)

diff --git a/src/pr/lib/normalize.js b/src/pr/lib/normalize.js
index d3d2051c..31b09a7e 100644
--- a/src/pr/lib/normalize.js
+++ b/src/pr/lib/normalize.js
@@ -106,10 +106,26 @@ const SOFT_NONNAME_WORDS = new Set([
   'lead', 'agent', 'live', 'first', 'trust', 'united', 'american', 'pacific',
 ]);
 
+// Role / category / occupation nouns that END a job title or service phrase but NEVER
+// end a person's name. Structural rule (Cody c3/c7): a title reads "<...> <role-noun>"
+// ("Utilities Field Inspector", "Event Coordination", "Transportation Group Leader"),
+// so a trailing role-noun ⇒ not a name. Beats chasing individual words forever.
+const ROLE_TAIL_WORDS = new Set([
+  'inspector', 'coordination', 'coordinator', 'manager', 'management', 'leader', 'lead',
+  'officer', 'director', 'president', 'analyst', 'specialist', 'administrator', 'associate',
+  'assistant', 'representative', 'agent', 'advisor', 'adviser', 'consultant', 'engineer',
+  'architect', 'accountant', 'attorney', 'counsel', 'partner', 'principal', 'chair',
+  'chairman', 'chairwoman', 'relations', 'communications', 'marketing', 'operations',
+  'development', 'services', 'solutions', 'group', 'team', 'department', 'division',
+  'inquiries', 'branding', 'design', 'strategy', 'finance', 'sales', 'lending',
+  'underwriting', 'escrow', 'title', 'realty', 'brokerage', 'inspection', 'appraisal',
+]);
+
 /**
  * Is this string plausibly a HUMAN name? Shape: 2–4 alpha tokens, ≤40 chars, no
- * digits/entities; NO hard non-name token anywhere; at least one token that isn't
- * soft filler. Callers can override for legit unusual names (audited).
+ * digits/entities; NO hard non-name token anywhere; the LAST token is not a role/category
+ * noun (titles end in roles, names end in surnames); at least one non-filler token.
+ * Callers can override for legit unusual names (audited).
  */
 function isLikelyPersonName(name) {
   const s = String(name || '').trim();
@@ -120,7 +136,8 @@ function isLikelyPersonName(name) {
   if (!tokens.every((t) => /^[A-Za-z'’.-]{2,}$/.test(t) || /^[A-Z]\.?$/.test(t))) return false;
   const low = tokens.map((t) => t.toLowerCase().replace(/[^a-z]/g, ''));
   if (low.some((t) => HARD_NONNAME_WORDS.has(t))) return false;
-  const hasProper = low.some((t) => !SOFT_NONNAME_WORDS.has(t));
+  if (ROLE_TAIL_WORDS.has(low[low.length - 1])) return false;   // trailing role noun ⇒ a title, not a name
+  const hasProper = low.some((t) => !SOFT_NONNAME_WORDS.has(t) && !ROLE_TAIL_WORDS.has(t));
   return hasProper;
 }
 

← 7b2724ce yoloforever: cycle 6 ledger — Arizona Phase 2 bootstrapped (  ·  back to Rentv  ·  yoloforever: cycle 7 ledger — AZ coverage 81%, name gate har cf8d9dc2 →