Files
Jyotisha/frontend/src/lib/consultation-birth-time-mode.ts
T
Jesse_ChenandClaude Opus 5.5 7f39c9346d feat(consult): adopted segment charts that cannot be determined reach the model as policy and one plain sentence (BUG-1138, T2)
The shared range loader attaches unreliableVargas for segment-v1 adoptions;
the consultation tool input carries it, runConsultationWorkflow strips it from
the engine payload and writes answer_policy.unreliable_vargas,
deterministic_claims_forbidden_for and a user-facing limitation. Legacy and
unrectified profiles are byte-identical (request, context, packet).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017eEAG8HD3mm8gsKXgk8uU8
2026-10-01 10:15:10 +08:00

125 lines
4.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { z } from "zod";
export const consultationBirthTimeModeSchema = z.enum([
"verified_chart",
"unverified_birth_time",
"declared_birth_window",
"general_no_birth_time",
]);
export type ConsultationBirthTimeMode = z.infer<typeof consultationBirthTimeModeSchema>;
export type NatalMinuteConsultationMode = Extract<
ConsultationBirthTimeMode,
"verified_chart" | "unverified_birth_time"
>;
export const UNVERIFIED_BIRTH_TIME_NOTICE = "使用未校正填报时间;分钟敏感结论的置信度已降低。";
export const HOSPITAL_REPORTED_BIRTH_TIME_NOTICE = "使用你填报的出生时间(医院记录)排盘";
export const FAMILY_EXACT_REPORTED_BIRTH_TIME_NOTICE = "使用你填报的出生时间排盘;分钟敏感结论的置信度已降低。";
export const ACCEPTED_RANGE_READING_INSTRUCTION = "当前采用的是一段可信出生区间,不是已确认的唯一出生分钟。区间里稳定的主题可按代表分钟读;会随分钟变的主题按范围读,不得写成单一分钟结论。";
export function unverifiedBirthTimeNotice(source: string | null | undefined): string {
if (source === "hospital_record") return HOSPITAL_REPORTED_BIRTH_TIME_NOTICE;
if (source === "family_exact") return FAMILY_EXACT_REPORTED_BIRTH_TIME_NOTICE;
return UNVERIFIED_BIRTH_TIME_NOTICE;
}
export function isNatalMinuteConsultationMode(
mode: ConsultationBirthTimeMode,
): mode is NatalMinuteConsultationMode {
return mode === "verified_chart" || mode === "unverified_birth_time";
}
export function shouldRunBirthChartWorkflow(mode: ConsultationBirthTimeMode): boolean {
return isNatalMinuteConsultationMode(mode);
}
export function shouldRunDeclaredWindowWorkflow(mode: ConsultationBirthTimeMode): boolean {
return mode === "declared_birth_window";
}
export function applyBirthTimeModeToWorkflowContext<
T extends {
consumer_context: {
answer_policy: Record<string, unknown>;
[key: string]: unknown;
};
[key: string]: unknown;
},
>(
context: T,
mode: ConsultationBirthTimeMode,
options?: { birthTimeSource?: string | null },
): T {
if (mode !== "unverified_birth_time") return context;
return {
...context,
consumer_context: {
...context.consumer_context,
birth_time_notice: unverifiedBirthTimeNotice(options?.birthTimeSource),
answer_policy: {
...context.consumer_context.answer_policy,
birth_time_confidence: "unverified_reported_time",
candidate_is_confirmed: false,
},
},
};
}
/** Labels follow VOICE.md (「婚恋(D9)会随分钟变」). */
export const UNRELIABLE_VARGA_LABELS: Readonly<Record<"D1" | "D9" | "D10", string>> = {
D1: "本命盘(D1)",
D9: "婚恋(D9)",
D10: "事业(D10)",
};
/**
* One plain sentence for charts a segment-v1 adoption could not determine (BUG-1138).
* Server-written, so the model repeats it rather than inventing its own caveat.
*/
export function unreliableVargasNotice(vargas: readonly ("D1" | "D9" | "D10")[]): string {
const names = vargas.map((varga) => UNRELIABLE_VARGA_LABELS[varga]).join("、");
const these = vargas.length > 1 ? "这几张盘" : "这张盘";
const it = vargas.length > 1 ? "它们" : "它";
const fallback = vargas.includes("D1") ? `涉及${it}的部分不按上升和宫位下结论` : `涉及${it}的部分只看本命盘和大运,不按${it}下结论`;
return `你的出生时间范围里,${names}的上升会变,${these}判断不了;${fallback}。`;
}
/**
* Carry the adopted chart tiers into the workflow context the model reads:
* `answer_policy.unreliable_vargas` / `deterministic_claims_forbidden_for` and a
* user-facing limitation sentence. A context without unreliable charts is returned
* as-is, so legacy and unrectified profiles are byte-for-byte unchanged.
*/
export function applyUnreliableVargasToWorkflowContext<
T extends {
consumer_context: {
answer_policy: Record<string, unknown>;
[key: string]: unknown;
};
[key: string]: unknown;
},
>(context: T, vargas: readonly ("D1" | "D9" | "D10")[] | undefined): T {
if (!vargas?.length) return context;
const notice = unreliableVargasNotice(vargas);
const policy = context.consumer_context.answer_policy;
const forbidden = Array.isArray(policy.deterministic_claims_forbidden_for) ? policy.deterministic_claims_forbidden_for : [];
const existing = typeof context.consumer_context.user_facing_limitation === "string"
? context.consumer_context.user_facing_limitation.trim()
: "";
return {
...context,
consumer_context: {
...context.consumer_context,
user_facing_limitation: existing ? `${existing} ${notice}` : notice,
answer_policy: {
...policy,
unreliable_vargas: [...vargas],
deterministic_claims_forbidden_for: [...new Set([...forbidden.map(String), ...vargas])],
},
},
};
}