fix(web): keep unrectified birth data usable without inventing a minute

Rectification stays optional. Reported minutes can consult and generate reports; date-plus-period uses a declared window instead of a midpoint or 00:00. Updates BUG-341.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Jesse_Chen
2026-08-21 15:29:58 +08:00
co-authored by Cursor
parent e7f4030e3f
commit 9958e00abc
34 changed files with 1441 additions and 169 deletions
@@ -9,6 +9,7 @@
import { createHash } from "node:crypto";
import type { SupabaseClient } from "@supabase/supabase-js";
import { resolveMissingBirthTimezoneOffset } from "../../birth-profile-timezone.ts";
import { declaredClockRange } from "../../declared-birth-window.ts";
import { normalizePersistedBirthDate } from "../../birth-time-intake-model.ts";
import {
resolveActiveSkillPackage,
@@ -113,13 +114,6 @@ function shiftedTime(time: string, offsetMinutes: number): string {
// 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(input: {
reportedTime: string | null;
@@ -133,21 +127,9 @@ function deriveCandidateRange(input: {
};
}
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");
const range = declaredClockRange({ source: input.source, period: input.period });
if (!range) throw new RectificationCaseServiceError("profile_incomplete");
return { start_time: range.startTime, end_time: range.endTime };
}
function baselineFingerprint(baseline: V9BaselineSnapshot): string {