File size: 20,479 Bytes
e375f30
25fe4a1
e375f30
25fe4a1
e375f30
 
 
 
 
 
25fe4a1
 
e375f30
 
 
 
 
7ce979b
e375f30
7ce979b
e375f30
8e4faae
1f1edd2
 
 
8e4faae
 
e375f30
 
7ce979b
e375f30
1f1edd2
 
7ce979b
8e4faae
 
04355f4
 
e375f30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7ce979b
e375f30
 
 
 
 
 
 
7ce979b
 
 
 
e375f30
7ce979b
e375f30
7ce979b
e375f30
 
 
 
 
 
 
7ce979b
e375f30
 
 
 
 
 
 
 
 
 
 
 
7ce979b
c440ffa
e375f30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
291850b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e375f30
 
 
291850b
aba2af9
e375f30
 
 
 
7ce979b
291850b
e375f30
291850b
 
 
 
 
e375f30
 
 
 
 
 
 
291850b
e375f30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25fe4a1
 
 
 
e375f30
25fe4a1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e375f30
 
 
 
 
25fe4a1
 
e375f30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25fe4a1
e375f30
 
 
 
 
 
7ce979b
 
 
e375f30
 
 
25fe4a1
 
 
 
 
e375f30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
291850b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e375f30
 
 
 
 
 
25fe4a1
e375f30
291850b
 
 
 
 
 
 
 
 
25fe4a1
e375f30
25fe4a1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e375f30
25fe4a1
 
e375f30
 
 
25fe4a1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e375f30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
291850b
 
 
 
 
e375f30
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25fe4a1
 
 
e375f30
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
/**
 * 并行分析器 - 6个Agent同时工作
 * 实现渐进式SSE推送,用户感知到的等待时间 = 最快Agent的返回时间
 * K线分为过去(出生到今年)和未来(今年到100岁)两个并行请求,提升生成速度
 */
import fetch from 'node-fetch';
import { nanoid } from 'nanoid';
import {
  AGENT_PROMPTS,
  AGENT_CORE_PROMPT,
  AGENT_KLINE_PAST_PROMPT,
  AGENT_KLINE_FUTURE_PROMPT,
  AGENT_CAREER_PROMPT,
  AGENT_MARRIAGE_PROMPT,
  AGENT_CRYPTO_PROMPT,
} from './agentPrompts.js';
import { generateFallbackKLine } from './baziCalculator.js';
import { buildApiRequest, parseApiResponse } from './apiConfig.js';

// 为不同Agent分配最适合的模型
const AGENT_MODEL_ASSIGNMENT = {
  core: 'grok-4-1-fast-reasoning',       // 核心命理 - 逻辑推理强
  kline_past: 'gpt-5-mini',     // 过去K线 - 数据结构化强
  kline_future: 'gpt-5-mini',   // 未来K线 - 数据结构化强
  career: 'gpt-5-mini',                  // 事业财富 - 综合能力
  marriage: 'grok-4-1-fast-non-reasoning',                     // 婚姻健康 - 快速响应
  crypto: 'grok-4-0709',                       // 币圈分析 - 币圈知识丰富
};

// 备用模型列表
const FALLBACK_MODELS = [
  'gpt-5-mini',
  'gpt-4.1',
  'gpt-4o',
  'grok-4-0709',
  'grok-4-1-fast-reasoning',
  'claude-sonnet-4-5',
  'claude-3-5-haiku-20241022',
];

/**
 * 发送SSE事件
 */
export const sendSSE = (res, event, data) => {
  if (!res.writableEnded) {
    res.write(`event: ${event}\n`);
    res.write(`data: ${JSON.stringify(data)}\n\n`);
  }
};

/**
 * 单个Agent请求
 */
