getzero11 commited on
Commit
2118cce
·
verified ·
1 Parent(s): d96ab32

Update src/marketResearchAgent.js

Browse files
Files changed (1) hide show
  1. src/marketResearchAgent.js +171 -81
src/marketResearchAgent.js CHANGED
@@ -1,29 +1,37 @@
1
- import crypto from "crypto";
2
- import { callLLM } from "./llm/callLLM.js";
3
-
4
- /* =========================================================
5
- PUBLIC ENTRY
6
- ========================================================= */
7
-
8
- export async function marketResearchAgent({ input, provider, model }) {
9
- if (!input?.keyword) {
10
- throw new Error("keyword is required");
11
- }
12
-
13
- const marketTitle = normalizeKeyword(input.keyword);
14
- const normalizedTitle = `Global ${marketTitle} Market and Forecast 2026–2033`;
15
-
 
 
 
 
 
 
 
 
16
  /* =======================================================
17
  1. MARKET SIZE WILL COME FROM LLM ANALYSIS
18
  ======================================================= */
19
 
20
  // Market size data will be provided by LLM analysis
21
  // No random generation - all data comes from AI analysis
22
-
23
- /* =======================================================
24
- 2. SINGLE AI RESEARCH PROMPT (MULTI-AXIS)
25
- ======================================================= */
26
-
27
  const researchPrompt = `
28
  You are a seasoned healthcare market research analyst. Analyze the global market for: "${marketTitle}"
29
 
@@ -90,23 +98,31 @@ RULES:
90
  5. Include at least 5 companies
91
  6. JSON only, no markdown
92
  `;
93
-
94
  let ai;
95
 
96
  try {
97
  const raw = await callLLM({ provider, model, prompt: researchPrompt });
98
  ai = extractJsonObject(raw);
99
  validateAI(ai);
 
100
  console.log('OpenClaw: LLM analysis successful');
101
  } catch (error) {
 
 
 
 
102
  console.log('OpenClaw: LLM failed:', error.message);
103
  ai = generateFallbackData(marketTitle);
104
  }
105
-
106
- /* =======================================================
107
- 3. DASHBOARD VIEW (INFOGRAPHIC-READY)
108
- ======================================================= */
109
-
 
 
 
110
  const dashboard_view = {
111
  marketTitle: ai?.marketTitle || `Global ${marketTitle} Market`,
112
 
@@ -124,6 +140,9 @@ RULES:
124
  ],
125
 
126
  marketSegments: ai?.marketSegments || [],
 
 
 
127
 
128
  drivers: (ai?.marketDrivers || []).map(d => ({
129
  driver: d,
@@ -136,14 +155,23 @@ RULES:
136
  company: c.company,
137
  share: c.player_marketShare_2025
138
  })),
 
 
 
 
 
 
 
 
 
139
 
140
  citation: "AI Market Analysis"
141
  };
142
-
143
- /* =======================================================
144
- 4. REPORT VIEW (PDF-READY)
145
- ======================================================= */
146
-
147
  const report_view = {
148
  marketTitle: ai?.marketTitle || marketTitle,
149
 
@@ -168,43 +196,44 @@ RULES:
168
 
169
  competitiveLandscape: ai?.competitiveLandscape || []
170
  };
171
-
172
- /* =======================================================
173
- 5. FINAL RESPONSE
174
- ======================================================= */
175
-
176
- return {
177
- meta: {
178
- job_id: input.job_id || `job_${crypto.randomUUID()}`,
179
- keyword: marketTitle,
180
- normalized_title: normalizedTitle,
181
- status: "completed",
182
- timestamp: new Date().toISOString(),
183
- provider_used: provider || "auto",
184
- model_used: model || "auto",
185
- confidence: "high"
186
- },
187
- dashboard_view,
188
- report_view
189
- };
190
- }
191
-
192
- /* =========================================================
193
- HELPERS & VALIDATION
194
- ========================================================= */
195
-
196
- function extractJsonObject(text) {
197
- if (!text) return null;
198
- const start = text.indexOf("{");
199
- const end = text.lastIndexOf("}");
200
- if (start === -1 || end === -1 || end <= start) return null;
201
- try {
202
- return JSON.parse(text.slice(start, end + 1));
203
- } catch {
204
- return null;
205
- }
206
- }
207
-
 
208
  function validateAI(obj) {
209
  if (!obj || typeof obj !== "object") {
210
  throw new Error("Invalid AI output - not an object");
@@ -220,19 +249,19 @@ function validateAI(obj) {
220
  }
221
  }
222
  }
