Skip to content

Commit 3d62001

Browse files
feat: clarify personality analysis evidence and themes
1 parent 381e2d9 commit 3d62001

22 files changed

Lines changed: 1803 additions & 639 deletions

frontend/app.js

Lines changed: 155 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,7 @@ let TERMS = {
384384
function _buildBingyaoCard(combos){
385385
let top = combos[0];
386386
let h = '<div class=bingyao-card>';
387-
h += '<div class=bingyao-badge>全局矛盾</div>';
387+
h += '<div class=bingyao-badge>规则候选</div>';
388388
h += '<div class=bingyao-title>' + esc(top.combo) + '</div>';
389389
h += '<div class=bingyao-directive>' + esc(top.directive) + '</div>';
390390
if (combos.length > 1){
@@ -398,29 +398,143 @@ function _buildBingyaoCard(combos){
398398
return h;
399399
}
400400

401-
function _buildShishenRank(scores){
401+
function _buildShishenRank(scoreData){
402402
let entries = [];
403-
for (let k in scores) entries.push({name: k, val: scores[k]});
403+
if (Array.isArray(scoreData)){
404+
for (let i = 0; i < scoreData.length; i++){
405+
entries.push({name: scoreData[i].name, val: scoreData[i].score, level: scoreData[i].level, breakdown: scoreData[i].breakdown || {}});
406+
}
407+
} else {
408+
for (let k in (scoreData || {})) entries.push({name: k, val: scoreData[k], breakdown: {}});
409+
}
404410
entries.sort(function(a,b){return b.val - a.val;});
405-
let top5 = entries.slice(0, 5);
406-
let maxVal = top5[0] ? top5[0].val : 10;
411+
entries = entries.filter(function(item){return item.val > 0;}).slice(0, 10);
412+
let maxVal = entries[0] ? entries[0].val : 1;
407413
let h = '<div class=shishen-rank>';
408-
h += '<div class=shishen-rank-title>十神强度</div>';
409-
for (let i = 0; i < top5.length; i++){
410-
let pct = Math.round(top5[i].val / maxVal * 100);
411-
h += '<div class=shishen-bar><span class=shishen-name>' + top5[i].name + '</span>';
412-
h += '<span class=shishen-val>' + top5[i].val.toFixed(1) + '</span>';
414+
h += '<div class=shishen-rank-title>十神工程分 <span>排序仅本盘内比较</span></div>';
415+
h += '<div class=evidence-scale-note>条形以本盘最高项为 100%;档位使用固定工程阈值,不代表人群等级。</div>';
416+
let sourceLabels = {tougan:'透干',hidden:'藏干',month_bonus:'月令',same_pillar_bonus:'同柱',heju_bonus:'合局','透干':'透干','藏干':'藏干','月令加成':'月令','同柱加成':'同柱','合局加成':'合局'};
417+
for (let i = 0; i < entries.length; i++){
418+
let pct = Math.round(entries[i].val / maxVal * 100);
419+
let details = [];
420+
for (let source in entries[i].breakdown){
421+
if (entries[i].breakdown[source]) details.push((sourceLabels[source] || source) + ' ' + Number(entries[i].breakdown[source]).toFixed(1));
422+
}
423+
h += '<div class=shishen-row><div class=shishen-bar><span class=shishen-name>' + esc(entries[i].name) + '</span>';
424+
h += '<span class=shishen-level>' + esc(entries[i].level || '') + '</span>';
425+
h += '<span class=shishen-val>' + Number(entries[i].val).toFixed(1) + '</span>';
413426
h += '<span class=shishen-fill style=width:' + pct + '%></span></div>';
427+
if (details.length) h += '<div class=shishen-breakdown>' + esc(details.join(' · ')) + '</div>';
428+
h += '</div>';
414429
}
415430
h += '</div>';
416431
return h;
417432
}
418433

434+
function _buildEvidenceDimensions(dimensions){
435+
let order = ['社交','感情','内心','决策','事业','财富观'];
436+
let h = '<div class=evidence-dimensions>';
437+
for (let i = 0; i < order.length; i++){
438+
let name = order[i];
439+
let item = dimensions && dimensions[name];
440+
if (!item) continue;
441+
h += '<div class=evidence-dimension><div class=evidence-dimension-head><strong>' + name + '</strong>';
442+
if (item.summary) h += '<span>' + esc(item.summary) + '</span>';
443+
if (item.comparison) h += '<span>' + esc(item.comparison) + '</span>';
444+
h += '</div><div class=evidence-signals>';
445+
let signals = item.signals || [];
446+
for (let j = 0; j < signals.length; j++){
447+
let valueText = signals[j].kind === 'weighted_score'
448+
? (signals[j].level + ' · ' + Number(signals[j].value).toFixed(1))
449+
: ('本盘相对值 ' + Number(signals[j].value).toFixed(1));
450+
h += '<div class=evidence-signal><span>' + esc(signals[j].display_label || signals[j].label) + '</span><strong>' + esc(valueText) + '</strong></div>';
451+
}
452+
h += '</div>';
453+
if (item.secondary) h += '<div class=evidence-secondary>次要方向:' + esc(item.secondary) + '</div>';
454+
let pending = item.pending_review || [];
455+
for (let k = 0; k < pending.length; k++){
456+
h += '<div class=evidence-pending>' + esc(pending[k].label) + ':' + esc(pending[k].value) + '(待复核规则)</div>';
457+
}
458+
h += '</div>';
459+
}
460+
return h + '</div>';
461+
}
462+
463+
function _evidenceNumber(value){
464+
let number = Number(value);
465+
return Number.isFinite(number) ? number.toFixed(1) : '';
466+
}
467+
468+
function _buildEvidenceScale(scale){
469+
if (!scale) return '';
470+
let thresholds = scale.thresholds || {};
471+
let medium = _evidenceNumber(thresholds.medium);
472+
let high = _evidenceNumber(thresholds.high);
473+
let params = scale.parameter_snapshot || {};
474+
let rows = [];
475+
if (medium && high) rows.push('档位:较弱 < ' + medium + ';中等 ' + medium + '–<' + high + ';较强 ≥ ' + high);
476+
if (_evidenceNumber(params.tougan_weight)) rows.push('透干 +' + _evidenceNumber(params.tougan_weight));
477+
let hidden = params.hidden_weights || {};
478+
let hiddenParts = [];
479+
for (let hiddenName of ['本气','中气','余气']){
480+
if (_evidenceNumber(hidden[hiddenName])) hiddenParts.push(hiddenName + ' +' + _evidenceNumber(hidden[hiddenName]));
481+
}
482+
if (hiddenParts.length) rows.push('藏干:' + hiddenParts.join(';'));
483+
if (_evidenceNumber(params.month_multiplier)) rows.push('月令同五行 ×' + _evidenceNumber(params.month_multiplier));
484+
if (_evidenceNumber(params.same_pillar_bonus)) rows.push('同柱同十神 +' + _evidenceNumber(params.same_pillar_bonus));
485+
let heju = params.heju_weights || {};
486+
let hejuParts = [];
487+
for (let hejuName of ['三会','三合','半合','地支六合']){
488+
if (_evidenceNumber(heju[hejuName])) hejuParts.push(hejuName + ' +' + _evidenceNumber(heju[hejuName]));
489+
}
490+
if (hejuParts.length) rows.push('合局(每个匹配十神一次):' + hejuParts.join(';'));
491+
if (!rows.length) return '';
492+
let h = '<details class=evidence-scale-details><summary>计分口径</summary><div class=evidence-scale-content>';
493+
for (let row of rows) h += '<div>' + esc(row) + '</div>';
494+
h += '<p>这些参数是工程启发式,不是古籍固定比例、概率、准确率或人群常模。</p></div></details>';
495+
return h;
496+
}
497+
498+
function _buildPersonalityEvidence(personality){
499+
let evidence = personality.evidence_view || {};
500+
let status = evidence.status || {};
501+
let weighted = evidence.weighted_scores || [];
502+
let legacyWeighted = personality.weighted_shishen && personality.weighted_shishen.scores;
503+
let hasLegacyWeighted = !!(legacyWeighted && typeof legacyWeighted === 'object' && Object.keys(legacyWeighted).length);
504+
let dimensions = evidence.dimensions || {};
505+
let combos = personality.bingyao_combos || [];
506+
let hasStatus = !!(status.strength || status.pattern || status.pattern_status);
507+
let hasEvidence = hasStatus || weighted.length > 0 || hasLegacyWeighted || Object.keys(dimensions).length > 0 || combos.length > 0;
508+
if (!hasEvidence) return '';
509+
510+
let h = '';
511+
if (hasStatus){
512+
h += '<div class=evidence-status>';
513+
if (status.strength) h += '<span><small>身强弱</small>' + esc(status.strength) + '</span>';
514+
if (status.pattern) h += '<span><small>格局</small>' + esc(status.pattern) + '</span>';
515+
if (status.pattern_status) h += '<span><small>格局状态</small>' + esc(status.pattern_status) + '</span>';
516+
h += '</div>';
517+
}
518+
h += _buildEvidenceScale(evidence.score_scale);
519+
if (combos.length) h += _buildBingyaoCard(combos);
520+
if (weighted.length) h += _buildShishenRank(weighted);
521+
else if (hasLegacyWeighted) h += _buildShishenRank(legacyWeighted);
522+
if (Object.keys(dimensions).length) h += _buildEvidenceDimensions(dimensions);
523+
let boundaries = evidence.fusion_boundaries || [];
524+
if (boundaries.length){
525+
h += '<div class=evidence-boundaries><strong>证据边界</strong><ul>';
526+
for (let bi = 0; bi < boundaries.length; bi++) h += '<li>' + esc(boundaries[bi]) + '</li>';
527+
h += '</ul></div>';
528+
}
529+
return h;
530+
}
531+
419532
function setPersonalityMode(mode){
420533
let body = document.getElementById('personalityBody');
421534
let raw = document.getElementById('personalityRaw');
422535
let btn = document.querySelector('.toggle-btn');
423536
if (!body || !raw) return;
537+
if (mode !== 'raw' && body.dataset.fusionFailed === 'true') return;
424538
if (mode === 'raw'){
425539
raw.style.display = 'block';
426540
body.style.display = 'none';
@@ -433,18 +547,34 @@ function setPersonalityMode(mode){
433547
body.style.display = 'block';
434548
if (btn){
435549
btn.dataset.personalityMode = 'fusion';
436-
btn.textContent = '\u67e5\u770b\u539f\u59cb\u6570\u636e';
550+
btn.textContent = '\u67e5\u770b\u5206\u6790\u4f9d\u636e';
437551
}
438552
}
439553
}
440554

441555
function showPersonalityRawFallback(message){
442556
let body = document.getElementById('personalityBody');
443-
if (body && message) body.dataset.fusionError = message;
557+
let text = document.querySelector('.personality-text');
558+
if (text){
559+
text.textContent = '';
560+
text.innerHTML = '';
561+
}
562+
if (body){
563+
if (message) body.dataset.fusionError = message;
564+
body.dataset.fusionFailed = 'true';
565+
}
444566
setPersonalityMode('raw');
567+
let btn = document.querySelector('.toggle-btn');
568+
if (btn){
569+
btn.disabled = true;
570+
btn.style.display = 'none';
571+
btn.textContent = 'AI报告生成失败';
572+
}
445573
}
446574

447575
function togglePersonalityMode(){
576+
let bodyForMode = document.getElementById('personalityBody');
577+
if (bodyForMode && bodyForMode.dataset.fusionFailed === 'true') return;
448578
let rawForMode = document.getElementById('personalityRaw');
449579
let btnForMode = document.querySelector('.toggle-btn');
450580
let currentMode = btnForMode && btnForMode.dataset.personalityMode;
@@ -981,51 +1111,32 @@ function render(d){
9811111
h += '</div>';
9821112
}
9831113

984-
// 性格与家境分析
1114+
// 性格分析
9851115
h += '</div></details>';
9861116
h += '</section>';
9871117

9881118
let fusionReady = d.personality && d.personality._fusion_ready;
1119+
let personalityEvidence = d.personality ? _buildPersonalityEvidence(d.personality) : '';
9891120
if (d.personality || d.family){
9901121
h += '<section class=report-section id=section-personality>';
991-
h += '<div class=report-section-head><div><span>性格关系</span><h2>行为模式与家庭背景</h2></div><p>默认阅读报告正文,需要核对时再展开规则依据。</p></div>';
1122+
h += '<div class=report-section-head><div><span>性格关系</span><h2>行为模式</h2></div><p>默认阅读报告正文,需要核对时再展开规则依据。</p></div>';
9921123
h += _buildModulePrompts('性格', ['这段性格最需要注意什么?', '这会如何影响关系?', '这条判断依据是什么?']);
9931124
}
9941125
if (d.personality){
995-
h += '<div class=section-title>' + (fusionReady ? '性格与家境' : '性格') + ' <span class=ask-ai-btn onclick="event.stopPropagation();openChat(\'性格\')">问AI</span></div>';
1126+
h += '<div class=section-title>性格 <span class=ask-ai-btn onclick="event.stopPropagation();openChat(\'性格\')">问AI</span></div>';
9961127
h += '<div class=info-panel>';
9971128

998-
// ── 病药高亮卡片 ──
999-
if (!fusionReady && d.personality.bingyao_combos && d.personality.bingyao_combos.length){
1000-
h += _buildBingyaoCard(d.personality.bingyao_combos);
1001-
}
1002-
1003-
// ── 十神排行 ──
1004-
if (!fusionReady && d.personality.weighted_shishen && d.personality.weighted_shishen.scores){
1005-
h += _buildShishenRank(d.personality.weighted_shishen.scores);
1006-
}
1007-
10081129
// ── 性格内容区 ──
10091130
h += '<div class=personality-body id=personalityBody>';
10101131
h += '<div class=personality-text>';
10111132
if (fusionReady){
10121133
h += '<span class=fusion-placeholder>⏳ AI 融合报告生成中...</span>';
1134+
} else if (personalityEvidence) {
1135+
h += '<div class=evidence-fallback-note>当前显示可核对的分析依据</div>';
10131136
} else {
1014-
h += d.personality.profile;
1137+
h += '<div class=empty-state>暂无可核对的性格分析依据</div>';
10151138
}
10161139
h += '</div>';
1017-
1018-
// 六维度网格:仅非融合模式显示
1019-
if (!fusionReady){
1020-
h += '<div class=traits-grid>';
1021-
let traitLabels = {社交:'社交',感情:'感情',决策:'决策',内心:'内心',事业:'事业',财富观:'财富观'};
1022-
for (let tk in traitLabels){
1023-
if (d.personality.traits && d.personality.traits[tk]){
1024-
h += '<div class=trait-tile><span class=trait-label>' + tk + '</span><span class=trait-text>' + d.personality.traits[tk] + '</span></div>';
1025-
}
1026-
}
1027-
h += '</div>';
1028-
}
10291140
h += '</div>';
10301141

10311142
if (fusionReady){
@@ -1039,34 +1150,19 @@ function render(d){
10391150
h += '<div class=fusion-feedback-detail hidden><span>哪部分偏差最大?</span>';
10401151
h += '<div class=fusion-section-options>';
10411152
h += '<button type=button onclick="submitFusionFeedback(\'core\')">核心画像</button>';
1042-
h += '<button type=button onclick="submitFusionFeedback(\'moments\')">三个瞬间</button>';
10431153
h += '<button type=button onclick="submitFusionFeedback(\'analysis\')">重点分析</button>';
1044-
h += '<button type=button onclick="submitFusionFeedback(\'misunderstood\')">容易被误解</button>';
1154+
h += '<button type=button onclick="submitFusionFeedback(\'structure\')">分类与结构</button>';
10451155
h += '<button type=button class=fusion-feedback-skip onclick="submitFusionFeedback(\'\')">跳过</button>';
10461156
h += '</div></div><div class=fusion-feedback-status role=status></div></div>';
10471157
}
10481158

10491159
// ── 融合/原始切换 ──
1050-
if (fusionReady){
1051-
h += '<div class=personality-toggle><button class=toggle-btn data-personality-mode=fusion onclick="togglePersonalityMode()" title="查看规则引擎原始数据">查看原始数据</button></div>';
1160+
if (fusionReady && personalityEvidence){
1161+
h += '<div class=personality-toggle><button class=toggle-btn data-personality-mode=fusion onclick="togglePersonalityMode()" title="查看本次分析采用的规则依据">查看分析依据</button></div>';
10521162
h += '<div class=personality-raw id=personalityRaw style="display:none">';
1053-
// 病药
1054-
if (d.personality.bingyao_combos && d.personality.bingyao_combos.length){
1055-
h += _buildBingyaoCard(d.personality.bingyao_combos);
1056-
}
1057-
// 十神排行
1058-
if (d.personality.weighted_shishen && d.personality.weighted_shishen.scores){
1059-
h += _buildShishenRank(d.personality.weighted_shishen.scores);
1060-
}
1061-
// profile + 六维度
1062-
h += '<div class=personality-profile-raw>' + (d.personality.profile || '') + '</div>';
1063-
h += '<div class=traits-grid>';
1064-
for (let tk2 in {社交:'社交',感情:'感情',决策:'决策',内心:'内心',事业:'事业',财富观:'财富观'}){
1065-
if (d.personality.traits && d.personality.traits[tk2]){
1066-
h += '<div class=trait-tile><span class=trait-label>' + tk2 + '</span><span class=trait-text>' + d.personality.traits[tk2] + '</span></div>';
1067-
}
1068-
}
1069-
h += '</div></div>';
1163+
h += personalityEvidence + '</div>';
1164+
} else if (personalityEvidence) {
1165+
h += '<div class=personality-raw id=personalityRaw>' + personalityEvidence + '</div>';
10701166
}
10711167

10721168
h += '</div>';

0 commit comments

Comments
 (0)