const makeAgentRequest = async (agentType, model, systemPrompt, userPrompt, timeoutMs = 60000) => {
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), timeoutMs);

  try {
    console.log(`[Agent:${agentType}] 使用模型 ${model} 开始请求...`);
    const startTime = Date.now();

    // 使用新的 API 配置系统
    const apiRequest = buildApiRequest(model, systemPrompt, userPrompt, 0.6);

    const response = await fetch(apiRequest.url, {
      method: 'POST',
      headers: apiRequest.headers,
      signal: controller.signal,
      body: JSON.stringify(apiRequest.body),
    });

    clearTimeout(timeoutId);
    const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);

    if (!response.ok) {
      const errText = await response.text();
      console.warn(`[Agent:${agentType}] 请求失败 (${elapsed}s): ${response.status} - ${errText.substring(0, 200)}`);
      return { success: false, agentType, error: `HTTP ${response.status}`, elapsed };
    }

    const responseText = await response.text();
    let jsonResult;
    try {
      jsonResult = JSON.parse(responseText);
    } catch (e) {
      console.warn(`[Agent:${agentType}] JSON解析失败 (${elapsed}s)`);
      return { success: false, agentType, error: 'INVALID_API_RESPONSE', elapsed };
    }

    // 使用新的响应解析系统
    let content = parseApiResponse(jsonResult, model);
    if (!content) {
      return { success: false, agentType, error: 'EMPTY_RESPONSE', elapsed };
    }

    // 清理内容
    content = content.trim();
    content = content.replace(/<think>[\s\S]*?<\/think>/gi, '').trim();
    content = content.replace(/^[\s\S]*?(?=\{)/m, '');
    if (content.startsWith('```json')) content = content.slice(7);
    else if (content.startsWith('```')) content = content.slice(3);
    if (content.endsWith('```')) content = content.slice(0, -3);
    content = content.trim();

    const jsonStart = content.indexOf('{');
    const jsonEnd = content.lastIndexOf('}');
    if (jsonStart !== -1 && jsonEnd !== -1 && jsonEnd > jsonStart) {
      content = content.slice(jsonStart, jsonEnd + 1);
    }

    let data;
    try {
      data = JSON.parse(content);
    } catch (parseErr) {
      console.warn(`[Agent:${agentType}] 内容JSON解析失败 (${elapsed}s): ${content.substring(0, 100)}`);
      return { success: false, agentType, error: 'INVALID_JSON_FORMAT', elapsed };
    }

    console.log(`[Agent:${agentType}] ✓ 成功 (${elapsed}s)`);
    return { success: true, agentType, data, elapsed, model };

  } catch (error) {
    clearTimeout(timeoutId);
    if (error.name === 'AbortError') {
      console.warn(`[Agent:${agentType}] 请求超时`);
      return { success: false, agentType, error: 'TIMEOUT' };
    }
    console.warn(`[Agent:${agentType}] 请求异常: ${error.message}`);
    return { success: false, agentType, error: error.message };
  }
};

/**
 * 验证Agent返回数据是否完整
 */
const validateAgentResponse = (agentType, data) => {
  if (!data || typeof data !== 'object') return false;

  const requiredFields = {
    core: ['summary', 'personality'],
    career: ['industry', 'wealth'],
    marriage: ['marriage', 'health'],
    crypto: ['crypto'],
    kline_past: ['chartPoints'],
    kline_future: ['chartPoints'],
  };

  const fields = requiredFields[agentType] || [];
  for (const field of fields) {
    if (!data[field] || (typeof data[field] === 'string' && data[field].trim().length < 10)) {
      console.warn(`[Agent:${agentType}] 字段 ${field} 缺失或内容太短`);
      return false;
    }
  }
  return true;
};

/**
 * 带重试的Agent请求
 */