223
-
224
- function normalizeKeyword(k) {
225
- return String(k).replace(/market/gi, "").trim();
226
- }
227
-
228
- function round(n) {
229
- return Math.round(n * 100) / 100;
230
- }
231
-
232
- function randomFloat(min, max) {
233
- return round(min + Math.random() * (max - min));
234
- }
235
-
236
  function randomInt(min, max) {
237
  return Math.floor(min + Math.random() * (max - min + 1));
238
  }
@@ -282,3 +311,64 @@ function generateFallbackData(marketTitle) {
282
  strategicRecommendations: ["Invest in R&D", "Expand to emerging markets", "Focus on digital transformation"]
283
  };
284
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import crypto from "crypto";
2
+ import { callLLM } from "./llm/callLLM.js";
3
+ import { validateMarketData, ValidationError, SCHEMA_VERSION } from "./schema/marketData.schema.js";
4
+ import { createLogger } from "./utils/errorLogger.js";
5
+
6
+ // Task 8.2: Add error logging to OpenClaw Agent
7
+ const logger = createLogger('OpenClaw Agent');
8
+
9
+ /* =========================================================
10
+ PUBLIC ENTRY
11
+ ========================================================= */
12
+
13
+ export async function marketResearchAgent({ input, provider, model }) {
14
+ if (!input?.keyword) {
15
+ logger.log('Missing keyword in input', { input });
16
+ throw new Error("keyword is required");
17
+ }
18
+
19
+ logger.info('Starting market research', { keyword: input.keyword, provider, model });
20
+
21
+ const marketTitle = normalizeKeyword(input.keyword);
22
+ const normalizedTitle = `Global ${marketTitle} Market and Forecast 2026–2033`;
23
+
24
  /* =======================================================
25
  1. MARKET SIZE WILL COME FROM LLM ANALYSIS
26
  ======================================================= */
27
 
28
  // Market size data will be provided by LLM analysis
29
  // No random generation - all data comes from AI analysis
30
+
31
+ /* =======================================================
32
+ 2. SINGLE AI RESEARCH PROMPT (MULTI-AXIS)
33
+ ======================================================= */
34
+
35
  const researchPrompt = `
36
  You are a seasoned healthcare market research analyst. Analyze the global market for: "${marketTitle}"
37
 
 
98
  5. Include at least 5 companies
99
  6. JSON only, no markdown
100
  `;
101
+
102
  let ai;
103
 
104
  try {
105
  const raw = await callLLM({ provider, model, prompt: researchPrompt });
106
  ai = extractJsonObject(raw);
107
  validateAI(ai);
108
+ logger.info('LLM analysis successful', { marketTitle });
109
  console.log('OpenClaw: LLM analysis successful');
110
  } catch (error) {
111
+ logger.warn('LLM analysis failed, using fallback data', {
112
+ error: error.message,
113
+ marketTitle
114
+ });
115
  console.log('OpenClaw: LLM failed:', error.message);
116
  ai = generateFallbackData(marketTitle);
117
  }
118
+
119
+ // Ensure all required data is present
120
+ ai = ensureCompleteData(ai, marketTitle);
121
+
122
+ /* =======================================================
123
+ 3. DASHBOARD VIEW (INFOGRAPHIC-READY)
124
+ ======================================================= */
125
+
126
  const dashboard_view = {
127
  marketTitle: ai?.marketTitle || `Global ${marketTitle} Market`,
128
 
 
140
  ],
141
 
142
  marketSegments: ai?.marketSegments || [],
143
+
144
+ // Add regional data for dashboard
145
+ regional: generateRegionalData(ai),
146
 
147
  drivers: (ai?.marketDrivers || []).map(d => ({
148
  driver: d,
 
155
  company: c.company,
156
  share: c.player_marketShare_2025
157
  })),
158
+
159
+ // Add segments data for dashboard charts
160
+ segments: (ai?.marketSegments || []).map(seg => ({
161
+ segment: seg.segmentName,
162
+ marketSize: calculateSegmentSize(seg, ai?.currentYear_2025 || 0),
163
+ growthRate: seg.segmentName_cagr_Forecast || 0,
164
+ marketShare: seg.subSegments?.[0]?.segment_marketShare_2025 || 0,
165
+ subSegments: seg.subSegments || []
166
+ })),
167
 
168
  citation: "AI Market Analysis"
169
  };
170
+
171
+ /* =======================================================
172
+ 4. REPORT VIEW (PDF-READY)
173
+ ======================================================= */
174
+
175
  const report_view = {
176
  marketTitle: ai?.marketTitle || marketTitle,
177
 
 
196
 
197
  competitiveLandscape: ai?.competitiveLandscape || []
198
  };
