fodda-mcp 1.32.1 → 1.46.31
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/README.md +14 -16
- package/dist/a2aHandler.d.ts +33 -3
- package/dist/a2aHandler.d.ts.map +1 -1
- package/dist/a2aHandler.js +189 -36
- package/dist/a2aHandler.js.map +1 -1
- package/dist/agentFacts.d.ts +21 -0
- package/dist/agentFacts.d.ts.map +1 -0
- package/dist/agentFacts.js +72 -0
- package/dist/agentFacts.js.map +1 -0
- package/dist/agents/fodda-researcher/index.d.ts +6 -0
- package/dist/agents/fodda-researcher/index.d.ts.map +1 -1
- package/dist/agents/fodda-researcher/index.js +18 -0
- package/dist/agents/fodda-researcher/index.js.map +1 -1
- package/dist/agents/fodda-researcher/skills.d.ts +1 -1
- package/dist/agents/fodda-researcher/skills.d.ts.map +1 -1
- package/dist/agents/fodda-researcher/skills.js +8 -3
- package/dist/agents/fodda-researcher/skills.js.map +1 -1
- package/dist/brandTemplate.d.ts +1 -1
- package/dist/brandTemplate.d.ts.map +1 -1
- package/dist/brandTemplate.js +179 -11
- package/dist/brandTemplate.js.map +1 -1
- package/dist/catalogCache.d.ts +14 -13
- package/dist/catalogCache.d.ts.map +1 -1
- package/dist/catalogCache.js +266 -67
- package/dist/catalogCache.js.map +1 -1
- package/dist/coverageRelevance.d.ts +156 -0
- package/dist/coverageRelevance.d.ts.map +1 -0
- package/dist/coverageRelevance.js +1062 -0
- package/dist/coverageRelevance.js.map +1 -0
- package/dist/deepResearch.d.ts +43 -0
- package/dist/deepResearch.d.ts.map +1 -0
- package/dist/deepResearch.js +498 -0
- package/dist/deepResearch.js.map +1 -0
- package/dist/enrichment.d.ts.map +1 -1
- package/dist/enrichment.js +51 -1
- package/dist/enrichment.js.map +1 -1
- package/dist/errorHandling.d.ts +1 -1
- package/dist/errorHandling.d.ts.map +1 -1
- package/dist/errorHandling.js +26 -6
- package/dist/errorHandling.js.map +1 -1
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +548 -38
- package/dist/index.js.map +1 -1
- package/dist/linkedinEngine.d.ts +150 -0
- package/dist/linkedinEngine.d.ts.map +1 -0
- package/dist/linkedinEngine.js +696 -0
- package/dist/linkedinEngine.js.map +1 -0
- package/dist/oauthRegisterShim.d.ts +13 -0
- package/dist/oauthRegisterShim.d.ts.map +1 -0
- package/dist/oauthRegisterShim.js +83 -0
- package/dist/oauthRegisterShim.js.map +1 -0
- package/dist/pricingCache.d.ts +6 -8
- package/dist/pricingCache.d.ts.map +1 -1
- package/dist/pricingCache.js +69 -13
- package/dist/pricingCache.js.map +1 -1
- package/dist/queryCache.d.ts +2 -0
- package/dist/queryCache.d.ts.map +1 -1
- package/dist/queryCache.js +11 -2
- package/dist/queryCache.js.map +1 -1
- package/dist/resources.d.ts +27 -0
- package/dist/resources.d.ts.map +1 -0
- package/dist/resources.js +126 -0
- package/dist/resources.js.map +1 -0
- package/dist/sessionTracker.d.ts +24 -2
- package/dist/sessionTracker.d.ts.map +1 -1
- package/dist/sessionTracker.js +198 -4
- package/dist/sessionTracker.js.map +1 -1
- package/dist/systemPrompt.d.ts +3 -3
- package/dist/systemPrompt.d.ts.map +1 -1
- package/dist/systemPrompt.js +81 -61
- package/dist/systemPrompt.js.map +1 -1
- package/dist/telemetry.d.ts +43 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +101 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/test_analyst.js +2 -2
- package/dist/test_analyst.js.map +1 -1
- package/dist/test_book_a_call_live.d.ts +2 -0
- package/dist/test_book_a_call_live.d.ts.map +1 -0
- package/dist/test_book_a_call_live.js +148 -0
- package/dist/test_book_a_call_live.js.map +1 -0
- package/dist/test_brand_intelligence_endpoint.d.ts +2 -0
- package/dist/test_brand_intelligence_endpoint.d.ts.map +1 -0
- package/dist/test_brand_intelligence_endpoint.js +20 -0
- package/dist/test_brand_intelligence_endpoint.js.map +1 -0
- package/dist/test_coverage_relevance.d.ts +12 -0
- package/dist/test_coverage_relevance.d.ts.map +1 -0
- package/dist/test_coverage_relevance.js +121 -0
- package/dist/test_coverage_relevance.js.map +1 -0
- package/dist/test_dcr_and_legacy_deprecation.d.ts +2 -0
- package/dist/test_dcr_and_legacy_deprecation.d.ts.map +1 -0
- package/dist/test_dcr_and_legacy_deprecation.js +148 -0
- package/dist/test_dcr_and_legacy_deprecation.js.map +1 -0
- package/dist/test_financial_snapshot_render.d.ts +2 -0
- package/dist/test_financial_snapshot_render.d.ts.map +1 -0
- package/dist/test_financial_snapshot_render.js +167 -0
- package/dist/test_financial_snapshot_render.js.map +1 -0
- package/dist/test_gap_alert.d.ts +12 -0
- package/dist/test_gap_alert.d.ts.map +1 -0
- package/dist/test_gap_alert.js +43 -0
- package/dist/test_gap_alert.js.map +1 -0
- package/dist/test_list_graphs_topics.d.ts +2 -0
- package/dist/test_list_graphs_topics.d.ts.map +1 -0
- package/dist/test_list_graphs_topics.js +26 -0
- package/dist/test_list_graphs_topics.js.map +1 -0
- package/dist/test_live_endpoint_runner.d.ts +2 -0
- package/dist/test_live_endpoint_runner.d.ts.map +1 -0
- package/dist/test_live_endpoint_runner.js +65 -0
- package/dist/test_live_endpoint_runner.js.map +1 -0
- package/dist/test_local_list_graphs.d.ts +2 -0
- package/dist/test_local_list_graphs.d.ts.map +1 -0
- package/dist/test_local_list_graphs.js +53 -0
- package/dist/test_local_list_graphs.js.map +1 -0
- package/dist/test_next_moves.d.ts +2 -0
- package/dist/test_next_moves.d.ts.map +1 -0
- package/dist/test_next_moves.js +349 -0
- package/dist/test_next_moves.js.map +1 -0
- package/dist/test_next_moves_transcripts.d.ts +2 -0
- package/dist/test_next_moves_transcripts.d.ts.map +1 -0
- package/dist/test_next_moves_transcripts.js +413 -0
- package/dist/test_next_moves_transcripts.js.map +1 -0
- package/dist/test_nike_cmo_alias.d.ts +2 -0
- package/dist/test_nike_cmo_alias.d.ts.map +1 -0
- package/dist/test_nike_cmo_alias.js +76 -0
- package/dist/test_nike_cmo_alias.js.map +1 -0
- package/dist/test_session_next_moves_telemetry.d.ts +2 -0
- package/dist/test_session_next_moves_telemetry.d.ts.map +1 -0
- package/dist/test_session_next_moves_telemetry.js +100 -0
- package/dist/test_session_next_moves_telemetry.js.map +1 -0
- package/dist/test_token_resolution.d.ts +2 -0
- package/dist/test_token_resolution.d.ts.map +1 -0
- package/dist/test_token_resolution.js +82 -0
- package/dist/test_token_resolution.js.map +1 -0
- package/dist/toolHandlers.d.ts +7 -3
- package/dist/toolHandlers.d.ts.map +1 -1
- package/dist/toolHandlers.js +1678 -364
- package/dist/toolHandlers.js.map +1 -1
- package/dist/tools.d.ts +7 -2
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +12 -3
- package/dist/tools.js.map +1 -1
- package/dist/types.d.ts +9 -46
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -1
- package/dist/verify_tools_endpoint.js +4 -3
- package/dist/verify_tools_endpoint.js.map +1 -1
- package/package.json +5 -2
- package/server.json +16 -3
- package/dist/scripts/simulate_gemini.d.ts +0 -2
- package/dist/scripts/simulate_gemini.d.ts.map +0 -1
- package/dist/scripts/simulate_gemini.js +0 -100
- package/dist/scripts/simulate_gemini.js.map +0 -1
- package/dist/scripts/verify_registry.d.ts +0 -2
- package/dist/scripts/verify_registry.d.ts.map +0 -1
- package/dist/scripts/verify_registry.js +0 -77
- package/dist/scripts/verify_registry.js.map +0 -1
- package/dist/src/index.d.ts +0 -2
- package/dist/src/index.d.ts.map +0 -1
- package/dist/src/index.js +0 -332
- package/dist/src/index.js.map +0 -1
- package/dist/src/tools.d.ts +0 -12
- package/dist/src/tools.d.ts.map +0 -1
- package/dist/src/tools.js +0 -104
- package/dist/src/tools.js.map +0 -1
- package/dist/src/types.d.ts +0 -44
- package/dist/src/types.d.ts.map +0 -1
- package/dist/src/types.js +0 -2
- package/dist/src/types.js.map +0 -1
- package/dist/src/verify.d.ts +0 -2
- package/dist/src/verify.d.ts.map +0 -1
- package/dist/src/verify.js +0 -47
- package/dist/src/verify.js.map +0 -1
- package/dist/src/verify_hmac.d.ts +0 -2
- package/dist/src/verify_hmac.d.ts.map +0 -1
- package/dist/src/verify_hmac.js +0 -106
- package/dist/src/verify_hmac.js.map +0 -1
- package/dist/src/verify_tools_endpoint.d.ts +0 -2
- package/dist/src/verify_tools_endpoint.d.ts.map +0 -1
- package/dist/src/verify_tools_endpoint.js +0 -40
- package/dist/src/verify_tools_endpoint.js.map +0 -1
- package/dist/test-waverunner-response.d.ts +0 -2
- package/dist/test-waverunner-response.d.ts.map +0 -1
- package/dist/test-waverunner-response.js +0 -20
- package/dist/test-waverunner-response.js.map +0 -1
package/dist/catalogCache.d.ts
CHANGED
|
@@ -48,12 +48,23 @@ export interface CatalogAnalyst {
|
|
|
48
48
|
analyst_id: string;
|
|
49
49
|
name: string;
|
|
50
50
|
description: string;
|
|
51
|
+
type?: string;
|
|
52
|
+
agent_type?: string;
|
|
53
|
+
kind?: string;
|
|
54
|
+
is_digital_twin?: boolean;
|
|
55
|
+
is_human_agent?: boolean;
|
|
56
|
+
consult_tool?: string;
|
|
57
|
+
price?: string;
|
|
58
|
+
offerings?: any[];
|
|
59
|
+
status?: string;
|
|
60
|
+
Status?: string;
|
|
51
61
|
}
|
|
52
62
|
/**
|
|
53
63
|
* Initialize the catalog cache. Call once at server startup.
|
|
54
64
|
* Fetches immediately, then sets up hourly refresh.
|
|
55
65
|
*/
|
|
56
66
|
export declare function initCatalogCache(): Promise<void>;
|
|
67
|
+
export declare function setCachedCatalogForTesting(catalog: CatalogResponse, analysts?: CatalogAnalyst[]): void;
|
|
57
68
|
/**
|
|
58
69
|
* Get the cached catalog. Returns null if not yet fetched.
|
|
59
70
|
*/
|
|
@@ -66,9 +77,7 @@ export declare function getAnalysts(): CatalogAnalyst[];
|
|
|
66
77
|
* Get all graphs from the cached catalog.
|
|
67
78
|
*/
|
|
68
79
|
export declare function getGraphs(): CatalogGraph[];
|
|
69
|
-
|
|
70
|
-
* Get only graphs with status === 'live'.
|
|
71
|
-
*/
|
|
80
|
+
export declare function getGraphDetails(graphId: string): CatalogGraph | undefined;
|
|
72
81
|
export declare function getLiveGraphs(): CatalogGraph[];
|
|
73
82
|
/**
|
|
74
83
|
* Get domain/curated graphs (graph_type === 'domain').
|
|
@@ -138,16 +147,6 @@ export declare function buildCuratedOnlyToolsBlock(): string;
|
|
|
138
147
|
* to inject into the system prompt, replacing the hardcoded blocks.
|
|
139
148
|
*/
|
|
140
149
|
export declare function buildDynamicPromptSections(): string | null;
|
|
141
|
-
/**
|
|
142
|
-
* Score how relevant a graph is to a query.
|
|
143
|
-
* Returns a 0-1 score based on keyword overlap between query terms and graph metadata.
|
|
144
|
-
*
|
|
145
|
-
* Scoring strategy:
|
|
146
|
-
* - Each query term that appears in the graph's searchable text adds to the score
|
|
147
|
-
* - Matches in topics/routing_keywords are weighted higher (2x) than description matches
|
|
148
|
-
* - Living graphs get a modest freshness boost (+0.05)
|
|
149
|
-
* - Brand-name queries (single capitalized word) always include domain graphs
|
|
150
|
-
*/
|
|
151
150
|
export declare function scoreGraphRelevance(query: string, g: CatalogGraph): number;
|
|
152
151
|
/**
|
|
153
152
|
* Classify a graph into its operational tier based on type + update cadence.
|
|
@@ -159,6 +158,7 @@ export interface GraphRelevanceResult {
|
|
|
159
158
|
graph: CatalogGraph;
|
|
160
159
|
score: number;
|
|
161
160
|
graphTier: 'living' | 'static_expert' | 'report' | 'supplemental' | 'skill';
|
|
161
|
+
isDirectMatch: boolean;
|
|
162
162
|
}
|
|
163
163
|
/**
|
|
164
164
|
* Get the graphs most relevant to a query, using 2-step routing:
|
|
@@ -194,6 +194,7 @@ export type SourceCandidate = {
|
|
|
194
194
|
score: number;
|
|
195
195
|
reason: string;
|
|
196
196
|
};
|
|
197
|
+
export declare function flushSourcePlanCache(): void;
|
|
197
198
|
/**
|
|
198
199
|
* Unified source selector: graph candidates from getRelevantGraphs()
|
|
199
200
|
* (UNCHANGED — same scoring, tier diversity, freshness bonus, same order),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"catalogCache.d.ts","sourceRoot":"","sources":["../src/catalogCache.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"catalogCache.d.ts","sourceRoot":"","sources":["../src/catalogCache.ts"],"names":[],"mappings":"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"}
|
package/dist/catalogCache.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* and is the same endpoint the website and app use.
|
|
7
7
|
*/
|
|
8
8
|
import axios from 'axios';
|
|
9
|
+
import { cacheClear } from './queryCache.js';
|
|
9
10
|
// ---------------------------------------------------------------------------
|
|
10
11
|
// Cache
|
|
11
12
|
// ---------------------------------------------------------------------------
|
|
@@ -51,6 +52,7 @@ async function fetchAnalysts() {
|
|
|
51
52
|
*/
|
|
52
53
|
export async function initCatalogCache() {
|
|
53
54
|
try {
|
|
55
|
+
cacheClear();
|
|
54
56
|
cachedCatalog = await fetchCatalog();
|
|
55
57
|
cachedAnalysts = await fetchAnalysts();
|
|
56
58
|
lastFetchedAt = Date.now();
|
|
@@ -77,6 +79,13 @@ export async function initCatalogCache() {
|
|
|
77
79
|
}
|
|
78
80
|
}, CATALOG_REFRESH_MS);
|
|
79
81
|
}
|
|
82
|
+
export function setCachedCatalogForTesting(catalog, analysts) {
|
|
83
|
+
cachedCatalog = catalog;
|
|
84
|
+
if (analysts)
|
|
85
|
+
cachedAnalysts = analysts;
|
|
86
|
+
lastFetchedAt = Date.now();
|
|
87
|
+
rebuildSearchIndex();
|
|
88
|
+
}
|
|
80
89
|
/**
|
|
81
90
|
* Get the cached catalog. Returns null if not yet fetched.
|
|
82
91
|
*/
|
|
@@ -93,11 +102,11 @@ export function getAnalysts() {
|
|
|
93
102
|
* Get all graphs from the cached catalog.
|
|
94
103
|
*/
|
|
95
104
|
export function getGraphs() {
|
|
96
|
-
return cachedCatalog?.graphs
|
|
105
|
+
return cachedCatalog?.graphs || [];
|
|
106
|
+
}
|
|
107
|
+
export function getGraphDetails(graphId) {
|
|
108
|
+
return getGraphs().find(g => g.graph_id === graphId);
|
|
97
109
|
}
|
|
98
|
-
/**
|
|
99
|
-
* Get only graphs with status === 'live'.
|
|
100
|
-
*/
|
|
101
110
|
export function getLiveGraphs() {
|
|
102
111
|
return getGraphs().filter(g => g.status === 'live');
|
|
103
112
|
}
|
|
@@ -152,20 +161,7 @@ export function getDomainGraphIds() {
|
|
|
152
161
|
* Generates: - graphId "retail" → "PSFK's Retail Graph"
|
|
153
162
|
*/
|
|
154
163
|
export function buildGraphNamingBlock() {
|
|
155
|
-
|
|
156
|
-
if (graphs.length === 0)
|
|
157
|
-
return ''; // fallback — will use hardcoded
|
|
158
|
-
const lines = [];
|
|
159
|
-
lines.push('GRAPH NAMING: Never call results "the Fodda graph." Fodda is the platform — the knowledge graphs are created by named experts. Always attribute to the expert:');
|
|
160
|
-
for (const g of graphs) {
|
|
161
|
-
const displayName = buildDisplayName(g);
|
|
162
|
-
const domainHint = g.domain ? ` (${g.domain})` : '';
|
|
163
|
-
lines.push(`- graphId "${g.graph_id}" → "${displayName}"${domainHint}`);
|
|
164
|
-
}
|
|
165
|
-
lines.push('Example: "PSFK\'s Retail Graph identifies Retailer-Operated Value-Recovery Programs as a top signal (score: 100)" — NOT "the Fodda graph shows..."');
|
|
166
|
-
lines.push('Fodda is the delivery mechanism. The experts are the authority.');
|
|
167
|
-
lines.push('When presenting results from community Pattern Graphs, use the creator\'s name in attribution (e.g., "According to [creator]\'s [Graph Name] Pattern Graph on Fodda...").');
|
|
168
|
-
return lines.join('\n');
|
|
164
|
+
return 'GRAPH NAMING: Never call results "the Fodda graph." Fodda is the platform — knowledge graphs are created by named experts. Always attribute each graph to its named expert; call `list_graphs` for graph names, curators, and domain details. Example: "PSFK\'s Retail Graph identifies Retailer-Operated Value-Recovery Programs as a top signal (score: 100)" — NOT "the Fodda graph shows..."';
|
|
169
165
|
}
|
|
170
166
|
/**
|
|
171
167
|
* Build a human-friendly display name for a graph.
|
|
@@ -517,6 +513,7 @@ let graphSearchTexts = new Map();
|
|
|
517
513
|
*/
|
|
518
514
|
function rebuildSearchIndex() {
|
|
519
515
|
graphSearchTexts.clear();
|
|
516
|
+
flushSourcePlanCache();
|
|
520
517
|
for (const g of getGraphs()) {
|
|
521
518
|
graphSearchTexts.set(g.graph_id, buildSearchableText(g));
|
|
522
519
|
}
|
|
@@ -531,47 +528,187 @@ function rebuildSearchIndex() {
|
|
|
531
528
|
* - Living graphs get a modest freshness boost (+0.05)
|
|
532
529
|
* - Brand-name queries (single capitalized word) always include domain graphs
|
|
533
530
|
*/
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
531
|
+
const QUERY_EXPANSION_MAP = {
|
|
532
|
+
// Wine & Beverages
|
|
533
|
+
wine: ['beverage', 'drink', 'food', 'hospitality', 'dining', 'nightlife', 'leisure', 'entertainment', 'luxury', 'home', 'appliances', 'kitchen', 'dining', 'cpg', 'retail'],
|
|
534
|
+
beverage: ['beverage', 'food', 'drink', 'cpg', 'retail', 'hospitality', 'dining', 'nightlife'],
|
|
535
|
+
beverages: ['beverage', 'food', 'drink', 'cpg', 'retail', 'hospitality', 'dining', 'nightlife'],
|
|
536
|
+
drinks: ['beverage', 'drink', 'food', 'cpg', 'retail', 'nightlife', 'hospitality'],
|
|
537
|
+
drink: ['beverage', 'drink', 'food', 'cpg', 'retail', 'nightlife', 'hospitality'],
|
|
538
|
+
mocktail: ['beverage', 'drink', 'food', 'hospitality', 'dining', 'nightlife', 'wellness'],
|
|
539
|
+
mocktails: ['beverage', 'drink', 'food', 'hospitality', 'dining', 'nightlife', 'wellness'],
|
|
540
|
+
vin: ['beverage', 'food', 'hospitality', 'dining'],
|
|
541
|
+
sommelier: ['beverage', 'food', 'hospitality', 'dining', 'nightlife'],
|
|
542
|
+
beer: ['beverage', 'drink', 'food', 'hospitality', 'nightlife', 'cpg'],
|
|
543
|
+
spirits: ['beverage', 'drink', 'food', 'hospitality', 'nightlife', 'luxury', 'cpg'],
|
|
544
|
+
cocktail: ['beverage', 'drink', 'food', 'hospitality', 'dining', 'nightlife'],
|
|
545
|
+
barware: ['beverage', 'home', 'appliances', 'dining', 'nightlife', 'tableware', 'retail'],
|
|
546
|
+
glassware: ['home', 'dining', 'beverage', 'design', 'nightlife', 'tableware', 'barware', 'retail'],
|
|
547
|
+
stemware: ['home', 'dining', 'beverage', 'design', 'luxury', 'tableware', 'barware', 'retail'],
|
|
548
|
+
decanter: ['home', 'dining', 'beverage', 'luxury', 'design', 'barware', 'tableware'],
|
|
549
|
+
fridge: ['appliances', 'home', 'kitchen', 'electronics', 'consumer-electronics', 'retail'],
|
|
550
|
+
fridges: ['appliances', 'home', 'kitchen', 'electronics', 'consumer-electronics', 'retail'],
|
|
551
|
+
refrigerator: ['appliances', 'home', 'kitchen', 'electronics', 'consumer-electronics', 'retail'],
|
|
552
|
+
cooler: ['appliances', 'home', 'kitchen', 'electronics', 'beverage', 'cooling', 'retail'],
|
|
553
|
+
coolers: ['appliances', 'home', 'kitchen', 'electronics', 'beverage', 'cooling', 'retail'],
|
|
554
|
+
cellar: ['home', 'luxury', 'architecture', 'design', 'interior', 'wine'],
|
|
555
|
+
cabinet: ['furniture', 'home', 'interior', 'design', 'decor', 'storage'],
|
|
556
|
+
furniture: ['home', 'design', 'living', 'decor', 'interior', 'retail'],
|
|
557
|
+
racking: ['home', 'design', 'storage'],
|
|
558
|
+
accessories: ['consumer-goods', 'home', 'retail', 'products', 'design'],
|
|
559
|
+
// Food & Dining
|
|
560
|
+
restaurant: ['food', 'beverage', 'dining', 'hospitality', 'nightlife'],
|
|
561
|
+
dining: ['food', 'beverage', 'hospitality', 'restaurant', 'dining-trends', 'nightlife', 'home'],
|
|
562
|
+
kitchen: ['home', 'appliances', 'food', 'design'],
|
|
563
|
+
// Home & Living
|
|
564
|
+
lighting: ['home', 'design', 'decor'],
|
|
565
|
+
decor: ['home', 'design', 'living'],
|
|
566
|
+
appliance: ['appliances', 'home', 'kitchen', 'consumer-electronics', 'retail'],
|
|
567
|
+
appliances: ['appliances', 'home', 'kitchen', 'consumer-electronics', 'retail'],
|
|
568
|
+
// Fashion & Apparel
|
|
569
|
+
sneakers: ['fashion', 'apparel', 'footwear', 'streetwear', 'retail'],
|
|
570
|
+
footwear: ['fashion', 'apparel', 'retail'],
|
|
571
|
+
clothing: ['fashion', 'apparel', 'retail'],
|
|
572
|
+
apparel: ['fashion', 'retail'],
|
|
573
|
+
// Beauty & Skincare
|
|
574
|
+
skincare: ['beauty', 'wellness', 'cpg', 'retail'],
|
|
575
|
+
cosmetics: ['beauty', 'cpg', 'retail'],
|
|
576
|
+
fragrance: ['beauty', 'luxury', 'cpg', 'retail'],
|
|
577
|
+
// Tech & Electronics
|
|
578
|
+
smartphone: ['technology', 'electronics', 'consumer-electronics', 'retail'],
|
|
579
|
+
gadget: ['technology', 'electronics', 'consumer-electronics'],
|
|
580
|
+
// Luxury & Travel
|
|
581
|
+
watch: ['luxury', 'fashion', 'retail'],
|
|
582
|
+
watches: ['luxury', 'fashion', 'retail'],
|
|
583
|
+
jewelry: ['luxury', 'fashion', 'retail'],
|
|
584
|
+
// Automotive & Mobility
|
|
585
|
+
ev: ['automotive', 'mobility', 'transportation', 'energy', 'sustainability', 'technology'],
|
|
586
|
+
electric: ['automotive', 'mobility', 'energy', 'sustainability', 'technology'],
|
|
587
|
+
automotive: ['automotive', 'mobility', 'transportation', 'manufacturing'],
|
|
588
|
+
car: ['automotive', 'mobility', 'transportation'],
|
|
589
|
+
cars: ['automotive', 'mobility', 'transportation'],
|
|
590
|
+
vehicle: ['automotive', 'mobility', 'transportation'],
|
|
591
|
+
vehicles: ['automotive', 'mobility', 'transportation'],
|
|
592
|
+
// Pet & Animal Care
|
|
593
|
+
pet: ['pet', 'cpg', 'food', 'wellness', 'retail'],
|
|
594
|
+
pets: ['pet', 'cpg', 'food', 'wellness', 'retail'],
|
|
595
|
+
dog: ['pet', 'cpg', 'food', 'wellness'],
|
|
596
|
+
cat: ['pet', 'cpg', 'food', 'wellness'],
|
|
597
|
+
// Tech, AI & SaaS
|
|
598
|
+
ai: ['technology', 'ai', 'software', 'enterprise', 'innovation'],
|
|
599
|
+
saas: ['technology', 'software', 'enterprise', 'b2b'],
|
|
600
|
+
software: ['technology', 'software', 'enterprise', 'b2b'],
|
|
601
|
+
cloud: ['technology', 'enterprise', 'software'],
|
|
602
|
+
cybersecurity: ['technology', 'enterprise', 'security'],
|
|
603
|
+
};
|
|
604
|
+
function scoreClauseRelevance(clause, g) {
|
|
605
|
+
const clauseLower = clause.toLowerCase().trim();
|
|
606
|
+
if (!clauseLower)
|
|
607
|
+
return 0;
|
|
537
608
|
const stopWords = new Set([
|
|
538
609
|
'trend', 'trends', 'consumer', 'consumers', 'report', 'reports',
|
|
539
610
|
'industry', 'data', 'future', 'insight', 'insights', 'analysis',
|
|
540
|
-
'what', 'how', 'why', 'who', 'when', 'where', 'are', 'the', 'and', 'for', 'with'
|
|
611
|
+
'what', 'how', 'why', 'who', 'when', 'where', 'are', 'the', 'and', 'for', 'with',
|
|
612
|
+
'run', 'fodda', 'deep', 'research', 'project', 'about', 'session', 'briefing',
|
|
613
|
+
'brief', 'study', 'overview', 'summary', 'deck', 'slides', 'presentation', 'topic',
|
|
614
|
+
'2024', '2025', '2026', '2027', '2030', 'year', 'years', 'strategic', 'breakdown',
|
|
615
|
+
'give', 'provide', 'show', 'tell', 'key'
|
|
541
616
|
]);
|
|
542
|
-
const
|
|
617
|
+
const terms = clauseLower
|
|
543
618
|
.split(/\s+/)
|
|
544
|
-
.
|
|
545
|
-
.map(t => t.replace(/[^a-z0-9]/g, '')) // Strip punctuation
|
|
619
|
+
.map(t => t.replace(/[^a-z0-9]/g, ''))
|
|
546
620
|
.filter(t => t.length > 2 && !stopWords.has(t));
|
|
547
|
-
if (
|
|
621
|
+
if (terms.length === 0)
|
|
548
622
|
return 0;
|
|
549
623
|
const searchText = graphSearchTexts.get(g.graph_id) || buildSearchableText(g);
|
|
550
|
-
const topicsText = [...(g.topics || []), ...(g.routing_keywords || [])].join(' ').toLowerCase();
|
|
624
|
+
const topicsText = [...(g.topics || []), ...(g.routing_keywords || []), g.name || '', g.graph_id || ''].join(' ').toLowerCase();
|
|
551
625
|
const domainText = (g.domain || '').toLowerCase();
|
|
552
626
|
let matchedTerms = 0;
|
|
553
627
|
let highValueMatches = 0;
|
|
554
|
-
for (const term of
|
|
628
|
+
for (const term of terms) {
|
|
555
629
|
if (searchText.includes(term)) {
|
|
556
630
|
matchedTerms++;
|
|
557
|
-
// High-value match: the term appears in topics, routing_keywords, or domain
|
|
558
631
|
if (topicsText.includes(term) || domainText.includes(term)) {
|
|
559
632
|
highValueMatches++;
|
|
560
633
|
}
|
|
561
634
|
}
|
|
562
635
|
}
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
//
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
const
|
|
571
|
-
|
|
572
|
-
|
|
636
|
+
let directScore = 0;
|
|
637
|
+
if (matchedTerms > 0) {
|
|
638
|
+
directScore = (matchedTerms / terms.length) * 0.6 + (highValueMatches / terms.length) * 0.5;
|
|
639
|
+
}
|
|
640
|
+
// Category Expansion Score
|
|
641
|
+
let expansionScore = 0;
|
|
642
|
+
const expandedSet = new Set();
|
|
643
|
+
for (const t of terms) {
|
|
644
|
+
if (QUERY_EXPANSION_MAP[t]) {
|
|
645
|
+
QUERY_EXPANSION_MAP[t].forEach(e => expandedSet.add(e));
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
if (expandedSet.size > 0) {
|
|
649
|
+
let expMatches = 0;
|
|
650
|
+
let expHighValueMatches = 0;
|
|
651
|
+
for (const exp of expandedSet) {
|
|
652
|
+
if (searchText.includes(exp)) {
|
|
653
|
+
expMatches++;
|
|
654
|
+
if (topicsText.includes(exp) || domainText.includes(exp)) {
|
|
655
|
+
expHighValueMatches++;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
if (expMatches > 0) {
|
|
660
|
+
expansionScore = 0.20 + 0.20 * Math.min(expMatches / 3, 1.0) + 0.15 * Math.min(expHighValueMatches / 2, 1.0);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
let finalScore = Math.max(directScore, expansionScore);
|
|
664
|
+
// Flagship retail graph anchor boost
|
|
665
|
+
if (g.graph_id === 'retail') {
|
|
666
|
+
finalScore += 0.30;
|
|
573
667
|
}
|
|
574
|
-
|
|
668
|
+
// Topic Specialist Boost: if graph's explicit topics, name or routing keywords match query terms directly
|
|
669
|
+
if (highValueMatches > 0) {
|
|
670
|
+
finalScore += 0.10;
|
|
671
|
+
}
|
|
672
|
+
// Specialist Category Boost & Generic Agency Cap
|
|
673
|
+
const specialistProductTerms = new Set([
|
|
674
|
+
'fridge', 'fridges', 'refrigerator', 'cooler', 'coolers', 'cellar', 'cabinet',
|
|
675
|
+
'furniture', 'glassware', 'stemware', 'decanter', 'appliance', 'appliances',
|
|
676
|
+
'barware', 'tableware', 'racking', 'kitchen', 'lighting', 'decor'
|
|
677
|
+
]);
|
|
678
|
+
const queryHasSpecialistProduct = terms.some(t => specialistProductTerms.has(t));
|
|
679
|
+
if (queryHasSpecialistProduct) {
|
|
680
|
+
const isGenericAgency = /bcg|dentsu|ecdb|kpmg|postpals|greenhouse|michaels|agency|consulting/i.test(g.graph_id + ' ' + (g.domain || ''));
|
|
681
|
+
const isSpecialistGraph = !isGenericAgency && /home|living|interior|decor|appliances?|kitchen|dining|beverage|food|hospitality|leisure/i.test(g.graph_id + ' ' + (g.domain || '') + ' ' + (g.topics || []).join(' ') + ' ' + (g.routing_keywords || []).join(' '));
|
|
682
|
+
if (isSpecialistGraph || g.graph_id === 'retail') {
|
|
683
|
+
finalScore += 0.20; // Specialist Category Boost
|
|
684
|
+
}
|
|
685
|
+
else if (isGenericAgency && directScore === 0) {
|
|
686
|
+
finalScore = Math.min(finalScore, 0.40); // Cap generic agency floor
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
// Generalized Narrow-Domain Mismatch Penalty
|
|
690
|
+
const narrowDomains = ['beauty', 'gaming', 'automotive', 'fashion'];
|
|
691
|
+
for (const dom of narrowDomains) {
|
|
692
|
+
const isNarrowGraph = (g.graph_id + (g.domain || '')).toLowerCase().includes(dom);
|
|
693
|
+
const queryHasDom = terms.some(t => t.includes(dom) || (QUERY_EXPANSION_MAP[t] && QUERY_EXPANSION_MAP[t].includes(dom)));
|
|
694
|
+
if (isNarrowGraph && !queryHasDom) {
|
|
695
|
+
finalScore -= 0.30;
|
|
696
|
+
break;
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
return Math.max(0, Math.min(finalScore, 1.0));
|
|
700
|
+
}
|
|
701
|
+
export function scoreGraphRelevance(query, g) {
|
|
702
|
+
const clauses = query
|
|
703
|
+
.split(/[,;\.\n]| and | with | for | about /i)
|
|
704
|
+
.map(c => c.trim())
|
|
705
|
+
.filter(c => c.length > 2);
|
|
706
|
+
if (clauses.length === 0)
|
|
707
|
+
return scoreClauseRelevance(query, g);
|
|
708
|
+
const scores = clauses.map(c => scoreClauseRelevance(c, g));
|
|
709
|
+
const maxScore = Math.max(...scores);
|
|
710
|
+
const avgScore = scores.reduce((a, b) => a + b, 0) / scores.length;
|
|
711
|
+
return Math.min((maxScore * 0.7) + (avgScore * 0.3), 1.0);
|
|
575
712
|
}
|
|
576
713
|
/**
|
|
577
714
|
* Classify a graph into its operational tier based on type + update cadence.
|
|
@@ -586,10 +723,11 @@ export function classifyGraphTier(g) {
|
|
|
586
723
|
const isRecurring = g.update_frequency &&
|
|
587
724
|
!['One-Off', 'Coming Soon'].includes(g.update_frequency);
|
|
588
725
|
// Domain graphs with recurring updates + experts with recurring updates = Living
|
|
589
|
-
|
|
726
|
+
// ('analyst' = expert graphs typed 'analyst' in the registry, e.g. Digital Twins)
|
|
727
|
+
if ((g.graph_type === 'domain' || g.graph_type === 'expert' || g.graph_type === 'analyst') && isRecurring) {
|
|
590
728
|
return 'living';
|
|
591
729
|
}
|
|
592
|
-
if (g.graph_type === 'expert')
|
|
730
|
+
if (g.graph_type === 'expert' || g.graph_type === 'analyst')
|
|
593
731
|
return 'static_expert';
|
|
594
732
|
if (g.graph_type === 'industry report')
|
|
595
733
|
return 'report';
|
|
@@ -610,8 +748,18 @@ export function classifyGraphTier(g) {
|
|
|
610
748
|
*/
|
|
611
749
|
export function getRelevantGraphs(query, minGraphs = 4, maxGraphs = 15, threshold = 0.10) {
|
|
612
750
|
const DEPRECATED_GRAPH_IDS = new Set(['waldo', 'psfk']);
|
|
613
|
-
|
|
614
|
-
|
|
751
|
+
// 'analyst' graphs (e.g. Digital Twin expert graphs typed 'analyst' in the
|
|
752
|
+
// registry) are searchable expert content — excluding them made registry-live
|
|
753
|
+
// graphs like jeff-longevity invisible to routing (QA 2026-08-20).
|
|
754
|
+
const ROUTABLE_TYPES = new Set(['domain', 'expert', 'industry report', 'analyst']);
|
|
755
|
+
const allLive = getLiveGraphs();
|
|
756
|
+
const allGraphs = allLive.filter(g => ROUTABLE_TYPES.has(g.graph_type) && !DEPRECATED_GRAPH_IDS.has(g.graph_id));
|
|
757
|
+
// Registry-status visibility: surface live graphs excluded from routing by
|
|
758
|
+
// type, so an unroutable analyst/expert graph is detectable in logs.
|
|
759
|
+
const typeExcluded = allLive.filter(g => !ROUTABLE_TYPES.has(g.graph_type) && g.graph_type !== 'supplemental' && g.graph_type !== 'skill');
|
|
760
|
+
if (typeExcluded.length > 0) {
|
|
761
|
+
console.error(`[graphRouter] ${typeExcluded.length} live graph(s) excluded by graph_type: ${typeExcluded.map(g => `${g.graph_id}(${g.graph_type})`).join(', ')}`);
|
|
762
|
+
}
|
|
615
763
|
if (allGraphs.length === 0)
|
|
616
764
|
return [];
|
|
617
765
|
// Runtime check: skip graphs that have no synced content in Neo4j.
|
|
@@ -620,45 +768,62 @@ export function getRelevantGraphs(query, minGraphs = 4, maxGraphs = 15, threshol
|
|
|
620
768
|
// Once the CE pipeline syncs them, they'll automatically start routing.
|
|
621
769
|
const hasContent = (g) => (g.trend_count > 0) || (g.last_synced !== null && g.last_synced !== undefined);
|
|
622
770
|
const syncedGraphs = allGraphs.filter(hasContent);
|
|
623
|
-
const
|
|
771
|
+
const shellGraphs = allGraphs.filter(g => !hasContent(g));
|
|
772
|
+
const skippedShells = shellGraphs.length;
|
|
624
773
|
if (skippedShells > 0) {
|
|
625
|
-
console.error(`[graphRouter] Skipped ${skippedShells} unsynced graph shell(s) (trend_count=0, last_synced=null)`);
|
|
774
|
+
console.error(`[graphRouter] Skipped ${skippedShells} unsynced graph shell(s) (trend_count=0, last_synced=null): ${shellGraphs.map(g => g.graph_id).join(', ')}`);
|
|
626
775
|
}
|
|
627
776
|
// ── Phase 0: Direct name matching ──
|
|
628
|
-
// When users explicitly name a graph, curator, or company (e.g. "Pull from
|
|
629
|
-
// TBWA/Alyson Stevens Macro, SIC (Ben Dietz), Marieke Neleman..."), keyword
|
|
630
|
-
// scoring dilutes these names across 100+ query terms, dropping them below
|
|
631
|
-
// the threshold. Direct matches get automatic inclusion at score 1.0.
|
|
632
777
|
const queryLower = query.toLowerCase();
|
|
633
778
|
const directMatchIds = new Set();
|
|
779
|
+
const matchesWordBoundary = (text, term) => {
|
|
780
|
+
if (!term || term.length < 3)
|
|
781
|
+
return false;
|
|
782
|
+
// Ignore generic topic/format words from forcing Phase 0 direct inclusion
|
|
783
|
+
const genericNames = new Set([
|
|
784
|
+
'trends', 'report', 'graph', 'data', '2026', 'state', 'outlook',
|
|
785
|
+
'beauty', 'food', 'home', 'retail', 'fashion', 'travel', 'design'
|
|
786
|
+
]);
|
|
787
|
+
if (genericNames.has(term.toLowerCase()))
|
|
788
|
+
return false;
|
|
789
|
+
const escaped = term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
790
|
+
const rx = new RegExp(`\\b${escaped}\\b`, 'i');
|
|
791
|
+
return rx.test(text);
|
|
792
|
+
};
|
|
634
793
|
for (const g of syncedGraphs) {
|
|
635
794
|
// Check graph_id (e.g., "alyson-stevens-macro", "sic")
|
|
636
|
-
if (
|
|
795
|
+
if (g.graph_id.length > 3 && matchesWordBoundary(queryLower, g.graph_id)) {
|
|
637
796
|
directMatchIds.add(g.graph_id);
|
|
638
797
|
continue;
|
|
639
798
|
}
|
|
640
799
|
// Check curator full name (e.g., "Alyson Stevens", "Ben Dietz")
|
|
641
|
-
const curatorLower = (g.curator || '').toLowerCase();
|
|
642
|
-
if (curatorLower && curatorLower.length >
|
|
800
|
+
const curatorLower = (g.curator || '').trim().toLowerCase();
|
|
801
|
+
if (curatorLower && curatorLower.length > 4 && matchesWordBoundary(queryLower, curatorLower)) {
|
|
643
802
|
directMatchIds.add(g.graph_id);
|
|
644
803
|
continue;
|
|
645
804
|
}
|
|
646
805
|
// Check curator last name (>3 chars to avoid false positives like "AI", "NIQ")
|
|
647
806
|
const curatorParts = curatorLower.split(/\s+/);
|
|
648
807
|
const curatorLastName = curatorParts[curatorParts.length - 1];
|
|
649
|
-
if (curatorLastName && curatorLastName.length > 3 && queryLower
|
|
808
|
+
if (curatorLastName && curatorLastName.length > 3 && matchesWordBoundary(queryLower, curatorLastName)) {
|
|
650
809
|
directMatchIds.add(g.graph_id);
|
|
651
810
|
continue;
|
|
652
811
|
}
|
|
653
812
|
// Check company name (e.g., "TBWA", "Delta", "Havas", "Kantar")
|
|
654
|
-
|
|
655
|
-
|
|
813
|
+
// Skip generic publisher/consultancy names so mentioning a firm doesn't force all its graphs
|
|
814
|
+
const companyLower = (g.company || '').trim().toLowerCase();
|
|
815
|
+
const genericPublishers = new Set([
|
|
816
|
+
'mckinsey', 'bcg', 'deloitte', 'kpmg', 'forrester', 'gartner',
|
|
817
|
+
'pwc', 'ey', 'capgemini', 'bain', 'niq', 'mintel', 'dentsu',
|
|
818
|
+
'accenture', 'youtube', 'google', 'microsoft'
|
|
819
|
+
]);
|
|
820
|
+
if (companyLower && companyLower.length > 3 && !genericPublishers.has(companyLower) && matchesWordBoundary(queryLower, companyLower)) {
|
|
656
821
|
directMatchIds.add(g.graph_id);
|
|
657
822
|
continue;
|
|
658
823
|
}
|
|
659
824
|
// Check graph name (e.g., "SIC", "Connection Index")
|
|
660
|
-
const nameLower = (g.name || '').toLowerCase();
|
|
661
|
-
if (nameLower && nameLower.length > 3 && queryLower
|
|
825
|
+
const nameLower = (g.name || '').trim().toLowerCase();
|
|
826
|
+
if (nameLower && nameLower.length > 3 && matchesWordBoundary(queryLower, nameLower)) {
|
|
662
827
|
directMatchIds.add(g.graph_id);
|
|
663
828
|
continue;
|
|
664
829
|
}
|
|
@@ -667,11 +832,15 @@ export function getRelevantGraphs(query, minGraphs = 4, maxGraphs = 15, threshol
|
|
|
667
832
|
console.error(`[graphRouter] Phase 0 direct name matches: ${[...directMatchIds].join(', ')}`);
|
|
668
833
|
}
|
|
669
834
|
// Score ALL graph types together — domain, expert, industry report compete on merit
|
|
670
|
-
const scored = syncedGraphs.map(g =>
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
835
|
+
const scored = syncedGraphs.map(g => {
|
|
836
|
+
const isDirect = directMatchIds.has(g.graph_id);
|
|
837
|
+
return {
|
|
838
|
+
graph: g,
|
|
839
|
+
score: isDirect ? 1.0 : scoreGraphRelevance(query, g),
|
|
840
|
+
graphTier: classifyGraphTier(g),
|
|
841
|
+
isDirectMatch: isDirect,
|
|
842
|
+
};
|
|
843
|
+
});
|
|
675
844
|
// Sort by score descending
|
|
676
845
|
scored.sort((a, b) => b.score - a.score);
|
|
677
846
|
// Take all above threshold, respecting min/max bounds
|
|
@@ -700,8 +869,22 @@ export function getRelevantGraphs(query, minGraphs = 4, maxGraphs = 15, threshol
|
|
|
700
869
|
results.push(best);
|
|
701
870
|
}
|
|
702
871
|
};
|
|
872
|
+
ensureTierPresent('living');
|
|
703
873
|
ensureTierPresent('report');
|
|
704
874
|
ensureTierPresent('static_expert');
|
|
875
|
+
const stopWords = new Set([
|
|
876
|
+
'trend', 'trends', 'consumer', 'consumers', 'report', 'reports',
|
|
877
|
+
'industry', 'data', 'future', 'insight', 'insights', 'analysis',
|
|
878
|
+
'what', 'how', 'why', 'who', 'when', 'where', 'are', 'the', 'and', 'for', 'with',
|
|
879
|
+
'run', 'fodda', 'deep', 'research', 'project', 'about', 'session', 'briefing',
|
|
880
|
+
'brief', 'study', 'overview', 'summary', 'deck', 'slides', 'presentation', 'topic',
|
|
881
|
+
'2024', '2025', '2026', '2027', '2030', 'year', 'years', 'strategic', 'breakdown',
|
|
882
|
+
'give', 'provide', 'show', 'tell', 'key'
|
|
883
|
+
]);
|
|
884
|
+
const debugTokens = query.toLowerCase().split(/\s+/).map(t => t.replace(/[^a-z0-9]/g, '')).filter(t => t.length > 2 && !stopWords.has(t));
|
|
885
|
+
const debugExpansions = new Set();
|
|
886
|
+
debugTokens.forEach(t => (QUERY_EXPANSION_MAP[t] || []).forEach(e => debugExpansions.add(e)));
|
|
887
|
+
console.error(`[graphRouter] Extracted tokens: [${debugTokens.join(', ')}] | Fired expansions: [${Array.from(debugExpansions).join(', ')}]`);
|
|
705
888
|
console.error(`[graphRouter] Query: "${query.substring(0, 60)}..." → ${results.length}/${syncedGraphs.length} synced graphs selected (${skippedShells} shells skipped): ${results.map(r => `${r.graph.graph_id}(${r.score.toFixed(2)},${r.graphTier})`).join(', ')}`);
|
|
706
889
|
return results;
|
|
707
890
|
}
|
|
@@ -743,11 +926,12 @@ const EARNINGS_SECTORS = ['retail', 'technology', 'travel', 'hospitality', 'luxu
|
|
|
743
926
|
// (economic indicators/FRED, demographic surveys/Census, trade data, food
|
|
744
927
|
// economics + agricultural production, commodity pricing).
|
|
745
928
|
const SUPPLEMENTAL_CATEGORY_KEYWORDS = {
|
|
746
|
-
macro: ['macro', 'economy', 'economic', 'inflation', 'gdp', 'interest rate', 'consumer confidence', 'consumer spending', 'recession', 'fred', 'cost of living', 'purchasing power', 'unemployment'],
|
|
747
|
-
demographics: ['demographic', 'demographics', 'population', 'census', 'household income', 'age distribution', 'migration'],
|
|
929
|
+
macro: ['macro', 'economy', 'economic', 'inflation', 'gdp', 'interest rate', 'consumer confidence', 'consumer sentiment', 'michigan sentiment', 'umcsent', 'consumer spending', 'recession', 'fred', 'cost of living', 'purchasing power', 'unemployment'],
|
|
930
|
+
demographics: ['demographic', 'demographics', 'population', 'census', 'household income', 'age distribution', 'migration', 'republican', 'democrat', 'bipartisan', 'party affiliation', 'pew', 'npors'],
|
|
748
931
|
trade: ['imports', 'exports', 'trade data', 'trade flows', 'tariff', 'tariffs', 'supply chain'],
|
|
749
932
|
food_economics: ['food prices', 'food costs', 'crop', 'agriculture', 'agricultural', 'nutrition', 'nutritional'],
|
|
750
933
|
commodities: ['commodity', 'commodities', 'raw materials', 'wholesale prices'],
|
|
934
|
+
science: ['science', 'scientific', 'clinical', 'pubmed', 'openalex', 'clinical trials', 'dermatology', 'peer-reviewed', 'academic', 'study', 'studies', 'journal', 'biomedical'],
|
|
751
935
|
};
|
|
752
936
|
/**
|
|
753
937
|
* Detect an earnings candidate for a query. Deterministic and conservative:
|
|
@@ -810,19 +994,33 @@ function detectSupplementalCandidates(query) {
|
|
|
810
994
|
}
|
|
811
995
|
return candidates;
|
|
812
996
|
}
|
|
997
|
+
const sourcePlanCache = new Map();
|
|
998
|
+
const SOURCE_CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
|
|
999
|
+
export function flushSourcePlanCache() {
|
|
1000
|
+
sourcePlanCache.clear();
|
|
1001
|
+
console.error('[catalogCache] Source plan cache flushed');
|
|
1002
|
+
}
|
|
813
1003
|
/**
|
|
814
1004
|
* Unified source selector: graph candidates from getRelevantGraphs()
|
|
815
1005
|
* (UNCHANGED — same scoring, tier diversity, freshness bonus, same order),
|
|
816
1006
|
* plus additive earnings/supplemental candidates appended after.
|
|
817
1007
|
*/
|
|
818
1008
|
export function getRelevantSources(query, opts) {
|
|
819
|
-
const
|
|
1009
|
+
const minGraphs = opts?.minGraphs ?? 2;
|
|
1010
|
+
const maxGraphs = opts?.maxGraphs ?? 15;
|
|
1011
|
+
const threshold = opts?.threshold ?? 0.10;
|
|
1012
|
+
const cacheKey = `v1.35.2:${query.trim().toLowerCase()}:${minGraphs}:${maxGraphs}:${threshold}`;
|
|
1013
|
+
const cached = sourcePlanCache.get(cacheKey);
|
|
1014
|
+
if (cached && (Date.now() - cached.timestamp < SOURCE_CACHE_TTL_MS)) {
|
|
1015
|
+
return cached.result;
|
|
1016
|
+
}
|
|
1017
|
+
const graphResults = getRelevantGraphs(query, minGraphs, maxGraphs, threshold);
|
|
820
1018
|
const candidates = graphResults.map(r => ({
|
|
821
1019
|
kind: 'graph',
|
|
822
1020
|
graphId: r.graph.graph_id,
|
|
823
1021
|
score: r.score,
|
|
824
1022
|
graphTier: r.graphTier,
|
|
825
|
-
reason: r.
|
|
1023
|
+
reason: r.isDirectMatch ? 'named directly in query' : `topic match (score ${r.score.toFixed(2)}, ${r.graphTier})`,
|
|
826
1024
|
}));
|
|
827
1025
|
const earnings = detectEarningsCandidate(query);
|
|
828
1026
|
if (earnings)
|
|
@@ -832,6 +1030,7 @@ export function getRelevantSources(query, opts) {
|
|
|
832
1030
|
if (extras.length > 0) {
|
|
833
1031
|
console.error(`[sourceRouter] Extra candidates appended: ${extras.map(c => c.kind === 'earnings' ? `earnings(${c.ticker || c.brand || c.sector || 'search'})` : `supplemental(${c.category})`).join(', ')}`);
|
|
834
1032
|
}
|
|
1033
|
+
sourcePlanCache.set(cacheKey, { result: candidates, timestamp: Date.now() });
|
|
835
1034
|
return candidates;
|
|
836
1035
|
}
|
|
837
1036
|
//# sourceMappingURL=catalogCache.js.map
|