Files
Jyotisha/frontend/src/components/rectification-range-delivery.tsx
T
Jesse_ChenandClaude Opus 5.5 7203d94e9c
Independent Staging Quality Gate / validate (push) Successful in 11m49s
Independent Staging Quality Gate / publish (push) Successful in 3m44s
fix(rectification): 区分不开时不显示「最可能」副标题(验收补改)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
2026-09-26 14:45:11 +08:00

175 lines
7.7 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 { windowIntervalsLabel } from "@/lib/rectification-agentic/core/candidate-window";
import type { RectificationCandidateResult } from "@/lib/rectification-candidate-result";
import {
RECTIFICATION_USER_COPY,
REPRESENTATIVE_MINUTE_DISCLAIMER,
rangeDeliveryEventCopy,
rangeDeliveryMostLikely,
rangeDeliveryShowsPercents,
} from "@/lib/rectification-agentic/user-copy";
import type { RangeDeliveryProjection } from "@/lib/rectification-agentic/v9/divergence-panel";
import {
SWITCH_TO_RECTIFIED_LABEL,
switchToRectifiedConfirmCopy,
} from "@/lib/rectification-agentic/birth-time-provenance";
import { RectificationVerificationReport } from "@/components/rectification-verification-report";
function columnHasTraits(column: RangeDeliveryProjection["columns"][number]): boolean {
return Boolean(column.traits.d9 || column.traits.d10 || column.traits.nakshatra.length > 0);
}
export function RectificationRangeDelivery({
result,
acceptingCandidateId,
readonly,
onAccept,
adoptLocked,
}: Readonly<{
result: RectificationCandidateResult;
acceptingCandidateId: string | null;
readonly: boolean;
onAccept: (candidateId: string) => void;
adoptLocked?: boolean;
}>) {
const delivery: RangeDeliveryProjection | null = result.rangeDelivery;
const range = delivery?.range ?? result.credibleRange;
const eventCount = delivery?.event_count ?? 0;
const selected = result.selectedTime;
const busy = Boolean(acceptingCandidateId) || readonly || adoptLocked;
const columns = delivery?.columns ?? [];
const rangeLabel = result.credibleIntervals?.length ? windowIntervalsLabel(result.credibleIntervals, result.declaredBirthDate)
: range ? `${range[0]}–${range[1]}` : null;
const title = rangeLabel
? eventCount > 0
? `${RECTIFICATION_USER_COPY.rangeDeliveryTitle} ${rangeLabel}(${rangeDeliveryEventCopy(eventCount)})`
: `${RECTIFICATION_USER_COPY.rangeDeliveryTitle} ${rangeLabel}`
: RECTIFICATION_USER_COPY.rangeDeliveryTitle;
const markdown = delivery?.verification_markdown
?? result.verificationReportMarkdown;
const sharedTraits = delivery?.shared_traits ?? [];
// BUG-691: while the birth record disagrees with the range the adopt entry says what
// it actually does — swap the charting clock — instead of the neutral 「更像这个」.
const recordConflict = delivery?.record_conflict ?? null;
const caption = delivery?.narrow_hint ?? null;
// D3: the range is the title; the representative minute is the subtitle.
// Hidden when the columns are indistinct (D4): 「最可能」 next to 「区分不开」
// would claim a lead the numbers do not have.
const mostLikely = delivery?.representative_time ?? result.representativeTime;
// D4: per-column numbers only when the first column leads by ≥ 5 points.
const showPercents = rangeDeliveryShowsPercents(columns);
const indistinct = columns.length >= 2 && !showPercents;
return (
<section className="rectification-candidates rectification-range-delivery" aria-label="生时校正区间">
<div className="rectification-candidates-heading">
<strong>{title}</strong>
{mostLikely && !indistinct ? (
<span className="rectification-range-delivery__most-likely">
{rangeDeliveryMostLikely(mostLikely)}
</span>
) : null}
</div>
{caption ? (
<p className="rectification-range-delivery__narrow">{caption}</p>
) : null}
{delivery?.provenance_line ? (
<p className="rectification-range-delivery__provenance">{delivery.provenance_line}</p>
) : null}
{adoptLocked ? (
<p className="rectification-range-delivery__narrow">
{RECTIFICATION_USER_COPY.rangeDeliveryAdoptLocked}
</p>
) : null}
{delivery?.tie_break_note ? (
<p className="rectification-range-delivery__narrow">{delivery.tie_break_note}</p>
) : null}
{indistinct ? (
<p className="rectification-range-delivery__indistinct">
{RECTIFICATION_USER_COPY.rangeDeliveryIndistinct}
</p>
) : null}
{sharedTraits.length > 0 ? (
<ul className="rectification-range-delivery__shared">
{sharedTraits.map((line) => (
<li key={line}>{line}</li>
))}
</ul>
) : null}
<ul className="rectification-range-delivery__columns">
{columns.map((column) => {
const candidateDate = result.candidates.find((candidate) => candidate.candidateId === column.candidate_id)?.candidate_date;
const relativeDay = candidateDate && result.declaredBirthDate && candidateDate !== result.declaredBirthDate
? candidateDate < result.declaredBirthDate ? "(前一天)" : "(后一天)" : "";
const adopted = selected === column.time;
const adopting = acceptingCandidateId === column.candidate_id;
const missing = column.fit == null || column.windows == null;
return (
<li
key={column.candidate_id}
className={`rectification-range-delivery__column${adopted ? " is-adopted" : ""}`}
>
<div className="rectification-range-delivery__time">{candidateDate ? `${candidateDate}${relativeDay} ${column.time}` : column.time}</div>
{candidateDate ? <p className="rectification-range-delivery__muted">采用后,排盘使用这一日期和时间;原填报日期保留。</p> : null}
{showPercents ? (
<p className="rectification-range-delivery__likelihood">
{RECTIFICATION_USER_COPY.rangeDeliveryRelativeLikelihood}
{" "}
{column.probability_percent}%
</p>
) : null}
{columnHasTraits(column) ? (
<div className="rectification-range-delivery__block">
{column.traits.d9 ? <p>{column.traits.d9}</p> : null}
{column.traits.d10 ? <p>{column.traits.d10}</p> : null}
{column.traits.nakshatra.map((line) => (
<p key={line}>{line}</p>
))}
</div>
) : null}
{missing ? (
<p className="rectification-range-delivery__muted">
{RECTIFICATION_USER_COPY.rangeDeliveryNotCompared}
</p>
) : (
<>
<p>{column.fit_line}</p>
<p>{column.window_line}</p>
</>
)}
<button
type="button"
className="rectification-range-delivery__accept"
aria-pressed={adopted}
disabled={busy || adopted}
onClick={() => onAccept(column.candidate_id)}
>
{adopting
? RECTIFICATION_USER_COPY.rangeDeliveryAdopting
: adopted
? RECTIFICATION_USER_COPY.rangeDeliveryAdopted
: recordConflict
? SWITCH_TO_RECTIFIED_LABEL
: RECTIFICATION_USER_COPY.rangeDeliveryMoreLikeThis}
</button>
{recordConflict && !adopted ? (
<p className="rectification-range-delivery__switch-note">
{switchToRectifiedConfirmCopy(recordConflict.reported_time, column.time)}
</p>
) : null}
</li>
);
})}
</ul>
{delivery?.more_label ? (
<p className="rectification-range-delivery__more">{delivery.more_label}</p>
) : null}
<p className="rectification-range-delivery__boundary">
{delivery?.boundary ?? REPRESENTATIVE_MINUTE_DISCLAIMER}
</p>
<RectificationVerificationReport markdown={markdown} />
</section>
);
}