@odla-ai/kg 0.3.2 → 0.4.1
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.
- package/dist/chunk-T6SYIVST.js +376 -0
- package/dist/chunk-T6SYIVST.js.map +1 -0
- package/dist/index.cjs +410 -36
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +330 -5
- package/dist/index.d.ts +330 -5
- package/dist/index.js +249 -38
- package/dist/index.js.map +1 -1
- package/dist/lenses-C7Q7Gvnx.d.cts +123 -0
- package/dist/lenses-C7Q7Gvnx.d.ts +123 -0
- package/dist/temporal/index.cjs +1 -689
- package/dist/temporal/index.cjs.map +1 -1
- package/dist/temporal/index.d.cts +4 -565
- package/dist/temporal/index.d.ts +4 -565
- package/dist/temporal/index.js +20 -977
- package/dist/temporal/index.js.map +1 -1
- package/package.json +3 -3
- package/dist/chunk-V3N2GRYU.js +0 -73
- package/dist/chunk-V3N2GRYU.js.map +0 -1
- package/dist/sources-nJyD0JUP.d.cts +0 -256
- package/dist/sources-nJyD0JUP.d.ts +0 -256
package/dist/index.cjs
CHANGED
|
@@ -21,8 +21,13 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
COMMIT_STEP_CONFIG: () => COMMIT_STEP_CONFIG,
|
|
24
|
+
FACTS_EXTRACT_SYSTEM: () => FACTS_EXTRACT_SYSTEM,
|
|
25
|
+
FACTS_EXTRACT_TOOL: () => FACTS_EXTRACT_TOOL,
|
|
26
|
+
FACTS_SEARCH_SYSTEM: () => FACTS_SEARCH_SYSTEM,
|
|
24
27
|
IO_STEP_CONFIG: () => IO_STEP_CONFIG,
|
|
25
28
|
LLM_STEP_CONFIG: () => LLM_STEP_CONFIG,
|
|
29
|
+
LOCATE_SYSTEM: () => LOCATE_SYSTEM,
|
|
30
|
+
LOCATE_TOOL: () => LOCATE_TOOL,
|
|
26
31
|
MODEL_TIERS: () => MODEL_TIERS,
|
|
27
32
|
Store: () => Store,
|
|
28
33
|
addUsage: () => addUsage,
|
|
@@ -30,14 +35,18 @@ __export(index_exports, {
|
|
|
30
35
|
asStepLike: () => asStepLike,
|
|
31
36
|
canonicalizeFragment: () => canonicalizeFragment,
|
|
32
37
|
connectorIds: () => connectorIds,
|
|
38
|
+
countyKey: () => countyKey,
|
|
33
39
|
createProvider: () => createProvider,
|
|
34
40
|
emptyUsage: () => emptyUsage,
|
|
35
41
|
enabledSources: () => enabledSources,
|
|
42
|
+
enrichCompany: () => enrichCompany,
|
|
36
43
|
exportFragment: () => exportFragment,
|
|
37
44
|
extractJson: () => extractJson,
|
|
38
45
|
extractJsonArray: () => extractJsonArray,
|
|
46
|
+
factsToFragment: () => factsToFragment,
|
|
39
47
|
fetchText: () => fetchText,
|
|
40
48
|
fetchUrl: () => fetchUrl,
|
|
49
|
+
findConnectors: () => findConnectors,
|
|
41
50
|
fragmentToOps: () => fragmentToOps,
|
|
42
51
|
getConnector: () => getConnector,
|
|
43
52
|
getProvider: () => getProvider,
|
|
@@ -46,6 +55,8 @@ __export(index_exports, {
|
|
|
46
55
|
ingestFresh: () => ingestFresh,
|
|
47
56
|
isLikelyPersonName: () => isLikelyPersonName,
|
|
48
57
|
jobsForHour: () => jobsForHour,
|
|
58
|
+
locatePrompt: () => locatePrompt,
|
|
59
|
+
locationFragment: () => locationFragment,
|
|
49
60
|
makeContext: () => makeContext,
|
|
50
61
|
mergeAttrs: () => mergeAttrs,
|
|
51
62
|
mergeRulesOf: () => mergeRulesOf,
|
|
@@ -53,6 +64,7 @@ __export(index_exports, {
|
|
|
53
64
|
naturalKeysOf: () => naturalKeysOf,
|
|
54
65
|
normalizeUrl: () => normalizeUrl,
|
|
55
66
|
resilientStep: () => resilientStep,
|
|
67
|
+
resolveCounties: () => resolveCounties,
|
|
56
68
|
resolveName: () => resolveName,
|
|
57
69
|
searchEntities: () => searchEntities,
|
|
58
70
|
slugify: () => slugify,
|
|
@@ -366,6 +378,404 @@ async function canonicalizeFragment(db, fragment, ontology) {
|
|
|
366
378
|
};
|
|
367
379
|
}
|
|
368
380
|
|
|
381
|
+
// src/temporal/prng.ts
|
|
382
|
+
function mulberry32(seed) {
|
|
383
|
+
let a = seed >>> 0;
|
|
384
|
+
return () => {
|
|
385
|
+
a = a + 1831565813 >>> 0;
|
|
386
|
+
let t = a;
|
|
387
|
+
t = Math.imul(t ^ t >>> 15, t | 1);
|
|
388
|
+
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
389
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
function shuffle(items, rand) {
|
|
393
|
+
const out = items.slice();
|
|
394
|
+
for (let i = out.length - 1; i > 0; i--) {
|
|
395
|
+
const j = Math.floor(rand() * (i + 1));
|
|
396
|
+
const tmp = out[i];
|
|
397
|
+
out[i] = out[j];
|
|
398
|
+
out[j] = tmp;
|
|
399
|
+
}
|
|
400
|
+
return out;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// src/temporal/communities.ts
|
|
404
|
+
function labelPropagation(nodes, edges, opts = {}) {
|
|
405
|
+
const { seed = 1, maxRounds = 20 } = opts;
|
|
406
|
+
const sorted = [...nodes].sort();
|
|
407
|
+
const labels = new Map(sorted.map((id) => [id, id]));
|
|
408
|
+
const neighbors = /* @__PURE__ */ new Map();
|
|
409
|
+
for (const e of edges) {
|
|
410
|
+
if (!labels.has(e.a) || !labels.has(e.b) || e.a === e.b) continue;
|
|
411
|
+
const w = e.weight ?? 1;
|
|
412
|
+
let listA = neighbors.get(e.a);
|
|
413
|
+
if (!listA) neighbors.set(e.a, listA = []);
|
|
414
|
+
listA.push({ id: e.b, weight: w });
|
|
415
|
+
let listB = neighbors.get(e.b);
|
|
416
|
+
if (!listB) neighbors.set(e.b, listB = []);
|
|
417
|
+
listB.push({ id: e.a, weight: w });
|
|
418
|
+
}
|
|
419
|
+
const rand = mulberry32(seed);
|
|
420
|
+
for (let round = 0; round < maxRounds; round++) {
|
|
421
|
+
const order = shuffle(sorted, rand);
|
|
422
|
+
let changed = false;
|
|
423
|
+
for (const id of order) {
|
|
424
|
+
const incident = neighbors.get(id);
|
|
425
|
+
if (!incident || incident.length === 0) continue;
|
|
426
|
+
const votes = /* @__PURE__ */ new Map();
|
|
427
|
+
for (const n of incident) {
|
|
428
|
+
const label = labels.get(n.id);
|
|
429
|
+
votes.set(label, (votes.get(label) ?? 0) + n.weight);
|
|
430
|
+
}
|
|
431
|
+
let best = [];
|
|
432
|
+
let bestWeight = -Infinity;
|
|
433
|
+
for (const [label, weight] of votes) {
|
|
434
|
+
if (weight > bestWeight) {
|
|
435
|
+
bestWeight = weight;
|
|
436
|
+
best = [label];
|
|
437
|
+
} else if (weight === bestWeight) {
|
|
438
|
+
best.push(label);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
const current = labels.get(id);
|
|
442
|
+
if (best.includes(current)) continue;
|
|
443
|
+
best.sort();
|
|
444
|
+
const pick = best.length === 1 ? best[0] : best[Math.floor(rand() * best.length)];
|
|
445
|
+
labels.set(id, pick);
|
|
446
|
+
changed = true;
|
|
447
|
+
}
|
|
448
|
+
if (!changed) break;
|
|
449
|
+
}
|
|
450
|
+
const members = /* @__PURE__ */ new Map();
|
|
451
|
+
for (const [id, label] of labels) {
|
|
452
|
+
const list = members.get(label);
|
|
453
|
+
list ? list.push(id) : members.set(label, [id]);
|
|
454
|
+
}
|
|
455
|
+
const canonical = /* @__PURE__ */ new Map();
|
|
456
|
+
for (const [label, ids] of members) canonical.set(label, ids[0]);
|
|
457
|
+
return new Map(sorted.map((id) => [id, canonical.get(labels.get(id))]));
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// src/temporal/lenses.ts
|
|
461
|
+
function reifiedBridgeTargets(fragment, opts) {
|
|
462
|
+
const bridgesOf = /* @__PURE__ */ new Map();
|
|
463
|
+
const targetsOf = /* @__PURE__ */ new Map();
|
|
464
|
+
for (const e of fragment.edges) {
|
|
465
|
+
if (e.from.type !== opts.reified) continue;
|
|
466
|
+
const to = `${e.to.type}:${e.to.key}`;
|
|
467
|
+
if (e.label === opts.bridgeLabel) (bridgesOf.get(e.from.key) ?? bridgesOf.set(e.from.key, []).get(e.from.key)).push(to);
|
|
468
|
+
if (e.label === opts.targetLabel) (targetsOf.get(e.from.key) ?? targetsOf.set(e.from.key, []).get(e.from.key)).push(to);
|
|
469
|
+
}
|
|
470
|
+
const out = /* @__PURE__ */ new Map();
|
|
471
|
+
for (const [reifiedKey, bridges] of bridgesOf) {
|
|
472
|
+
for (const bridge of bridges) {
|
|
473
|
+
for (const target of targetsOf.get(reifiedKey) ?? []) {
|
|
474
|
+
(out.get(bridge) ?? out.set(bridge, /* @__PURE__ */ new Set()).get(bridge)).add(target);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
return out;
|
|
479
|
+
}
|
|
480
|
+
function projectBridges(bridges, opts = {}) {
|
|
481
|
+
const { maxPerBridge = 12, viaCap = 4 } = opts;
|
|
482
|
+
const acc = /* @__PURE__ */ new Map();
|
|
483
|
+
for (const [bridge, targetSet] of [...bridges.entries()].sort((x, y) => x[0] < y[0] ? -1 : 1)) {
|
|
484
|
+
const targets = [...targetSet].sort();
|
|
485
|
+
if (targets.length < 2 || targets.length > maxPerBridge) continue;
|
|
486
|
+
for (let i = 0; i < targets.length; i++) {
|
|
487
|
+
for (let j = i + 1; j < targets.length; j++) {
|
|
488
|
+
const key = `${targets[i]}|${targets[j]}`;
|
|
489
|
+
const hit = acc.get(key) ?? { a: targets[i], b: targets[j], w: 0, via: [] };
|
|
490
|
+
hit.w += 1;
|
|
491
|
+
if (hit.via.length < viaCap) hit.via.push(bridge);
|
|
492
|
+
acc.set(key, hit);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
return [...acc.keys()].sort().map((k) => acc.get(k));
|
|
497
|
+
}
|
|
498
|
+
function blendPairs(sets) {
|
|
499
|
+
const acc = /* @__PURE__ */ new Map();
|
|
500
|
+
for (const { pairs, weight } of sets) {
|
|
501
|
+
if (weight <= 0 || pairs.length === 0) continue;
|
|
502
|
+
const max = Math.max(...pairs.map((p) => p.w));
|
|
503
|
+
for (const p of pairs) {
|
|
504
|
+
const key = `${p.a}|${p.b}`;
|
|
505
|
+
const hit = acc.get(key) ?? { a: p.a, b: p.b, w: 0, via: [] };
|
|
506
|
+
hit.w += p.w / max * weight;
|
|
507
|
+
for (const v of p.via) if (hit.via.length < 6 && !hit.via.includes(v)) hit.via.push(v);
|
|
508
|
+
acc.set(key, hit);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
return [...acc.keys()].sort().map((key) => acc.get(key));
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
// src/temporal/bridges.ts
|
|
515
|
+
function bridgingScores(edges, labels) {
|
|
516
|
+
const cross = /* @__PURE__ */ new Map();
|
|
517
|
+
const foreign = /* @__PURE__ */ new Map();
|
|
518
|
+
const touch = (id, otherLabel, w) => {
|
|
519
|
+
cross.set(id, (cross.get(id) ?? 0) + w);
|
|
520
|
+
(foreign.get(id) ?? foreign.set(id, /* @__PURE__ */ new Set()).get(id)).add(otherLabel);
|
|
521
|
+
};
|
|
522
|
+
for (const e of edges) {
|
|
523
|
+
const la = labels.get(e.a);
|
|
524
|
+
const lb = labels.get(e.b);
|
|
525
|
+
if (la === void 0 || lb === void 0 || la === lb) continue;
|
|
526
|
+
const w = e.weight ?? 1;
|
|
527
|
+
touch(e.a, lb, w);
|
|
528
|
+
touch(e.b, la, w);
|
|
529
|
+
}
|
|
530
|
+
let max = 0;
|
|
531
|
+
const raw = /* @__PURE__ */ new Map();
|
|
532
|
+
for (const [id, w] of cross) {
|
|
533
|
+
const r = w * foreign.get(id).size;
|
|
534
|
+
raw.set(id, r);
|
|
535
|
+
if (r > max) max = r;
|
|
536
|
+
}
|
|
537
|
+
const out = /* @__PURE__ */ new Map();
|
|
538
|
+
for (const [id] of labels) {
|
|
539
|
+
const r = raw.get(id) ?? 0;
|
|
540
|
+
const own = labels.get(id);
|
|
541
|
+
const groups = r > 0 ? [own, ...[...foreign.get(id)].sort()] : [own];
|
|
542
|
+
out.set(id, { score: max > 0 ? r / max : 0, groups });
|
|
543
|
+
}
|
|
544
|
+
return out;
|
|
545
|
+
}
|
|
546
|
+
function topBridges(bridging, n = 5) {
|
|
547
|
+
return [...bridging.entries()].filter(([, b]) => b.score > 0).sort((x, y) => y[1].score - x[1].score || (x[0] < y[0] ? -1 : 1)).slice(0, n).map(([id, b]) => ({ id, score: Math.round(b.score * 100) / 100, groups: b.groups }));
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
// src/graph/connectors.ts
|
|
551
|
+
function findConnectors(fragment, config) {
|
|
552
|
+
const nameOf = /* @__PURE__ */ new Map();
|
|
553
|
+
for (const n of fragment.nodes) {
|
|
554
|
+
const label = n.attrs.name ?? n.attrs.title;
|
|
555
|
+
if (typeof label === "string" && label) nameOf.set(`${n.type}:${n.key}`, label);
|
|
556
|
+
}
|
|
557
|
+
const sets = config.lenses.map((l) => ({
|
|
558
|
+
pairs: projectBridges(reifiedBridgeTargets(fragment, { reified: l.reified, bridgeLabel: l.bridge, targetLabel: l.of }), {
|
|
559
|
+
maxPerBridge: config.maxPerBridge ?? 12
|
|
560
|
+
}),
|
|
561
|
+
weight: l.weight ?? 1
|
|
562
|
+
}));
|
|
563
|
+
const pairs = blendPairs([...sets, ...config.extraPairs ?? []]);
|
|
564
|
+
if (pairs.length === 0) return { connectors: [], communities: 0, nodes: 0 };
|
|
565
|
+
const nodes = [...new Set(pairs.flatMap((p) => [p.a, p.b]))].sort();
|
|
566
|
+
const edges = pairs.map((p) => ({ a: p.a, b: p.b, weight: p.w }));
|
|
567
|
+
const labels = labelPropagation(nodes, edges, { seed: 1 });
|
|
568
|
+
const groupName = /* @__PURE__ */ new Map();
|
|
569
|
+
for (const id of nodes) {
|
|
570
|
+
const g = labels.get(id);
|
|
571
|
+
if (!groupName.has(g)) groupName.set(g, nameOf.get(id) ?? id);
|
|
572
|
+
}
|
|
573
|
+
const bridging = bridgingScores(edges, labels);
|
|
574
|
+
const viaOf = (id) => {
|
|
575
|
+
const names = /* @__PURE__ */ new Set();
|
|
576
|
+
for (const p of pairs) {
|
|
577
|
+
if (p.a !== id && p.b !== id) continue;
|
|
578
|
+
for (const v of p.via) names.add(nameOf.get(v) ?? v.split(":").pop());
|
|
579
|
+
}
|
|
580
|
+
return [...names].slice(0, 6);
|
|
581
|
+
};
|
|
582
|
+
const connectors = topBridges(bridging, config.top ?? 12).map((b) => ({
|
|
583
|
+
ref: b.id,
|
|
584
|
+
name: nameOf.get(b.id) ?? b.id,
|
|
585
|
+
score: b.score,
|
|
586
|
+
groups: b.groups.map((g) => groupName.get(g)),
|
|
587
|
+
// every label has an anchor name
|
|
588
|
+
via: viaOf(b.id)
|
|
589
|
+
}));
|
|
590
|
+
return { connectors, communities: new Set(labels.values()).size, nodes: nodes.length };
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// src/util/identity.ts
|
|
594
|
+
function slugify(input) {
|
|
595
|
+
return input.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60).replace(/-+$/g, "");
|
|
596
|
+
}
|
|
597
|
+
function normalizeUrl(raw) {
|
|
598
|
+
try {
|
|
599
|
+
const u = new URL(raw);
|
|
600
|
+
u.hash = "";
|
|
601
|
+
const drop = [/^utm_/i, /^ref$/i, /^ref_/i, /^fbclid$/i, /^gclid$/i, /^mc_/i];
|
|
602
|
+
for (const key of [...u.searchParams.keys()]) {
|
|
603
|
+
if (drop.some((re) => re.test(key))) u.searchParams.delete(key);
|
|
604
|
+
}
|
|
605
|
+
let s = u.toString();
|
|
606
|
+
s = s.replace(/\/$/, "");
|
|
607
|
+
return s;
|
|
608
|
+
} catch {
|
|
609
|
+
return raw.trim().replace(/#.*$/, "").replace(/\/$/, "");
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
function urlHash(raw) {
|
|
613
|
+
const s = normalizeUrl(raw);
|
|
614
|
+
let h = 2166136261;
|
|
615
|
+
for (let i = 0; i < s.length; i++) {
|
|
616
|
+
h ^= s.charCodeAt(i);
|
|
617
|
+
h = Math.imul(h, 16777619);
|
|
618
|
+
}
|
|
619
|
+
return (h >>> 0).toString(36);
|
|
620
|
+
}
|
|
621
|
+
function isLikelyPersonName(name) {
|
|
622
|
+
const n = name.trim();
|
|
623
|
+
if (!n || /\d/.test(n)) return false;
|
|
624
|
+
const parts = n.split(/\s+/);
|
|
625
|
+
if (parts.length < 2 || parts.length > 4) return false;
|
|
626
|
+
return parts.every((p) => /^[A-Z][a-zA-Z'’.-]*$/.test(p));
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// src/enrich/facts.ts
|
|
630
|
+
var LOCATE_SYSTEM = "You resolve US business locations to counties. For each company you are given a city and/or a free-text region. Name the US county containing that city (for independent cities, use the city's own county-equivalent name) plus its 2-letter state. Use nulls when the location is ambiguous, outside the US, or unknown.";
|
|
631
|
+
var LOCATE_TOOL = {
|
|
632
|
+
name: "resolve_counties",
|
|
633
|
+
description: "County name + state per company",
|
|
634
|
+
parameters: {
|
|
635
|
+
type: "object",
|
|
636
|
+
properties: {
|
|
637
|
+
items: {
|
|
638
|
+
type: "array",
|
|
639
|
+
items: {
|
|
640
|
+
type: "object",
|
|
641
|
+
properties: {
|
|
642
|
+
slug: { type: "string" },
|
|
643
|
+
county: { type: ["string", "null"], description: 'county name WITHOUT the word "County", e.g. "Santa Clara"' },
|
|
644
|
+
state: { type: ["string", "null"], description: "2-letter USPS state" }
|
|
645
|
+
},
|
|
646
|
+
required: ["slug", "county", "state"]
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
},
|
|
650
|
+
required: ["items"]
|
|
651
|
+
}
|
|
652
|
+
};
|
|
653
|
+
function locatePrompt(batch) {
|
|
654
|
+
return "Companies:\n" + batch.map((c) => `- slug ${c.slug}: city=${c.city ?? "?"} region=${c.region ?? "?"}`).join("\n");
|
|
655
|
+
}
|
|
656
|
+
var FACTS_SEARCH_SYSTEM = "You are a research assistant with web search. Find concrete facts about the company: founding year/date, headquarters city and state, founders and key executives (names, roles), and funding (rounds, dates, amounts, investor names). Prefer official/reputable sources. Report dense factual notes; say 'not found' for anything you cannot establish. Do not speculate.";
|
|
657
|
+
var FACTS_EXTRACT_SYSTEM = "Extract structured facts from the research notes. Use null for anything the notes do not establish.";
|
|
658
|
+
var FACTS_EXTRACT_TOOL = {
|
|
659
|
+
name: "company_facts",
|
|
660
|
+
description: "Structured company facts from research notes",
|
|
661
|
+
parameters: {
|
|
662
|
+
type: "object",
|
|
663
|
+
properties: {
|
|
664
|
+
founded: { type: ["string", "null"], description: "YYYY or YYYY-MM or YYYY-MM-DD" },
|
|
665
|
+
city: { type: ["string", "null"] },
|
|
666
|
+
state: { type: ["string", "null"], description: "2-letter USPS" },
|
|
667
|
+
people: {
|
|
668
|
+
type: "array",
|
|
669
|
+
items: {
|
|
670
|
+
type: "object",
|
|
671
|
+
properties: { name: { type: "string" }, role: { type: "string" } },
|
|
672
|
+
required: ["name", "role"]
|
|
673
|
+
}
|
|
674
|
+
},
|
|
675
|
+
rounds: {
|
|
676
|
+
type: "array",
|
|
677
|
+
items: {
|
|
678
|
+
type: "object",
|
|
679
|
+
properties: {
|
|
680
|
+
round: { type: ["string", "null"] },
|
|
681
|
+
date: { type: ["string", "null"] },
|
|
682
|
+
amount: { type: ["string", "null"] },
|
|
683
|
+
investors: { type: "array", items: { type: "string" } }
|
|
684
|
+
},
|
|
685
|
+
required: ["investors"]
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
},
|
|
689
|
+
required: ["people", "rounds"]
|
|
690
|
+
}
|
|
691
|
+
};
|
|
692
|
+
var DATE_RE = /^\d{4}(-\d{2}){0,2}$/;
|
|
693
|
+
var isoDate = (v) => !DATE_RE.test(v) ? null : v.length === 4 ? `${v}-01-01` : v.length === 7 ? `${v}-01` : v;
|
|
694
|
+
var countyKey = (name, st) => `${String(name).toLowerCase().replace(/\bcounty\b|\bparish\b|\bborough\b/g, "").replace(/[^a-z]/g, "")}|${String(st).toUpperCase()}`;
|
|
695
|
+
function factsToFragment(company, draft) {
|
|
696
|
+
const nodes = [];
|
|
697
|
+
const edges = [];
|
|
698
|
+
const attrs = {};
|
|
699
|
+
const founded = draft.founded ? isoDate(draft.founded) : null;
|
|
700
|
+
if (founded) attrs.founded = founded;
|
|
701
|
+
if (draft.city && !company.city) attrs.city = String(draft.city).slice(0, 60);
|
|
702
|
+
if (Object.keys(attrs).length > 0) {
|
|
703
|
+
nodes.push({ type: "company", key: company.slug, attrs: { name: company.name, ...attrs } });
|
|
704
|
+
}
|
|
705
|
+
const anchorDate = attrs.founded ?? company.founded ?? company.first_seen ?? "";
|
|
706
|
+
for (const p of (draft.people ?? []).slice(0, 5)) {
|
|
707
|
+
const personSlug = slugify(p.name);
|
|
708
|
+
if (!personSlug || personSlug === company.slug) continue;
|
|
709
|
+
nodes.push({ type: "person", key: personSlug, attrs: { name: p.name } });
|
|
710
|
+
const key = `${personSlug}~${company.slug}`;
|
|
711
|
+
nodes.push({ type: "affiliation", key, attrs: { date: anchorDate, role: String(p.role).slice(0, 60) } });
|
|
712
|
+
edges.push({ from: { type: "affiliation", key }, to: { type: "person", key: personSlug }, label: "of_person" });
|
|
713
|
+
edges.push({ from: { type: "affiliation", key }, to: { type: "company", key: company.slug }, label: "of_company" });
|
|
714
|
+
}
|
|
715
|
+
(draft.rounds ?? []).slice(0, 4).forEach((r, i) => {
|
|
716
|
+
const investors = (r.investors ?? []).map(slugify).filter(Boolean).slice(0, 6);
|
|
717
|
+
const date = r.date ? isoDate(r.date) : null;
|
|
718
|
+
if (investors.length === 0 && !date) return;
|
|
719
|
+
const key = `${company.slug}~e${i}`;
|
|
720
|
+
const rAttrs = { date: date ?? anchorDate };
|
|
721
|
+
if (r.round) rAttrs.round = String(r.round).slice(0, 40);
|
|
722
|
+
if (r.amount) rAttrs.amount = String(r.amount).slice(0, 40);
|
|
723
|
+
nodes.push({ type: "funding_round", key, attrs: rAttrs });
|
|
724
|
+
edges.push({ from: { type: "funding_round", key }, to: { type: "company", key: company.slug }, label: "round_of" });
|
|
725
|
+
for (const [j, invSlug] of investors.entries()) {
|
|
726
|
+
nodes.push({ type: "investor", key: invSlug, attrs: { name: (r.investors ?? [])[j] } });
|
|
727
|
+
edges.push({ from: { type: "funding_round", key }, to: { type: "investor", key: invSlug }, label: "raised_from" });
|
|
728
|
+
}
|
|
729
|
+
});
|
|
730
|
+
return { nodes, edges };
|
|
731
|
+
}
|
|
732
|
+
function locationFragment(slug, name, fips) {
|
|
733
|
+
return {
|
|
734
|
+
nodes: [{ type: "company", key: slug, attrs: { name, fips } }],
|
|
735
|
+
edges: [{ from: { type: "company", key: slug }, to: { type: "county", key: fips }, label: "located_in" }]
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// src/enrich/run.ts
|
|
740
|
+
async function enrichCompany(ai, models, company) {
|
|
741
|
+
const where = `${company.city ? `, ${company.city}` : ""}${company.region ? ` (${company.region})` : ""}`;
|
|
742
|
+
const { text } = await ai.search({
|
|
743
|
+
model: models.search,
|
|
744
|
+
system: FACTS_SEARCH_SYSTEM,
|
|
745
|
+
user: `Company: ${company.name}${where}. Research its founding, headquarters, key people, and funding.`,
|
|
746
|
+
maxTokens: 900
|
|
747
|
+
});
|
|
748
|
+
const extractOnce = () => ai.extract({
|
|
749
|
+
model: models.extract,
|
|
750
|
+
system: FACTS_EXTRACT_SYSTEM,
|
|
751
|
+
user: `Company: ${company.name}
|
|
752
|
+
|
|
753
|
+
Notes:
|
|
754
|
+
${text}`,
|
|
755
|
+
tool: FACTS_EXTRACT_TOOL,
|
|
756
|
+
maxTokens: 4e3
|
|
757
|
+
// reasoning headroom before the forced tool call
|
|
758
|
+
});
|
|
759
|
+
const { value } = await extractOnce().catch(extractOnce);
|
|
760
|
+
return factsToFragment(company, value);
|
|
761
|
+
}
|
|
762
|
+
async function resolveCounties(ai, model, rows, gazetteer) {
|
|
763
|
+
const { value } = await ai.extract({
|
|
764
|
+
model,
|
|
765
|
+
system: LOCATE_SYSTEM,
|
|
766
|
+
user: locatePrompt(rows),
|
|
767
|
+
tool: LOCATE_TOOL,
|
|
768
|
+
maxTokens: 6e3
|
|
769
|
+
// reasoning models spend tokens before the tool call
|
|
770
|
+
});
|
|
771
|
+
const out = [];
|
|
772
|
+
for (const item of value.items ?? []) {
|
|
773
|
+
const fips = item.county && item.state ? gazetteer.get(countyKey(item.county, item.state)) : void 0;
|
|
774
|
+
if (fips) out.push({ slug: item.slug, fips });
|
|
775
|
+
}
|
|
776
|
+
return out;
|
|
777
|
+
}
|
|
778
|
+
|
|
369
779
|
// src/llm/provider.ts
|
|
370
780
|
var import_ai = require("@odla-ai/ai");
|
|
371
781
|
var MODEL_TIERS = {
|
|
@@ -607,42 +1017,6 @@ var gdeltConnector = {
|
|
|
607
1017
|
}
|
|
608
1018
|
};
|
|
609
1019
|
|
|
610
|
-
// src/util/identity.ts
|
|
611
|
-
function slugify(input) {
|
|
612
|
-
return input.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 60).replace(/-+$/g, "");
|
|
613
|
-
}
|
|
614
|
-
function normalizeUrl(raw) {
|
|
615
|
-
try {
|
|
616
|
-
const u = new URL(raw);
|
|
617
|
-
u.hash = "";
|
|
618
|
-
const drop = [/^utm_/i, /^ref$/i, /^ref_/i, /^fbclid$/i, /^gclid$/i, /^mc_/i];
|
|
619
|
-
for (const key of [...u.searchParams.keys()]) {
|
|
620
|
-
if (drop.some((re) => re.test(key))) u.searchParams.delete(key);
|
|
621
|
-
}
|
|
622
|
-
let s = u.toString();
|
|
623
|
-
s = s.replace(/\/$/, "");
|
|
624
|
-
return s;
|
|
625
|
-
} catch {
|
|
626
|
-
return raw.trim().replace(/#.*$/, "").replace(/\/$/, "");
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
function urlHash(raw) {
|
|
630
|
-
const s = normalizeUrl(raw);
|
|
631
|
-
let h = 2166136261;
|
|
632
|
-
for (let i = 0; i < s.length; i++) {
|
|
633
|
-
h ^= s.charCodeAt(i);
|
|
634
|
-
h = Math.imul(h, 16777619);
|
|
635
|
-
}
|
|
636
|
-
return (h >>> 0).toString(36);
|
|
637
|
-
}
|
|
638
|
-
function isLikelyPersonName(name) {
|
|
639
|
-
const n = name.trim();
|
|
640
|
-
if (!n || /\d/.test(n)) return false;
|
|
641
|
-
const parts = n.split(/\s+/);
|
|
642
|
-
if (parts.length < 2 || parts.length > 4) return false;
|
|
643
|
-
return parts.every((p) => /^[A-Z][a-zA-Z'’.-]*$/.test(p));
|
|
644
|
-
}
|
|
645
|
-
|
|
646
1020
|
// src/connectors/fred.ts
|
|
647
1021
|
async function fetchSeries(seriesId, apiKey, start) {
|
|
648
1022
|
const url = `https://api.stlouisfed.org/fred/series/observations?series_id=${encodeURIComponent(seriesId)}&api_key=${encodeURIComponent(apiKey)}&file_type=json&observation_start=${encodeURIComponent(start)}`;
|