199
+
200
+ /* =======================================================
201
+ 5. FINAL RESPONSE
202
+ ======================================================= */
203
+
204
+ return {
205
+ meta: {
206
+ job_id: input.job_id || `job_${crypto.randomUUID()}`,
207
+ keyword: marketTitle,
208
+ normalized_title: normalizedTitle,
209
+ status: "completed",
210
+ timestamp: new Date().toISOString(),
211
+ provider_used: provider || "auto",
212
+ model_used: model || "auto",
213
+ confidence: "high",
214
+ schemaVersion: SCHEMA_VERSION // Task 9.2: Include schema version
215
+ },
216
+ dashboard_view,
217
+ report_view
218
+ };
219
+ }
220
+
221
+ /* =========================================================
222
+ HELPERS & VALIDATION
223
+ ========================================================= */
224
+
225
+ function extractJsonObject(text) {
226
+ if (!text) return null;
227
+ const start = text.indexOf("{");
228
+ const end = text.lastIndexOf("}");
229
+ if (start === -1 || end === -1 || end <= start) return null;
230
+ try {
231
+ return JSON.parse(text.slice(start, end + 1));
232
+ } catch {
233
+ return null;
234
+ }
235
+ }
236
+
237
  function validateAI(obj) {
238
  if (!obj || typeof obj !== "object") {
239
  throw new Error("Invalid AI output - not an object");
 
249
  }
250
  }
251
  }
252
+
253
+ function normalizeKeyword(k) {
254
+ return String(k).replace(/market/gi, "").trim();
255
+ }
256
+
257
+ function round(n) {
258
+ return Math.round(n * 100) / 100;
259
+ }
260
+
261
+ function randomFloat(min, max) {
262
+ return round(min + Math.random() * (max - min));
263
+ }
264
+
265
  function randomInt(min, max) {
266
  return Math.floor(min + Math.random() * (max - min + 1));
267
  }
 
311
  strategicRecommendations: ["Invest in R&D", "Expand to emerging markets", "Focus on digital transformation"]
312
  };
313
  }
314
+
315
+ /**
316
+ * Ensure all required data fields are present
317
+ */
318
+ function ensureCompleteData(data, marketTitle) {
319
+ // Ensure marketSegments have subSegments arrays
320
+ if (Array.isArray(data.marketSegments)) {
321
+ data.marketSegments = data.marketSegments.map(segment => ({
322
+ ...segment,
323
+ subSegments: Array.isArray(segment.subSegments) ? segment.subSegments : []
324
+ }));
325
+ }
326
+
327
+ // Ensure minimum required fields
328
+ return {
329
+ ...data,
330
+ marketTitle: data.marketTitle || `Global ${marketTitle} Market`,
331
+ executiveOverview: data.executiveOverview || `Market analysis for ${marketTitle}.`,
332
+ marketSegments: data.marketSegments || [],
333
+ marketDrivers: data.marketDrivers || [],
334
+ competitiveLandscape: data.competitiveLandscape || []
335
+ };
336
+ }
337
+
338
+ /**
339
+ * Generate regional market data
340
+ */
341
+ function generateRegionalData(aiData) {
342
+ const totalMarket = aiData?.currentYear_2025 || 0;
343
+
344
+ // Default regional distribution
345
+ const regions = [
346
+ { region: 'North America', percentage: 35 },
347
+ { region: 'Europe', percentage: 28 },
348
+ { region: 'Asia Pacific', percentage: 25 },
349
+ { region: 'Latin America', percentage: 7 },
350
+ { region: 'Middle East & Africa', percentage: 5 }
351
+ ];
352
+
353
+ return regions.map(r => ({
354
+ region: r.region,
355
+ share: r.percentage,
356
+ marketSize: round((totalMarket * r.percentage) / 100),
357
+ growthRate: aiData?.global_cagr_Forecast || 0
358
+ }));
359
+ }
360
+
361
+ /**
362
+ * Calculate segment market size
363
+ */
364
+ function calculateSegmentSize(segment, totalMarket) {
365
+ if (!segment.subSegments || segment.subSegments.length === 0) {
366
+ return round(totalMarket * 0.2); // Default 20% if no data
367
+ }
368
+
369
+ const avgShare = segment.subSegments.reduce((sum, sub) => {
370
+ return sum + (sub.segment_marketShare_2025 || 0);
371
+ }, 0) / segment.subSegments.length;
372
+
373
+ return round((totalMarket * avgShare) / 100);
374
+ }