fodda-mcp 1.46.31 → 1.46.97
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 -94
- 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 +1 -1
- package/dist/brandTemplate.d.ts +1 -1
- package/dist/brandTemplate.d.ts.map +1 -1
- package/dist/brandTemplate.js +162 -74
- package/dist/brandTemplate.js.map +1 -1
- package/dist/catalogCache.d.ts +29 -1
- package/dist/catalogCache.d.ts.map +1 -1
- package/dist/catalogCache.js +267 -16
- package/dist/catalogCache.js.map +1 -1
- package/dist/coverageRelevance.d.ts +97 -2
- package/dist/coverageRelevance.d.ts.map +1 -1
- package/dist/coverageRelevance.js +959 -164
- package/dist/coverageRelevance.js.map +1 -1
- package/dist/editorialFill.d.ts +5 -0
- package/dist/editorialFill.d.ts.map +1 -1
- package/dist/editorialFill.js +1 -1
- package/dist/editorialFill.js.map +1 -1
- package/dist/enrichment.d.ts +26 -0
- package/dist/enrichment.d.ts.map +1 -1
- package/dist/enrichment.js +394 -31
- package/dist/enrichment.js.map +1 -1
- package/dist/errorHandling.d.ts +35 -7
- package/dist/errorHandling.d.ts.map +1 -1
- package/dist/errorHandling.js +225 -155
- package/dist/errorHandling.js.map +1 -1
- package/dist/index.d.ts +25 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +351 -143
- package/dist/index.js.map +1 -1
- package/dist/jobs/jlens_quarterly_sweep.d.ts +62 -0
- package/dist/jobs/jlens_quarterly_sweep.d.ts.map +1 -0
- package/dist/jobs/jlens_quarterly_sweep.js +160 -0
- package/dist/jobs/jlens_quarterly_sweep.js.map +1 -0
- package/dist/pricingCache.d.ts.map +1 -1
- package/dist/pricingCache.js +41 -1
- package/dist/pricingCache.js.map +1 -1
- package/dist/queryCache.d.ts.map +1 -1
- package/dist/queryCache.js +2 -0
- package/dist/queryCache.js.map +1 -1
- package/dist/reportBriefing.d.ts +89 -0
- package/dist/reportBriefing.d.ts.map +1 -0
- package/dist/reportBriefing.js +662 -0
- package/dist/reportBriefing.js.map +1 -0
- package/dist/searchTemplate.d.ts.map +1 -1
- package/dist/searchTemplate.js +65 -53
- package/dist/searchTemplate.js.map +1 -1
- package/dist/services/graphRouter.d.ts +7 -0
- package/dist/services/graphRouter.d.ts.map +1 -0
- package/dist/services/graphRouter.js +7 -0
- package/dist/services/graphRouter.js.map +1 -0
- package/dist/services/notifications.d.ts +49 -0
- package/dist/services/notifications.d.ts.map +1 -0
- package/dist/services/notifications.js +206 -0
- package/dist/services/notifications.js.map +1 -0
- package/dist/sessionTracker.d.ts +19 -2
- package/dist/sessionTracker.d.ts.map +1 -1
- package/dist/sessionTracker.js +69 -16
- package/dist/sessionTracker.js.map +1 -1
- package/dist/skillClient.d.ts.map +1 -1
- package/dist/skillClient.js +11 -6
- package/dist/skillClient.js.map +1 -1
- package/dist/svgVisuals.d.ts +17 -19
- package/dist/svgVisuals.d.ts.map +1 -1
- package/dist/svgVisuals.js +189 -233
- package/dist/svgVisuals.js.map +1 -1
- package/dist/systemPrompt.d.ts +7 -2
- package/dist/systemPrompt.d.ts.map +1 -1
- package/dist/systemPrompt.js +110 -68
- package/dist/systemPrompt.js.map +1 -1
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +15 -0
- package/dist/telemetry.js.map +1 -1
- package/dist/test_adjacent_trends_polymorphic.d.ts +2 -0
- package/dist/test_adjacent_trends_polymorphic.d.ts.map +1 -0
- package/dist/test_adjacent_trends_polymorphic.js +154 -0
- package/dist/test_adjacent_trends_polymorphic.js.map +1 -0
- package/dist/test_book_a_call_live.js +18 -5
- package/dist/test_book_a_call_live.js.map +1 -1
- package/dist/test_byo_mcp_submission_clarity.d.ts +2 -0
- package/dist/test_byo_mcp_submission_clarity.d.ts.map +1 -0
- package/dist/test_byo_mcp_submission_clarity.js +142 -0
- package/dist/test_byo_mcp_submission_clarity.js.map +1 -0
- package/dist/test_capabilities_guidance.d.ts +2 -0
- package/dist/test_capabilities_guidance.d.ts.map +1 -0
- package/dist/test_capabilities_guidance.js +372 -0
- package/dist/test_capabilities_guidance.js.map +1 -0
- package/dist/test_client_provenance.d.ts +2 -0
- package/dist/test_client_provenance.d.ts.map +1 -0
- package/dist/test_client_provenance.js +77 -0
- package/dist/test_client_provenance.js.map +1 -0
- package/dist/test_connection_surface_cleanup.d.ts +2 -0
- package/dist/test_connection_surface_cleanup.d.ts.map +1 -0
- package/dist/test_connection_surface_cleanup.js +203 -0
- package/dist/test_connection_surface_cleanup.js.map +1 -0
- package/dist/test_consult_routing.d.ts +2 -0
- package/dist/test_consult_routing.d.ts.map +1 -0
- package/dist/test_consult_routing.js +86 -0
- package/dist/test_consult_routing.js.map +1 -0
- package/dist/test_consult_split.d.ts +2 -0
- package/dist/test_consult_split.d.ts.map +1 -0
- package/dist/test_consult_split.js +30 -0
- package/dist/test_consult_split.js.map +1 -0
- package/dist/test_coverage_relevance.js +111 -70
- package/dist/test_coverage_relevance.js.map +1 -1
- package/dist/test_coverage_replay.d.ts +2 -0
- package/dist/test_coverage_replay.d.ts.map +1 -0
- package/dist/test_coverage_replay.js +170 -0
- package/dist/test_coverage_replay.js.map +1 -0
- package/dist/test_dcr_and_legacy_deprecation.js +45 -50
- package/dist/test_dcr_and_legacy_deprecation.js.map +1 -1
- package/dist/test_demand_signal_logging.d.ts +2 -0
- package/dist/test_demand_signal_logging.d.ts.map +1 -0
- package/dist/test_demand_signal_logging.js +92 -0
- package/dist/test_demand_signal_logging.js.map +1 -0
- package/dist/test_earnings_activity_scope.d.ts +5 -0
- package/dist/test_earnings_activity_scope.d.ts.map +1 -0
- package/dist/test_earnings_activity_scope.js +101 -0
- package/dist/test_earnings_activity_scope.js.map +1 -0
- package/dist/test_earnings_divergence_live.d.ts +5 -0
- package/dist/test_earnings_divergence_live.d.ts.map +1 -0
- package/dist/test_earnings_divergence_live.js +135 -0
- package/dist/test_earnings_divergence_live.js.map +1 -0
- package/dist/test_earnings_hygiene_and_binding.d.ts +5 -0
- package/dist/test_earnings_hygiene_and_binding.d.ts.map +1 -0
- package/dist/test_earnings_hygiene_and_binding.js +104 -0
- package/dist/test_earnings_hygiene_and_binding.js.map +1 -0
- package/dist/test_exec_quotes_render.d.ts +2 -0
- package/dist/test_exec_quotes_render.d.ts.map +1 -0
- package/dist/test_exec_quotes_render.js +86 -0
- package/dist/test_exec_quotes_render.js.map +1 -0
- package/dist/test_expert_intro.d.ts +2 -0
- package/dist/test_expert_intro.d.ts.map +1 -0
- package/dist/test_expert_intro.js +57 -0
- package/dist/test_expert_intro.js.map +1 -0
- package/dist/test_expert_layer.d.ts +11 -0
- package/dist/test_expert_layer.d.ts.map +1 -0
- package/dist/test_expert_layer.js +356 -0
- package/dist/test_expert_layer.js.map +1 -0
- package/dist/test_gap_alert.js +8 -0
- package/dist/test_gap_alert.js.map +1 -1
- package/dist/test_grok_offering_surfaces.d.ts +2 -0
- package/dist/test_grok_offering_surfaces.d.ts.map +1 -0
- package/dist/test_grok_offering_surfaces.js +155 -0
- package/dist/test_grok_offering_surfaces.js.map +1 -0
- package/dist/test_human_agent_live.d.ts +2 -0
- package/dist/test_human_agent_live.d.ts.map +1 -0
- package/dist/test_human_agent_live.js +48 -0
- package/dist/test_human_agent_live.js.map +1 -0
- package/dist/test_identity_gap.d.ts +2 -0
- package/dist/test_identity_gap.d.ts.map +1 -0
- package/dist/test_identity_gap.js +171 -0
- package/dist/test_identity_gap.js.map +1 -0
- package/dist/test_intelligence_and_verifier.d.ts +17 -0
- package/dist/test_intelligence_and_verifier.d.ts.map +1 -0
- package/dist/test_intelligence_and_verifier.js +162 -0
- package/dist/test_intelligence_and_verifier.js.map +1 -0
- package/dist/test_latency_mcp_brief5.d.ts +12 -0
- package/dist/test_latency_mcp_brief5.d.ts.map +1 -0
- package/dist/test_latency_mcp_brief5.js +200 -0
- package/dist/test_latency_mcp_brief5.js.map +1 -0
- package/dist/test_limit_and_credit_card_capture.d.ts +21 -0
- package/dist/test_limit_and_credit_card_capture.d.ts.map +1 -0
- package/dist/test_limit_and_credit_card_capture.js +216 -0
- package/dist/test_limit_and_credit_card_capture.js.map +1 -0
- package/dist/test_list_graphs_topics.js +1 -1
- package/dist/test_list_graphs_topics.js.map +1 -1
- package/dist/test_live_deployed_mcp_search.d.ts +2 -0
- package/dist/test_live_deployed_mcp_search.d.ts.map +1 -0
- package/dist/test_live_deployed_mcp_search.js +85 -0
- package/dist/test_live_deployed_mcp_search.js.map +1 -0
- package/dist/test_live_deployed_split.d.ts +2 -0
- package/dist/test_live_deployed_split.d.ts.map +1 -0
- package/dist/test_live_deployed_split.js +72 -0
- package/dist/test_live_deployed_split.js.map +1 -0
- package/dist/test_live_endpoint_runner.js +2 -2
- package/dist/test_live_endpoint_runner.js.map +1 -1
- package/dist/test_live_mcp.js +1 -1
- package/dist/test_live_mcp.js.map +1 -1
- package/dist/test_live_neil_widget.d.ts +2 -0
- package/dist/test_live_neil_widget.d.ts.map +1 -0
- package/dist/test_live_neil_widget.js +84 -0
- package/dist/test_live_neil_widget.js.map +1 -0
- package/dist/test_longpoll_status.d.ts +2 -0
- package/dist/test_longpoll_status.d.ts.map +1 -0
- package/dist/test_longpoll_status.js +117 -0
- package/dist/test_longpoll_status.js.map +1 -0
- package/dist/test_mcp_find_expert_and_vocabulary_routing.d.ts +2 -0
- package/dist/test_mcp_find_expert_and_vocabulary_routing.d.ts.map +1 -0
- package/dist/test_mcp_find_expert_and_vocabulary_routing.js +130 -0
- package/dist/test_mcp_find_expert_and_vocabulary_routing.js.map +1 -0
- package/dist/test_next_moves.js +644 -279
- package/dist/test_next_moves.js.map +1 -1
- package/dist/test_next_moves_transcripts.js +122 -40
- package/dist/test_next_moves_transcripts.js.map +1 -1
- package/dist/test_on_request_caller_identity.d.ts +13 -0
- package/dist/test_on_request_caller_identity.d.ts.map +1 -0
- package/dist/test_on_request_caller_identity.js +131 -0
- package/dist/test_on_request_caller_identity.js.map +1 -0
- package/dist/test_on_request_intercept.d.ts +10 -0
- package/dist/test_on_request_intercept.d.ts.map +1 -0
- package/dist/test_on_request_intercept.js +95 -0
- package/dist/test_on_request_intercept.js.map +1 -0
- package/dist/test_onboarding_consent_and_errors.d.ts +2 -0
- package/dist/test_onboarding_consent_and_errors.d.ts.map +1 -0
- package/dist/test_onboarding_consent_and_errors.js +110 -0
- package/dist/test_onboarding_consent_and_errors.js.map +1 -0
- package/dist/test_onboarding_live_scenarios.d.ts +2 -0
- package/dist/test_onboarding_live_scenarios.d.ts.map +1 -0
- package/dist/test_onboarding_live_scenarios.js +189 -0
- package/dist/test_onboarding_live_scenarios.js.map +1 -0
- package/dist/test_referral_live.d.ts +2 -0
- package/dist/test_referral_live.d.ts.map +1 -0
- package/dist/test_referral_live.js +33 -0
- package/dist/test_referral_live.js.map +1 -0
- package/dist/test_referral_piers.d.ts +2 -0
- package/dist/test_referral_piers.d.ts.map +1 -0
- package/dist/test_referral_piers.js +33 -0
- package/dist/test_referral_piers.js.map +1 -0
- package/dist/test_report_editorial_briefing.d.ts +21 -0
- package/dist/test_report_editorial_briefing.d.ts.map +1 -0
- package/dist/test_report_editorial_briefing.js +356 -0
- package/dist/test_report_editorial_briefing.js.map +1 -0
- package/dist/test_search_graph_quality.d.ts +2 -0
- package/dist/test_search_graph_quality.d.ts.map +1 -0
- package/dist/test_search_graph_quality.js +493 -0
- package/dist/test_search_graph_quality.js.map +1 -0
- package/dist/test_service_to_service_auth.d.ts +2 -0
- package/dist/test_service_to_service_auth.d.ts.map +1 -0
- package/dist/test_service_to_service_auth.js +199 -0
- package/dist/test_service_to_service_auth.js.map +1 -0
- package/dist/test_sources_live.d.ts +2 -0
- package/dist/test_sources_live.d.ts.map +1 -0
- package/dist/test_sources_live.js +30 -0
- package/dist/test_sources_live.js.map +1 -0
- package/dist/test_specialist_and_analyst_tiers.d.ts +2 -0
- package/dist/test_specialist_and_analyst_tiers.d.ts.map +1 -0
- package/dist/test_specialist_and_analyst_tiers.js +155 -0
- package/dist/test_specialist_and_analyst_tiers.js.map +1 -0
- package/dist/test_v2_2_visual_hardening.d.ts +5 -0
- package/dist/test_v2_2_visual_hardening.d.ts.map +1 -0
- package/dist/test_v2_2_visual_hardening.js +333 -0
- package/dist/test_v2_2_visual_hardening.js.map +1 -0
- package/dist/test_verify_deprecate_render_spec.d.ts +2 -0
- package/dist/test_verify_deprecate_render_spec.d.ts.map +1 -0
- package/dist/test_verify_deprecate_render_spec.js +217 -0
- package/dist/test_verify_deprecate_render_spec.js.map +1 -0
- package/dist/test_verify_neil_demo.d.ts +2 -0
- package/dist/test_verify_neil_demo.d.ts.map +1 -0
- package/dist/test_verify_neil_demo.js +149 -0
- package/dist/test_verify_neil_demo.js.map +1 -0
- package/dist/test_waitable_research_and_grounded_links.d.ts +2 -0
- package/dist/test_waitable_research_and_grounded_links.d.ts.map +1 -0
- package/dist/test_waitable_research_and_grounded_links.js +129 -0
- package/dist/test_waitable_research_and_grounded_links.js.map +1 -0
- package/dist/test_widget_guards.d.ts +2 -0
- package/dist/test_widget_guards.d.ts.map +1 -0
- package/dist/test_widget_guards.js +229 -0
- package/dist/test_widget_guards.js.map +1 -0
- package/dist/test_zero_retention_alerts.d.ts +14 -0
- package/dist/test_zero_retention_alerts.d.ts.map +1 -0
- package/dist/test_zero_retention_alerts.js +76 -0
- package/dist/test_zero_retention_alerts.js.map +1 -0
- package/dist/toolHandlers.d.ts +38 -1
- package/dist/toolHandlers.d.ts.map +1 -1
- package/dist/toolHandlers.js +3856 -938
- package/dist/toolHandlers.js.map +1 -1
- package/dist/tools.d.ts +20 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +25 -4
- package/dist/tools.js.map +1 -1
- package/dist/verify_discoverability.d.ts +2 -0
- package/dist/verify_discoverability.d.ts.map +1 -0
- package/dist/verify_discoverability.js +53 -0
- package/dist/verify_discoverability.js.map +1 -0
- package/dist/verify_tools_endpoint.js +16 -3
- package/dist/verify_tools_endpoint.js.map +1 -1
- package/dist/widgetShell.d.ts +2 -2
- package/dist/widgetShell.d.ts.map +1 -1
- package/dist/widgetShell.js +1 -1
- package/dist/widgetShell.js.map +1 -1
- package/package.json +1 -1
- package/server.json +4 -32
- package/dist/oauthRegisterShim.d.ts +0 -13
- package/dist/oauthRegisterShim.d.ts.map +0 -1
- package/dist/oauthRegisterShim.js +0 -83
- package/dist/oauthRegisterShim.js.map +0 -1
|
@@ -0,0 +1,662 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 5-Pillar Editorial & Network Synthesis for Report Intelligence.
|
|
3
|
+
*
|
|
4
|
+
* Implements the Fodda Editorial Briefing Architecture:
|
|
5
|
+
* - Pillar 1: Core Tension / Topline Hook
|
|
6
|
+
* - Pillar 2: Core Shifts & Dramatic Evidence (3-5 structural shifts + metrics)
|
|
7
|
+
* - Pillar 3: Cross-Graph Network Evidence (validates / contrasts / related with provenance)
|
|
8
|
+
* - Pillar 4: Human Expert Digital Twin Spotlight (twin position + consult link)
|
|
9
|
+
* - Pillar 5: Actionable Follow-Up Moves & Quick Links (runnable tool calls)
|
|
10
|
+
*/
|
|
11
|
+
import { callGemini } from './editorialFill.js';
|
|
12
|
+
import { getLiveGraphs, getGraphs, getAnalysts } from './catalogCache.js';
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Evidence Extraction (handles object & array forms from live API)
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
export function extractEvidenceFromRow(row) {
|
|
17
|
+
const stats = [];
|
|
18
|
+
const snippets = [];
|
|
19
|
+
const caseStudies = [];
|
|
20
|
+
if (!row)
|
|
21
|
+
return { stats: [], snippets: [], caseStudies: [] };
|
|
22
|
+
// 1. Object form: evidence: { statistics: [...], case_studies: [...], quotes: [...] }
|
|
23
|
+
if (row.evidence && typeof row.evidence === 'object' && !Array.isArray(row.evidence)) {
|
|
24
|
+
if (Array.isArray(row.evidence.statistics)) {
|
|
25
|
+
for (const s of row.evidence.statistics) {
|
|
26
|
+
const statText = s.stat || s.percentage || s.metric || s.number || (typeof s === 'string' ? s : s.text);
|
|
27
|
+
if (statText)
|
|
28
|
+
stats.push(String(statText).trim());
|
|
29
|
+
const snip = s.snippet || s.description || s.title || (typeof s === 'string' ? s : '');
|
|
30
|
+
if (snip)
|
|
31
|
+
snippets.push(String(snip).trim());
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
if (Array.isArray(row.evidence.case_studies)) {
|
|
35
|
+
for (const cs of row.evidence.case_studies) {
|
|
36
|
+
const csTitle = cs.title || cs.name || cs.brand || (typeof cs === 'string' ? cs : '');
|
|
37
|
+
const csSnip = cs.snippet || cs.description || cs.summary || (typeof cs === 'string' ? cs : '');
|
|
38
|
+
if (csTitle || csSnip) {
|
|
39
|
+
const fullCs = [csTitle, csSnip].filter(Boolean).join(': ');
|
|
40
|
+
caseStudies.push(fullCs);
|
|
41
|
+
snippets.push(fullCs);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (Array.isArray(row.evidence.quotes)) {
|
|
46
|
+
for (const q of row.evidence.quotes) {
|
|
47
|
+
const snip = q.snippet || q.text || q.quote || (typeof q === 'string' ? q : '');
|
|
48
|
+
if (snip)
|
|
49
|
+
snippets.push(String(snip).trim());
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
if (Array.isArray(row.evidence.items)) {
|
|
53
|
+
for (const item of row.evidence.items) {
|
|
54
|
+
const snip = typeof item === 'string' ? item : (item.snippet || item.title || '');
|
|
55
|
+
if (snip)
|
|
56
|
+
snippets.push(String(snip).trim());
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
else if (Array.isArray(row.evidence)) {
|
|
61
|
+
// Array form: evidence: [{ title, snippet, stat, percentage }]
|
|
62
|
+
for (const e of row.evidence) {
|
|
63
|
+
const statText = e.stat || e.percentage || e.metric || e.number;
|
|
64
|
+
if (statText)
|
|
65
|
+
stats.push(String(statText).trim());
|
|
66
|
+
const snip = typeof e === 'string' ? e : (e.snippet || e.title || e.description || '');
|
|
67
|
+
if (snip)
|
|
68
|
+
snippets.push(String(snip).trim());
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// 2. Direct top-level fields
|
|
72
|
+
if (row.stat || row.percentage)
|
|
73
|
+
stats.push(String(row.stat || row.percentage).trim());
|
|
74
|
+
if (Array.isArray(row.metrics)) {
|
|
75
|
+
for (const m of row.metrics)
|
|
76
|
+
stats.push(String(m).trim());
|
|
77
|
+
}
|
|
78
|
+
if (Array.isArray(row.statistics)) {
|
|
79
|
+
for (const s of row.statistics) {
|
|
80
|
+
const st = typeof s === 'string' ? s : (s.stat || s.text || s.snippet);
|
|
81
|
+
if (st)
|
|
82
|
+
stats.push(String(st).trim());
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (Array.isArray(row.case_studies)) {
|
|
86
|
+
for (const cs of row.case_studies) {
|
|
87
|
+
const csText = typeof cs === 'string' ? cs : [cs.title || cs.name, cs.snippet || cs.summary].filter(Boolean).join(': ');
|
|
88
|
+
if (csText)
|
|
89
|
+
caseStudies.push(csText);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
stats: Array.from(new Set(stats)).filter(Boolean),
|
|
94
|
+
snippets: Array.from(new Set(snippets)).filter(Boolean),
|
|
95
|
+
caseStudies: Array.from(new Set(caseStudies)).filter(Boolean),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
// Helpers: Graph & Expert Resolution
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
function normalizeText(text) {
|
|
102
|
+
return (text || '').toLowerCase().replace(/[^a-z0-9\s]/g, ' ').trim();
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Resolve primary report graph from query string and result rows.
|
|
106
|
+
*/
|
|
107
|
+
export function resolvePrimaryReportGraph(query, resultRows = []) {
|
|
108
|
+
const allGraphs = getLiveGraphs().length > 0 ? getLiveGraphs() : getGraphs();
|
|
109
|
+
const reportGraphs = allGraphs.filter(g => g.graph_type === 'industry report' || g.graph_type === 'report');
|
|
110
|
+
if (reportGraphs.length === 0)
|
|
111
|
+
return undefined;
|
|
112
|
+
const normQuery = normalizeText(query);
|
|
113
|
+
const queryTokens = normQuery.split(/\s+/).filter(t => t.length > 2);
|
|
114
|
+
// 1. Direct match on organization/curator or graph name in query
|
|
115
|
+
let bestGraph;
|
|
116
|
+
let maxMatchScore = 0;
|
|
117
|
+
for (const g of reportGraphs) {
|
|
118
|
+
let matchScore = 0;
|
|
119
|
+
const normName = normalizeText(g.name);
|
|
120
|
+
const normCurator = normalizeText(g.curator);
|
|
121
|
+
const normCompany = normalizeText(g.company);
|
|
122
|
+
const normId = normalizeText(g.graph_id.replace(/-/g, ' '));
|
|
123
|
+
if (normCurator && normQuery.includes(normCurator))
|
|
124
|
+
matchScore += 10;
|
|
125
|
+
if (normCompany && normQuery.includes(normCompany))
|
|
126
|
+
matchScore += 10;
|
|
127
|
+
if (normName && normQuery.includes(normName))
|
|
128
|
+
matchScore += 8;
|
|
129
|
+
if (normId && normQuery.includes(normId))
|
|
130
|
+
matchScore += 5;
|
|
131
|
+
// Token matches
|
|
132
|
+
for (const token of queryTokens) {
|
|
133
|
+
if (normName.includes(token))
|
|
134
|
+
matchScore += 1;
|
|
135
|
+
if (normCurator.includes(token))
|
|
136
|
+
matchScore += 2;
|
|
137
|
+
if (normCompany.includes(token))
|
|
138
|
+
matchScore += 2;
|
|
139
|
+
}
|
|
140
|
+
if (matchScore > maxMatchScore) {
|
|
141
|
+
maxMatchScore = matchScore;
|
|
142
|
+
bestGraph = g;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (bestGraph && maxMatchScore >= 5) {
|
|
146
|
+
return bestGraph;
|
|
147
|
+
}
|
|
148
|
+
// 2. Resolve from highest density / highest scoring graph in result rows
|
|
149
|
+
if (resultRows.length > 0) {
|
|
150
|
+
const graphCounts = new Map();
|
|
151
|
+
for (const row of resultRows) {
|
|
152
|
+
const gid = row.graph_id || row.graphId || '';
|
|
153
|
+
if (!gid)
|
|
154
|
+
continue;
|
|
155
|
+
const existing = graphCounts.get(gid) || { count: 0, totalScore: 0 };
|
|
156
|
+
existing.count += 1;
|
|
157
|
+
const rawScore = Number(row.relevance_score ?? row.score ?? 0.5);
|
|
158
|
+
existing.totalScore += rawScore;
|
|
159
|
+
graphCounts.set(gid, existing);
|
|
160
|
+
}
|
|
161
|
+
let topGid = '';
|
|
162
|
+
let topWeightedScore = -1;
|
|
163
|
+
for (const [gid, stat] of graphCounts.entries()) {
|
|
164
|
+
const weighted = stat.count * 2 + stat.totalScore;
|
|
165
|
+
if (weighted > topWeightedScore) {
|
|
166
|
+
topWeightedScore = weighted;
|
|
167
|
+
topGid = gid;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (topGid) {
|
|
171
|
+
const matched = reportGraphs.find(g => g.graph_id === topGid) || getGraphs().find(g => g.graph_id === topGid);
|
|
172
|
+
if (matched)
|
|
173
|
+
return matched;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return bestGraph;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Filter and match human expert digital twin.
|
|
180
|
+
* Returns undefined if no genuine topical overlap exists (no arbitrary fallback).
|
|
181
|
+
*/
|
|
182
|
+
export function matchHumanExpertTwin(query, primaryGraph) {
|
|
183
|
+
const allAnalysts = getAnalysts();
|
|
184
|
+
const humanTwins = allAnalysts.filter(a => {
|
|
185
|
+
return a.is_human_agent === true ||
|
|
186
|
+
a.is_digital_twin === true ||
|
|
187
|
+
a.isVerifiedRealPerson === true ||
|
|
188
|
+
a.kind === 'human_agent' ||
|
|
189
|
+
a.kind === 'human_twin' ||
|
|
190
|
+
a.type === 'human_agent' ||
|
|
191
|
+
a.type === 'human_twin' ||
|
|
192
|
+
a.agent_type === 'human_agent' ||
|
|
193
|
+
a.agent_type === 'human_twin';
|
|
194
|
+
});
|
|
195
|
+
if (humanTwins.length === 0)
|
|
196
|
+
return undefined;
|
|
197
|
+
const normQuery = normalizeText(`${query} ${primaryGraph?.name || ''} ${primaryGraph?.domain || ''} ${Array.isArray(primaryGraph?.topics) ? primaryGraph.topics.join(' ') : ''}`);
|
|
198
|
+
const tokens = normQuery.split(/\s+/).filter(t => t.length > 3);
|
|
199
|
+
let bestExpert;
|
|
200
|
+
let maxOverlap = 0;
|
|
201
|
+
for (const expert of humanTwins) {
|
|
202
|
+
let score = 0;
|
|
203
|
+
const expertContext = normalizeText(`${expert.name} ${expert.expert_in || ''} ${expert.what_they_offer || ''} ${Array.isArray(expert.topics) ? expert.topics.join(' ') : ''}`);
|
|
204
|
+
for (const t of tokens) {
|
|
205
|
+
if (expertContext.includes(t))
|
|
206
|
+
score += 1;
|
|
207
|
+
}
|
|
208
|
+
if (score > maxOverlap) {
|
|
209
|
+
maxOverlap = score;
|
|
210
|
+
bestExpert = expert;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// Strict: only match when there is positive evidence of topical overlap
|
|
214
|
+
return maxOverlap > 0 ? bestExpert : undefined;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Partition result rows into primary report rows vs. other report graph candidate rows.
|
|
218
|
+
*/
|
|
219
|
+
export function partitionReportResults(resultRows, primaryGraphId) {
|
|
220
|
+
const primaryRows = [];
|
|
221
|
+
const networkRows = [];
|
|
222
|
+
const seenPrimaryText = new Set();
|
|
223
|
+
for (const row of resultRows) {
|
|
224
|
+
const gid = row.graph_id || row.graphId;
|
|
225
|
+
const rowText = `${row.name || row.trend_name || ''} ${row.summary || row.description || ''}`;
|
|
226
|
+
if (primaryGraphId && gid === primaryGraphId) {
|
|
227
|
+
primaryRows.push(row);
|
|
228
|
+
seenPrimaryText.add(normalizeText(rowText));
|
|
229
|
+
const ev = extractEvidenceFromRow(row);
|
|
230
|
+
for (const s of ev.snippets)
|
|
231
|
+
seenPrimaryText.add(normalizeText(s));
|
|
232
|
+
for (const cs of ev.caseStudies)
|
|
233
|
+
seenPrimaryText.add(normalizeText(cs));
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
networkRows.push(row);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// If no primary rows were separated (e.g. query spanned multiple graphs equally), split top rows
|
|
240
|
+
if (primaryRows.length === 0 && resultRows.length > 0) {
|
|
241
|
+
const topGid = resultRows[0]?.graph_id || resultRows[0]?.graphId;
|
|
242
|
+
for (const row of resultRows) {
|
|
243
|
+
const gid = row.graph_id || row.graphId;
|
|
244
|
+
if (gid === topGid) {
|
|
245
|
+
primaryRows.push(row);
|
|
246
|
+
seenPrimaryText.add(normalizeText(`${row.name || row.trend_name || ''} ${row.summary || row.description || ''}`));
|
|
247
|
+
const ev = extractEvidenceFromRow(row);
|
|
248
|
+
for (const s of ev.snippets)
|
|
249
|
+
seenPrimaryText.add(normalizeText(s));
|
|
250
|
+
for (const cs of ev.caseStudies)
|
|
251
|
+
seenPrimaryText.add(normalizeText(cs));
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
networkRows.push(row);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
// Server-side deduplication of network rows against primary rows
|
|
259
|
+
const dedupedNetworkRows = networkRows.filter(nr => {
|
|
260
|
+
const nrTitle = normalizeText(nr.name || nr.trend_name || '');
|
|
261
|
+
const nrText = normalizeText(`${nr.name || nr.trend_name || ''} ${nr.summary || nr.description || ''}`);
|
|
262
|
+
const nrEv = extractEvidenceFromRow(nr);
|
|
263
|
+
const nrAllTexts = [nrTitle, nrText, ...nrEv.snippets, ...nrEv.caseStudies].map(normalizeText);
|
|
264
|
+
for (const pText of seenPrimaryText) {
|
|
265
|
+
for (const text of nrAllTexts) {
|
|
266
|
+
if (text.length > 15 && (pText.includes(text) || (text.length > 25 && pText.includes(text.slice(0, 25))))) {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return true;
|
|
272
|
+
});
|
|
273
|
+
return {
|
|
274
|
+
primaryRows,
|
|
275
|
+
networkRows: dedupedNetworkRows,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
async function runGeminiSynthesis(params) {
|
|
279
|
+
const primarySummary = params.primaryRows.slice(0, 5).map((r, i) => {
|
|
280
|
+
const name = r.name || r.trend_name || `Shift ${i + 1}`;
|
|
281
|
+
const desc = r.summary || r.description || '';
|
|
282
|
+
const ev = extractEvidenceFromRow(r);
|
|
283
|
+
const statsStr = ev.stats.slice(0, 3).join(', ');
|
|
284
|
+
const snipsStr = ev.snippets.slice(0, 2).join('; ');
|
|
285
|
+
return `[Shift ${i + 1}] "${name}": ${desc}\n - Stats: ${statsStr || 'None'}\n - Evidence: ${snipsStr || 'None'}`;
|
|
286
|
+
}).join('\n');
|
|
287
|
+
const networkSummary = params.networkRows.slice(0, 4).map((r, i) => {
|
|
288
|
+
const gid = r.graph_id || r.graphId || `graph-${i}`;
|
|
289
|
+
const matchedGraph = getLiveGraphs().find(g => g.graph_id === gid) || getGraphs().find(g => g.graph_id === gid);
|
|
290
|
+
const gName = matchedGraph?.name || r.graph_name || r.graphName || gid;
|
|
291
|
+
const curator = matchedGraph?.curator || matchedGraph?.company || r.curator || r.organization || 'Research Partner';
|
|
292
|
+
const name = r.name || r.trend_name || 'Signal';
|
|
293
|
+
const desc = r.summary || r.description || '';
|
|
294
|
+
const ev = extractEvidenceFromRow(r);
|
|
295
|
+
const evSnippet = ev.snippets[0] || desc;
|
|
296
|
+
return `[Signal ${i + 1}] GraphID: "${gid}" | Report: "${gName}" by ${curator} | Finding: "${name}" - ${evSnippet}`;
|
|
297
|
+
}).join('\n');
|
|
298
|
+
const expertContext = params.expert
|
|
299
|
+
? `Matched Expert Twin: ${params.expert.name} (Focus: ${params.expert.expert_in || params.expert.what_they_offer || 'Strategy'})`
|
|
300
|
+
: 'None';
|
|
301
|
+
const prompt = `---
|
|
302
|
+
title: Fodda 5-Pillar Report Synthesis
|
|
303
|
+
compliance: RFC-2119
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
### MISSION
|
|
307
|
+
You are an elite research director synthesizing an executive analyst briefing from Fodda's knowledge graph network.
|
|
308
|
+
|
|
309
|
+
### INPUT DATA
|
|
310
|
+
- User Query: "${params.query}"
|
|
311
|
+
- Primary Report: "${params.reportName}"
|
|
312
|
+
- Primary Report Findings:
|
|
313
|
+
${primarySummary || '(Thin primary findings)'}
|
|
314
|
+
|
|
315
|
+
- Related Network Signals from other industry reports:
|
|
316
|
+
${networkSummary || '(No other report signals)'}
|
|
317
|
+
|
|
318
|
+
- ${expertContext}
|
|
319
|
+
|
|
320
|
+
### REQUIREMENTS
|
|
321
|
+
Produce a single JSON object with these exact keys:
|
|
322
|
+
1. "topline_hook": A 1-2 sentence provocative thesis and core tension stopping the reader. Do NOT lead with dry bibliographic metadata.
|
|
323
|
+
2. "shifts": Array of 3-5 structural shifts. Each must have:
|
|
324
|
+
- "title": Sharp, evocative shift title.
|
|
325
|
+
- "narrative": 1-2 sentences explaining why this shift matters.
|
|
326
|
+
- "stats": Array of 1-3 concrete data points, metrics, or percentages extracted from the evidence.
|
|
327
|
+
- "evidence": Array of 1-2 concrete proof points or case study mentions.
|
|
328
|
+
3. "network_signals": Array of 2-3 cross-graph connections. For each signal:
|
|
329
|
+
- "graph_id": The exact GraphID provided in the input.
|
|
330
|
+
- "signal_type": MUST be either "validates", "contrasts", or "related".
|
|
331
|
+
- "connection": 1 crisp sentence explaining how this other report supports or contrasts the primary finding.
|
|
332
|
+
4. "expert_stance": ${params.expert ? 'A 1-sentence analytical perspective in the voice/philosophy of the matched expert twin regarding this topic.' : 'null'}
|
|
333
|
+
|
|
334
|
+
STRICT RULE: Do NOT mention tokens, shared payment tokens, SPT, or pricing mechanics.`;
|
|
335
|
+
try {
|
|
336
|
+
const resp = await callGemini(prompt, 'gemini-2.0-flash', 1500);
|
|
337
|
+
if (!resp.text || resp.error)
|
|
338
|
+
return null;
|
|
339
|
+
let parsed;
|
|
340
|
+
try {
|
|
341
|
+
parsed = JSON.parse(resp.text);
|
|
342
|
+
}
|
|
343
|
+
catch {
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
if (typeof parsed === 'object' && parsed !== null) {
|
|
347
|
+
return parsed;
|
|
348
|
+
}
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
// ---------------------------------------------------------------------------
|
|
356
|
+
// Pre-Rendered Markdown Formatter
|
|
357
|
+
// ---------------------------------------------------------------------------
|
|
358
|
+
function renderBriefingMarkdown(payload) {
|
|
359
|
+
const lines = [];
|
|
360
|
+
lines.push(`# Strategic Briefing: ${payload.reportTitle}`);
|
|
361
|
+
lines.push('');
|
|
362
|
+
lines.push(`> **Core Tension:** ${payload.toplineHook}`);
|
|
363
|
+
lines.push('');
|
|
364
|
+
lines.push('---');
|
|
365
|
+
lines.push('');
|
|
366
|
+
// Pillar 2: Core Shifts
|
|
367
|
+
lines.push('### 📊 Core Structural Shifts & Evidence');
|
|
368
|
+
lines.push('');
|
|
369
|
+
for (const shift of payload.shifts) {
|
|
370
|
+
lines.push(`#### • ${shift.title}`);
|
|
371
|
+
lines.push(`${shift.narrative}`);
|
|
372
|
+
if (shift.stats.length > 0) {
|
|
373
|
+
lines.push('');
|
|
374
|
+
for (const stat of shift.stats) {
|
|
375
|
+
lines.push(` - **Key Metric:** ${stat}`);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
if (shift.evidence.length > 0) {
|
|
379
|
+
lines.push('');
|
|
380
|
+
for (const ev of shift.evidence) {
|
|
381
|
+
lines.push(` - **Evidence:** ${ev}`);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
lines.push('');
|
|
385
|
+
}
|
|
386
|
+
// Pillar 3: Cross-Graph Network Evidence
|
|
387
|
+
if (payload.networkSignals.length > 0) {
|
|
388
|
+
lines.push('---');
|
|
389
|
+
lines.push('');
|
|
390
|
+
lines.push('### 🌐 Cross-Graph Network Signals');
|
|
391
|
+
lines.push('');
|
|
392
|
+
for (const sig of payload.networkSignals) {
|
|
393
|
+
const badge = sig.signal_type === 'validates'
|
|
394
|
+
? '✅ **Validates**'
|
|
395
|
+
: (sig.signal_type === 'contrasts' ? '⚡ **Contrasts**' : '🔗 **Related Signal**');
|
|
396
|
+
const curatorInfo = sig.curator ? ` (${sig.curator})` : '';
|
|
397
|
+
lines.push(`- ${badge} → [${sig.source_report}](${sig.url})${curatorInfo}: ${sig.snippet}`);
|
|
398
|
+
}
|
|
399
|
+
lines.push('');
|
|
400
|
+
}
|
|
401
|
+
// Pillar 4: Expert Spotlight
|
|
402
|
+
if (payload.expertSpotlight) {
|
|
403
|
+
lines.push('---');
|
|
404
|
+
lines.push('');
|
|
405
|
+
lines.push('### 👤 Human Expert Digital Twin Spotlight');
|
|
406
|
+
lines.push('');
|
|
407
|
+
lines.push(`**Expert Alignment:** ${payload.expertSpotlight.name}`);
|
|
408
|
+
lines.push(`*Why Matched:* ${payload.expertSpotlight.why_matched}`);
|
|
409
|
+
lines.push(`> *"${payload.expertSpotlight.position}"*`);
|
|
410
|
+
if (payload.expertSpotlight.consult_url) {
|
|
411
|
+
lines.push(`→ **Consult ${payload.expertSpotlight.name}'s Twin:** [${payload.expertSpotlight.consult_url}](${payload.expertSpotlight.consult_url})`);
|
|
412
|
+
}
|
|
413
|
+
lines.push('');
|
|
414
|
+
}
|
|
415
|
+
// Pillar 5: Follow-Up Prompts
|
|
416
|
+
if (payload.followUps.length > 0) {
|
|
417
|
+
lines.push('---');
|
|
418
|
+
lines.push('');
|
|
419
|
+
lines.push('### 🎯 Suggested Deep-Dive Moves');
|
|
420
|
+
lines.push('');
|
|
421
|
+
for (const fu of payload.followUps) {
|
|
422
|
+
lines.push(`- **${fu.prompt}** `);
|
|
423
|
+
lines.push(` *Run Tool:* \`${fu.tool}(${JSON.stringify(fu.args)})\` | [Open in Fodda](${fu.app_url})`);
|
|
424
|
+
}
|
|
425
|
+
lines.push('');
|
|
426
|
+
}
|
|
427
|
+
return lines.join('\n').trim();
|
|
428
|
+
}
|
|
429
|
+
// ---------------------------------------------------------------------------
|
|
430
|
+
// Main Builder: buildReportEditorialBriefing
|
|
431
|
+
// ---------------------------------------------------------------------------
|
|
432
|
+
export async function buildReportEditorialBriefing(params) {
|
|
433
|
+
const { data, query, annotatedData } = params;
|
|
434
|
+
const rawRows = Array.isArray(data?.results)
|
|
435
|
+
? data.results
|
|
436
|
+
: (Array.isArray(data?.trends) ? data.trends : (Array.isArray(data?.data) ? data.data : []));
|
|
437
|
+
// 1. Resolve Primary Graph & Partition Rows
|
|
438
|
+
const primaryGraph = resolvePrimaryReportGraph(query, rawRows);
|
|
439
|
+
const primaryGraphId = primaryGraph?.graph_id;
|
|
440
|
+
const reportTitle = primaryGraph?.name || (rawRows[0]?.graph_name) || (rawRows[0]?.graph_id) || 'Industry Report Intelligence';
|
|
441
|
+
const { primaryRows, networkRows } = partitionReportResults(rawRows, primaryGraphId);
|
|
442
|
+
// 2. Pre-computed Ingestion-Time 5-Pillar Metadata (from CE / API)
|
|
443
|
+
const graphEditorial = (primaryGraphId && data?.graph_editorial?.[primaryGraphId]) || {};
|
|
444
|
+
const precomputedCoreTension = primaryGraph?.core_tension || graphEditorial.core_tension || rawRows[0]?.core_tension;
|
|
445
|
+
const precomputedSignals = primaryGraph?.cross_graph_signals || graphEditorial.cross_graph_signals || rawRows[0]?.cross_graph_signals;
|
|
446
|
+
const precomputedTwinSlug = primaryGraph?.matched_human_twin_slug || graphEditorial.matched_human_twin_slug || rawRows[0]?.matched_human_twin_slug;
|
|
447
|
+
const precomputedExpertQuote = primaryGraph?.expert_stance_quote || graphEditorial.expert_stance_quote || rawRows[0]?.expert_stance_quote;
|
|
448
|
+
// 3. Match Human Expert Digital Twin (Prefer pre-computed twin slug if available)
|
|
449
|
+
let matchedExpert;
|
|
450
|
+
if (precomputedTwinSlug) {
|
|
451
|
+
matchedExpert = getAnalysts().find(a => (a.slug || a.expertSlug || a.analyst_id) === precomputedTwinSlug);
|
|
452
|
+
}
|
|
453
|
+
if (!matchedExpert) {
|
|
454
|
+
matchedExpert = matchHumanExpertTwin(query, primaryGraph);
|
|
455
|
+
}
|
|
456
|
+
// 4. Single Unified Gemini Synthesis Call (if any fields need LLM fill)
|
|
457
|
+
const needsGemini = !precomputedCoreTension || !precomputedSignals || precomputedSignals.length === 0;
|
|
458
|
+
const geminiOutput = needsGemini ? await runGeminiSynthesis({
|
|
459
|
+
query,
|
|
460
|
+
reportName: reportTitle,
|
|
461
|
+
primaryRows,
|
|
462
|
+
networkRows,
|
|
463
|
+
expert: matchedExpert,
|
|
464
|
+
}) : null;
|
|
465
|
+
// 5. Construct Topline Hook (Pillar 1)
|
|
466
|
+
let toplineHookText = precomputedCoreTension || geminiOutput?.topline_hook?.slice(0, 350);
|
|
467
|
+
let isHookGenerated = !precomputedCoreTension && Boolean(geminiOutput?.topline_hook);
|
|
468
|
+
if (!toplineHookText) {
|
|
469
|
+
isHookGenerated = false;
|
|
470
|
+
if (primaryRows.length > 0) {
|
|
471
|
+
const topRow = primaryRows[0];
|
|
472
|
+
const name = topRow?.name || topRow?.trend_name || 'Emerging Dynamics';
|
|
473
|
+
const sum = topRow?.summary || topRow?.description || 'key strategic shifts across the sector';
|
|
474
|
+
toplineHookText = `Findings from ${reportTitle} indicate that ${name} is driving ${sum}.`;
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
toplineHookText = `No published report intelligence was found specifically matching "${query}". Exploring adjacent domain signals or consulting a specialist analyst can help bridge this gap.`;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
// 5. Construct Structural Shifts (Pillar 2)
|
|
481
|
+
const shifts = [];
|
|
482
|
+
if (geminiOutput?.shifts && Array.isArray(geminiOutput.shifts) && geminiOutput.shifts.length > 0) {
|
|
483
|
+
for (let i = 0; i < Math.min(geminiOutput.shifts.length, 4); i++) {
|
|
484
|
+
const s = geminiOutput.shifts[i];
|
|
485
|
+
if (!s)
|
|
486
|
+
continue;
|
|
487
|
+
const matchingRow = primaryRows[i] || primaryRows[0];
|
|
488
|
+
const rowEv = matchingRow ? extractEvidenceFromRow(matchingRow) : { stats: [], snippets: [], caseStudies: [] };
|
|
489
|
+
const finalStats = Array.isArray(s.stats) && s.stats.length > 0
|
|
490
|
+
? s.stats.slice(0, 3)
|
|
491
|
+
: rowEv.stats.slice(0, 3);
|
|
492
|
+
const finalEvidence = Array.isArray(s.evidence) && s.evidence.length > 0
|
|
493
|
+
? s.evidence.slice(0, 2)
|
|
494
|
+
: rowEv.snippets.slice(0, 2);
|
|
495
|
+
shifts.push({
|
|
496
|
+
title: (s.title || `Shift ${i + 1}`).slice(0, 100),
|
|
497
|
+
narrative: (s.narrative || '').slice(0, 300),
|
|
498
|
+
stats: finalStats,
|
|
499
|
+
evidence: finalEvidence,
|
|
500
|
+
generated: true,
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
else {
|
|
505
|
+
// Fallback: Mechanical extraction from primary rows
|
|
506
|
+
const fallbackRows = (primaryRows.length > 0 ? primaryRows : rawRows).slice(0, 4);
|
|
507
|
+
for (let i = 0; i < fallbackRows.length; i++) {
|
|
508
|
+
const row = fallbackRows[i];
|
|
509
|
+
const title = (row?.name || row?.trend_name || `Shift ${i + 1}`).slice(0, 100);
|
|
510
|
+
const narrative = (row?.summary || row?.description || 'Structural shift identified in report data.').slice(0, 300);
|
|
511
|
+
const ev = extractEvidenceFromRow(row);
|
|
512
|
+
shifts.push({
|
|
513
|
+
title,
|
|
514
|
+
narrative,
|
|
515
|
+
stats: ev.stats.slice(0, 3),
|
|
516
|
+
evidence: ev.snippets.slice(0, 2),
|
|
517
|
+
generated: false,
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
// 6. Construct Network Signals (Pillar 3)
|
|
522
|
+
const networkSignals = [];
|
|
523
|
+
if (precomputedSignals && Array.isArray(precomputedSignals) && precomputedSignals.length > 0) {
|
|
524
|
+
for (const sig of precomputedSignals.slice(0, 3)) {
|
|
525
|
+
const gid = sig.graph_id || '';
|
|
526
|
+
const matchedGraph = getLiveGraphs().find(g => g.graph_id === gid) || getGraphs().find(g => g.graph_id === gid);
|
|
527
|
+
const sourceReport = matchedGraph?.name || gid || 'Industry Report';
|
|
528
|
+
const curator = matchedGraph?.curator || matchedGraph?.company || 'Research Partner';
|
|
529
|
+
networkSignals.push({
|
|
530
|
+
title: sig.source_case_study || `Signal from ${sourceReport}`,
|
|
531
|
+
source_report: sourceReport,
|
|
532
|
+
graph_id: gid,
|
|
533
|
+
curator: curator ? String(curator) : undefined,
|
|
534
|
+
signal_type: sig.signal_type || 'validates',
|
|
535
|
+
snippet: (sig.connection_summary || sig.source_case_study || '').slice(0, 250),
|
|
536
|
+
url: matchedGraph?.webpage_url || `https://app.fodda.ai/graphs/${encodeURIComponent(gid)}`,
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
else {
|
|
541
|
+
const candidateNetRows = networkRows.slice(0, 3);
|
|
542
|
+
for (let i = 0; i < candidateNetRows.length; i++) {
|
|
543
|
+
const nr = candidateNetRows[i];
|
|
544
|
+
const gid = nr?.graph_id || nr?.graphId || '';
|
|
545
|
+
const matchedGraph = getLiveGraphs().find(g => g.graph_id === gid) || getGraphs().find(g => g.graph_id === gid);
|
|
546
|
+
const sourceReport = matchedGraph?.name || nr?.graph_name || nr?.graphName || gid || 'Industry Report';
|
|
547
|
+
const curator = matchedGraph?.curator || matchedGraph?.company || nr?.curator || nr?.organization || 'Research Partner';
|
|
548
|
+
// Match LLM signal classification if available
|
|
549
|
+
const matchedGeminiSig = geminiOutput?.network_signals?.find(s => s.graph_id === gid || s.graph_id === sourceReport);
|
|
550
|
+
const signalType = matchedGeminiSig?.signal_type || 'related';
|
|
551
|
+
const ev = extractEvidenceFromRow(nr);
|
|
552
|
+
const snippet = (matchedGeminiSig?.connection || ev.snippets[0] || nr?.summary || nr?.description || nr?.name || nr?.trend_name || '').slice(0, 250);
|
|
553
|
+
const rawScore = Number(nr?.relevance_score ?? nr?.score);
|
|
554
|
+
networkSignals.push({
|
|
555
|
+
title: nr?.name || nr?.trend_name || `Signal from ${sourceReport}`,
|
|
556
|
+
source_report: sourceReport,
|
|
557
|
+
graph_id: gid,
|
|
558
|
+
curator: curator ? String(curator) : undefined,
|
|
559
|
+
signal_type: signalType,
|
|
560
|
+
snippet,
|
|
561
|
+
score: !isNaN(rawScore) ? rawScore : undefined,
|
|
562
|
+
url: matchedGraph?.webpage_url || `https://app.fodda.ai/graphs/${encodeURIComponent(gid)}`,
|
|
563
|
+
});
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
// 7. Construct Expert Spotlight (Pillar 4)
|
|
567
|
+
let expertSpotlight;
|
|
568
|
+
if (matchedExpert) {
|
|
569
|
+
const rawSlug = matchedExpert.slug || matchedExpert.expertSlug || matchedExpert.analyst_id || '';
|
|
570
|
+
const isCleanSlug = /^[a-z0-9-]+$/.test(rawSlug);
|
|
571
|
+
const consultUrl = isCleanSlug ? `https://expert.fodda.ai/${rawSlug}` : undefined;
|
|
572
|
+
const position = (precomputedExpertQuote || geminiOutput?.expert_stance || matchedExpert.what_they_offer || `Offers strategic advisory on ${matchedExpert.expert_in || 'market shifts'}.`).slice(0, 250);
|
|
573
|
+
const whyMatched = `Specializes in ${matchedExpert.expert_in || matchedExpert.name}'s strategic domain`;
|
|
574
|
+
expertSpotlight = {
|
|
575
|
+
name: matchedExpert.name,
|
|
576
|
+
slug: isCleanSlug ? rawSlug : undefined,
|
|
577
|
+
analyst_id: matchedExpert.analyst_id,
|
|
578
|
+
why_matched: whyMatched,
|
|
579
|
+
position,
|
|
580
|
+
consult_url: consultUrl,
|
|
581
|
+
consult_tool: 'consult_human_agent',
|
|
582
|
+
generated: !precomputedExpertQuote && Boolean(geminiOutput?.expert_stance),
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
// 8. Reshape Follow-Ups (Pillar 5)
|
|
586
|
+
const followUps = [];
|
|
587
|
+
// Follow-up 1: Deep dive on top shift or primary report
|
|
588
|
+
if (shifts.length > 0 && shifts[0]) {
|
|
589
|
+
const topShift = shifts[0];
|
|
590
|
+
followUps.push({
|
|
591
|
+
prompt: `Deep dive into "${topShift.title}" in ${reportTitle}`,
|
|
592
|
+
tool: 'get_report_intelligence',
|
|
593
|
+
args: { query: `${reportTitle} ${topShift.title}` },
|
|
594
|
+
app_url: primaryGraphId
|
|
595
|
+
? `https://app.fodda.ai/graphs/${encodeURIComponent(primaryGraphId)}`
|
|
596
|
+
: `https://app.fodda.ai`,
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
// Follow-up 2: Consult matched human twin
|
|
600
|
+
if (matchedExpert) {
|
|
601
|
+
followUps.push({
|
|
602
|
+
prompt: `Consult ${matchedExpert.name} on strategic execution`,
|
|
603
|
+
tool: 'consult_human_agent',
|
|
604
|
+
args: {
|
|
605
|
+
analyst_id: matchedExpert.analyst_id,
|
|
606
|
+
query: `How does ${reportTitle} impact strategic priorities for brands?`,
|
|
607
|
+
},
|
|
608
|
+
app_url: expertSpotlight?.consult_url || `https://app.fodda.ai`,
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
// Follow-up 3: Cross-graph query
|
|
612
|
+
if (networkSignals.length > 0 && networkSignals[0]) {
|
|
613
|
+
const netSig = networkSignals[0];
|
|
614
|
+
followUps.push({
|
|
615
|
+
prompt: `Explore cross-industry validation from ${netSig.source_report}`,
|
|
616
|
+
tool: 'get_report_intelligence',
|
|
617
|
+
args: { query: `${netSig.source_report} ${query}` },
|
|
618
|
+
app_url: netSig.url,
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
// If fewer than 3 follow-ups generated, pull from annotatedData.next_moves candidates if available
|
|
622
|
+
if (followUps.length < 3 && annotatedData?.next_moves?.candidates && Array.isArray(annotatedData.next_moves.candidates)) {
|
|
623
|
+
for (const c of annotatedData.next_moves.candidates) {
|
|
624
|
+
if (followUps.length >= 3)
|
|
625
|
+
break;
|
|
626
|
+
if (c.tool && c.prompt) {
|
|
627
|
+
followUps.push({
|
|
628
|
+
prompt: c.prompt,
|
|
629
|
+
tool: c.tool,
|
|
630
|
+
args: c.args || {},
|
|
631
|
+
app_url: 'https://app.fodda.ai',
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
// 9. Pre-Render Markdown
|
|
637
|
+
const briefingMarkdown = renderBriefingMarkdown({
|
|
638
|
+
reportTitle,
|
|
639
|
+
toplineHook: toplineHookText,
|
|
640
|
+
shifts,
|
|
641
|
+
networkSignals,
|
|
642
|
+
expertSpotlight,
|
|
643
|
+
followUps,
|
|
644
|
+
});
|
|
645
|
+
return {
|
|
646
|
+
view: 'editorial',
|
|
647
|
+
query,
|
|
648
|
+
report_title: reportTitle,
|
|
649
|
+
primary_graph_id: primaryGraphId,
|
|
650
|
+
topline_hook: {
|
|
651
|
+
text: toplineHookText,
|
|
652
|
+
generated: isHookGenerated,
|
|
653
|
+
},
|
|
654
|
+
shifts,
|
|
655
|
+
network_signals: networkSignals,
|
|
656
|
+
expert_spotlight: expertSpotlight,
|
|
657
|
+
follow_ups: followUps,
|
|
658
|
+
briefing_markdown: briefingMarkdown,
|
|
659
|
+
coverage: annotatedData?.coverage || data?.coverage,
|
|
660
|
+
};
|
|
661
|
+
}
|
|
662
|
+
//# sourceMappingURL=reportBriefing.js.map
|