Files
Jyotisha/frontend/src/components/rectification-segment-delivery.tsx
T
jesse-uxandJesse_Chen 6e16dc5e16 fix(rectification): keep one delivery and adopt with the birth date
Accept and GET now decide with the same birth snapshot as the answer, so an under-age probe cannot flip can_adopt. The exit skips a second write when this reply already delivered. The card stays without an adopt button when adoption is not allowed, and a refusal shows the Chinese sentence.
2026-10-06 16:03:57 +08:00

84 lines
5.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { SIGN_LABELS, TIER_LABELS, type SegmentSummary } from "@/lib/rectification-agentic/core/segment-summary";
import type { RangeDeliveryProjection } from "@/lib/rectification-agentic/v9/divergence-panel";
import { RECTIFICATION_USER_COPY, REPRESENTATIVE_MINUTE_DISCLAIMER } from "@/lib/rectification-agentic/user-copy";
import { SWITCH_TO_RECTIFIED_LABEL } from "@/lib/rectification-agentic/birth-time-provenance";
import { RectificationVerificationReport } from "@/components/rectification-verification-report";
import { protectVargaReport } from "@/lib/rectification-agentic/v9/delivery-window";
import { unknownProductDeliveryCopy } from "@/lib/rectification-agentic/v9/product-delivery";
export function RectificationSegmentDelivery({ summary, delivery, rangeLabel, selection, resultId, busy, accepting, onAccept, adoptLocked, markdown, candidateDate, canAdopt = false }: Readonly<{
summary: SegmentSummary | null;
delivery: RangeDeliveryProjection;
rangeLabel: string | null;
selection?: Readonly<{ resultId: string; candidateId: string; date: string; time: string; contract: "segment-v1" }> | null;
resultId: string;
busy: boolean;
accepting: boolean;
onAccept: (candidateId: string) => void;
adoptLocked?: boolean;
markdown?: string | null;
candidateDate?: string;
canAdopt?: boolean;
}>) {
const adoption = summary?.adoption_minute;
const candidateId = delivery.representative_candidate_id;
const exactAdoption = Boolean(adoption?.date && selection?.contract === "segment-v1" && selection.resultId === resultId
&& selection.candidateId === candidateId && selection.date === adoption.date && selection.time === adoption.time);
// BUG-1173: a post-adopt answer can move the server's adoption minute. Only when the adopted
// minute left the verdict (server `readopt_minute`) does the button come back, as 「改用 HH:MM」.
const readopt = delivery.readopt_minute ?? null;
const priorAdoption = selection?.contract === "segment-v1" && selection.resultId === resultId ? selection : null;
const adopted = exactAdoption || Boolean(priorAdoption && !readopt);
const charts = summary?.charts.filter((chart) => chart.tier !== "blocked") ?? [];
const blocked = summary?.charts.some((chart) => chart.tier === "blocked") === true;
const reportBlocked = !summary || blocked;
const showAdopt = Boolean(canAdopt && adoption && candidateId && !adoptLocked);
return (
<section className="rectification-candidates rectification-range-delivery" aria-label="生时校正盘型">
<div className="rectification-candidates-heading"><strong>用哪张盘解读</strong></div>
<ul className="rectification-range-delivery__shared">
{charts.map((chart) => (
<li key={chart.chart}>
{chart.chart} 上升:{chart.sign === null ? "分不开" : SIGN_LABELS[chart.sign]}
({TIER_LABELS[chart.tier]}{chart.tied ? ",头段并列" : ""})
{chart.tier !== "certain" && chart.alternatives.length > 0
? `,备选:${chart.alternatives.map((sign) => SIGN_LABELS[sign]).join("、")}` : ""}
</li>
))}
</ul>
{blocked ? <p className="rectification-range-delivery__narrow">分盘解读需要更准的出生时间</p> : null}
{rangeLabel ? <p className="rectification-range-delivery__muted">分钟范围 {rangeLabel}</p> : null}
{!summary && delivery.product_delivery ? <p className="rectification-range-delivery__indistinct">{unknownProductDeliveryCopy(delivery.product_delivery)}</p> : null}
{summary && !summary.scan_complete ? <p className="rectification-range-delivery__muted">完整窗口扫描暂不可用;候选盘型不能证明全窗一致。{candidateDate ? `候选代表日期 ${candidateDate};原填报日期保留。` : ""}</p> : null}
{adoptLocked ? <p className="rectification-range-delivery__narrow">{RECTIFICATION_USER_COPY.rangeDeliveryAdoptLocked}</p> : null}
{delivery.provenance_line ? <p className="rectification-range-delivery__provenance">{delivery.provenance_line}</p> : null}
{showAdopt && adoption && candidateId ? (
<>
<p className="rectification-range-delivery__muted">采用后按 {adoption.date ? `${adoption.date} ` : ""}{adoption.time} 排盘;原填报资料保留。</p>
<button type="button" className="rectification-range-delivery__accept" disabled={busy || adopted}
aria-pressed={adopted} onClick={() => onAccept(candidateId)}>
{accepting ? RECTIFICATION_USER_COPY.rangeDeliveryAdopting : adopted ? RECTIFICATION_USER_COPY.rangeDeliveryAdopted
: readopt && priorAdoption ? `改用 ${readopt}`
: delivery.record_conflict ? SWITCH_TO_RECTIFIED_LABEL : `改用 ${charts.map((chart) => chart.chart).join(" + ")} 盘解读`}
</button>
{readopt && priorAdoption && !adopted ? (
<p className="rectification-range-delivery__switch-note">现在按 {priorAdoption.time} 排盘;改用后按 {readopt} 排盘,原填报资料保留。</p>
) : null}
{delivery.record_conflict && !adopted ? (
<p className="rectification-range-delivery__switch-note">
选它之后,排盘会从出生记录时间 {delivery.record_conflict.reported_time} 换成 {adoption.date ? `${adoption.date} ` : ""}{adoption.time}。
</p>
) : null}
</>
) : adopted ? (
<p className="rectification-range-delivery__muted">{RECTIFICATION_USER_COPY.rangeDeliveryAdopted}</p>
) : null}
<p className="rectification-range-delivery__boundary">{delivery.boundary || REPRESENTATIVE_MINUTE_DISCLAIMER}</p>
<RectificationVerificationReport markdown={protectVargaReport(markdown === undefined ? delivery.verification_markdown : markdown, reportBlocked, delivery.product_delivery?.targets ?? summary?.targets,
!summary ? "完整窗口扫描暂不可用;不能提供分盘解读推荐。" : undefined)} />
</section>
);
}