const makeAgentRequestWithRetry = async (agentType, apiBaseUrl, apiKey, systemPrompt, userPrompt, maxRetries = 2) => {
  const primaryModel = AGENT_MODEL_ASSIGNMENT[agentType] || 'gemini-1.5-pro';
  const modelsToTry = [primaryModel, ...FALLBACK_MODELS.filter(m => m !== primaryModel)];

  for (const model of modelsToTry) {
    for (let attempt = 1; attempt <= maxRetries; attempt++) {
      const result = await makeAgentRequest(agentType, model, systemPrompt, userPrompt);

      if (result.success) {
        // 验证返回数据是否完整
        if (validateAgentResponse(agentType, result.data)) {
          return result;
        }
        console.warn(`[Agent:${agentType}] 模型 ${model} 返回数据不完整,尝试重新请求...`);
      }

      // 如果是最后一次尝试这个模型,切换到下一个模型
      if (attempt === maxRetries) {
        console.warn(`[Agent:${agentType}] 模型 ${model} 失败,尝试备用模型...`);
      } else {
        // 等待后重试
        await new Promise(r => setTimeout(r, 1500));
      }
    }
  }

  return { success: false, agentType, error: 'ALL_ATTEMPTS_FAILED' };
};

/**
 * 构建Agent用户提示词
 */
const buildAgentUserPrompt = (input, skeletonData, agentType) => {
  const genderStr = input.gender === 'Male' ? '男 (乾造)' : '女 (坤造)';

  // 精简的时间线数据
  const timelineStr = JSON.stringify(skeletonData.timeline.slice(0, 30).map(t => ({
    a: t.age,
    y: t.year,
    gz: t.ganZhi,
    dy: t.daYun
  })));

  const baseInfo = `
【命主信息】
性别:${genderStr}
姓名:${input.name || '未提供'}
出生年份:${input.birthYear}
出生地点:${input.birthPlace || '未提供'}

【八字四柱】
年柱:${skeletonData.bazi[0]}
月柱:${skeletonData.bazi[1]}
日柱:${skeletonData.bazi[2]}
时柱:${skeletonData.bazi[3]}

【大运信息】
起运年龄:${skeletonData.startAge}
大运顺逆:${skeletonData.direction}
`;

  // 根据Agent类型添加特定信息
  const currentYear = new Date().getFullYear();
  const birthYear = parseInt(input.birthYear, 10);
  const currentAge = currentYear - birthYear + 1;

  switch (agentType) {
    case 'kline_past': {
      // 过去K线:从出生到今年
      const pastTimeline = skeletonData.timeline.filter(t => t.year <= currentYear);
      const pastTimelineStr = JSON.stringify(pastTimeline.map(t => ({
        a: t.age,
        y: t.year,
        gz: t.ganZhi,
        dy: t.daYun
      })));
      return baseInfo + `\n【当前年份】${currentYear}年(${currentAge}岁)\n【待填充的过去时间轴(出生到今年)】\n${pastTimelineStr}`;
    }

    case 'kline_future': {
      // 未来K线:从今年到100岁
      const futureTimeline = skeletonData.timeline.filter(t => t.year >= currentYear);
      const futureTimelineStr = JSON.stringify(futureTimeline.map(t => ({
        a: t.age,
        y: t.year,
        gz: t.ganZhi,
        dy: t.daYun
      })));
      return baseInfo + `\n【当前年份】${currentYear}年(${currentAge}岁)\n【待填充的未来时间轴(今年到100岁)】\n${futureTimelineStr}`;
    }

    case 'core':
      return baseInfo + `\n【前30年时间轴参考】\n${timelineStr}\n\n请深度分析此八字的核心命理结构。`;

    case 'career':
      return baseInfo + `\n请专注分析此八字的事业财富运势。`;

    case 'marriage':
      return baseInfo + `\n请专注分析此八字的婚姻感情和健康状况。`;

    case 'crypto':
      return baseInfo + `\n当前年份:${currentYear}\n请专注分析此八字的币圈交易运势和投机潜力。`;

    default:
      return baseInfo;
  }
};

/**
 * 并行执行6个Agent分析
 * @param {object} input - 用户输入
 * @param {object} skeletonData - 时间线骨架
 * @param {object} res - SSE响应对象
 * @param {function} onProgress - 进度回调
 */
