fodda-mcp 1.46.97 → 1.46.132
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 +15 -9
- package/dist/a2aHandler.d.ts +36 -2
- package/dist/a2aHandler.d.ts.map +1 -1
- package/dist/a2aHandler.js +24 -1
- package/dist/a2aHandler.js.map +1 -1
- package/dist/brandTemplate.d.ts +1 -1
- package/dist/brandTemplate.d.ts.map +1 -1
- package/dist/brandTemplate.js +226 -118
- package/dist/brandTemplate.js.map +1 -1
- package/dist/catalogCache.d.ts +21 -0
- package/dist/catalogCache.d.ts.map +1 -1
- package/dist/catalogCache.js +287 -39
- package/dist/catalogCache.js.map +1 -1
- package/dist/coverageRelevance.d.ts +82 -10
- package/dist/coverageRelevance.d.ts.map +1 -1
- package/dist/coverageRelevance.js +1274 -190
- package/dist/coverageRelevance.js.map +1 -1
- package/dist/editorialFill.d.ts.map +1 -1
- package/dist/editorialFill.js +13 -1
- package/dist/editorialFill.js.map +1 -1
- package/dist/enrichment.d.ts +19 -0
- package/dist/enrichment.d.ts.map +1 -1
- package/dist/enrichment.js +86 -3
- package/dist/enrichment.js.map +1 -1
- package/dist/foddaClient.d.ts +26 -0
- package/dist/foddaClient.d.ts.map +1 -0
- package/dist/foddaClient.js +195 -0
- package/dist/foddaClient.js.map +1 -0
- package/dist/helpService.d.ts +45 -0
- package/dist/helpService.d.ts.map +1 -0
- package/dist/helpService.js +428 -0
- package/dist/helpService.js.map +1 -0
- package/dist/index.d.ts +4 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +131 -174
- package/dist/index.js.map +1 -1
- package/dist/pricingCache.d.ts.map +1 -1
- package/dist/pricingCache.js +118 -83
- package/dist/pricingCache.js.map +1 -1
- package/dist/searchTemplate.d.ts.map +1 -1
- package/dist/searchTemplate.js +16 -4
- package/dist/searchTemplate.js.map +1 -1
- package/dist/skillClient.d.ts.map +1 -1
- package/dist/skillClient.js +5 -31
- package/dist/skillClient.js.map +1 -1
- package/dist/systemPrompt.d.ts +3 -2
- package/dist/systemPrompt.d.ts.map +1 -1
- package/dist/systemPrompt.js +253 -103
- package/dist/systemPrompt.js.map +1 -1
- package/dist/test_claude_offering_and_oauth.js +205 -0
- package/dist/toolGuide.d.ts +56 -0
- package/dist/toolGuide.d.ts.map +1 -0
- package/dist/toolGuide.js +330 -0
- package/dist/toolGuide.js.map +1 -0
- package/dist/toolHandlers.d.ts +3 -2
- package/dist/toolHandlers.d.ts.map +1 -1
- package/dist/toolHandlers.js +1774 -330
- package/dist/toolHandlers.js.map +1 -1
- package/dist/tools.d.ts +1 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +19 -18
- package/dist/tools.js.map +1 -1
- package/dist/types.d.ts +39 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +5 -3
- package/server.json +2 -2
- package/dist/test_adjacent_trends_polymorphic.d.ts +0 -2
- package/dist/test_adjacent_trends_polymorphic.d.ts.map +0 -1
- package/dist/test_adjacent_trends_polymorphic.js +0 -154
- package/dist/test_adjacent_trends_polymorphic.js.map +0 -1
- package/dist/test_analyst.d.ts +0 -2
- package/dist/test_analyst.d.ts.map +0 -1
- package/dist/test_analyst.js +0 -39
- package/dist/test_analyst.js.map +0 -1
- package/dist/test_analyst_local.d.ts +0 -2
- package/dist/test_analyst_local.d.ts.map +0 -1
- package/dist/test_analyst_local.js +0 -51
- package/dist/test_analyst_local.js.map +0 -1
- package/dist/test_book_a_call_live.d.ts +0 -2
- package/dist/test_book_a_call_live.d.ts.map +0 -1
- package/dist/test_book_a_call_live.js +0 -161
- package/dist/test_book_a_call_live.js.map +0 -1
- package/dist/test_brand_intelligence_endpoint.d.ts +0 -2
- package/dist/test_brand_intelligence_endpoint.d.ts.map +0 -1
- package/dist/test_brand_intelligence_endpoint.js +0 -20
- package/dist/test_brand_intelligence_endpoint.js.map +0 -1
- package/dist/test_byo_mcp_submission_clarity.d.ts +0 -2
- package/dist/test_byo_mcp_submission_clarity.d.ts.map +0 -1
- package/dist/test_byo_mcp_submission_clarity.js +0 -142
- package/dist/test_byo_mcp_submission_clarity.js.map +0 -1
- package/dist/test_capabilities_guidance.d.ts +0 -2
- package/dist/test_capabilities_guidance.d.ts.map +0 -1
- package/dist/test_capabilities_guidance.js +0 -372
- package/dist/test_capabilities_guidance.js.map +0 -1
- package/dist/test_client_provenance.d.ts +0 -2
- package/dist/test_client_provenance.d.ts.map +0 -1
- package/dist/test_client_provenance.js +0 -77
- package/dist/test_client_provenance.js.map +0 -1
- package/dist/test_connection_surface_cleanup.d.ts +0 -2
- package/dist/test_connection_surface_cleanup.d.ts.map +0 -1
- package/dist/test_connection_surface_cleanup.js +0 -203
- package/dist/test_connection_surface_cleanup.js.map +0 -1
- package/dist/test_consult_routing.d.ts +0 -2
- package/dist/test_consult_routing.d.ts.map +0 -1
- package/dist/test_consult_routing.js +0 -86
- package/dist/test_consult_routing.js.map +0 -1
- package/dist/test_consult_split.d.ts +0 -2
- package/dist/test_consult_split.d.ts.map +0 -1
- package/dist/test_consult_split.js +0 -30
- package/dist/test_consult_split.js.map +0 -1
- package/dist/test_coverage_relevance.d.ts +0 -12
- package/dist/test_coverage_relevance.d.ts.map +0 -1
- package/dist/test_coverage_relevance.js +0 -162
- package/dist/test_coverage_relevance.js.map +0 -1
- package/dist/test_coverage_replay.d.ts +0 -2
- package/dist/test_coverage_replay.d.ts.map +0 -1
- package/dist/test_coverage_replay.js +0 -170
- package/dist/test_coverage_replay.js.map +0 -1
- package/dist/test_dcr_and_legacy_deprecation.d.ts +0 -2
- package/dist/test_dcr_and_legacy_deprecation.d.ts.map +0 -1
- package/dist/test_dcr_and_legacy_deprecation.js +0 -143
- package/dist/test_dcr_and_legacy_deprecation.js.map +0 -1
- package/dist/test_demand_signal_logging.d.ts +0 -2
- package/dist/test_demand_signal_logging.d.ts.map +0 -1
- package/dist/test_demand_signal_logging.js +0 -92
- package/dist/test_demand_signal_logging.js.map +0 -1
- package/dist/test_earnings_activity_scope.d.ts +0 -5
- package/dist/test_earnings_activity_scope.d.ts.map +0 -1
- package/dist/test_earnings_activity_scope.js +0 -101
- package/dist/test_earnings_activity_scope.js.map +0 -1
- package/dist/test_earnings_divergence_live.d.ts +0 -5
- package/dist/test_earnings_divergence_live.d.ts.map +0 -1
- package/dist/test_earnings_divergence_live.js +0 -135
- package/dist/test_earnings_divergence_live.js.map +0 -1
- package/dist/test_earnings_hygiene_and_binding.d.ts +0 -5
- package/dist/test_earnings_hygiene_and_binding.d.ts.map +0 -1
- package/dist/test_earnings_hygiene_and_binding.js +0 -104
- package/dist/test_earnings_hygiene_and_binding.js.map +0 -1
- package/dist/test_exec_quotes_render.d.ts +0 -2
- package/dist/test_exec_quotes_render.d.ts.map +0 -1
- package/dist/test_exec_quotes_render.js +0 -86
- package/dist/test_exec_quotes_render.js.map +0 -1
- package/dist/test_expert_intro.d.ts +0 -2
- package/dist/test_expert_intro.d.ts.map +0 -1
- package/dist/test_expert_intro.js +0 -57
- package/dist/test_expert_intro.js.map +0 -1
- package/dist/test_expert_layer.d.ts +0 -11
- package/dist/test_expert_layer.d.ts.map +0 -1
- package/dist/test_expert_layer.js +0 -356
- package/dist/test_expert_layer.js.map +0 -1
- package/dist/test_financial_snapshot_render.d.ts +0 -2
- package/dist/test_financial_snapshot_render.d.ts.map +0 -1
- package/dist/test_financial_snapshot_render.js +0 -167
- package/dist/test_financial_snapshot_render.js.map +0 -1
- package/dist/test_gap_alert.d.ts +0 -12
- package/dist/test_gap_alert.d.ts.map +0 -1
- package/dist/test_gap_alert.js +0 -51
- package/dist/test_gap_alert.js.map +0 -1
- package/dist/test_grok_offering_surfaces.d.ts +0 -2
- package/dist/test_grok_offering_surfaces.d.ts.map +0 -1
- package/dist/test_grok_offering_surfaces.js +0 -155
- package/dist/test_grok_offering_surfaces.js.map +0 -1
- package/dist/test_human_agent_live.d.ts +0 -2
- package/dist/test_human_agent_live.d.ts.map +0 -1
- package/dist/test_human_agent_live.js +0 -48
- package/dist/test_human_agent_live.js.map +0 -1
- package/dist/test_identity_gap.d.ts +0 -2
- package/dist/test_identity_gap.d.ts.map +0 -1
- package/dist/test_identity_gap.js +0 -171
- package/dist/test_identity_gap.js.map +0 -1
- package/dist/test_intelligence_and_verifier.d.ts +0 -17
- package/dist/test_intelligence_and_verifier.d.ts.map +0 -1
- package/dist/test_intelligence_and_verifier.js +0 -162
- package/dist/test_intelligence_and_verifier.js.map +0 -1
- package/dist/test_latency_mcp_brief5.d.ts +0 -12
- package/dist/test_latency_mcp_brief5.d.ts.map +0 -1
- package/dist/test_latency_mcp_brief5.js +0 -200
- package/dist/test_latency_mcp_brief5.js.map +0 -1
- package/dist/test_limit_and_credit_card_capture.d.ts +0 -21
- package/dist/test_limit_and_credit_card_capture.d.ts.map +0 -1
- package/dist/test_limit_and_credit_card_capture.js +0 -216
- package/dist/test_limit_and_credit_card_capture.js.map +0 -1
- package/dist/test_list_graphs_topics.d.ts +0 -2
- package/dist/test_list_graphs_topics.d.ts.map +0 -1
- package/dist/test_list_graphs_topics.js +0 -26
- package/dist/test_list_graphs_topics.js.map +0 -1
- package/dist/test_live_deployed_mcp_search.d.ts +0 -2
- package/dist/test_live_deployed_mcp_search.d.ts.map +0 -1
- package/dist/test_live_deployed_mcp_search.js +0 -85
- package/dist/test_live_deployed_mcp_search.js.map +0 -1
- package/dist/test_live_deployed_split.d.ts +0 -2
- package/dist/test_live_deployed_split.d.ts.map +0 -1
- package/dist/test_live_deployed_split.js +0 -72
- package/dist/test_live_deployed_split.js.map +0 -1
- package/dist/test_live_endpoint_runner.d.ts +0 -2
- package/dist/test_live_endpoint_runner.d.ts.map +0 -1
- package/dist/test_live_endpoint_runner.js +0 -65
- package/dist/test_live_endpoint_runner.js.map +0 -1
- package/dist/test_live_mcp.d.ts +0 -2
- package/dist/test_live_mcp.d.ts.map +0 -1
- package/dist/test_live_mcp.js +0 -49
- package/dist/test_live_mcp.js.map +0 -1
- package/dist/test_live_neil_widget.d.ts +0 -2
- package/dist/test_live_neil_widget.d.ts.map +0 -1
- package/dist/test_live_neil_widget.js +0 -84
- package/dist/test_live_neil_widget.js.map +0 -1
- package/dist/test_local_list_graphs.d.ts +0 -2
- package/dist/test_local_list_graphs.d.ts.map +0 -1
- package/dist/test_local_list_graphs.js +0 -53
- package/dist/test_local_list_graphs.js.map +0 -1
- package/dist/test_longpoll_status.d.ts +0 -2
- package/dist/test_longpoll_status.d.ts.map +0 -1
- package/dist/test_longpoll_status.js +0 -117
- package/dist/test_longpoll_status.js.map +0 -1
- package/dist/test_mcp_find_expert_and_vocabulary_routing.d.ts +0 -2
- package/dist/test_mcp_find_expert_and_vocabulary_routing.d.ts.map +0 -1
- package/dist/test_mcp_find_expert_and_vocabulary_routing.js +0 -130
- package/dist/test_mcp_find_expert_and_vocabulary_routing.js.map +0 -1
- package/dist/test_next_moves.d.ts +0 -2
- package/dist/test_next_moves.d.ts.map +0 -1
- package/dist/test_next_moves.js +0 -714
- package/dist/test_next_moves.js.map +0 -1
- package/dist/test_next_moves_transcripts.d.ts +0 -2
- package/dist/test_next_moves_transcripts.d.ts.map +0 -1
- package/dist/test_next_moves_transcripts.js +0 -495
- package/dist/test_next_moves_transcripts.js.map +0 -1
- package/dist/test_nike_cmo_alias.d.ts +0 -2
- package/dist/test_nike_cmo_alias.d.ts.map +0 -1
- package/dist/test_nike_cmo_alias.js +0 -76
- package/dist/test_nike_cmo_alias.js.map +0 -1
- package/dist/test_on_request_caller_identity.d.ts +0 -13
- package/dist/test_on_request_caller_identity.d.ts.map +0 -1
- package/dist/test_on_request_caller_identity.js +0 -131
- package/dist/test_on_request_caller_identity.js.map +0 -1
- package/dist/test_on_request_intercept.d.ts +0 -10
- package/dist/test_on_request_intercept.d.ts.map +0 -1
- package/dist/test_on_request_intercept.js +0 -95
- package/dist/test_on_request_intercept.js.map +0 -1
- package/dist/test_onboarding_consent_and_errors.d.ts +0 -2
- package/dist/test_onboarding_consent_and_errors.d.ts.map +0 -1
- package/dist/test_onboarding_consent_and_errors.js +0 -110
- package/dist/test_onboarding_consent_and_errors.js.map +0 -1
- package/dist/test_onboarding_live_scenarios.d.ts +0 -2
- package/dist/test_onboarding_live_scenarios.d.ts.map +0 -1
- package/dist/test_onboarding_live_scenarios.js +0 -189
- package/dist/test_onboarding_live_scenarios.js.map +0 -1
- package/dist/test_referral_live.d.ts +0 -2
- package/dist/test_referral_live.d.ts.map +0 -1
- package/dist/test_referral_live.js +0 -33
- package/dist/test_referral_live.js.map +0 -1
- package/dist/test_referral_piers.d.ts +0 -2
- package/dist/test_referral_piers.d.ts.map +0 -1
- package/dist/test_referral_piers.js +0 -33
- package/dist/test_referral_piers.js.map +0 -1
- package/dist/test_report_editorial_briefing.d.ts +0 -21
- package/dist/test_report_editorial_briefing.d.ts.map +0 -1
- package/dist/test_report_editorial_briefing.js +0 -356
- package/dist/test_report_editorial_briefing.js.map +0 -1
- package/dist/test_search_graph_quality.d.ts +0 -2
- package/dist/test_search_graph_quality.d.ts.map +0 -1
- package/dist/test_search_graph_quality.js +0 -493
- package/dist/test_search_graph_quality.js.map +0 -1
- package/dist/test_service_to_service_auth.d.ts +0 -2
- package/dist/test_service_to_service_auth.d.ts.map +0 -1
- package/dist/test_service_to_service_auth.js +0 -199
- package/dist/test_service_to_service_auth.js.map +0 -1
- package/dist/test_session_next_moves_telemetry.d.ts +0 -2
- package/dist/test_session_next_moves_telemetry.d.ts.map +0 -1
- package/dist/test_session_next_moves_telemetry.js +0 -100
- package/dist/test_session_next_moves_telemetry.js.map +0 -1
- package/dist/test_skills.d.ts +0 -9
- package/dist/test_skills.d.ts.map +0 -1
- package/dist/test_skills.js +0 -267
- package/dist/test_skills.js.map +0 -1
- package/dist/test_sources_live.d.ts +0 -2
- package/dist/test_sources_live.d.ts.map +0 -1
- package/dist/test_sources_live.js +0 -30
- package/dist/test_sources_live.js.map +0 -1
- package/dist/test_specialist_and_analyst_tiers.d.ts +0 -2
- package/dist/test_specialist_and_analyst_tiers.d.ts.map +0 -1
- package/dist/test_specialist_and_analyst_tiers.js +0 -155
- package/dist/test_specialist_and_analyst_tiers.js.map +0 -1
- package/dist/test_token_resolution.d.ts +0 -2
- package/dist/test_token_resolution.d.ts.map +0 -1
- package/dist/test_token_resolution.js +0 -82
- package/dist/test_token_resolution.js.map +0 -1
- package/dist/test_v2_2_visual_hardening.d.ts +0 -5
- package/dist/test_v2_2_visual_hardening.d.ts.map +0 -1
- package/dist/test_v2_2_visual_hardening.js +0 -333
- package/dist/test_v2_2_visual_hardening.js.map +0 -1
- package/dist/test_verify_deprecate_render_spec.d.ts +0 -2
- package/dist/test_verify_deprecate_render_spec.d.ts.map +0 -1
- package/dist/test_verify_deprecate_render_spec.js +0 -217
- package/dist/test_verify_deprecate_render_spec.js.map +0 -1
- package/dist/test_verify_neil_demo.d.ts +0 -2
- package/dist/test_verify_neil_demo.d.ts.map +0 -1
- package/dist/test_verify_neil_demo.js +0 -149
- package/dist/test_verify_neil_demo.js.map +0 -1
- package/dist/test_waitable_research_and_grounded_links.d.ts +0 -2
- package/dist/test_waitable_research_and_grounded_links.d.ts.map +0 -1
- package/dist/test_waitable_research_and_grounded_links.js +0 -129
- package/dist/test_waitable_research_and_grounded_links.js.map +0 -1
- package/dist/test_widget_guards.d.ts +0 -2
- package/dist/test_widget_guards.d.ts.map +0 -1
- package/dist/test_widget_guards.js +0 -229
- package/dist/test_widget_guards.js.map +0 -1
- package/dist/test_zero_retention_alerts.d.ts +0 -14
- package/dist/test_zero_retention_alerts.d.ts.map +0 -1
- package/dist/test_zero_retention_alerts.js +0 -76
- package/dist/test_zero_retention_alerts.js.map +0 -1
- package/dist/verify_discoverability.d.ts +0 -2
- package/dist/verify_discoverability.d.ts.map +0 -1
- package/dist/verify_discoverability.js +0 -53
- package/dist/verify_discoverability.js.map +0 -1
package/dist/brandTemplate.js
CHANGED
|
@@ -12,38 +12,52 @@ import { fillBrandOneLiner, fillTrendFootprintIntro, fillMarketDataIntro, replac
|
|
|
12
12
|
import { esc, FODDA_LOGO_URL } from './widgetShell.js';
|
|
13
13
|
import { getDomainGraphIds } from './catalogCache.js';
|
|
14
14
|
const PRESSURE_COLORS = {
|
|
15
|
-
'
|
|
15
|
+
'Shared category': { bg: '#E6F1FB', color: '#2E6BE5', label: 'Shared category' },
|
|
16
|
+
'Appears alongside': { bg: '#F5F0FF', color: '#663399', label: 'Appears alongside' },
|
|
16
17
|
'Adjacent signal': { bg: '#E6F1FB', color: '#2E6BE5', label: 'Adjacent signal' },
|
|
17
18
|
'Crossover mention': { bg: '#EEEDFE', color: '#7C6AB5', label: 'Crossover mention' },
|
|
18
19
|
'Related brand': { bg: '#F5F0FF', color: '#663399', label: 'Related brand' },
|
|
19
|
-
//
|
|
20
|
-
'
|
|
21
|
-
'
|
|
22
|
-
'
|
|
20
|
+
// Backward compat aliases mapped to neutral wording
|
|
21
|
+
'Direct competitor': { bg: '#E6F1FB', color: '#2E6BE5', label: 'Shared category' },
|
|
22
|
+
'Heritage challenger': { bg: '#F5F0FF', color: '#663399', label: 'Appears alongside' },
|
|
23
|
+
'Sibling challenger': { bg: '#F5F0FF', color: '#663399', label: 'Appears alongside' },
|
|
24
|
+
'Premium challenger': { bg: '#E6F1FB', color: '#2E6BE5', label: 'Shared category' },
|
|
23
25
|
'Co-creation partner': { bg: '#EAF3DE', color: '#3A8F5C', label: 'Co-creation partner' },
|
|
24
26
|
'Tech partner': { bg: '#EAF3DE', color: '#3A8F5C', label: 'Tech partner' },
|
|
25
27
|
'Culture collaborator': { bg: '#FBEAF0', color: '#C94F7A', label: 'Culture collaborator' },
|
|
26
|
-
'Crossover threat': { bg: '#EEEDFE', color: '#7C6AB5', label: 'Crossover
|
|
27
|
-
'Category shadow': { bg: '#EEEDFE', color: '#7C6AB5', label: '
|
|
28
|
+
'Crossover threat': { bg: '#EEEDFE', color: '#7C6AB5', label: 'Crossover mention' },
|
|
29
|
+
'Category shadow': { bg: '#EEEDFE', color: '#7C6AB5', label: 'Crossover mention' },
|
|
28
30
|
};
|
|
29
|
-
const DEFAULT_PRESSURE = { bg: '#
|
|
30
|
-
// Sector-aware pressure type assignment based on graph overlap
|
|
31
|
+
const DEFAULT_PRESSURE = { bg: '#F5F0FF', color: '#663399', label: 'Appears alongside' };
|
|
32
|
+
// Sector-aware pressure type assignment based on graph overlap or explicit API relationship_type
|
|
31
33
|
function guessPressureType(_index, competitor, brandGraphIds, domainGraphIds) {
|
|
34
|
+
if (competitor?.relationship_type) {
|
|
35
|
+
const rel = String(competitor.relationship_type).toLowerCase();
|
|
36
|
+
if (rel === 'competitor' || rel === 'peer')
|
|
37
|
+
return 'Shared category';
|
|
38
|
+
if (rel === 'adjacent')
|
|
39
|
+
return 'Adjacent signal';
|
|
40
|
+
if (rel === 'partner')
|
|
41
|
+
return 'Co-creation partner';
|
|
42
|
+
if (rel === 'crossover')
|
|
43
|
+
return 'Crossover mention';
|
|
44
|
+
return 'Appears alongside';
|
|
45
|
+
}
|
|
32
46
|
const compGraphArr = competitor?.graphIds || [];
|
|
33
47
|
if (compGraphArr.length > 0 && brandGraphIds && brandGraphIds.size > 0) {
|
|
34
48
|
const compGraphs = new Set(compGraphArr);
|
|
35
49
|
const sharedGraphs = [...compGraphs].filter(g => brandGraphIds.has(g));
|
|
36
50
|
if (sharedGraphs.length === 0)
|
|
37
51
|
return 'Crossover mention';
|
|
38
|
-
// Any shared DOMAIN graph =
|
|
52
|
+
// Any shared DOMAIN graph = shared category (both in retail, both in beauty, etc.)
|
|
39
53
|
const sharedDomain = domainGraphIds ? sharedGraphs.filter(g => domainGraphIds.has(g)) : [];
|
|
40
54
|
if (sharedDomain.length > 0)
|
|
41
|
-
return '
|
|
55
|
+
return 'Shared category';
|
|
42
56
|
// Shared expert graph only = adjacent signal (co-mentioned in research, not same market)
|
|
43
57
|
return 'Adjacent signal';
|
|
44
58
|
}
|
|
45
|
-
// Fallback: no graph data — conservative label
|
|
46
|
-
return '
|
|
59
|
+
// Fallback: no graph data — conservative neutral label
|
|
60
|
+
return 'Appears alongside';
|
|
47
61
|
}
|
|
48
62
|
// ---------------------------------------------------------------------------
|
|
49
63
|
// Network node orbit positions (fixed, up to 5 competitors)
|
|
@@ -165,6 +179,7 @@ function velocityClass(trend) {
|
|
|
165
179
|
case 'accelerating': return { cls: 'vc-up', label: 'rising ↑' };
|
|
166
180
|
case 'stable': return { cls: 'vc-steady', label: 'steady →' };
|
|
167
181
|
case 'decelerating': return { cls: 'vc-slow', label: 'slowing ↓' };
|
|
182
|
+
case 'untracked': return { cls: 'vc-untracked', label: 'UNTRACKED' };
|
|
168
183
|
default: return { cls: 'vc-build', label: 'building ↗' };
|
|
169
184
|
}
|
|
170
185
|
}
|
|
@@ -299,13 +314,16 @@ function formatStockPrice(priceWindow) {
|
|
|
299
314
|
export async function renderBrandWidget(profile) {
|
|
300
315
|
const brand = profile.brand || 'Brand';
|
|
301
316
|
const trends = profile.trend_footprint || [];
|
|
302
|
-
const evidence = profile.evidence_items || [];
|
|
303
|
-
const competitors = profile.competitive_context?.
|
|
317
|
+
const evidence = profile.evidence_items || profile.direct_evidence || [];
|
|
318
|
+
const competitors = (profile.competitive_context?.peers && profile.competitive_context.peers.length > 0)
|
|
319
|
+
? profile.competitive_context.peers
|
|
320
|
+
: (profile.competitive_context?.co_occurring_brands || profile.competitive_context?.co_occurring || []);
|
|
304
321
|
const crossGraph = profile.cross_graph_presence || [];
|
|
305
322
|
const supplemental = profile.supplemental_signals || {};
|
|
306
323
|
const lcDist = profile.summary?.lifecycle_distribution || {};
|
|
307
324
|
const buildingCount = (lcDist.building || 0) + (lcDist.emerging || 0);
|
|
308
325
|
const fadingCount = (lcDist.fading || 0) + (lcDist.mature || 0);
|
|
326
|
+
const isEmptyCoverage = profile.coverage?.status === 'empty' || (trends.length === 0 && evidence.length === 0);
|
|
309
327
|
// Enrich trends with lifecycle if missing
|
|
310
328
|
trends.forEach((t) => {
|
|
311
329
|
if (!t.lifecycle || t.lifecycle === 'unknown') {
|
|
@@ -340,24 +358,29 @@ export async function renderBrandWidget(profile) {
|
|
|
340
358
|
}
|
|
341
359
|
});
|
|
342
360
|
let rawVelocityTrend = 'stable';
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
361
|
+
if (isEmptyCoverage || trends.length === 0 || (profile.summary?.total_evidence_items || 0) === 0) {
|
|
362
|
+
rawVelocityTrend = 'untracked';
|
|
363
|
+
}
|
|
364
|
+
else {
|
|
365
|
+
const totalWithMomentum = accCount + bldCount + stdCount + slowCount;
|
|
366
|
+
if (totalWithMomentum > 0) {
|
|
367
|
+
if ((accCount + bldCount) > slowCount && accCount >= bldCount && accCount > 0) {
|
|
368
|
+
rawVelocityTrend = 'accelerating';
|
|
369
|
+
}
|
|
370
|
+
else if ((accCount + bldCount) > slowCount && bldCount > 0) {
|
|
371
|
+
rawVelocityTrend = 'building';
|
|
372
|
+
}
|
|
373
|
+
else if (slowCount > (accCount + bldCount) && slowCount > stdCount) {
|
|
374
|
+
rawVelocityTrend = 'decelerating';
|
|
375
|
+
}
|
|
376
|
+
else {
|
|
377
|
+
rawVelocityTrend = 'stable';
|
|
378
|
+
}
|
|
353
379
|
}
|
|
354
380
|
else {
|
|
355
|
-
rawVelocityTrend = 'stable';
|
|
381
|
+
rawVelocityTrend = profile.summary?.evidence_velocity?.trend || 'stable';
|
|
356
382
|
}
|
|
357
383
|
}
|
|
358
|
-
else {
|
|
359
|
-
rawVelocityTrend = profile.summary?.evidence_velocity?.trend || 'stable';
|
|
360
|
-
}
|
|
361
384
|
const velocity = velocityClass(rawVelocityTrend);
|
|
362
385
|
const timeline = profile.activity_timeline || [];
|
|
363
386
|
// Build set of graphIds the target brand is present in (for sector-aware competitor labels)
|
|
@@ -586,19 +609,27 @@ export async function renderBrandWidget(profile) {
|
|
|
586
609
|
const competitorListHtml = competitors.slice(0, 8).map((c, i) => {
|
|
587
610
|
const pType = guessPressureType(i, c, brandGraphIds, domainGIds);
|
|
588
611
|
const colors = PRESSURE_COLORS[pType] || DEFAULT_PRESSURE;
|
|
612
|
+
const bName = c.brand || c.name || '';
|
|
613
|
+
const count = c.co_occurrences ?? c.count ?? 1;
|
|
614
|
+
const countText = c.relationship_type === 'partner'
|
|
615
|
+
? `Partner in ${count} initiative${count !== 1 ? 's' : ''}`
|
|
616
|
+
: `Appears alongside in ${count} evidence item${count !== 1 ? 's' : ''}`;
|
|
589
617
|
return `<div class="cc">
|
|
590
618
|
<div>
|
|
591
|
-
<div class="cn">${esc(
|
|
592
|
-
<div class="cd"
|
|
619
|
+
<div class="cn">${esc(bName)}</div>
|
|
620
|
+
<div class="cd">${countText}</div>
|
|
593
621
|
</div>
|
|
594
622
|
<div class="ca">
|
|
595
623
|
<span class="pb" style="background:${colors.bg};color:${colors.color};">${colors.label}</span>
|
|
596
|
-
<button class="cv" onclick="sendPrompt('brand intelligence: ${esc(
|
|
624
|
+
<button class="cv" onclick="sendPrompt('brand intelligence: ${esc(bName)}')">Explore ↗</button>
|
|
597
625
|
</div>
|
|
598
626
|
</div>`;
|
|
599
627
|
}).join('\n');
|
|
600
628
|
// ── Compare buttons ──
|
|
601
|
-
const compareButtonsHtml = competitors.slice(0, 4).map((c) =>
|
|
629
|
+
const compareButtonsHtml = competitors.slice(0, 4).map((c) => {
|
|
630
|
+
const bName = c.brand || c.name || '';
|
|
631
|
+
return `<button class="cp" onclick="sendPrompt('brand intelligence: ${esc(bName)}')">${esc(bName)} ↗</button>`;
|
|
632
|
+
}).join('\n ');
|
|
602
633
|
// ── Google Trends ──
|
|
603
634
|
const gt = buildGoogleTrendsSVG(supplemental?.google_trends);
|
|
604
635
|
const hasGoogleTrendsData = gt.polyline !== '0,88 300,88'; // false when API returned empty time-series
|
|
@@ -620,6 +651,18 @@ export async function renderBrandWidget(profile) {
|
|
|
620
651
|
return true;
|
|
621
652
|
}).sort((a, b) => (b.reviews || 0) - (a.reviews || 0));
|
|
622
653
|
const amazonProductsHtml = products.slice(0, 4).map((p) => `<div class="ap"><div><div class="an2">${esc(p.name || p.title)}</div><div class="am2">${esc(p.price || '')}</div></div><div><div class="astar">${'★'.repeat(Math.round(p.rating || 0))}${'☆'.repeat(5 - Math.round(p.rating || 0))}</div><div class="am2">${(p.reviews || 0).toLocaleString()} reviews</div></div></div>`).join('\n');
|
|
654
|
+
const totalListings = amazon.product_count || 0;
|
|
655
|
+
const analyzed = amazon.products_analyzed || products.length || 0;
|
|
656
|
+
let amazonBasisSub = 'across listings';
|
|
657
|
+
if (analyzed > 0 && totalListings > analyzed) {
|
|
658
|
+
amazonBasisSub = `based on sample of ${analyzed} of ${totalListings.toLocaleString()} listings`;
|
|
659
|
+
}
|
|
660
|
+
else if (analyzed > 0) {
|
|
661
|
+
amazonBasisSub = `based on ${analyzed} listings`;
|
|
662
|
+
}
|
|
663
|
+
else if (totalListings > 0) {
|
|
664
|
+
amazonBasisSub = `based on ${totalListings.toLocaleString()} listings`;
|
|
665
|
+
}
|
|
623
666
|
// ── Geographic bars ──
|
|
624
667
|
const geoDist = profile.geographic_distribution || [];
|
|
625
668
|
const geoMax = Math.max(...geoDist.map((g) => g.count || 0), 1);
|
|
@@ -646,29 +689,136 @@ export async function renderBrandWidget(profile) {
|
|
|
646
689
|
if (!matchedSector) {
|
|
647
690
|
matchedSector = censusSubs.find((c) => (c.name || '').toLowerCase().includes('non-store') || (c.name || '').toLowerCase().includes('e-commerce'));
|
|
648
691
|
}
|
|
649
|
-
const totalRetail = censusSnap?.total_retail_sales_millions;
|
|
650
|
-
const totalMom = censusSnap?.month_over_month_change_pct;
|
|
692
|
+
const totalRetail = censusSnap?.headline_retail_and_food_services?.sales_millions || censusSnap?.total_retail_sales_millions || censusSnap?.latest_sales_m;
|
|
693
|
+
const totalMom = censusSnap?.headline_retail_and_food_services?.mom_change_pct ?? censusSnap?.month_over_month_change_pct ?? censusSnap?.mom_change_pct;
|
|
651
694
|
const beaLabel = matchedSector ? esc(matchedSector.name).replace(/ Stores$/, '') : 'Retail sector';
|
|
652
695
|
const beaValue = matchedSector?.sales_millions ? `$${(matchedSector.sales_millions / 1000).toFixed(1)}B` : '—';
|
|
653
696
|
const beaSub = matchedSector?.mom_change_pct != null ? `${matchedSector.mom_change_pct > 0 ? '+' : ''}${matchedSector.mom_change_pct.toFixed(1)}% MoM` : '';
|
|
654
|
-
const beaChangeLabel = '
|
|
697
|
+
const beaChangeLabel = 'US retail & food';
|
|
655
698
|
const beaChangeValue = totalRetail ? `$${(totalRetail / 1000).toFixed(0)}B` : '—';
|
|
656
699
|
const beaChangeSub = totalMom != null ? `${totalMom > 0 ? '+' : ''}${totalMom.toFixed(1)}% MoM` : '';
|
|
700
|
+
// ── Conditional section HTML builders (Honour coverage status & data boundaries) ──
|
|
701
|
+
const emptyStateCalloutHtml = isEmptyCoverage
|
|
702
|
+
? `<div class="ec" style="margin-top:1rem;padding:1.25rem;">
|
|
703
|
+
<div class="et" style="font-size:13px;font-weight:600;">Broaden your search</div>
|
|
704
|
+
<div class="ex" style="margin-top:6px;line-height:1.6;">Fodda does not currently track curated trend evidence specifically mentioning <strong>${esc(brand)}</strong>. Try exploring broader category trends in our domain graphs or consult a specialist practitioner for strategic perspective.</div>
|
|
705
|
+
</div>`
|
|
706
|
+
: '';
|
|
707
|
+
const caseStudiesSectionHtml = (!isEmptyCoverage && evidenceHtml)
|
|
708
|
+
? `<div class="sec">Case Studies</div>\n${evidenceHtml}`
|
|
709
|
+
: '';
|
|
710
|
+
const trendsSectionHtml = (!isEmptyCoverage && (trendsHtml || weakSignalsHtml))
|
|
711
|
+
? `<div class="sec">Relevant trends</div>
|
|
712
|
+
{{TREND_FOOTPRINT_INTRO}}
|
|
713
|
+
<div class="lcb">{{LIFECYCLE_BAR}}</div>
|
|
714
|
+
<div class="lcl">{{LIFECYCLE_LEGEND}}</div>
|
|
715
|
+
${trendsHtml}
|
|
716
|
+
${weakSignalsHtml}`
|
|
717
|
+
: '';
|
|
718
|
+
const competitiveSectionHtml = (!isEmptyCoverage && competitors.length > 0)
|
|
719
|
+
? `<div class="sec">Co-occurring brands</div>\n${competitorListHtml}`
|
|
720
|
+
: '';
|
|
721
|
+
const earningsSectionHtml = (!isEmptyCoverage && earningsHtml)
|
|
722
|
+
? `<div class="sl2">Quarterly Earnings & Wall Street Intelligence</div>
|
|
723
|
+
${earningsHtml}
|
|
724
|
+
<p class="note">Source: Fodda Institutional Earnings Intelligence (SEC Filings & Executive Earnings Call Transcripts).</p>`
|
|
725
|
+
: '';
|
|
726
|
+
const financialSnapshotSectionHtml = (!isEmptyCoverage && financialSnapshotHtml)
|
|
727
|
+
? `<div class="sl2">Institutional Financial Snapshot</div>
|
|
728
|
+
${financialSnapshotHtml}
|
|
729
|
+
<p class="note">Source: Institutional SEC & Market Financial Filings.</p>`
|
|
730
|
+
: '';
|
|
731
|
+
// Supplemental section gating: never render no_signal / no_brand_match or untracked brands
|
|
732
|
+
const censusHasSignal = censusRaw?.data_status !== 'no_signal' && censusRaw?.data_status !== 'no_brand_match' && (totalRetail != null || matchedSector?.sales_millions != null);
|
|
733
|
+
const censusSectionHtml = (!isEmptyCoverage && censusHasSignal)
|
|
734
|
+
? `<div class="sl2">US retail sales — Census Bureau (National market context)</div>
|
|
735
|
+
<div class="sg">
|
|
736
|
+
<div class="sk"><div class="skl">{{BEA_STAT_1_LABEL}}</div><div class="skv">{{BEA_STAT_1_VALUE}}</div><div class="sks">{{BEA_STAT_1_SUB}}</div></div>
|
|
737
|
+
<div class="sk"><div class="skl">{{BEA_STAT_2_LABEL}}</div><div class="skv">{{BEA_STAT_2_VALUE}}</div><div class="sks">{{BEA_STAT_2_SUB}}</div></div>
|
|
738
|
+
</div>
|
|
739
|
+
<p class="note">Macroeconomic context from the US Census Bureau. National retail volume, not brand-specific sales.</p>`
|
|
740
|
+
: '';
|
|
741
|
+
const gtHasSignal = supplemental?.google_trends?.data_status !== 'no_signal' && supplemental?.google_trends?.data_status !== 'no_brand_match' && hasGoogleTrendsData;
|
|
742
|
+
const gtSectionHtml = (!isEmptyCoverage && gtHasSignal) ? `<div class="sl2">${gt.description}</div>
|
|
743
|
+
<svg viewBox="0 0 300 96" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:96px;margin-bottom:4px;">
|
|
744
|
+
<defs>
|
|
745
|
+
<linearGradient id="gtg" x1="0" y1="0" x2="0" y2="1">
|
|
746
|
+
<stop offset="0%" stop-color="var(--fodda-accent, #663399)" stop-opacity=".18"/>
|
|
747
|
+
<stop offset="100%" stop-color="var(--fodda-accent, #663399)" stop-opacity="0"/>
|
|
748
|
+
</linearGradient>
|
|
749
|
+
</defs>
|
|
750
|
+
<polygon points="${gt.polygon}" fill="url(#gtg)"/>
|
|
751
|
+
<polyline points="${gt.polyline}" fill="none" stroke="var(--fodda-accent, #663399)" stroke-width="1.5" stroke-linejoin="round"/>
|
|
752
|
+
<line x1="0" y1="88" x2="300" y2="88" stroke="var(--color-border-tertiary)" stroke-width=".5"/>
|
|
753
|
+
<text x="2" y="93" font-size="7" fill="var(--color-text-secondary)" font-family="monospace">${gt.startDateLabel}</text>
|
|
754
|
+
<text x="150" y="93" font-size="7" fill="var(--color-text-secondary)" text-anchor="middle" font-family="monospace">${gt.peakLabel}</text>
|
|
755
|
+
<text x="298" y="93" font-size="7" fill="var(--color-text-secondary)" text-anchor="end" font-family="monospace">${gt.nowLabel}</text>
|
|
756
|
+
<circle cx="${gt.peakX}" cy="${gt.peakY}" r="2" fill="var(--fodda-accent, #663399)"/>
|
|
757
|
+
<circle cx="300" cy="${gt.annY}" r="2.5" fill="var(--fodda-accent, #663399)"/>
|
|
758
|
+
<text x="${gt.annX}" y="${gt.annY}" font-size="7" fill="var(--fodda-accent, #663399)" text-anchor="end" font-family="monospace">${gt.annText}</text>
|
|
759
|
+
</svg>
|
|
760
|
+
<p class="note">{{GT_CAPTION}}</p>` : '';
|
|
761
|
+
const gtComparisonSectionHtml = (!isEmptyCoverage && gtHasSignal && gt.comparisonBarsHtml) ? `<div class="sl2">Search interest — brand vs co-occurring brands</div>
|
|
762
|
+
${gt.comparisonBarsHtml}` : '';
|
|
763
|
+
const relatedQueriesSectionHtml = (!isEmptyCoverage && gtHasSignal && gt.relatedQueriesHtml) ? `<div class="sl2">Top related queries</div>
|
|
764
|
+
<div class="rq">${gt.relatedQueriesHtml}</div>
|
|
765
|
+
<p class="note">{{RELATED_QUERIES_NOTE}}</p>` : '';
|
|
766
|
+
const wikiHasSignal = rawWiki?.data_status !== 'no_signal' && rawWiki?.data_status !== 'no_brand_match' && wikiData.length > 0;
|
|
767
|
+
const wikiSectionHtml = (!isEmptyCoverage && wikiHasSignal)
|
|
768
|
+
? `<div class="sl2">Wikipedia — avg daily pageviews</div>
|
|
769
|
+
${wikiBarsHtml}
|
|
770
|
+
<p class="note">{{WIKI_NOTE}}</p>`
|
|
771
|
+
: '';
|
|
772
|
+
const amazonHasSignal = amazonRaw?.data_status !== 'no_signal' && amazonRaw?.data_status !== 'no_brand_match' && (totalListings > 0 || products.length > 0);
|
|
773
|
+
const amazonSectionHtml = (!isEmptyCoverage && amazonHasSignal)
|
|
774
|
+
? `<div class="sl2">Amazon footprint</div>
|
|
775
|
+
<div class="sg">
|
|
776
|
+
<div class="sk"><div class="skl">{{AMAZON_STAT_1_LABEL}}</div><div class="skv">{{AMAZON_STAT_1_VALUE}}</div><div class="sks">{{AMAZON_STAT_1_SUB}}</div></div>
|
|
777
|
+
<div class="sk"><div class="skl">{{AMAZON_STAT_2_LABEL}}</div><div class="skv">{{AMAZON_STAT_2_VALUE}}</div><div class="sks">{{AMAZON_STAT_2_SUB}}</div></div>
|
|
778
|
+
<div class="sk"><div class="skl">{{AMAZON_STAT_3_LABEL}}</div><div class="skv">{{AMAZON_STAT_3_VALUE}}</div><div class="sks">{{AMAZON_STAT_3_SUB}}</div></div>
|
|
779
|
+
<div class="sk"><div class="skl">{{AMAZON_STAT_4_LABEL}}</div><div class="skv">{{AMAZON_STAT_4_VALUE}}</div><div class="sks">{{AMAZON_STAT_4_SUB}}</div></div>
|
|
780
|
+
</div>
|
|
781
|
+
${amazonProductsHtml}
|
|
782
|
+
<p class="note">{{AMAZON_CAPTION}}</p>`
|
|
783
|
+
: '';
|
|
784
|
+
const geoSectionHtml = (!isEmptyCoverage && geoBarsHtml)
|
|
785
|
+
? `<div class="sl2">Geographic spread</div>
|
|
786
|
+
${geoBarsHtml}`
|
|
787
|
+
: '';
|
|
788
|
+
const marketDataItems = [
|
|
789
|
+
censusSectionHtml,
|
|
790
|
+
gtSectionHtml,
|
|
791
|
+
gtComparisonSectionHtml,
|
|
792
|
+
relatedQueriesSectionHtml,
|
|
793
|
+
wikiSectionHtml,
|
|
794
|
+
amazonSectionHtml,
|
|
795
|
+
geoSectionHtml,
|
|
796
|
+
].filter(Boolean);
|
|
797
|
+
const marketDataSectionHtml = (!isEmptyCoverage && marketDataItems.length > 0)
|
|
798
|
+
? `<div class="sec">Market data</div>
|
|
799
|
+
{{MARKET_DATA_INTRO}}
|
|
800
|
+
${marketDataItems.join('\n')}`
|
|
801
|
+
: '';
|
|
657
802
|
// ── Source pills ──
|
|
658
803
|
const graphNames = crossGraph.map((g) => g.graphName).filter(Boolean);
|
|
659
804
|
const sourcePills = [
|
|
660
805
|
...graphNames.map((n) => `<span class="gp">${esc(n)}</span>`),
|
|
661
|
-
(earningsSource === 'truth_layer' || earnings.length > 0) ? '<span class="gp">Earnings Intelligence</span>' : '',
|
|
662
|
-
(marketData && marketData.data_status !== 'no_signal') ? '<span class="gp">Financial Snapshot</span>' : '',
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
806
|
+
(!isEmptyCoverage && (earningsSource === 'truth_layer' || earnings.length > 0)) ? '<span class="gp">Earnings Intelligence</span>' : '',
|
|
807
|
+
(!isEmptyCoverage && marketData && marketData.data_status !== 'no_signal') ? '<span class="gp">Financial Snapshot</span>' : '',
|
|
808
|
+
(!isEmptyCoverage && gtHasSignal) ? '<span class="gp">Google Trends</span>' : '',
|
|
809
|
+
(!isEmptyCoverage && wikiHasSignal) ? '<span class="gp">Wikipedia</span>' : '',
|
|
810
|
+
(!isEmptyCoverage && amazonHasSignal) ? '<span class="gp">Amazon</span>' : '',
|
|
811
|
+
(!isEmptyCoverage && censusHasSignal) ? '<span class="gp">US Census</span>' : '',
|
|
667
812
|
].filter(Boolean).join('\n ');
|
|
668
813
|
// ── Export slots (data-driven labels) ──
|
|
669
|
-
const exportLabels = [
|
|
814
|
+
const exportLabels = isEmptyCoverage ? [
|
|
815
|
+
{ label: 'Category brief', desc: `Explore market dynamics in ${brand}'s category`, prompt: `Write category brief for ${brand}'s market` },
|
|
816
|
+
{ label: 'Find specialist', desc: `Consult experts covering this space`, prompt: `Find specialists who cover ${brand}'s category` },
|
|
817
|
+
{ label: 'Category signals', desc: `Emerging opportunities in this sector`, prompt: `Emerging signals for ${brand}'s category` },
|
|
818
|
+
{ label: 'Market research', desc: `Broad research across retail & consumer graphs`, prompt: `Research trends in ${brand}'s industry` },
|
|
819
|
+
] : [
|
|
670
820
|
{ label: 'Editorial brief', desc: `Strategic analysis of ${brand}'s innovation position`, prompt: `Write editorial brief for ${brand}` },
|
|
671
|
-
{ label: '
|
|
821
|
+
{ label: 'Co-occurring landscape', desc: `Analysis of brands appearing alongside ${brand}`, prompt: `Compare brands appearing alongside ${brand}` },
|
|
672
822
|
{ label: 'Weak signal forecast', desc: `Emerging opportunities on ${brand}'s horizon`, prompt: `Weak signal forecast for ${brand}` },
|
|
673
823
|
{ label: 'Steal this idea', desc: `Actionable concepts from ${brand}'s playbook`, prompt: `Steal this idea from ${brand}` },
|
|
674
824
|
];
|
|
@@ -686,38 +836,19 @@ export async function renderBrandWidget(profile) {
|
|
|
686
836
|
'TRENDS_HTML': trendsHtml,
|
|
687
837
|
'WEAK_SIGNALS_HTML': weakSignalsHtml,
|
|
688
838
|
'EVIDENCE_HTML': evidenceHtml,
|
|
689
|
-
'
|
|
690
|
-
'
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
</linearGradient>
|
|
697
|
-
</defs>
|
|
698
|
-
<polygon points="${gt.polygon}" fill="url(#gtg)"/>
|
|
699
|
-
<polyline points="${gt.polyline}" fill="none" stroke="var(--fodda-accent, #663399)" stroke-width="1.5" stroke-linejoin="round"/>
|
|
700
|
-
<line x1="0" y1="88" x2="300" y2="88" stroke="var(--color-border-tertiary)" stroke-width=".5"/>
|
|
701
|
-
<text x="2" y="93" font-size="7" fill="var(--color-text-secondary)" font-family="monospace">${gt.startDateLabel}</text>
|
|
702
|
-
<text x="150" y="93" font-size="7" fill="var(--color-text-secondary)" text-anchor="middle" font-family="monospace">${gt.peakLabel}</text>
|
|
703
|
-
<text x="298" y="93" font-size="7" fill="var(--color-text-secondary)" text-anchor="end" font-family="monospace">${gt.nowLabel}</text>
|
|
704
|
-
<circle cx="${gt.peakX}" cy="${gt.peakY}" r="2" fill="var(--fodda-accent, #663399)"/>
|
|
705
|
-
<circle cx="300" cy="${gt.annY}" r="2.5" fill="var(--fodda-accent, #663399)"/>
|
|
706
|
-
<text x="${gt.annX}" y="${gt.annY}" font-size="7" fill="var(--fodda-accent, #663399)" text-anchor="end" font-family="monospace">${gt.annText}</text>
|
|
707
|
-
</svg>
|
|
708
|
-
<p class="note">{{GT_CAPTION}}</p>` : '',
|
|
709
|
-
'GT_COMPARISON_SECTION_HTML': gt.comparisonBarsHtml ? `<div class="sl2">Search interest — brand vs competitors</div>
|
|
710
|
-
${gt.comparisonBarsHtml}` : '',
|
|
711
|
-
'RELATED_QUERIES_SECTION_HTML': gt.relatedQueriesHtml ? `<div class="sl2">Top related queries</div>
|
|
712
|
-
<div class="rq">${gt.relatedQueriesHtml}</div>
|
|
713
|
-
<p class="note">{{RELATED_QUERIES_NOTE}}</p>` : '',
|
|
714
|
-
'WIKI_BARS_HTML': wikiBarsHtml,
|
|
839
|
+
'EMPTY_STATE_CALLOUT_HTML': emptyStateCalloutHtml,
|
|
840
|
+
'CASE_STUDIES_SECTION_HTML': caseStudiesSectionHtml,
|
|
841
|
+
'TRENDS_SECTION_HTML': trendsSectionHtml,
|
|
842
|
+
'COMPETITIVE_SECTION_HTML': competitiveSectionHtml,
|
|
843
|
+
'MARKET_DATA_SECTION_HTML': marketDataSectionHtml,
|
|
844
|
+
'EARNINGS_SECTION_HTML': earningsSectionHtml,
|
|
845
|
+
'FINANCIAL_SNAPSHOT_SECTION_HTML': financialSnapshotSectionHtml,
|
|
715
846
|
'AMAZON_STAT_1_LABEL': 'Listings',
|
|
716
847
|
'AMAZON_STAT_1_VALUE': (amazon.product_count || 0).toLocaleString(),
|
|
717
848
|
'AMAZON_STAT_1_SUB': 'total listings',
|
|
718
849
|
'AMAZON_STAT_2_LABEL': 'Median price',
|
|
719
850
|
'AMAZON_STAT_2_VALUE': amazon?.price_range?.median || amazon?.median_price || '—',
|
|
720
|
-
'AMAZON_STAT_2_SUB':
|
|
851
|
+
'AMAZON_STAT_2_SUB': amazonBasisSub,
|
|
721
852
|
'AMAZON_STAT_3_LABEL': 'Avg rating',
|
|
722
853
|
'AMAZON_STAT_3_VALUE': String(amazon?.average_rating || '—'),
|
|
723
854
|
'AMAZON_STAT_3_SUB': 'product average',
|
|
@@ -725,7 +856,7 @@ export async function renderBrandWidget(profile) {
|
|
|
725
856
|
'AMAZON_STAT_4_VALUE': products[0] ? (products[0].reviews || 0).toLocaleString() : '—',
|
|
726
857
|
'AMAZON_STAT_4_SUB': products[0] ? esc(products[0].name || products[0].title || '') : '',
|
|
727
858
|
'AMAZON_PRODUCTS_HTML': amazonProductsHtml,
|
|
728
|
-
'AMAZON_CAPTION': 'Snapshot only. Source: Amazon.',
|
|
859
|
+
'AMAZON_CAPTION': 'Snapshot sample only. Not a full catalogue census. Source: Amazon.',
|
|
729
860
|
'BEA_STAT_1_LABEL': beaLabel,
|
|
730
861
|
'BEA_STAT_1_VALUE': beaValue,
|
|
731
862
|
'BEA_STAT_1_SUB': beaSub,
|
|
@@ -734,12 +865,6 @@ export async function renderBrandWidget(profile) {
|
|
|
734
865
|
'BEA_STAT_2_SUB': beaChangeSub,
|
|
735
866
|
'GEO_SECTION_HTML': geoBarsHtml ? `<div class="sl2">Geographic spread</div>
|
|
736
867
|
${geoBarsHtml}` : '',
|
|
737
|
-
'EARNINGS_SECTION_HTML': earningsHtml ? `<div class="sl2">Quarterly Earnings & Wall Street Intelligence</div>
|
|
738
|
-
${earningsHtml}
|
|
739
|
-
<p class="note">Source: Fodda Institutional Earnings Intelligence (SEC Filings & Executive Earnings Call Transcripts).</p>` : '',
|
|
740
|
-
'FINANCIAL_SNAPSHOT_SECTION_HTML': financialSnapshotHtml ? `<div class="sl2">Institutional Financial Snapshot</div>
|
|
741
|
-
${financialSnapshotHtml}
|
|
742
|
-
<p class="note">Source: Institutional SEC & Market Financial Filings.</p>` : '',
|
|
743
868
|
'SOURCE_PILLS_HTML': sourcePills,
|
|
744
869
|
'EXPORT_1_LABEL': exportLabels[0].label,
|
|
745
870
|
'EXPORT_1_DESC': exportLabels[0].desc,
|
|
@@ -758,11 +883,16 @@ export async function renderBrandWidget(profile) {
|
|
|
758
883
|
const fills = { ...slotFills };
|
|
759
884
|
// ── Parallel Gemini fills: one-liner + section intros ──
|
|
760
885
|
const [oneLiner, trendIntro, marketIntro] = await Promise.allSettled([
|
|
761
|
-
fillBrandOneLiner(brand, trends, velocity.label, competitors.slice(0, 3).map((c) => c.brand)),
|
|
886
|
+
fillBrandOneLiner(brand, trends, velocity.label, competitors.slice(0, 3).map((c) => c.brand || c.name || '')),
|
|
762
887
|
fillTrendFootprintIntro(brand, trends, profile.summary?.lifecycle_distribution || {}),
|
|
763
888
|
fillMarketDataIntro(brand, supplemental),
|
|
764
889
|
]);
|
|
765
|
-
|
|
890
|
+
if (isEmptyCoverage || trends.length === 0 || evidence.length === 0) {
|
|
891
|
+
fills['ONE_LINER'] = `Fodda has no curated trend evidence mentioning ${brand} yet.`;
|
|
892
|
+
}
|
|
893
|
+
else {
|
|
894
|
+
fills['ONE_LINER'] = oneLiner.status === 'fulfilled' ? oneLiner.value : `${brand} is ${velocity.label} across ${trends.length} trend${trends.length !== 1 ? 's' : ''}.`;
|
|
895
|
+
}
|
|
766
896
|
const trendIntroText = trendIntro.status === 'fulfilled' ? trendIntro.value : '';
|
|
767
897
|
fills['TREND_FOOTPRINT_INTRO'] = trendIntroText ? `<p class="si">${esc(trendIntroText)}</p>` : '';
|
|
768
898
|
const marketIntroText = marketIntro.status === 'fulfilled' ? marketIntro.value : '';
|
|
@@ -775,9 +905,16 @@ export async function renderBrandWidget(profile) {
|
|
|
775
905
|
fills['RELATED_QUERIES_NOTE'] = supplemental?.google_trends?.related_queries?.length ? `Top related searches when people look for ${brand}.` : '';
|
|
776
906
|
fills['WIKI_NOTE'] = wikiData.length > 0 ? `Daily Wikipedia pageviews for ${brand}-related articles. Higher = more cultural attention.` : '';
|
|
777
907
|
const safeBrandName = esc(brand).replace(/'/g, "\\'");
|
|
778
|
-
const
|
|
779
|
-
|
|
780
|
-
|
|
908
|
+
const comp0 = competitors[0]?.brand || competitors[0]?.name;
|
|
909
|
+
const safeCompName = comp0 ? esc(comp0).replace(/'/g, "\\'") : 'top rival';
|
|
910
|
+
fills['SUGGESTED_NEXT_HTML'] = isEmptyCoverage
|
|
911
|
+
? `<div style="display:flex;flex-wrap:wrap;gap:6px;">
|
|
912
|
+
<button class="btn-out" onclick="sendPrompt('Explore broader trends in ${safeBrandName}\\'s category')">Category trends</button>
|
|
913
|
+
<button class="btn-out" onclick="sendPrompt('Find domain specialists who cover ${safeBrandName}\\'s space')">Find expert</button>
|
|
914
|
+
<button class="btn-out" onclick="sendPrompt('Search market research on ${safeBrandName}')">Market research</button>
|
|
915
|
+
</div>`
|
|
916
|
+
: `<div style="display:flex;flex-wrap:wrap;gap:6px;">
|
|
917
|
+
<button class="btn-out" onclick="sendPrompt('What are other brands alongside ${safeBrandName} doing differently?')">Category landscape</button>
|
|
781
918
|
<button class="btn-out" onclick="sendPrompt('Show me the evidence behind the strongest trend for ${safeBrandName}')">Deep dive</button>
|
|
782
919
|
<button class="btn-out" onclick="sendPrompt('Compare ${safeBrandName} vs ${safeCompName}')">Head-to-head</button>
|
|
783
920
|
</div>`;
|
|
@@ -846,6 +983,7 @@ export const TEMPLATE = `
|
|
|
846
983
|
.vc-build{color:var(--color-text-info);}
|
|
847
984
|
.vc-steady{color:var(--color-text-secondary);}
|
|
848
985
|
.vc-slow{color:var(--color-text-warning);}
|
|
986
|
+
.vc-untracked{color:var(--color-text-secondary);opacity:0.7;}
|
|
849
987
|
.pv{font-size:14px;font-family:Georgia,serif;font-style:italic;font-weight:400;line-height:1.65;padding:14px 16px;background:var(--pl);border:1px solid var(--p);border-left-width:3px;border-radius:4px;margin-bottom:1.25rem;color:var(--color-text-primary);}
|
|
850
988
|
.lcb{display:flex;height:8px;border-radius:4px;overflow:hidden;margin-bottom:.75rem;gap:2px;}
|
|
851
989
|
.lcl{display:flex;gap:12px;margin-bottom:1.25rem;flex-wrap:wrap;}
|
|
@@ -940,48 +1078,18 @@ export const TEMPLATE = `
|
|
|
940
1078
|
|
|
941
1079
|
<div class="pv">{{ONE_LINER}}</div>
|
|
942
1080
|
|
|
943
|
-
|
|
944
|
-
|
|
1081
|
+
{{EMPTY_STATE_CALLOUT_HTML}}
|
|
1082
|
+
|
|
1083
|
+
{{CASE_STUDIES_SECTION_HTML}}
|
|
945
1084
|
|
|
946
1085
|
{{EARNINGS_SECTION_HTML}}
|
|
947
1086
|
{{FINANCIAL_SNAPSHOT_SECTION_HTML}}
|
|
948
1087
|
|
|
949
|
-
|
|
950
|
-
{{TREND_FOOTPRINT_INTRO}}
|
|
951
|
-
<div class="lcb">{{LIFECYCLE_BAR}}</div>
|
|
952
|
-
<div class="lcl">{{LIFECYCLE_LEGEND}}</div>
|
|
953
|
-
{{TRENDS_HTML}}
|
|
954
|
-
{{WEAK_SIGNALS_HTML}}
|
|
1088
|
+
{{TRENDS_SECTION_HTML}}
|
|
955
1089
|
|
|
956
1090
|
{{COMPETITIVE_SECTION_HTML}}
|
|
957
1091
|
|
|
958
|
-
|
|
959
|
-
{{MARKET_DATA_INTRO}}
|
|
960
|
-
<div class="sl2">US retail sales — Census Bureau</div>
|
|
961
|
-
<div class="sg">
|
|
962
|
-
<div class="sk"><div class="skl">{{BEA_STAT_1_LABEL}}</div><div class="skv">{{BEA_STAT_1_VALUE}}</div><div class="sks">{{BEA_STAT_1_SUB}}</div></div>
|
|
963
|
-
<div class="sk"><div class="skl">{{BEA_STAT_2_LABEL}}</div><div class="skv">{{BEA_STAT_2_VALUE}}</div><div class="sks">{{BEA_STAT_2_SUB}}</div></div>
|
|
964
|
-
</div>
|
|
965
|
-
|
|
966
|
-
{{GT_SECTION_HTML}}
|
|
967
|
-
{{GT_COMPARISON_SECTION_HTML}}
|
|
968
|
-
{{RELATED_QUERIES_SECTION_HTML}}
|
|
969
|
-
|
|
970
|
-
<div class="sl2">Wikipedia — avg daily pageviews</div>
|
|
971
|
-
{{WIKI_BARS_HTML}}
|
|
972
|
-
<p class="note">{{WIKI_NOTE}}</p>
|
|
973
|
-
|
|
974
|
-
<div class="sl2">Amazon footprint</div>
|
|
975
|
-
<div class="sg">
|
|
976
|
-
<div class="sk"><div class="skl">{{AMAZON_STAT_1_LABEL}}</div><div class="skv">{{AMAZON_STAT_1_VALUE}}</div><div class="sks">{{AMAZON_STAT_1_SUB}}</div></div>
|
|
977
|
-
<div class="sk"><div class="skl">{{AMAZON_STAT_2_LABEL}}</div><div class="skv">{{AMAZON_STAT_2_VALUE}}</div><div class="sks">{{AMAZON_STAT_2_SUB}}</div></div>
|
|
978
|
-
<div class="sk"><div class="skl">{{AMAZON_STAT_3_LABEL}}</div><div class="skv">{{AMAZON_STAT_3_VALUE}}</div><div class="sks">{{AMAZON_STAT_3_SUB}}</div></div>
|
|
979
|
-
<div class="sk"><div class="skl">{{AMAZON_STAT_4_LABEL}}</div><div class="skv">{{AMAZON_STAT_4_VALUE}}</div><div class="sks">{{AMAZON_STAT_4_SUB}}</div></div>
|
|
980
|
-
</div>
|
|
981
|
-
{{AMAZON_PRODUCTS_HTML}}
|
|
982
|
-
<p class="note">{{AMAZON_CAPTION}}</p>
|
|
983
|
-
|
|
984
|
-
{{GEO_SECTION_HTML}}
|
|
1092
|
+
{{MARKET_DATA_SECTION_HTML}}
|
|
985
1093
|
|
|
986
1094
|
<div class="sec">Explore further</div>
|
|
987
1095
|
{{SUGGESTED_NEXT_HTML}}
|