@@ -1,28 +1,29 @@
/**
* Editorial personal-report reader inspired by a research memo:
*
* 1. Subject, metadata and four fast facts
* 2. Executive judgement and priorities
* 3. D1 chart with a compac t evidence table
* 4. Additional charts and thematic narrativ es
* 5. E vidence appendix and disclaimer
* 1. Subject, metadata and fast facts
* 2. Executive judgement and v2 report scope
* 3. Natal foundation and structured char t evidence
* 4. Thematic/current-phase narratives and action not es
* 5. Blocked disclosures, e vidence appendix, provenance and disclaimer
*
* The visual order is deliberately answer-first. All content still comes from
* the validated ReportDocument v1 ; no report text, chart or evidence is
* fabricated in the reader.
* The visual order is deliberately answer-first. All content comes from the
* validated stored v1 or current v2 ReportDocument; no report text, chart,
* HTML, CSS or SVG is fabricated from model output in the reader.
*/
"use client" ;
import { useMemo } from "react" ;
import { findChart , VedicChartSvg } from "./vedic-chart-svg" ;
import type {
ChartV1 ,
ClaimStatus ,
EvidenceAppendix ,
ReportDocumentV1 ,
ThematicSectionV1 ,
import { hasRealChartData , VedicChartSvg } from "./vedic-chart-svg" ;
import {
isReportDocumentV2 ,
type ChartV1 ,
type ClaimStatus ,
type EvidenceAppendix ,
type ReportDocument ,
type ReportDocumentV2 ,
} from "@/lib/personal-report-contract" ;
const CLAIM_STATUS_LABELS : Record < ClaimStatus , string > = {
@@ -53,11 +54,36 @@ const CONFLICT_STATUS_LABELS: Record<string, string> = {
resolved : "已解决" ,
} ;
const DEPTH_LABELS : Record < ReportDocumentV2 [ "depth" ] , string > = {
concise : "精简" ,
standard : "标准" ,
deep : "深入" ,
research : "研究" ,
} ;
const THEME_LABELS : Record < string , string > = {
general : "综合" ,
career : "事业" ,
wealth : "财富" ,
marriage : "婚恋" ,
health : "健康" ,
education : "教育" ,
timing : "应期" ,
} ;
const ACTION_PRIORITY_LABELS : Record < ReportDocumentV2 [ "actionNotes" ] [ number ] [ "priority" ] , string > = {
now : "现在" ,
next : "下一步" ,
watch : "持续观察" ,
} ;
const PLANET_ALIASES = {
sun : [ "太阳" , "Sun" , "Surya" ] ,
moon : [ "月亮" , "月球" , "Moon" , "Chandra" ] ,
} as const ;
type ReportChart = ReportDocument [ "charts" ] [ number ] ;
function claimStatusLabel ( status : string ) : string {
return CLAIM_STATUS_LABELS [ status as ClaimStatus ] ? ? status ;
}
@@ -125,7 +151,39 @@ function planetPlacement(chart: ChartV1 | undefined, aliases: readonly string[])
return planet ? ` ${ planet . sign } · 第 ${ planet . houseNumber } 宫 ` : "暂无数据" ;
}
function ReportCover ( { document , d1 } : { document : ReportDocumentV1 ; d1 : ChartV1 | undefined } ) {
function themeLabel ( theme : string ) : string {
return THEME_LABELS [ theme ] ? ? theme ;
}
function chartEvidenceRefs ( chart : ReportChart ) : readonly string [ ] {
return "evidenceRefs" in chart ? chart . evidenceRefs : [ ] ;
}
function structuredChart ( chart : ReportChart ) : ChartV1 {
// V2 only widens the allowed chart id set and adds evidenceRefs; the
// structured houses/planets consumed by VedicChartSvg are unchanged.
return chart as ChartV1 ;
}
function hasStructuredChartData ( chart : ReportChart ) : boolean {
return hasRealChartData ( structuredChart ( chart ) ) ;
}
function EvidenceLinks ( { refs , knownEvidenceIds } : {
refs : readonly string [ ] ;
knownEvidenceIds : ReadonlySet < string > ;
} ) {
if ( refs . length === 0 ) return null ;
return (
< ul className = "personal-report-evidence-links" >
{ refs . map ( ( ref ) = > knownEvidenceIds . has ( ref ) ? (
< li key = { ref } > < a href = { ` #evidence- ${ ref } ` } > 证 据 · { ref } < / a > < / li >
) : < li key = { ref } > 证 据 · { ref } < / li > ) }
< / ul >
) ;
}
function ReportCover ( { document , d1 } : { document : ReportDocument ; d1 : ChartV1 | undefined } ) {
const risingSign = d1 ? . houses . find ( ( house ) = > house . houseNumber === 1 ) ? . sign ? ? "暂无数据" ;
const facts = [
{ value : risingSign , label : "上升星座" } ,
@@ -133,6 +191,7 @@ function ReportCover({ document, d1 }: { document: ReportDocumentV1; d1: ChartV1
{ value : planetPlacement ( d1 , PLANET_ALIASES . moon ) , label : "月亮落点" } ,
{ value : claimStatusLabel ( document . executiveSummary . overallClaimStatus ) , label : "证据状态" } ,
] ;
const v2 = isReportDocumentV2 ( document ) ? document : null ;
return (
< header className = "personal-report-cover personal-report-section" >
@@ -145,6 +204,8 @@ function ReportCover({ document, d1 }: { document: ReportDocumentV1; d1: ChartV1
< div > < dt > 出 生 地 < / dt > < dd > { document . subject . birthPlaceLabel } < / dd > < / div >
< div > < dt > 生 时 状 态 < / dt > < dd > { BIRTH_TIME_STATUS_LABELS [ document . subject . birthTimeStatus ] ? ? document . subject . birthTimeStatus } < / dd > < / div >
< div > < dt > 呈 现 模 式 < / dt > < dd > { document . presentationMode === "research" ? "研究模式" : "标准模式" } < / dd > < / div >
{ v2 && < div > < dt > 报 告 深 度 < / dt > < dd > { DEPTH_LABELS [ v2 . depth ] } < / dd > < / div > }
{ v2 && < div > < dt > 请 求 主 题 < / dt > < dd > { v2 . requestedThemes . map ( themeLabel ) . join ( "、" ) } < / dd > < / div > }
< div > < dt > 生 成 日 期 < / dt > < dd > { formatGeneratedAt ( document . generatedAt ) } < / dd > < / div >
< / dl >
< / div >
@@ -160,8 +221,12 @@ function ReportCover({ document, d1 }: { document: ReportDocumentV1; d1: ChartV1
) ;
}
function ExecutiveSummarySection ( { document } : { document : ReportDocumentV1 } ) {
function ExecutiveSummarySection ( { document , knownEvidenceIds } : {
document : ReportDocument ;
knownEvidenceIds : ReadonlySet < string > ;
} ) {
const summary = document . executiveSummary ;
const evidenceRefs = "evidenceRefs" in summary ? summary . evidenceRefs : [ ] ;
return (
< section aria-labelledby = "report-summary" className = "personal-report-section personal-report-summary" >
< div className = "personal-report-section-heading" >
@@ -182,6 +247,36 @@ function ExecutiveSummarySection({ document }: { document: ReportDocumentV1 }) {
) }
< / >
) }
< EvidenceLinks refs = { evidenceRefs } knownEvidenceIds = { knownEvidenceIds } / >
< / section >
) ;
}
function NatalFoundationSection ( { document , knownEvidenceIds } : {
document : ReportDocumentV2 ;
knownEvidenceIds : ReadonlySet < string > ;
} ) {
const section = document . natalFoundation ;
return (
< section aria-labelledby = "report-natal-foundation" className = "personal-report-section personal-report-themes" >
< div className = "personal-report-section-heading" >
< h2 id = "report-natal-foundation" > { section . title } < / h2 >
< / div >
< article className = "personal-report-theme" >
< header > < h3 > 本 命 基 础 判 断 < / h3 > < ClaimStatus status = { section . claimStatus } / > < / header >
< NarrativeText text = { section . narrative } / >
{ ( section . keyFactors . length > 0 || section . caveats . length > 0 ) && (
< div className = "personal-report-theme-details" >
{ section . keyFactors . length > 0 && (
< div > < h4 > 关 键 因 素 < / h4 > < ul > { section . keyFactors . map ( ( factor , index ) = > < li key = { index } > { factor } < / li > ) } < / ul > < / div >
) }
{ section . caveats . length > 0 && (
< div > < h4 > 边 界 与 保 留 < / h4 > < ul > { section . caveats . map ( ( caveat , index ) = > < li key = { index } > { caveat } < / li > ) } < / ul > < / div >
) }
< / div >
) }
< EvidenceLinks refs = { section . evidenceRefs } knownEvidenceIds = { knownEvidenceIds } / >
< / article >
< / section >
) ;
}
@@ -217,7 +312,10 @@ function D1EvidenceTable({ chart }: { chart: ChartV1 }) {
) ;
}
function LeadChartSection ( { chart } : { chart : ChartV1 | undefined } ) {
function LeadChartSection ( { chart , knownEvidenceIds } : {
chart : ReportChart | undefined ;
knownEvidenceIds : ReadonlySet < string > ;
} ) {
if ( ! chart ) return null ;
return (
< section aria-labelledby = "report-charts" className = "personal-report-section personal-report-lead-chart" >
@@ -227,10 +325,11 @@ function LeadChartSection({ chart }: { chart: ChartV1 | undefined }) {
< h2 id = "report-charts" > 本 命 盘 < / h2 >
< span > { chart . title } < / span >
< / div >
< VedicChartSvg chart = { chart } ariaLabel = { ` ${ chart . title } 命盘,共 ${ chart . houses . length } 宫数据 ` } / >
< VedicChartSvg chart = { structuredChart ( chart ) } ariaLabel = { ` ${ chart . title } 命盘,共 ${ chart . houses . length } 宫数据 ` } / >
< figcaption > 图 1 · { chart . title } 。 数 字 为 宫 位 编 号 , 宫 内 列 出 星 座 与 主 要 落 点 。 < / figcaption >
< EvidenceLinks refs = { chartEvidenceRefs ( chart ) } knownEvidenceIds = { knownEvidenceIds } / >
< / figure >
< D1EvidenceTable chart = { chart } / >
< D1EvidenceTable chart = { structuredChart ( chart ) } / >
< / div >
< / section >
) ;
@@ -256,7 +355,10 @@ function PlanetTable({ chart }: { chart: ChartV1 }) {
) ;
}
function AdditionalChartsSection ( { charts } : { charts : ChartV1 [ ] } ) {
function AdditionalChartsSection ( { charts , knownEvidenceIds } : {
charts : ReportChart [ ] ;
knownEvidenceIds : ReadonlySet < string > ;
} ) {
if ( charts . length === 0 ) return null ;
return (
< section aria-labelledby = "report-additional-charts" className = "personal-report-section personal-report-additional-charts" >
@@ -267,8 +369,9 @@ function AdditionalChartsSection({ charts }: { charts: ChartV1[] }) {
{ charts . map ( ( chart ) = > (
< article key = { chart . id } className = "personal-report-chart-card" >
< header > < h3 > { chart . title } < / h3 > < ClaimStatus status = { chart . claimStatus } / > < / header >
< VedicChartSvg chart = { chart } ariaLabel = { ` ${ chart . title } 命盘,共 ${ chart . houses . length } 宫数据 ` } / >
< PlanetTable chart = { chart } / >
< VedicChartSvg chart = { structuredChart ( chart ) } ariaLabel = { ` ${ chart . title } 命盘,共 ${ chart . houses . length } 宫数据 ` } / >
< PlanetTable chart = { structuredChart ( chart ) } / >
< EvidenceLinks refs = { chartEvidenceRefs ( chart ) } knownEvidenceIds = { knownEvidenceIds } / >
< / article >
) ) }
< / div >
@@ -276,24 +379,10 @@ function AdditionalChartsSection({ charts }: { charts: ChartV1[] }) {
) ;
}
function EvidenceChips ( { section , knownEvidenceIds } : { section : ThematicSectionV1 ; knownEvidenceIds : Set < string > } ) {
if ( section . evidenceRefs . length === 0 ) return null ;
return (
< ul className = "personal-report-evidence-links" >
{ section . evidenceRefs . map ( ( ref ) = > knownEvidenceIds . has ( ref ) ? (
< li key = { ref } > < a href = { ` #evidence- ${ ref } ` } > 证 据 · { ref } < / a > < / li >
) : < li key = { ref } > 证 据 · { ref } < / li > ) }
< / ul >
) ;
}
function ThematicNarrativeSection ( { document } : { document : ReportDocumentV1 } ) {
const knownEvidenceIds = useMemo ( ( ) = > new Set ( [
. . . document . evidenceAppendix . techniqueAudit . map ( ( row ) = > row . id ) ,
. . . document . evidenceAppendix . conflicts . map ( ( row ) = > row . id ) ,
. . . document . evidenceAppendix . calculationEvidence . map ( ( row ) = > row . id ) ,
] ) , [ document ] ) ;
function ThematicNarrativeSection ( { document , knownEvidenceIds } : {
document : ReportDocument ;
knownEvidenceIds : ReadonlySet < string > ;
} ) {
if ( document . thematicNarrative . length === 0 ) return null ;
return (
< section aria-labelledby = "report-themes" className = "personal-report-section personal-report-themes" >
@@ -310,14 +399,93 @@ function ThematicNarrativeSection({ document }: { document: ReportDocumentV1 })
{ ( section . actions . length > 0 || section . caveats . length > 0 ) && (
< div className = "personal-report-theme-details" >
{ section . actions . length > 0 && (
< div > < h4 > 可 执 行 建 议 < / h4 > < ul > { section . actions . map ( ( action , actionIndex ) = > < li key = { actionIndex } > { action } < / li > ) } < / ul > < / div >
< div > < h4 > 可 执 行 建 议 < / h4 > < ul > { section . actions . map ( ( action , index ) = > < li key = { index } > { action } < / li > ) } < / ul > < / div >
) }
{ section . caveats . length > 0 && (
< div > < h4 > 待 核 验 与 边 界 < / h4 > < ul > { section . caveats . map ( ( caveat , caveatIndex ) = > < li key = { caveatIndex } > { caveat } < / li > ) } < / ul > < / div >
< div > < h4 > 待 核 验 与 边 界 < / h4 > < ul > { section . caveats . map ( ( caveat , index ) = > < li key = { index } > { caveat } < / li > ) } < / ul > < / div >
) }
< / div >
) }
< EvidenceChips section = { section } knownEvidenceIds = { knownEvidenceIds } / >
< EvidenceLinks refs = { section . evidenceRefs } knownEvidenceIds = { knownEvidenceIds } / >
< / article >
) ) }
< / section >
) ;
}
function CurrentPhaseSection ( { document , knownEvidenceIds } : {
document : ReportDocumentV2 ;
knownEvidenceIds : ReadonlySet < string > ;
} ) {
const section = document . currentPhase ;
if ( ! section ) return null ;
return (
< section aria-labelledby = "report-current-phase" className = "personal-report-section personal-report-themes" >
< div className = "personal-report-section-heading" >
< p > { section . phaseLabel } < / p >
< h2 id = "report-current-phase" > { section . title } < / h2 >
< / div >
< article className = "personal-report-theme" >
< header > < h3 > 阶 段 说 明 < / h3 > < ClaimStatus status = { section . claimStatus } / > < / header >
< NarrativeText text = { section . narrative } / >
{ ( section . timingNotes . length > 0 || section . caveats . length > 0 ) && (
< div className = "personal-report-theme-details" >
{ section . timingNotes . length > 0 && (
< div > < h4 > 阶 段 观 察 < / h4 > < ul > { section . timingNotes . map ( ( note , index ) = > < li key = { index } > { note } < / li > ) } < / ul > < / div >
) }
{ section . caveats . length > 0 && (
< div > < h4 > 应 期 边 界 < / h4 > < ul > { section . caveats . map ( ( caveat , index ) = > < li key = { index } > { caveat } < / li > ) } < / ul > < / div >
) }
< / div >
) }
< EvidenceLinks refs = { section . evidenceRefs } knownEvidenceIds = { knownEvidenceIds } / >
< / article >
< / section >
) ;
}
function ActionNotesSection ( { document , knownEvidenceIds } : {
document : ReportDocumentV2 ;
knownEvidenceIds : ReadonlySet < string > ;
} ) {
if ( document . actionNotes . length === 0 ) return null ;
return (
< section aria-labelledby = "report-action-notes" className = "personal-report-section personal-report-themes" >
< div className = "personal-report-section-heading" >
< h2 id = "report-action-notes" > 行 动 记 录 < / h2 >
< / div >
{ document . actionNotes . map ( ( note ) = > (
< article key = { note . id } className = "personal-report-theme" >
< header > < h3 > { note . title } < / h3 > < span className = "personal-report-claim-status" > 优 先 级 : { ACTION_PRIORITY_LABELS [ note . priority ] } < / span > < / header >
< NarrativeText text = { note . note } / >
< EvidenceLinks refs = { note . evidenceRefs } knownEvidenceIds = { knownEvidenceIds } / >
< / article >
) ) }
< / section >
) ;
}
function BlockedConflictDisclosureSection ( { document , knownEvidenceIds } : {
document : ReportDocumentV2 ;
knownEvidenceIds : ReadonlySet < string > ;
} ) {
if ( document . blockedConflictDisclosure . length === 0 ) return null ;
return (
< section aria-labelledby = "report-blocked-disclosures" className = "personal-report-section personal-report-themes" >
< div className = "personal-report-section-heading" >
< h2 id = "report-blocked-disclosures" > 阻 塞 与 冲 突 披 露 < / h2 >
< / div >
{ document . blockedConflictDisclosure . map ( ( section , index ) = > (
< article key = { ` ${ section . theme } - ${ index } ` } className = "personal-report-theme" >
< header > < h3 > { themeLabel ( section . theme ) } · { section . title } < / h3 > < ClaimStatus status = { section . claimStatus } / > < / header >
< NarrativeText text = { section . reason } / >
< div className = "personal-report-theme-details" >
< div > < h4 > 缺 失 证 据 < / h4 > < ul > { section . missingEvidence . map ( ( item , itemIndex ) = > < li key = { itemIndex } > { item } < / li > ) } < / ul > < / div >
{ section . conflictNotes . length > 0 && (
< div > < h4 > 冲 突 说 明 < / h4 > < ul > { section . conflictNotes . map ( ( note , noteIndex ) = > < li key = { noteIndex } > { note } < / li > ) } < / ul > < / div >
) }
< / div >
< EvidenceLinks refs = { section . evidenceRefs } knownEvidenceIds = { knownEvidenceIds } / >
< / article >
) ) }
< / section >
@@ -345,7 +513,7 @@ function AuditTable({ appendix }: { appendix: EvidenceAppendix }) {
) ;
}
function EvidenceAppendixSection ( { document } : { document : ReportDocumentV1 } ) {
function EvidenceAppendixSection ( { document } : { document : ReportDocument } ) {
const appendix = document . evidenceAppendix ;
const expandedByDefault = appendix . expandedByDefault || document . presentationMode === "research" ;
const hasContent = appendix . techniqueAudit . length > 0 || appendix . conflicts . length > 0
@@ -395,28 +563,52 @@ function EvidenceAppendixSection({ document }: { document: ReportDocumentV1 }) {
) ;
}
function DisclaimerSection ( { document } : { document : ReportDocumentV1 } ) {
function ProvenanceDisclaimerSection ( { document } : { document : ReportDocument } ) {
const provenance = document . provenance ;
const skillLabel = provenance . skillName
? ` ${ provenance . skillName } ${ provenance . skillVersion ? ` · ${ provenance . skillVersion } ` : "" } `
: "旧版报告未记录命名技能" ;
return (
< section aria-labelledby = "report-disclaimer" className = "personal-report-section personal-report-disclaimer" >
< h2 id = "report-disclaimer" > 声 明 < / h2 > < p > { document . disclaimer } < / p >
< h2 id = "report-disclaimer" > 来 源 与 声 明 < / h2 >
< dl className = "personal-report-metadata personal-report-avoid-break" >
< div > < dt > 生 成 技 能 < / dt > < dd > { skillLabel } < / dd > < / div >
< div > < dt > 合 同 版 本 < / dt > < dd > { provenance . reportContractVersion } < / dd > < / div >
< div > < dt > 源 提 交 < / dt > < dd > { provenance . skillSourceCommit ? ? "未记录" } < / dd > < / div >
< div > < dt > 技 能 快 照 < / dt > < dd > { provenance . skillSnapshotSha256 } < / dd > < / div >
< div > < dt > 计 算 哈 希 < / dt > < dd > { provenance . calculationHash } < / dd > < / div >
< div > < dt > 证 据 哈 希 < / dt > < dd > { provenance . evidenceHash } < / dd > < / div >
< / dl >
< p > { document . disclaimer } < / p >
< / section >
) ;
}
export function PersonalReportDocumentView ( { document } : { document : ReportDocumentV1 } ) {
const d1 = findChart ( document . charts , "D1" ) ;
const additionalCharts = [ findChart ( document . charts , "D9 " ) , findChart ( document . charts , "D10" ) ]
. filter ( ( chart ) : chart is ChartV1 = > chart !== undefined ) ;
export function PersonalReportDocumentView ( { document } : { document : ReportDocument } ) {
const charts : ReportChart [ ] = [ . . . document . charts ] ;
const d1 = charts . find ( ( chart ) = > chart . id === "D1 " && hasStructuredChartData ( chart ) ) ;
const additionalCharts = charts . filter ( ( chart ) = > chart . id !== "D1" && hasStructuredChartData ( chart ) ) ;
const knownEvidenceIds = useMemo ( ( ) = > new Set ( [
. . . document . evidenceAppendix . techniqueAudit . map ( ( row ) = > row . id ) ,
. . . document . evidenceAppendix . conflicts . map ( ( row ) = > row . id ) ,
. . . document . evidenceAppendix . calculationEvidence . map ( ( row ) = > row . id ) ,
] ) , [ document ] ) ;
const v2 = isReportDocumentV2 ( document ) ? document : null ;
return (
< article className = "personal-report-document" >
< ReportCover document = { document } d1 = { d1 } / >
< ExecutiveSummarySection document = { document } / >
< LeadChartSection chart = { d1 } / >
< AdditionalChartsSection charts = { additionalCharts } / >
< ThematicNarrativeSection document = { document } / >
< article className = "personal-report-document" data-report-schema-version = { document . schemaVersion } >
< ReportCover document = { document } d1 = { d1 ? structuredChart ( d1 ) : undefined } / >
< ExecutiveSummarySection document = { document } knownEvidenceIds = { knownEvidenceIds } / >
{ v2 && < NatalFoundationSection document = { v2 } knownEvidenceIds = { knownEvidenceIds } / > }
< LeadChartSection chart = { d1 } knownEvidenceIds = { knownEvidenceIds } / >
< AdditionalChartsSection charts = { additionalCharts } knownEvidenceIds = { knownEvidenceIds } / >
< ThematicNarrativeSection document = { document } knownEvidenceIds = { knownEvidenceIds } / >
{ v2 && < CurrentPhaseSection document = { v2 } knownEvidenceIds = { knownEvidenceIds } / > }
{ v2 && < ActionNotesSection document = { v2 } knownEvidenceIds = { knownEvidenceIds } / > }
{ v2 && < BlockedConflictDisclosureSection document = { v2 } knownEvidenceIds = { knownEvidenceIds } / > }
< EvidenceAppendixSection document = { document } / >
< DisclaimerSection document = { document } / >
< ProvenanceDisclaimerSection document = { document } / >
< footer className = "personal-report-footer" >
< span > 个 人 命 盘 研 读 · { document . presentationMode === "research" ? "研究模式" : "标准模式" } < / span >
< span > 报 告 编 号 { document . reportId } < / span >