export const runParallelAgents = async (input, skeletonData, res, onProgress) => {
  // 不再需要固定的 API URL 和 Key,由 apiConfig.js 根据模型自动选择
  const apiBaseUrl = null; // 不再使用
  const apiKey = null; // 不再使用

  const agents = [
    { type: 'core', prompt: AGENT_CORE_PROMPT, priority: 1 },
    { type: 'kline_past', prompt: AGENT_KLINE_PAST_PROMPT, priority: 2 },
    { type: 'kline_future', prompt: AGENT_KLINE_FUTURE_PROMPT, priority: 3 },
    { type: 'career', prompt: AGENT_CAREER_PROMPT, priority: 4 },
    { type: 'marriage', prompt: AGENT_MARRIAGE_PROMPT, priority: 5 },
    { type: 'crypto', prompt: AGENT_CRYPTO_PROMPT, priority: 6 },
  ];

  onProgress(`启动 ${agents.length} 个专业Agent并行分析...`);

  const results = {};
  const completedAgents = [];

  // 创建所有Agent的Promise
  const agentPromises = agents.map(agent => {
    const userPrompt = buildAgentUserPrompt(input, skeletonData, agent.type);

    return makeAgentRequestWithRetry(
      agent.type,
      apiBaseUrl,
      apiKey,
      agent.prompt,
      userPrompt
    ).then(result => {
      if (result.success) {
        results[agent.type] = result.data;
        completedAgents.push(agent.type);

        // 立即推送该Agent的结果
        sendSSE(res, `agent_${agent.type}_complete`, {
          agentType: agent.type,
          data: result.data,
          elapsed: result.elapsed,
          model: result.model,
          completedCount: completedAgents.length,
          totalAgents: agents.length,
        });

        onProgress(`✓ Agent[${agent.type}] 完成 (${result.elapsed}s) - 已完成 ${completedAgents.length}/${agents.length}`);
      } else {
        onProgress(`✗ Agent[${agent.type}] 失败: ${result.error}`);
        sendSSE(res, `agent_${agent.type}_error`, {
          agentType: agent.type,
          error: result.error,
        });
      }
      return result;
    });
  });

  // 等待所有Agent完成
  const allResults = await Promise.allSettled(agentPromises);

  // 汇总结果
  const successCount = allResults.filter(r => r.status === 'fulfilled' && r.value?.success).length;

  onProgress(`并行分析完成: ${successCount}/${agents.length} 成功`);

  return {
    success: successCount > 0,
    results,
    completedAgents,
    totalAgents: agents.length,
    successCount,
  };
};

/**
 * 基于八字生成事业财富降级内容
 * @param {object} core - 核心分析结果
 * @param {object} skeletonData - 时间线骨架数据
 */
const generateCareerFallback = (core, skeletonData) => {
  const bazi = core?.bazi || skeletonData?.bazi || [];
  const dayPillar = bazi[2] || '';
  const dayGan = dayPillar ? dayPillar[0] : '';

  // 基于日主五行推断适合行业
  const dayGanIndustries = {
    '甲': { industries: '教育、文化、出版、环保、园艺', element: '木' },
    '乙': { industries: '设计、美容、花艺、服装、医药', element: '木' },
    '丙': { industries: '能源、娱乐、餐饮、传媒、演艺', element: '火' },
    '丁': { industries: '科技、电子、文化创意、教育培训', element: '火' },
    '戊': { industries: '房地产、建筑、矿业、农业、物流', element: '土' },
    '己': { industries: '农业、食品、陶瓷、中介、服务业', element: '土' },
    '庚': { industries: '金融、机械、汽车、五金、军工', element: '金' },
    '辛': { industries: '珠宝、精密仪器、法律、金融、美容', element: '金' },
    '壬': { industries: '物流、航运、旅游、水产、饮料', element: '水' },
    '癸': { industries: '咨询、教育、医疗、心理、艺术', element: '水' },
  };

  const info = dayGanIndustries[dayGan] || { industries: '综合服务类行业', element: '平衡' };

  const industry = `根据八字日主「${dayGan || '未知'}」分析,您五行属${info.element},事业适合方向包括:${info.industries}。日主强弱影响事业发展模式,建议结合实际情况选择最适合自己的发展道路。命局中官杀星代表事业机遇,财星代表财富获取能力,需综合判断以获得最佳事业规划。`;

  const wealth = `从财运角度分析,八字中财星的强弱决定了财富获取的方式和规模。${dayGan ? `日主${dayGan}` : '您的命局'}具有一定的理财天赋,建议稳健投资为主。正财代表稳定收入如工资薪金,偏财代表投资理财等非固定收入。根据大运流年的不同,财运会有起伏变化,宜把握财运旺盛的年份积极进取。`;

  return {
    industry,
    industryScore: 6,
    wealth,
    wealthScore: 6,
    recommendedIndustries: [
      { name: info.industries.split('、')[0], reason: `五行属${info.element},与命局相合` },
      { name: info.industries.split('、')[1] || '综合服务', reason: '命理分析推荐' }
    ],
    wealthPattern: '正偏财兼有',
    wealthPotential: '中等偏上',
  };
};

