fix(rectification): anchor candidate windows to civil dates across midnight
Independent Staging Quality Gate / validate (push) Successful in 13m27s
Independent Staging Quality Gate / publish (push) Failing after 1h0m1s

Carry explicit local date intervals instead of inferring the day from clock
order. Cluster width, delivery, adoption, and reports keep the actual civil
date; adopted date is stored separately from the reported birth_date.

Algorithm identity is scoring-9 / spec-v5. Scoring weights, confirmation
thresholds, and Skill version are unchanged. Isolated Linux final-3 gates
passed; four pre-existing Python failures remain. This is not a production
release.
This commit is contained in:
jesse-ux
2026-09-21 02:55:00 +08:00
parent 3be740f84d
commit b85c4a686a
115 changed files with 106484 additions and 315 deletions
@@ -1,6 +1,7 @@
"use client";
import { ChevronRight } from "lucide-react";
import { effectiveBirthDate } from "@/lib/effective-birth-date";
import { useState, type Dispatch, FormEvent, SetStateAction } from "react";
import { ChartProfileForm } from "@/components/chart-profile-form";
import { activeChartStorageKey, deleteCloudChartProfile, saveCloudChartProfile, updateCloudChartProfile } from "@/lib/home-cloud-sync";
@@ -205,7 +206,7 @@ export function ChartLibraryPanel({
<span className={`chart-role-badge${isSelf ? " is-self" : ""}`}>{isSelf ? "本人" : chartRelationshipLabel(record.relationship)}</span>
{isSelf ? <span className="chart-default-badge">当前默认</span> : null}
</div>
<small>{record.profile.date || "出生日期待补全"} · {profileBirthTimeLabel(record.profile)} · {profilePlaceLabel(record.profile)}</small>
<small>{effectiveBirthDate(record.profile) || "出生日期待补全"} · {profileBirthTimeLabel(record.profile)} · {profilePlaceLabel(record.profile)}</small>
<small>{profileBirthTimeStatusLabel(record.profile)} · {formatChartUpdatedAt(record.updatedAt)}</small>
</div>
<ChevronRight className="chart-library-item-chevron" aria-hidden="true" />
@@ -16,6 +16,7 @@ import {
} from "@/lib/rectification-candidate-result";
import {
windowScanLayerLabel,
lagnaContrastIntervalLabel,
type WindowScanLayer,
} from "@/lib/rectification-agentic/v9/refinement-packet";
import { rectificationBoardEmptyCopy } from "@/lib/rectification-surface-state";
@@ -210,8 +211,8 @@ function RectificationBoardBody({
<p>{engineMeaningToDisplayCopy(contrast.user_meaning)}</p>
<ul>
{contrast.intervals.map((interval) => (
<li key={`${interval.start}-${interval.end}`}>
{interval.start}-{interval.end}:{interval.lagna}
<li key={`${interval.start_at ?? interval.start}-${interval.end_at ?? interval.end}`}>
{lagnaContrastIntervalLabel(interval)}:{interval.lagna}
{interval.lords.l1 ? `;一/四/七/十宫主 ${[interval.lords.l1, interval.lords.l4, interval.lords.l7, interval.lords.l10].filter(Boolean).join("、")}` : ""}
</li>
))}
@@ -1,5 +1,6 @@
"use client";
import { windowIntervalsLabel } from "@/lib/rectification-agentic/core/candidate-window";
import type { RectificationCandidateResult } from "@/lib/rectification-candidate-result";
import {
RECTIFICATION_USER_COPY,
@@ -36,10 +37,12 @@ export function RectificationRangeDelivery({
const selected = result.selectedTime;
const busy = Boolean(acceptingCandidateId) || readonly || adoptLocked;
const columns = delivery?.columns ?? [];
const title = range
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} ${range[0]}–${range[1]}(${rangeDeliveryEventCopy(eventCount)})`
: `${RECTIFICATION_USER_COPY.rangeDeliveryTitle} ${range[0]}–${range[1]}`
? `${RECTIFICATION_USER_COPY.rangeDeliveryTitle} ${rangeLabel}(${rangeDeliveryEventCopy(eventCount)})`
: `${RECTIFICATION_USER_COPY.rangeDeliveryTitle} ${rangeLabel}`
: RECTIFICATION_USER_COPY.rangeDeliveryTitle;
const markdown = delivery?.verification_markdown
?? result.verificationReportMarkdown;
@@ -77,6 +80,9 @@ export function RectificationRangeDelivery({
) : 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;
@@ -85,7 +91,8 @@ export function RectificationRangeDelivery({
key={column.candidate_id}
className={`rectification-range-delivery__column${adopted ? " is-adopted" : ""}`}
>
<div className="rectification-range-delivery__time">{column.time}</div>
<div className="rectification-range-delivery__time">{candidateDate ? `${candidateDate}${relativeDay} ${column.time}` : column.time}</div>
{candidateDate ? <p className="rectification-range-delivery__muted">采用后,排盘使用这一日期和时间;原填报日期保留。</p> : null}
<p className="rectification-range-delivery__likelihood">
{RECTIFICATION_USER_COPY.rangeDeliveryRelativeLikelihood}
{" "}