170 lines
6.8 KiB
TypeScript
170 lines
6.8 KiB
TypeScript
import { format, isValid, parse } from "date-fns";
|
||
import type { JourneySnapshot } from "./birth-time-journey.ts";
|
||
|
||
const birthDatePattern = "yyyy-MM-dd";
|
||
|
||
export type BirthTimeSource =
|
||
| ""
|
||
| "hospital_record"
|
||
| "family_exact"
|
||
| "approximate"
|
||
| "period_only"
|
||
| "unknown"
|
||
| "legacy_import";
|
||
|
||
export type BirthTimePeriod =
|
||
| ""
|
||
| "early_morning"
|
||
| "morning"
|
||
| "afternoon"
|
||
| "evening"
|
||
| "late_night";
|
||
|
||
export type BirthTimeStatus =
|
||
| ""
|
||
| "reported"
|
||
| "assessing"
|
||
| "rectifying"
|
||
| "candidate"
|
||
| "confirmed";
|
||
|
||
export type BirthTimeDraft = {
|
||
readonly date: string;
|
||
readonly time: string;
|
||
readonly reportedTime: string;
|
||
readonly birthTimeSource: BirthTimeSource;
|
||
readonly birthTimePeriod: BirthTimePeriod;
|
||
readonly birthTimeClue: string;
|
||
readonly uncertaintyBeforeMinutes: number | null;
|
||
readonly uncertaintyAfterMinutes: number | null;
|
||
readonly birthTimeStatus: BirthTimeStatus;
|
||
};
|
||
|
||
export type BirthTimeDraftPatch = Partial<BirthTimeDraft>;
|
||
|
||
export function parseBirthDate(value: string): Date | undefined {
|
||
if (value === "") return undefined;
|
||
const parsed = parse(value, birthDatePattern, new Date(2000, 0, 1));
|
||
if (!isValid(parsed) || format(parsed, birthDatePattern) !== value) return undefined;
|
||
return parsed;
|
||
}
|
||
|
||
export function formatBirthDate(value: Date): string {
|
||
return format(value, birthDatePattern);
|
||
}
|
||
|
||
export const birthTimeSourceOptions = [
|
||
{ value: "hospital_record", label: "出生证明或医院记录", hint: "先检查前后两分钟是否稳定" },
|
||
{ value: "family_exact", label: "家人明确记得具体时间", hint: "进行 5—15 分钟轻量校正" },
|
||
{ value: "approximate", label: "只记得大概几点", hint: "按你选择的误差范围扫描" },
|
||
{ value: "period_only", label: "只知道早晨、上午、下午或晚上", hint: "先从时段范围做粗筛" },
|
||
{ value: "unknown", label: "完全不知道", hint: "不要求你随便填写具体时间" },
|
||
] as const;
|
||
|
||
export const birthTimePeriodOptions = [
|
||
{ value: "early_morning", label: "凌晨 / 清晨(04:00—07:59)" },
|
||
{ value: "morning", label: "上午(08:00—11:59)" },
|
||
{ value: "afternoon", label: "下午(12:00—17:59)" },
|
||
{ value: "evening", label: "晚上(18:00—22:59)" },
|
||
{ value: "late_night", label: "深夜(23:00—03:59)" },
|
||
] as const;
|
||
|
||
const periodLabels = {
|
||
"": "未选择时段",
|
||
early_morning: "凌晨或清晨",
|
||
morning: "上午",
|
||
afternoon: "下午",
|
||
evening: "晚上",
|
||
late_night: "深夜",
|
||
} as const satisfies Record<BirthTimePeriod, string>;
|
||
|
||
const intentCopy = {
|
||
confirm_stable_record: "医院记录前后两分钟内结构稳定,可以直接作为当前排盘时间。",
|
||
explain_sensitive_boundary: "这个时间靠近敏感边界,需要先做轻量校正,暂不直接排盘。",
|
||
explain_assessment_unavailable: "暂时无法完成稳定性检查,系统不会冒险应用这个具体时间。",
|
||
start_light_rectification: "先用几个高区分度问题检查家人记忆范围。",
|
||
start_standard_rectification: "这个误差范围内存在多个候选,需要先回答几个生活经历问题。",
|
||
start_period_rectification: "已保留你知道的时段,接下来先做粗粒度候选筛选。",
|
||
collect_time_clues: "不会要求你猜一个具体时间,先从家人线索和大致时段开始。",
|
||
continue_rectification_questions: "已记录这条线索,还需要更多证据才能形成候选范围。",
|
||
present_saved_candidate_range: "目前只能保存候选范围,还没有足够证据应用到具体分钟。",
|
||
collect_dated_life_events: "选择题已经完成,请补充几条带日期的关键经历,用于比较实际候选时间。",
|
||
explain_event_evidence_insufficient: "现有事件还不能稳定区分候选时间,可以调整日期或补充其他领域的经历。",
|
||
present_candidate_result: "确定性评分已经形成候选范围,但当前证据只允许保存,不能直接应用。",
|
||
confirm_candidate_time: "候选结果达到应用门槛,仍需你明确确认后才会成为当前排盘时间。",
|
||
confirmed_candidate_time: "候选时间已按你的确认设为当前排盘使用时间。",
|
||
} as const satisfies Record<JourneySnapshot["assistantIntent"], string>;
|
||
|
||
export function assistantIntentCopy(intent: JourneySnapshot["assistantIntent"]) {
|
||
return intentCopy[intent];
|
||
}
|
||
|
||
export function isBirthTimeDraftReady(draft: BirthTimeDraft) {
|
||
if (!draft.date) return false;
|
||
switch (draft.birthTimeSource) {
|
||
case "hospital_record":
|
||
case "legacy_import":
|
||
return Boolean(draft.reportedTime || draft.time);
|
||
case "family_exact":
|
||
return Boolean(draft.reportedTime)
|
||
&& [5, 10, 15].includes(draft.uncertaintyBeforeMinutes ?? -1)
|
||
&& draft.uncertaintyBeforeMinutes === draft.uncertaintyAfterMinutes;
|
||
case "approximate":
|
||
return Boolean(draft.reportedTime)
|
||
&& [15, 30, 60].includes(draft.uncertaintyBeforeMinutes ?? -1)
|
||
&& draft.uncertaintyBeforeMinutes === draft.uncertaintyAfterMinutes;
|
||
case "period_only":
|
||
return Boolean(draft.birthTimePeriod);
|
||
case "unknown":
|
||
return true;
|
||
case "":
|
||
return false;
|
||
default: {
|
||
const exhaustive: never = draft.birthTimeSource;
|
||
return exhaustive;
|
||
}
|
||
}
|
||
}
|
||
|
||
export function birthTimePersistenceValues(draft: BirthTimeDraft) {
|
||
const reportedTime = draft.reportedTime || draft.time || null;
|
||
const uncertainty = draft.birthTimeSource === "hospital_record"
|
||
? 2
|
||
: draft.birthTimeSource === "family_exact" || draft.birthTimeSource === "approximate"
|
||
? draft.uncertaintyBeforeMinutes
|
||
: null;
|
||
return {
|
||
reported_birth_time: reportedTime,
|
||
birth_time_source: draft.birthTimeSource || null,
|
||
birth_time_period: draft.birthTimePeriod || null,
|
||
birth_time_clue: draft.birthTimeClue.trim() || null,
|
||
uncertainty_before_minutes: uncertainty,
|
||
uncertainty_after_minutes: uncertainty,
|
||
};
|
||
}
|
||
|
||
export function describeBirthTimeDraft(draft: BirthTimeDraft) {
|
||
const [year, month, day] = draft.date.split("-").map(Number);
|
||
const date = `${year}年${month}月${day}日`;
|
||
switch (draft.birthTimeSource) {
|
||
case "hospital_record":
|
||
return `${date} ${draft.reportedTime}(医院记录)`;
|
||
case "family_exact":
|
||
return `${date} ${draft.reportedTime}(家人明确记得,前后 ${draft.uncertaintyBeforeMinutes} 分钟)`;
|
||
case "approximate":
|
||
return `${date},约 ${draft.reportedTime}(前后 ${draft.uncertaintyBeforeMinutes} 分钟)`;
|
||
case "period_only":
|
||
return `${date},${periodLabels[draft.birthTimePeriod]}`;
|
||
case "unknown":
|
||
return `${date},具体时间未知`;
|
||
case "legacy_import":
|
||
return `${date} ${draft.reportedTime || draft.time}(既有已确认资料)`;
|
||
case "":
|
||
return date;
|
||
default: {
|
||
const exhaustive: never = draft.birthTimeSource;
|
||
return exhaustive;
|
||
}
|
||
}
|
||
}
|