feat(rectification): add refinement packet without unique-minute claims

Surface D9/D10 change minutes, event-dasha match copy, dual-dasha conflict, and a post-adopt consult handoff so users can keep narrowing or start a reading from a representative time.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-08-20 07:47:10 +08:00
co-authored by Cursor
parent 464bc33202
commit a3196584d2
19 changed files with 1384 additions and 40 deletions
@@ -33,6 +33,7 @@ export type ConversationalBirthTimeRectificationProps = Readonly<{
onPendingChange?: (pending: boolean) => void;
onProfileIncomplete?: () => void;
onSaved?: (time: string, status: "accepted" | "confirmed") => void;
onStartConsultation?: () => void;
pendingConsultationQuestion?: string | null;
onRestart?: () => void;
}>;
@@ -101,6 +101,92 @@ function RectificationCandidateCards({
);
}
function RectificationRefinementPanel({
result,
}: Readonly<{ result: RectificationCandidateResult }>) {
const transitions = result.windowTransitions;
const ledger = result.eventDashaLedger;
const agreement = result.dashaAgreement;
const contrast = result.lagnaContrast;
const nakshatra = result.nakshatraBoundary;
const stage = result.precisionStage;
if (
transitions.length === 0
&& ledger.length === 0
&& !agreement
&& !contrast
&& !nakshatra?.near_boundary
&& !stage
) return null;
return (
<section className="rectification-refinement" aria-label="校正收窄观察">
{stage && (
<p className="rectification-refinement__stage">{stage.user_meaning}</p>
)}
{transitions.length > 0 && (
<div>
<strong></strong>
<ul>
{transitions.map((item) => (
<li key={`${item.layer}-${item.at}`}>{item.user_meaning}</li>
))}
</ul>
</div>
)}
{contrast && (
<div>
<strong></strong>
<p>{contrast.user_meaning}</p>
<ul>
{contrast.intervals.map((interval) => (
<li key={`${interval.start}-${interval.end}`}>
{interval.start}-{interval.end}{interval.lagna}
{interval.lords.l1 ? `;一/四/七/十宫主 ${[interval.lords.l1, interval.lords.l4, interval.lords.l7, interval.lords.l10].filter(Boolean).join("、")}` : ""}
</li>
))}
</ul>
</div>
)}
{ledger.length > 0 && (
<div>
<strong></strong>
<ul>
{ledger.map((item) => (
<li key={item.summary}>{item.user_meaning}</li>
))}
</ul>
</div>
)}
{agreement && (
<p>{agreement.user_meaning}</p>
)}
{nakshatra?.near_boundary && nakshatra.user_meaning && (
<div>
<strong></strong>
<p>{nakshatra.user_meaning}</p>
</div>
)}
</section>
);
}
function RectificationOosPanel({
prompts,
}: Readonly<{ prompts: RectificationCandidateResult["oosBlindPrompts"] }>) {
if (prompts.length === 0) return null;
return (
<section className="rectification-refinement" aria-label="盘外对照">
<strong></strong>
<p></p>
<ul>
{prompts.map((item) => (
<li key={item.domain}>{item.user_meaning}</li>
))}
</ul>
</section>
);
}
type RectificationAgenticChatProps = Readonly<{
caseId: string;
sessionId: string;
@@ -115,6 +201,7 @@ type RectificationAgenticChatProps = Readonly<{
onPendingChange?: (pending: boolean) => void;
onProfileIncomplete?: () => void;
onSaved?: (time: string, status: "accepted" | "confirmed") => void;
onStartConsultation?: () => void;
pendingConsultationQuestion?: string | null;
onRestart?: () => void;
}>;
@@ -212,6 +299,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
onPendingChange,
onProfileIncomplete,
onSaved,
onStartConsultation,
pendingConsultationQuestion,
onRestart,
} = props;
@@ -637,10 +725,23 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
<RectificationHouseTableView table={candidateResult.houseTable} />
</div>
)}
{candidateResult && <RectificationRefinementPanel result={candidateResult} />}
{savedTime && (
<p className="rectification-saved" role="status">
{savedStatus === "confirmed" ? "已确认校正时间" : "当前排盘时间(代表性候选,还不能确认唯一分钟)"}{savedTime}使
</p>
<div className="rectification-saved-wrap">
<p className="rectification-saved" role="status">
{savedStatus === "confirmed" ? "已确认校正时间" : "当前排盘时间(代表性候选,还不能确认唯一分钟)"}{savedTime}使
</p>
{savedStatus === "accepted" && candidateResult && (
<RectificationOosPanel prompts={candidateResult.oosBlindPrompts} />
)}
{savedStatus === "accepted" && onStartConsultation && (
<div className="rectification-consult-handoff">
<Button type="button" onClick={onStartConsultation}>
</Button>
</div>
)}
</div>
)}
{error && <p className="error-message" role="alert">{error}</p>}
{readonly && (