diff --git a/jyotish-app/main.js b/jyotish-app/main.js index c9ef3b5e..b4e5c88f 100644 --- a/jyotish-app/main.js +++ b/jyotish-app/main.js @@ -5580,6 +5580,11 @@ function buildRelationshipReportTemplate(data = {}, deep = null) { const spouseStatus = deep?.spouseStatus || null; const ulDkTiming = deep?.ulDkTiming || null; const strictNarrative = data.ai_prompt_pack?.evidence_snapshot?.relationship_narrative || data.relationship_narrative || deep?.relationshipNarrative || null; + const vedastroOverview = data.ai_prompt_pack?.evidence_snapshot?.vedastro_overview + || data.vedastro_overview + || deep?.vedastroOverview + || null; + const lifeEventGraph = data.life_event_graph || deep?.lifeEventGraph || null; const strictMarkdown = typeof strictNarrative?.markdown === 'string' ? strictNarrative.markdown : ''; const strictSecondaryContext = strictMarkdown.includes('public_formalization_candidate') || (Array.isArray(strictNarrative?.strengths) && strictNarrative.strengths.some(item => String(item).includes('public_formalization_candidate'))) @@ -5626,6 +5631,8 @@ function buildRelationshipReportTemplate(data = {}, deep = null) { if (hasPublicFormalizationCandidate) { strengths.push('public_formalization_candidate 说明当前更偏向公开化/关系可见度候选,而不是法律婚姻本身。'); } + const vedastroNarrative = buildVedAstroOverviewNarrative(vedastroOverview); + if (vedastroNarrative) strengths.push(vedastroNarrative); if (!strengths.length) strengths.push('尚未出现足够稳定的强项,建议先补完整出生盘与 D9 复核。'); const risks = weakKutas.map(item => `${item.name} ${item.score}/${item.max}:需重点观察${relationshipKutaMeaning(item.name)}。`); if (kuja?.verdict === 'hard' || kuja?.verdict === 'watch') risks.push(kuja.note || kuja.label); @@ -5637,6 +5644,9 @@ function buildRelationshipReportTemplate(data = {}, deep = null) { if (hasPublicFormalizationCandidate) { risks.push('当前即便存在合盘支持与公开化候选,也不能误读成接近结婚;若 weak core promise、dual dasha 或 external timing 未收敛,仍应保持保守。'); } + if (vedastroOverview?.status === 'ok') { + risks.push('VedAstro 概览提示只代表单日外部雷达快照,overview only,不替代长周期精扫与本地主裁决。'); + } if (!hasDeep) risks.push('当前只完成基础 Ashtakoot,缺少 D9、Kuja Dosha 与 Dasha 同步证据。'); if (!risks.length) risks.push('未见明显红旗,但仍需结合双方完整星盘与现实互动。'); const evidence = [ @@ -5661,6 +5671,7 @@ function buildRelationshipReportTemplate(data = {}, deep = null) { '先确认双方出生时间精度,再复核 D9 与 7宫/7主。', '把低分 Kuta 对应到现实互动议题,不用单一分数替代沟通。', '若 Kuja 或 Dasha 出现压力,优先安排边界、节奏和冲突处理观察期。', + ...(vedastroOverview?.status === 'ok' ? ['先把 VedAstro 概览提示当作外部补证,再决定是否值得做长周期精扫。'] : []), ...(hasPublicFormalizationCandidate ? [ '若当前更偏向 public_formalization_candidate,请把它理解为关系公开化候选,而不是婚姻逼近。', '先不要把高 Ashtakoot 分数翻译成婚姻逼近,应先复核 promise、dual dasha 与 external timing。', @@ -5669,12 +5680,23 @@ function buildRelationshipReportTemplate(data = {}, deep = null) { boundaries: [ '合盘报告只提供传统 Jyotish 证据,不替代个人选择。', '总分通过不等于关系必然稳定;总分偏低也不等于关系不可经营。', + ...(vedastroOverview?.status === 'ok' ? ['VedAstro 概览提示属于 main-entry overview only,不替代长周期精扫,也不得越权改写主标签。'] : []), ...(hasPublicFormalizationCandidate ? ['public_formalization_candidate 只表示公开化候选,不得越权抬升 legal_marriage,也不能误读成接近结婚。'] : []), ...((Array.isArray(strictNarrative?.boundaries) ? strictNarrative.boundaries : [])), ].slice(0, 5), + vedastroOverview, + lifeEventGraph, }; } +function buildVedAstroOverviewNarrative(overview) { + if (!overview || typeof overview !== 'object' || overview.status !== 'ok') return ''; + const marriage = overview.top_events_by_domain?.marriage || null; + const label = marriage?.signal_label || marriage?.event_id || '外部婚恋窗口'; + const when = marriage?.start || overview.reference_date || '-'; + return `VedAstro 概览提示:${label}(${when});这是 main-entry overview only,不替代长周期精扫。`; +} + function collectSpouseStatusStrengths(spouseStatus) { return ['self', 'partner'].map(key => { const item = spouseStatus?.[key]; @@ -6188,6 +6210,7 @@ function renderRelationshipReport(report = {}) { `).join('')} + ${renderLifeEventGraphSummary(report.lifeEventGraph, report.vedastroOverview)}
${renderRelationshipReportList('支持证据', report.strengths)} ${renderRelationshipReportList('需要观察', report.risks)} @@ -6200,6 +6223,28 @@ function renderRelationshipReport(report = {}) { `; } +function renderLifeEventGraphSummary(graph, vedastroOverview) { + const nodes = Array.isArray(graph?.event_nodes) ? graph.event_nodes : []; + const overviewNode = nodes.find(node => node?.kind === 'external_overview'); + const externalWindow = nodes.find(node => node?.kind === 'external_window'); + if (!overviewNode && !externalWindow && vedastroOverview?.status !== 'ok') return ''; + const items = []; + if (overviewNode) { + items.push(`VedAstro main-entry overview · ${overviewNode.search_scope || 'single_day_overview'} · ${overviewNode.reference_date || '-'}`); + } else if (vedastroOverview?.status === 'ok') { + items.push(`VedAstro main-entry overview · ${vedastroOverview.search_scope || 'single_day_overview'} · ${vedastroOverview.reference_date || '-'}`); + } + if (externalWindow) { + items.push(`${externalWindow.label || 'external_window'} · ${externalWindow.start || '-'}`); + } + return ` +
+ Life Event Graph 摘要 + ${(items.length ? items : ['external_overview']).map(item => `${escapeHtml(item)}`).join('')} +
+ `; +} + function renderRelationshipReportList(label, items = []) { const list = Array.isArray(items) && items.length ? items : ['暂无可展示条目。']; return ` diff --git a/tests/test_frontend_productization.py b/tests/test_frontend_productization.py index 5f11dfe8..fb0c8d73 100644 --- a/tests/test_frontend_productization.py +++ b/tests/test_frontend_productization.py @@ -1011,6 +1011,21 @@ def test_trust_center_exposes_user_runnable_vedastro_range_scan() -> None: assert token in main +def test_main_relationship_report_and_life_graph_surface_vedastro_overview_tokens() -> None: + main = read("main.js") + + for token in [ + "VedAstro 概览提示", + "overview only,不替代长周期精扫", + "buildVedAstroOverviewNarrative", + "renderLifeEventGraphSummary", + "VedAstro main-entry overview", + "single_day_overview", + "external_overview", + ]: + assert token in main + + def test_github_release_quality_gate_runs_browser_release_profile() -> None: workflow = (ROOT / ".github" / "workflows" / "release-quality-gate.yml").read_text(encoding="utf-8") for token in [ @@ -1454,6 +1469,31 @@ def test_rectification_ui_exposes_decision_plan_and_execution_order() -> None: assert "rect-plan" in rect_ui +def test_rectification_ui_guides_yes_no_interview_from_existing_event_assets() -> None: + rect_engine = read("rectification-engine.js") + rect_ui = read("rectification.js") + + for token in [ + "buildRectificationInterviewQuestions", + "rectificationInterviewAnswersToEvents", + "EVENT_COLLECTION_GUIDE", + "EVENT_CATEGORIES", + "guided_rectification_interview", + ]: + assert token in rect_engine + + for token in [ + "rect-interview", + 'data-rect-answer="yes"', + 'data-rect-answer="no"', + 'data-rect-answer="other"', + "collectInterviewEvents", + "rect-window-start", + "rect-window-end", + ]: + assert token in rect_ui + + def test_remedies_ui_keeps_evidence_boundary_and_hidden_json() -> None: main = read("main.js") style = read("style.css")