Download server/coreDocumentEngine.js from rxb0506/lifekline: direct link, hf CLI and curl.
- Browser
- Download file 13.9 kB
-
https://huggingface.co/spaces/rxb0506/lifekline/resolve/main/server/coreDocumentEngine.js
- Command line
-
hf download hf://spaces/rxb0506/lifekline/server/coreDocumentEngine.js
-
curl -L -o coreDocumentEngine.js https://huggingface.co/spaces/rxb0506/lifekline/resolve/main/server/coreDocumentEngine.js
13.9 kB
| /** | |
| * 核心文档引擎 - Core Document Engine for Bazi Profiles | |
| * | |
| * 职责: | |
| * 1. 为每个用户档案生成核心文档(100年命理数据) | |
| * 2. 管理核心文档的缓存、验证和重新生成 | |
| * 3. 确保同一八字返回相同的核心结论 | |
| * | |
| * 核心文档结构: | |
| * - 100年生命时间线(chartPoints) | |
| * - 命理核心分析(personality_core, career_core等) | |
| * - K线数据(kline_data) | |
| * - 巅峰年/低谷年 | |
| */ | |
| import { getUserProfileById, updateProfileCoreDocumentStatus, getDb, nowIso } from './database.js'; | |
| import { getCachedAnalysis, cacheAnalysis, computeBaziHash } from './cacheManager.js'; | |
| import { calculateLifeTimeline, generateFallbackKLine } from './baziCalculator.js'; | |
| /** | |
| * 生成核心文档 | |
| * @param {object} profile - 用户档案对象 | |
| * @param {boolean} skipCache - 是否跳过缓存检查(强制重新生成) | |
| * @returns {Promise<object>} 核心文档对象 | |
| */ | |
| export const generateCoreDocument = async (profile, skipCache = false) => { | |
| try { | |
| console.log(`[CoreDocEngine] 开始生成核心文档 - Profile ID: ${profile.id}`); | |
| // 1. 更新状态为"生成中" | |
| updateProfileCoreDocumentStatus(profile.id, 'generating'); | |
| // 2. 计算100年生命时间线 | |
| 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} 年`); | |
| // 3. 计算八字哈希 | |
| const baziHash = computeBaziHash( | |
| profile.yearPillar, | |
| profile.monthPillar, | |
| profile.dayPillar, | |
| profile.hourPillar | |
| ); | |
| // 4. 检查缓存(除非跳过) | |
| let cachedAnalysis = null; | |
| if (!skipCache) { | |
| cachedAnalysis = getCachedAnalysis(baziHash, profile.gender); | |
| if (cachedAnalysis) { | |
| console.log(`[CoreDocEngine] 找到缓存分析 - Hash: ${baziHash}`); | |
| } | |
| } | |
| // 5. 如果没有缓存,触发分析生成(当前使用降级算法) | |
| 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}`); | |
| } | |
| // 6. 更新档案状态为"就绪" | |
| 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}`); | |
| } | |
| }; | |
| /** | |
| * 获取核心文档 | |
| * @param {string} profileId - 档案ID | |
| * @returns {Promise<object>} 核心文档对象,包含验证状态 | |
| */ | |
| export const getCoreDocument = async (profileId) => { | |
| try { | |
| console.log(`[CoreDocEngine] 获取核心文档 - Profile ID: ${profileId}`); | |
| // 1. 获取档案 | |
| const profile = getUserProfileById(profileId); | |
| if (!profile) { | |
| throw new Error('档案不存在'); | |
| } | |
| // 2. 计算八字哈希 | |
| const baziHash = computeBaziHash( | |
| profile.yearPillar, | |
| profile.monthPillar, | |
| profile.dayPillar, | |
| profile.hourPillar | |
| ); | |
| // 3. 查询缓存 | |
| const cachedAnalysis = getCachedAnalysis(baziHash, profile.gender); | |
| // 4. 如果找到缓存,构建文档并返回 | |
| 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' | |
| }; | |
| } | |
| // 5. 如果没有缓存,生成新文档 | |
| 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}`); | |
| } | |
| }; | |
| /** | |
| * 验证核心文档完整性 | |
| * @param {object} doc - 核心文档对象 | |
| * @returns {object} 验证结果 { valid: boolean, missing: string[], score: number } | |
| */ | |
| export const validateCoreDocument = (doc) => { | |
| const missing = []; | |
| let score = 0; | |
| const maxScore = 100; | |
| // 1. 检查 chartPoints 是否存在且有约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; // chartPoints 占30分 | |
| } | |
| // 2. 检查必需字段:personalityCore | |
| if (!doc.personalityCore || !doc.personalityCore.content) { | |
| missing.push('personality_core'); | |
| } else { | |
| score += 15; | |
| } | |
| // 3. 检查必需字段:careerCore | |
| if (!doc.careerCore || !doc.careerCore.content) { | |
| missing.push('career_core'); | |
| } else { | |
| score += 15; | |
| } | |
| // 4. 检查必需字段:klineData | |
| 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; | |
| } | |
| // 5. 检查可选字段:wealthCore | |
| if (doc.wealthCore && doc.wealthCore.content) { | |
| score += 5; | |
| } | |
| // 6. 检查可选字段:marriageCore | |
| if (doc.marriageCore && doc.marriageCore.content) { | |
| score += 5; | |
| } | |
| // 7. 检查可选字段:healthCore | |
| if (doc.healthCore && doc.healthCore.content) { | |
| score += 5; | |
| } | |
| // 8. 检查可选字段:peakYears 和 troughYears | |
| 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; | |
| } | |
| // 9. 检查可选字段:luckyElements | |
| if (doc.luckyElements && Object.keys(doc.luckyElements).length > 0) { | |
| score += 3; | |
| } | |
| // 10. 检查可选字段:physicalTraits | |
| 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(', ')}` | |
| }; | |
| }; | |
| /** | |
| * 强制重新生成核心文档 | |
| * @param {string} profileId - 档案ID | |
| * @param {string} reason - 重新生成原因 | |
| * @returns {Promise<object>} 新的核心文档对象 | |
| */ | |
| export const regenerateCoreDocument = async (profileId, reason = '手动触发') => { | |
| try { | |
| console.log(`[CoreDocEngine] 重新生成核心文档 - Profile ID: ${profileId}, 原因: ${reason}`); | |
| // 1. 获取档案 | |
| const profile = getUserProfileById(profileId); | |
| if (!profile) { | |
| throw new Error('档案不存在'); | |
| } | |
| // 2. 计算八字哈希 | |
| const baziHash = computeBaziHash( | |
| profile.yearPillar, | |
| profile.monthPillar, | |
| profile.dayPillar, | |
| profile.hourPillar | |
| ); | |
| // 3. 删除现有缓存 | |
| 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}`); | |
| } | |
| // 4. 记录重新生成日志 | |
| console.log(`[CoreDocEngine] 重新生成原因: ${reason}`); | |
| // 5. 生成新文档(跳过缓存检查) | |
| 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}`); | |
| } | |
| }; | |
| /** | |
| * 批量生成核心文档(用于系统维护) | |
| * @param {string[]} profileIds - 档案ID数组 | |
| * @returns {Promise<object>} 批量生成结果统计 | |
| */ | |
| 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 | |
| }; | |