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
125 lines
4.8 KiB
TypeScript
125 lines
4.8 KiB
TypeScript
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])],
|
||
},
|
||
},
|
||
};
|
||
}
|