/**
 * 合并多个Agent的结果为最终分析
 * @param {object} agentResults - 各Agent返回的结果
 * @param {object} skeletonData - 时间线骨架数据(用于K线降级)
 */
export const mergeAgentResults = (agentResults, skeletonData = null) => {
  const { core, kline_past, kline_future, career, marriage, crypto } = agentResults;

  // 如果career数据缺失,生成降级内容
  const careerFallback = (!career?.industry || !career?.wealth)
    ? generateCareerFallback(core, skeletonData)
    : null;

  if (careerFallback) {
    console.log('[mergeAgentResults] Career数据缺失,使用降级内容生成事业财富分析');
  }

  // K线数据:合并过去和未来的K线数据
  let chartPoints = [];
  const currentYear = new Date().getFullYear();

  // 获取过去K线数据
  const pastPoints = kline_past?.chartPoints || [];
  // 获取未来K线数据
  const futurePoints = kline_future?.chartPoints || [];

  // 检查两个K线Agent是否都有数据
  const hasPastData = pastPoints.length > 0;
  const hasFutureData = futurePoints.length > 0;

  if (hasPastData && hasFutureData) {
    // 最佳情况:两段K线数据都有,正常合并
    const allPoints = [...pastPoints];

    // 未来K线避免重复年份
    for (const point of futurePoints) {
      if (!allPoints.some(p => p.year === point.year && p.age === point.age)) {
        allPoints.push(point);
      }
    }

    chartPoints = allPoints.sort((a, b) => a.age - b.age);
    console.log(`[mergeAgentResults] K线完整合并: 过去${pastPoints.length}年 + 未来${futurePoints.length}年 = 总${chartPoints.length}年`);

  } else if (hasPastData || hasFutureData) {
    // 部分数据:只有一段K线数据,使用fallback补全缺失部分
    console.warn(`[mergeAgentResults] K线数据不完整: 过去=${pastPoints.length}年, 未来=${futurePoints.length}年,使用fallback补全`);

    if (skeletonData) {
      const fallbackPoints = generateFallbackKLine(skeletonData);
      const existingYears = new Set([
        ...pastPoints.map(p => p.year),
        ...futurePoints.map(p => p.year)
      ]);

      // 合并已有数据
      const allPoints = [...pastPoints, ...futurePoints];

      // 用fallback填补缺失的年份
      for (const point of fallbackPoints) {
        if (!existingYears.has(point.year)) {
          allPoints.push(point);
        }
      }

      chartPoints = allPoints.sort((a, b) => a.age - b.age);
      console.log(`[mergeAgentResults] K线混合合并: AI数据${pastPoints.length + futurePoints.length}年 + Fallback补全 = 总${chartPoints.length}年`);
    } else {
      // 无skeleton数据,只能用现有数据
      chartPoints = [...pastPoints, ...futurePoints].sort((a, b) => a.age - b.age);
      console.warn(`[mergeAgentResults] 无skeleton数据,仅使用部分K线: ${chartPoints.length}年`);
    }

  } else if (skeletonData) {
    // 两段K线都失败,完全使用降级算法
    console.log('[mergeAgentResults] K线Agent全部失败,使用完整降级算法生成K线数据');
    chartPoints = generateFallbackKLine(skeletonData);
  }

  // 最终数据验证:确保数据点数量足够(至少50年)
  const MIN_CHART_POINTS = 50;
  if (chartPoints.length < MIN_CHART_POINTS && skeletonData) {
    console.warn(`[mergeAgentResults] K线数据不足(${chartPoints.length}点 < ${MIN_CHART_POINTS}点),使用完整fallback替换`);
    chartPoints = generateFallbackKLine(skeletonData);
    console.log(`[mergeAgentResults] Fallback生成完成: ${chartPoints.length}年`);
  }

  // 合并过去和未来的关键事件
  const pastEvents = kline_past?.pastEvents || core?.pastEvents || [];
  const futureEvents = kline_future?.futureEvents || core?.futureEvents || [];
  const keyYears = [
    ...(kline_past?.keyYears || []),
    ...(kline_future?.keyYears || [])
  ].sort((a, b) => a.year - b.year);

  return {
    // 基础信息
    bazi: core?.bazi || [],
    summary: core?.summary || '命理分析完成',
    summaryScore: core?.summaryScore || 5,

    // 核心Agent - 性格/六亲/风水
    personality: core?.personality || '',
    personalityScore: core?.personalityScore || 5,
    family: core?.family || '',
    familyScore: core?.familyScore || 5,
    fengShui: core?.fengShui || '',
    fengShuiScore: core?.fengShuiScore || 5,

    // 个人特征
    appearance: core?.appearance || '',
    bodyType: core?.bodyType || '',
    skin: core?.skin || '',
    characterSummary: core?.characterSummary || '',

    // 事业Agent - 使用fallback如果原数据缺失
    industry: career?.industry || careerFallback?.industry || '暂无事业分析,请稍后重试',
    industryScore: career?.industryScore || careerFallback?.industryScore || 5,
    wealth: career?.wealth || careerFallback?.wealth || '暂无财富分析,请稍后重试',
    wealthScore: career?.wealthScore || careerFallback?.wealthScore || 5,

    // 婚姻健康Agent
    marriage: marriage?.marriage || '',
    marriageScore: marriage?.marriageScore || 5,
    health: marriage?.health || '',
    healthScore: marriage?.healthScore || 5,
    healthBodyParts: marriage?.healthBodyParts || [],

    // 币圈Agent
    crypto: crypto?.crypto || '',
    cryptoScore: crypto?.cryptoScore || 5,
    cryptoYear: crypto?.cryptoYear || '待定',
    cryptoStyle: crypto?.cryptoStyle || '现货定投',

    // K线Agent - 使用已计算的chartPoints(含降级逻辑)
    chartPoints: chartPoints,

    // 运势预测
    monthlyFortune: core?.monthlyFortune || marriage?.monthlyFortune || '',
    monthlyHighlights: core?.monthlyHighlights || [],
    yearlyFortune: core?.yearlyFortune || career?.yearlyFortune || '',
    yearlyKeyEvents: core?.yearlyKeyEvents || career?.yearlyKeyEvents || [],

    // 幸运元素
    luckyColors: core?.luckyColors || [],
    luckyDirections: core?.luckyDirections || [],
    luckyZodiac: core?.luckyZodiac || [],
    luckyNumbers: core?.luckyNumbers || [],

    // 重点日期和事件
    keyDatesThisYear: core?.keyDatesThisYear || [],
    keyDatesThisMonth: core?.keyDatesThisMonth || [],
    pastEvents: pastEvents,
    futureEvents: futureEvents,
    keyYears: keyYears,
  };
};

export default {
  runParallelAgents,
  mergeAgentResults,
  sendSSE,
};