fix(rectification): open cases for uncertain birth times
Independent Staging Quality Gate / validate (push) Successful in 12m32s
Independent Staging Quality Gate / publish (push) Successful in 9m9s

This commit is contained in:
Jesse_Chen
2026-08-15 20:24:43 +08:00
parent 995da2d4b4
commit 5b023e9480
3 changed files with 138 additions and 14 deletions
@@ -108,16 +108,45 @@ function shiftedTime(time: string, offsetMinutes: number): string {
return `${String(Math.floor(normalized / 60)).padStart(2, "0")}:${String(normalized % 60).padStart(2, "0")}`;
}
// This is an engine execution boundary, not a user-declared uncertainty. Fresh
// Cases always start from reported_birth_time with enough room to rectify.
// This is an engine execution boundary, not a user-declared uncertainty.
// Exact-time Cases receive a movable search radius; imprecise declarations keep
// the honest server-owned range instead of inventing a baseline minute.
const FRESH_CASE_SEARCH_RADIUS_MINUTES = 15;
const PERIOD_CANDIDATE_RANGES = {
early_morning: { start_time: "04:00", end_time: "07:59" },
morning: { start_time: "08:00", end_time: "11:59" },
afternoon: { start_time: "12:00", end_time: "17:59" },
evening: { start_time: "18:00", end_time: "22:59" },
late_night: { start_time: "23:00", end_time: "03:59" },
} as const;
function deriveCandidateRange(reportedTime: string | null): { start_time: string; end_time: string } {
if (!reportedTime) throw new RectificationCaseServiceError("profile_incomplete");
return {
start_time: shiftedTime(reportedTime, -FRESH_CASE_SEARCH_RADIUS_MINUTES),
end_time: shiftedTime(reportedTime, FRESH_CASE_SEARCH_RADIUS_MINUTES),
};
function deriveCandidateRange(input: {
reportedTime: string | null;
source: string;
period: string | null;
}): { start_time: string; end_time: string } {
if (input.reportedTime) {
return {
start_time: shiftedTime(input.reportedTime, -FRESH_CASE_SEARCH_RADIUS_MINUTES),
end_time: shiftedTime(input.reportedTime, FRESH_CASE_SEARCH_RADIUS_MINUTES),
};
}
if (input.source === "period_only" || input.source === "legacy_import") {
const periodRange = input.period && Object.hasOwn(PERIOD_CANDIDATE_RANGES, input.period)
? PERIOD_CANDIDATE_RANGES[input.period as keyof typeof PERIOD_CANDIDATE_RANGES]
: null;
if (periodRange) return periodRange;
if (input.source === "period_only") {
throw new RectificationCaseServiceError("profile_incomplete");
}
}
if (input.source === "unknown" || input.source === "legacy_import") {
return { start_time: "00:00", end_time: "23:59" };
}
throw new RectificationCaseServiceError("profile_incomplete");
}
function baselineFingerprint(baseline: V9BaselineSnapshot): string {
@@ -187,7 +216,7 @@ export async function loadV9RectificationProfile(
userId,
baseline,
baselineFingerprint: baselineFingerprint(baseline),
candidateRange: deriveCandidateRange(reportedTime),
candidateRange: deriveCandidateRange({ reportedTime, source, period }),
};
}