fix(rectification): label declared birth time as record or estimate (BUG-690)
Independent Staging Quality Gate / validate (push) Failing after 8m55s
Independent Staging Quality Gate / publish (push) Skipped

Hospital records, family estimates and period-only windows now keep distinct copy on the board and range card. Scoring still uses the reported clock as the search-window centre. Hospital minutes outside the current range are stated with the offset and no preference.
This commit is contained in:
jesse-ux
2026-09-14 23:20:15 +08:00
parent a266b6b727
commit c5fbee56a6
25 changed files with 531 additions and 35 deletions
@@ -73,6 +73,7 @@ import {
persistedQuestionSurface,
interviewStopReasonFromSnapshot,
interviewSessionOutcomeFromSnapshot,
birthTimeSourceFromSnapshot,
type RectificationCaseSnapshotPayload,
} from "@/lib/rectification-surface-state";
import { RectificationTimeline } from "@/components/rectification-timeline";
@@ -394,6 +395,7 @@ type CaseSnapshotState = Readonly<{
stage: RectificationTimelineStage | null;
interviewStopReason: string | null;
interviewSessionOutcome: string | null;
birthTimeSource: ReturnType<typeof birthTimeSourceFromSnapshot>;
}>;
function nextUserActionIdFromSnapshot(payload: RectificationCaseSnapshotPayload | null): string | null {
@@ -419,6 +421,7 @@ function caseSnapshotState(payload: RectificationCaseSnapshotPayload | null): Ca
stage: caseStageFromSnapshot(payload.case?.stage),
interviewStopReason: interviewStopReasonFromSnapshot(payload),
interviewSessionOutcome: interviewSessionOutcomeFromSnapshot(payload),
birthTimeSource: birthTimeSourceFromSnapshot(payload),
};
}
@@ -464,6 +467,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
const [nextUserActionId, setNextUserActionId] = useState<string | null>(() => caseSnapshotState(initialSnapshot)?.nextUserActionId ?? null);
const [interviewStopReason, setInterviewStopReason] = useState<string | null>(() => caseSnapshotState(initialSnapshot)?.interviewStopReason ?? null);
const [interviewSessionOutcome, setInterviewSessionOutcome] = useState<string | null>(() => caseSnapshotState(initialSnapshot)?.interviewSessionOutcome ?? null);
const [birthTimeSource, setBirthTimeSource] = useState(() => caseSnapshotState(initialSnapshot)?.birthTimeSource ?? birthTimeSourceFromSnapshot(initialSnapshot));
const [caseSnapshotLoaded, setCaseSnapshotLoaded] = useState(initialSnapshot !== null);
const [questionRetryAttempts, setQuestionRetryAttempts] = useState(0);
const [questionRepairAttempts, setQuestionRepairAttempts] = useState(0);
@@ -637,6 +641,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
setNextUserActionId(nextActionId || null);
setInterviewStopReason(interviewStopReasonFromSnapshot(snapshot));
setInterviewSessionOutcome(interviewSessionOutcomeFromSnapshot(snapshot));
setBirthTimeSource(birthTimeSourceFromSnapshot(snapshot));
setCaseSnapshotLoaded(true);
if (nextQuestion || nextChoice || acceptedTime || confirmedTime) {
setQuestionRepairAttempts(0);
@@ -1683,6 +1688,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
<RectificationBoardPeek
result={candidateResult}
declaredTime={declaredTime}
birthTimeSource={birthTimeSource}
expanded={boardOpen}
boardId={boardId}
onOpen={toggleBoard}
@@ -1988,6 +1994,7 @@ export function RectificationAgenticChat(props: RectificationAgenticChatProps) {
<RectificationBoard
result={candidateResult}
declaredTime={declaredTime}
birthTimeSource={birthTimeSource}
savedStatus={savedStatus}
diff={boardDiff}
compact={compactBoard}
@@ -39,6 +39,7 @@ function LayerChips({ layers }: Readonly<{ layers: readonly WindowScanLayer[] }>
function RectificationBoardBody({
result,
declaredTime,
birthTimeSource,
savedStatus,
diff,
compact,
@@ -47,6 +48,7 @@ function RectificationBoardBody({
}: Readonly<{
result: RectificationCandidateResult | null;
declaredTime: string | null;
birthTimeSource?: string | null;
savedStatus: "accepted" | "confirmed" | null;
diff: RectificationBoardDiff;
compact: boolean;
@@ -55,7 +57,7 @@ function RectificationBoardBody({
}>) {
const table = result ? workingRectificationHouseTable(result) : null;
const workingTime = workingRectificationTime(result);
const emptyCopy = rectificationBoardEmptyCopy(declaredTime);
const emptyCopy = rectificationBoardEmptyCopy(declaredTime, birthTimeSource);
const grouped = result ? groupWindowTransitions(result.windowTransitions) : [];
const scoringMinutes = grouped.filter((row) => row.scoringLayers.length > 0);
const displayMinutes = grouped.filter((row) => row.displayLayers.length > 0);
@@ -220,6 +222,7 @@ function RectificationBoardBody({
export function RectificationBoard({
result,
declaredTime,
birthTimeSource,
savedStatus,
diff,
compact,
@@ -230,6 +233,7 @@ export function RectificationBoard({
}: Readonly<{
result: RectificationCandidateResult | null;
declaredTime: string | null;
birthTimeSource?: string | null;
savedStatus: "accepted" | "confirmed" | null;
diff: RectificationBoardDiff;
compact: boolean;
@@ -265,6 +269,7 @@ export function RectificationBoard({
<RectificationBoardBody
result={result}
declaredTime={declaredTime}
birthTimeSource={birthTimeSource}
savedStatus={savedStatus}
diff={diff}
compact={compact}
@@ -287,17 +292,19 @@ export function RectificationBoard({
export function RectificationBoardPeek({
result,
declaredTime = null,
birthTimeSource,
expanded,
boardId,
onOpen,
}: Readonly<{
result: RectificationCandidateResult | null;
declaredTime?: string | null;
birthTimeSource?: string | null;
expanded: boolean;
boardId: string;
onOpen: () => void;
}>) {
const peek = rectificationBoardPeekCopy(result, declaredTime);
const peek = rectificationBoardPeekCopy(result, declaredTime, birthTimeSource);
return (
<button
className="rectification-board-peek"
@@ -65,6 +65,9 @@ export function RectificationRangeDelivery({
{RECTIFICATION_USER_COPY.rangeDeliveryCollectClosed}
</p>
) : null}
{delivery?.provenance_line ? (
<p className="rectification-range-delivery__provenance">{delivery.provenance_line}</p>
) : null}
{delivery?.tie_break_available && onTieBreak && !readonly ? (
<button
type="button"