@@ -384,7 +384,7 @@ let TERMS = {
384384function _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+
419532function 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
441555function 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
447575function 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