| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | import { getUserProfileById, updateProfileCoreDocumentStatus, getDb, nowIso } from './database.js'; |
| | import { getCachedAnalysis, cacheAnalysis, computeBaziHash } from './cacheManager.js'; |
| | import { calculateLifeTimeline, generateFallbackKLine } from './baziCalculator.js'; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | export const generateCoreDocument = async (profile, skipCache = false) => { |
| | try { |
| | console.log(`[CoreDocEngine] 开始生成核心文档 - Profile ID: ${profile.id}`); |
| |
|
| | |
| | updateProfileCoreDocumentStatus(profile.id, 'generating'); |
| |
|
| | |
| | const timelineData = calculateLifeTimeline({ |
| | birthYear: profile.birthYear, |
| | gender: profile.gender === 'male' ? 'Male' : 'Female', |
| | yearPillar: profile.yearPillar, |
| | monthPillar: profile.monthPillar, |
| | dayPillar: profile.dayPillar, |
| | hourPillar: profile.hourPillar, |
| | startAge: profile.startAge, |
| | firstDaYun: profile.firstDaYun |
| | }); |
| |
|
| | console.log(`[CoreDocEngine] 时间线计算完成 - ${timelineData.timeline.length} 年`); |
| |
|
| | |
| | const baziHash = computeBaziHash( |
| | profile.yearPillar, |
| | profile.monthPillar, |
| | profile.dayPillar, |
| | profile.hourPillar |
| | ); |
| |
|
| | |
| | let cachedAnalysis = null; |
| | if (!skipCache) { |
| | cachedAnalysis = getCachedAnalysis(baziHash, profile.gender); |
| | if (cachedAnalysis) { |
| | console.log(`[CoreDocEngine] 找到缓存分析 - Hash: ${baziHash}`); |
| | } |
| | } |
| |
|
| | |
| | let coreDocument; |
| | if (cachedAnalysis) { |
| | |
| | coreDocument = { |
| | profileId: profile.id, |
| | baziHash, |
| | chartPoints: cachedAnalysis.klineData || [], |
| | personalityCore: cachedAnalysis.personalityCore, |
| | careerCore: cachedAnalysis.careerCore, |
| | wealthCore: cachedAnalysis.wealthCore, |
| | marriageCore: cachedAnalysis.marriageCore, |
| | healthCore: cachedAnalysis.healthCore, |
| | klineData: cachedAnalysis.klineData, |
| | peakYears: cachedAnalysis.peakYears, |
| | troughYears: cachedAnalysis.troughYears, |
| | cryptoCore: cachedAnalysis.cryptoCore, |
| | luckyElements: cachedAnalysis.luckyElements, |
| | physicalTraits: cachedAnalysis.physicalTraits, |
| | modelUsed: cachedAnalysis.modelUsed, |
| | generatedAt: nowIso(), |
| | fromCache: true |
| | }; |
| | } else { |
| | |
| | console.log(`[CoreDocEngine] 生成新的核心分析 - 使用降级算法`); |
| |
|
| | const klineData = generateFallbackKLine(timelineData); |
| |
|
| | |
| | const sortedByScore = [...klineData].sort((a, b) => b.score - a.score); |
| | const peakYears = sortedByScore.slice(0, 5).map(p => ({ |
| | year: p.year, |
| | age: p.age, |
| | score: p.score, |
| | reason: p.reason |
| | })); |
| | const troughYears = sortedByScore.slice(-5).reverse().map(p => ({ |
| | year: p.year, |
| | age: p.age, |
| | score: p.score, |
| | reason: p.reason |
| | })); |
| |
|
| | |
| | coreDocument = { |
| | profileId: profile.id, |
| | baziHash, |
| | chartPoints: klineData, |
| | personalityCore: { |
| | content: '基于四柱八字的性格分析(降级版)', |
| | score: 5 |
| | }, |
| | careerCore: { |
| | content: '基于四柱八字的事业分析(降级版)', |
| | score: 5 |
| | }, |
| | wealthCore: { |
| | content: '基于四柱八字的财运分析(降级版)', |
| | score: 5 |
| | }, |
| | marriageCore: { |
| | content: '基于四柱八字的婚姻分析(降级版)', |
| | score: 5 |
| | }, |
| | healthCore: { |
| | content: '基于四柱八字的健康分析(降级版)', |
| | score: 5, |
| | bodyParts: [] |
| | }, |
| | klineData, |
| | peakYears, |
| | troughYears, |
| | cryptoCore: { |
| | content: '暂无币圈分析', |
| | score: 5 |
| | }, |
| | luckyElements: { |
| | colors: [], |
| | directions: [], |
| | zodiac: [], |
| | numbers: [] |
| | }, |
| | physicalTraits: { |
| | appearance: '', |
| | bodyType: '', |
| | skin: '', |
| | characterSummary: '' |
| | }, |
| | modelUsed: 'fallback_v1', |
| | generatedAt: nowIso(), |
| | fromCache: false |
| | }; |
| |
|
| | |
| | cacheAnalysis({ |
| | baziHash, |
| | gender: profile.gender, |
| | structuralData: { |
| | bazi: [profile.yearPillar, profile.monthPillar, profile.dayPillar, profile.hourPillar], |
| | summaryScore: 5 |
| | }, |
| | personalityCore: coreDocument.personalityCore, |
| | careerCore: coreDocument.careerCore, |
| | wealthCore: coreDocument.wealthCore, |
| | marriageCore: coreDocument.marriageCore, |
| | healthCore: coreDocument.healthCore, |
| | klineData, |
| | peakYears, |
| | troughYears, |
| | cryptoCore: coreDocument.cryptoCore, |
| | luckyElements: coreDocument.luckyElements, |
| | physicalTraits: coreDocument.physicalTraits, |
| | modelUsed: 'fallback_v1', |
| | version: 1 |
| | }); |
| |
|
| | console.log(`[CoreDocEngine] 核心分析已缓存 - Hash: ${baziHash}`); |
| | } |
| |
|
| | |
| | updateProfileCoreDocumentStatus(profile.id, 'ready'); |
| | console.log(`[CoreDocEngine] 核心文档生成完成 - Profile ID: ${profile.id}`); |
| |
|
| | return coreDocument; |
| |
|
| | } catch (error) { |
| | console.error(`[CoreDocEngine] 生成核心文档失败:`, error); |
| |
|
| | |
| | updateProfileCoreDocumentStatus(profile.id, 'failed'); |
| |
|
| | throw new Error(`生成核心文档失败: ${error.message}`); |
| | } |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | export const getCoreDocument = async (profileId) => { |
| | try { |
| | console.log(`[CoreDocEngine] 获取核心文档 - Profile ID: ${profileId}`); |
| |
|
| | |
| | const profile = getUserProfileById(profileId); |
| | if (!profile) { |
| | throw new Error('档案不存在'); |
| | } |
| |
|
| | |
| | const baziHash = computeBaziHash( |
| | profile.yearPillar, |
| | profile.monthPillar, |
| | profile.dayPillar, |
| | profile.hourPillar |
| | ); |
| |
|
| | |
| | const cachedAnalysis = getCachedAnalysis(baziHash, profile.gender); |
| |
|
| | |
| | if (cachedAnalysis && cachedAnalysis.klineData && cachedAnalysis.klineData.length > 0) { |
| | const document = { |
| | profileId: profile.id, |
| | baziHash, |
| | chartPoints: cachedAnalysis.klineData, |
| | personalityCore: cachedAnalysis.personalityCore, |
| | careerCore: cachedAnalysis.careerCore, |
| | wealthCore: cachedAnalysis.wealthCore, |
| | marriageCore: cachedAnalysis.marriageCore, |
| | healthCore: cachedAnalysis.healthCore, |
| | klineData: cachedAnalysis.klineData, |
| | peakYears: cachedAnalysis.peakYears, |
| | troughYears: cachedAnalysis.troughYears, |
| | cryptoCore: cachedAnalysis.cryptoCore, |
| | luckyElements: cachedAnalysis.luckyElements, |
| | physicalTraits: cachedAnalysis.physicalTraits, |
| | modelUsed: cachedAnalysis.modelUsed, |
| | generatedAt: cachedAnalysis.createdAt, |
| | fromCache: true |
| | }; |
| |
|
| | |
| | const validation = validateCoreDocument(document); |
| |
|
| | console.log(`[CoreDocEngine] 核心文档已从缓存返回 - 验证分数: ${validation.score}`); |
| |
|
| | return { |
| | document, |
| | validation, |
| | status: 'ready' |
| | }; |
| | } |
| |
|
| | |
| | console.log(`[CoreDocEngine] 缓存未找到,生成新核心文档`); |
| | const document = await generateCoreDocument(profile); |
| | const validation = validateCoreDocument(document); |
| |
|
| | return { |
| | document, |
| | validation, |
| | status: 'ready' |
| | }; |
| |
|
| | } catch (error) { |
| | console.error(`[CoreDocEngine] 获取核心文档失败:`, error); |
| | throw new Error(`获取核心文档失败: ${error.message}`); |
| | } |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | export const validateCoreDocument = (doc) => { |
| | const missing = []; |
| | let score = 0; |
| | const maxScore = 100; |
| |
|
| | |
| | if (!doc.chartPoints || !Array.isArray(doc.chartPoints)) { |
| | missing.push('chartPoints'); |
| | } else if (doc.chartPoints.length === 0) { |
| | missing.push('chartPoints (empty)'); |
| | } else if (doc.chartPoints.length < 90) { |
| | missing.push('chartPoints (不足100年)'); |
| | score += 10; |
| | } else { |
| | score += 30; |
| | } |
| |
|
| | |
| | if (!doc.personalityCore || !doc.personalityCore.content) { |
| | missing.push('personality_core'); |
| | } else { |
| | score += 15; |
| | } |
| |
|
| | |
| | if (!doc.careerCore || !doc.careerCore.content) { |
| | missing.push('career_core'); |
| | } else { |
| | score += 15; |
| | } |
| |
|
| | |
| | if (!doc.klineData || !Array.isArray(doc.klineData)) { |
| | missing.push('kline_data'); |
| | } else if (doc.klineData.length === 0) { |
| | missing.push('kline_data (empty)'); |
| | } else { |
| | score += 20; |
| | } |
| |
|
| | |
| | if (doc.wealthCore && doc.wealthCore.content) { |
| | score += 5; |
| | } |
| |
|
| | |
| | if (doc.marriageCore && doc.marriageCore.content) { |
| | score += 5; |
| | } |
| |
|
| | |
| | if (doc.healthCore && doc.healthCore.content) { |
| | score += 5; |
| | } |
| |
|
| | |
| | if (doc.peakYears && Array.isArray(doc.peakYears) && doc.peakYears.length > 0) { |
| | score += 3; |
| | } |
| | if (doc.troughYears && Array.isArray(doc.troughYears) && doc.troughYears.length > 0) { |
| | score += 2; |
| | } |
| |
|
| | |
| | if (doc.luckyElements && Object.keys(doc.luckyElements).length > 0) { |
| | score += 3; |
| | } |
| |
|
| | |
| | if (doc.physicalTraits && Object.keys(doc.physicalTraits).length > 0) { |
| | score += 2; |
| | } |
| |
|
| | const valid = missing.length === 0 && score >= 80; |
| |
|
| | return { |
| | valid, |
| | missing, |
| | score, |
| | maxScore, |
| | message: valid |
| | ? '核心文档完整' |
| | : `核心文档不完整,缺失字段: ${missing.join(', ')}` |
| | }; |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | export const regenerateCoreDocument = async (profileId, reason = '手动触发') => { |
| | try { |
| | console.log(`[CoreDocEngine] 重新生成核心文档 - Profile ID: ${profileId}, 原因: ${reason}`); |
| |
|
| | |
| | const profile = getUserProfileById(profileId); |
| | if (!profile) { |
| | throw new Error('档案不存在'); |
| | } |
| |
|
| | |
| | const baziHash = computeBaziHash( |
| | profile.yearPillar, |
| | profile.monthPillar, |
| | profile.dayPillar, |
| | profile.hourPillar |
| | ); |
| |
|
| | |
| | const db = getDb(); |
| | const deleteStmt = db.prepare(` |
| | DELETE FROM bazi_analysis_cache |
| | WHERE bazi_hash = ? AND gender = ? |
| | `); |
| | const result = deleteStmt.run(baziHash, profile.gender); |
| |
|
| | if (result.changes > 0) { |
| | console.log(`[CoreDocEngine] 已删除旧缓存 - Hash: ${baziHash}, 删除条数: ${result.changes}`); |
| | } |
| |
|
| | |
| | console.log(`[CoreDocEngine] 重新生成原因: ${reason}`); |
| |
|
| | |
| | const newDocument = await generateCoreDocument(profile, true); |
| |
|
| | console.log(`[CoreDocEngine] 核心文档重新生成完成 - Profile ID: ${profileId}`); |
| |
|
| | return { |
| | document: newDocument, |
| | regenerated: true, |
| | reason, |
| | timestamp: nowIso() |
| | }; |
| |
|
| | } catch (error) { |
| | console.error(`[CoreDocEngine] 重新生成核心文档失败:`, error); |
| | throw new Error(`重新生成核心文档失败: ${error.message}`); |
| | } |
| | }; |
| |
|
| | |
| | |
| | |
| | |
| | |
| | export const batchGenerateCoreDocuments = async (profileIds) => { |
| | console.log(`[CoreDocEngine] 批量生成核心文档 - 数量: ${profileIds.length}`); |
| |
|
| | const results = { |
| | total: profileIds.length, |
| | success: 0, |
| | failed: 0, |
| | errors: [] |
| | }; |
| |
|
| | for (const profileId of profileIds) { |
| | try { |
| | await generateCoreDocument({ id: profileId }); |
| | results.success++; |
| | } catch (error) { |
| | results.failed++; |
| | results.errors.push({ |
| | profileId, |
| | error: error.message |
| | }); |
| | } |
| | } |
| |
|
| | console.log(`[CoreDocEngine] 批量生成完成 - 成功: ${results.success}, 失败: ${results.failed}`); |
| |
|
| | return results; |
| | }; |
| |
|
| | export default { |
| | generateCoreDocument, |
| | getCoreDocument, |
| | validateCoreDocument, |
| | regenerateCoreDocument, |
| | batchGenerateCoreDocuments |
| | }; |
| |
|