@agent-pattern-labs/leads-rig 0.1.7 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -82,4 +82,8 @@ The package also carries the `@agent-pattern-labs/iso-*` helper ecosystem for tr
82
82
  - page visits
83
83
  - ingest requests
84
84
 
85
+ Lead records preserve optional enrichment fields such as `linkedinUrl`, `phone`,
86
+ `address`, `streetAddress`, `location`, `city`, `region`, `postalCode`, and
87
+ `country` through normalization, deduplication, and ingest.
88
+
85
89
  The validator intentionally accepts the same defaults the local runtime normalizes, but it fails missing source evidence, invalid URL fields, invalid `emailType`, invalid `verificationStatus`, invalid confidence, person leads without email, and generic catch-all inboxes such as `info@`, `hello@`, or similar organizational aliases. Any email record must be a named `person` lead with a non-generic email and a human owner visible in the evidence; role inboxes, unknown-owner emails, blocked emails, unsupported emails, and unnamed person-like emails fail validation. Summaries expose `goodLeadCount`, which only counts those high-quality named human email records.
@@ -162,7 +162,15 @@ export function normalizeLead(input, { now = new Date().toISOString() } = {}) {
162
162
  const verificationStatus = normalizeVerificationStatus(input.verificationStatus, emailType);
163
163
  const confidence = clampInteger(input.confidence, 0, 100, 0);
164
164
  const foundAt = normalizeDateTime(input.foundAt, now);
165
+ const linkedinUrl = normalizeLinkedInUrl(input.linkedinUrl);
165
166
  const phone = stringValue(input.phone);
167
+ const address = compactWhitespace(input.address);
168
+ const streetAddress = compactWhitespace(input.streetAddress);
169
+ const location = compactWhitespace(input.location);
170
+ const city = compactWhitespace(input.city);
171
+ const region = compactWhitespace(input.region);
172
+ const postalCode = compactWhitespace(input.postalCode);
173
+ const country = compactWhitespace(input.country);
166
174
  const socialUrls = normalizeUrlList(input.socialUrls);
167
175
  const contactUrls = normalizeUrlList(input.contactUrls);
168
176
 
@@ -187,7 +195,15 @@ export function normalizeLead(input, { now = new Date().toISOString() } = {}) {
187
195
  verificationStatus,
188
196
  confidence,
189
197
  warnings: normalizeWarnings(input.warnings),
198
+ linkedinUrl,
190
199
  phone,
200
+ address,
201
+ streetAddress,
202
+ location,
203
+ city,
204
+ region,
205
+ postalCode,
206
+ country,
191
207
  socialUrls,
192
208
  contactUrls,
193
209
  foundAt,
@@ -547,6 +563,15 @@ function normalizeLeadRecord(lead) {
547
563
  sources,
548
564
  evidence: sources[0]?.evidence || compactWhitespace(lead?.evidence),
549
565
  warnings: normalizeWarnings(lead?.warnings),
566
+ linkedinUrl: normalizeLinkedInUrl(lead?.linkedinUrl),
567
+ phone: stringValue(lead?.phone),
568
+ address: compactWhitespace(lead?.address),
569
+ streetAddress: compactWhitespace(lead?.streetAddress),
570
+ location: compactWhitespace(lead?.location),
571
+ city: compactWhitespace(lead?.city),
572
+ region: compactWhitespace(lead?.region),
573
+ postalCode: compactWhitespace(lead?.postalCode),
574
+ country: compactWhitespace(lead?.country),
550
575
  socialUrls: normalizeUrlList(lead?.socialUrls),
551
576
  contactUrls: normalizeUrlList(lead?.contactUrls),
552
577
  };
@@ -574,7 +599,15 @@ export function mergeLeadRecords(current, candidate) {
574
599
  ? secondary.verificationStatus
575
600
  : primary.verificationStatus,
576
601
  confidence: Math.max(primary.confidence || 0, secondary.confidence || 0),
602
+ linkedinUrl: primary.linkedinUrl || secondary.linkedinUrl,
577
603
  phone: primary.phone || secondary.phone,
604
+ address: longerString(primary.address, secondary.address),
605
+ streetAddress: primary.streetAddress || secondary.streetAddress,
606
+ location: longerString(primary.location, secondary.location),
607
+ city: primary.city || secondary.city,
608
+ region: primary.region || secondary.region,
609
+ postalCode: primary.postalCode || secondary.postalCode,
610
+ country: primary.country || secondary.country,
578
611
  socialUrls: normalizeUrlList([...(primary.socialUrls || []), ...(secondary.socialUrls || [])]),
579
612
  contactUrls: normalizeUrlList([...(primary.contactUrls || []), ...(secondary.contactUrls || [])]),
580
613
  warnings: normalizeWarnings([...(primary.warnings || []), ...(secondary.warnings || [])]),
@@ -842,6 +875,25 @@ function normalizeUrlList(value) {
842
875
  return urls;
843
876
  }
844
877
 
878
+ function normalizeLinkedInUrl(value) {
879
+ let raw = stringValue(value);
880
+ if (!raw) return '';
881
+ if (!raw.includes('://')) raw = `https://${raw}`;
882
+
883
+ try {
884
+ const url = new URL(raw);
885
+ if (url.protocol !== 'http:' && url.protocol !== 'https:') return '';
886
+ const host = url.hostname.toLowerCase();
887
+ if (host !== 'linkedin.com' && !host.endsWith('.linkedin.com')) return '';
888
+
889
+ const path = url.pathname.replace(/\/+$/, '');
890
+ if (!/^\/(in|pub|company|school|showcase)\//i.test(path)) return '';
891
+ return `https://www.linkedin.com${path}`;
892
+ } catch {
893
+ return '';
894
+ }
895
+ }
896
+
845
897
  function normalizeDateTime(value, fallback) {
846
898
  const raw = stringValue(value);
847
899
  if (!raw) return fallback;
@@ -876,6 +928,12 @@ function compactWhitespace(value) {
876
928
  return stringValue(value).replace(/\s+/g, ' ');
877
929
  }
878
930
 
931
+ function longerString(left, right) {
932
+ const first = stringValue(left);
933
+ const second = stringValue(right);
934
+ return second.length > first.length ? second : first;
935
+ }
936
+
879
937
  function displayCompany(domain) {
880
938
  const clean = normalizeDomain(domain);
881
939
  if (!clean) return '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-pattern-labs/leads-rig",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Agentic public-web lead discovery harness with portable ingest artifacts",
5
5
  "type": "module",
6
6
  "author": "Razroo",
@@ -30,7 +30,15 @@
30
30
  },
31
31
  { "name": "confidence", "type": "integer", "required": true, "min": 0, "max": 100 },
32
32
  { "name": "warnings", "type": "json" },
33
+ { "name": "linkedinUrl", "type": "url" },
33
34
  { "name": "phone", "type": "string" },
35
+ { "name": "address", "type": "string" },
36
+ { "name": "streetAddress", "type": "string" },
37
+ { "name": "location", "type": "string" },
38
+ { "name": "city", "type": "string" },
39
+ { "name": "region", "type": "string" },
40
+ { "name": "postalCode", "type": "string" },
41
+ { "name": "country", "type": "string" },
34
42
  { "name": "socialUrls", "type": "json" },
35
43
  { "name": "contactUrls", "type": "json" },
36
44
  { "name": "foundAt", "type": "datetime" }
@@ -74,10 +74,30 @@
74
74
  "items": { "type": "string" },
75
75
  "default": []
76
76
  },
77
+ "linkedinUrl": {
78
+ "type": "string",
79
+ "description": "Optional canonical public LinkedIn profile or organization URL."
80
+ },
77
81
  "phone": {
78
82
  "type": "string",
79
83
  "description": "Optional normalized phone number found near the public contact evidence."
80
84
  },
85
+ "address": {
86
+ "type": "string",
87
+ "description": "Optional complete public business address."
88
+ },
89
+ "streetAddress": {
90
+ "type": "string",
91
+ "description": "Optional street-address component."
92
+ },
93
+ "location": {
94
+ "type": "string",
95
+ "description": "Optional display location for the lead."
96
+ },
97
+ "city": { "type": "string" },
98
+ "region": { "type": "string" },
99
+ "postalCode": { "type": "string" },
100
+ "country": { "type": "string" },
81
101
  "socialUrls": {
82
102
  "type": "array",
83
103
  "items": { "type": "string" },