196 lines
6.1 KiB
TypeScript
196 lines
6.1 KiB
TypeScript
import { birthTimeAssessmentSchema, candidateResultSchema, lifeEventSchema } from "../src/lib/birth-time-journey.ts";
|
|
import { createBirthTimeJourneyService } from "../src/lib/birth-time-journey-service.ts";
|
|
import type {
|
|
BirthTimeJourneyStore,
|
|
LegacyBirthTimeJourneyEngine,
|
|
LegacyStoredRectificationCase,
|
|
} from "../src/lib/birth-time-journey-service.ts";
|
|
import type { EvidenceDomain } from "../src/lib/birth-time-question-planner.ts";
|
|
import {
|
|
journeyCaseId,
|
|
memoryStore,
|
|
} from "./birth-time-journey-memory-store.ts";
|
|
|
|
class UnexpectedTestCallError extends Error {
|
|
readonly name = "UnexpectedTestCallError";
|
|
}
|
|
|
|
export { journeyCaseId, memoryStore };
|
|
export const draftActionId = "45857b75-4718-4590-aaf5-7113a03ea765";
|
|
export const confirmActionId = "5cb071d6-6d99-46be-85dc-a9bf59ef6ac5";
|
|
export const secondActionId = "0790866c-ad5e-4a45-b2b4-a5c73f6be6ea";
|
|
export const thirdActionId = "0ef52e51-ab5f-453b-81e5-adb44a929224";
|
|
|
|
export const hospitalAssessment = birthTimeAssessmentSchema.parse({
|
|
date: "1993-04-17",
|
|
source: "hospital_record",
|
|
reportedTime: "08:16",
|
|
uncertaintyBeforeMinutes: 2,
|
|
uncertaintyAfterMinutes: 2,
|
|
location: { lat: 31.2304, lon: 121.4737, tz: 8 },
|
|
});
|
|
|
|
export const approximateAssessment = birthTimeAssessmentSchema.parse({
|
|
date: "1993-04-17",
|
|
source: "approximate",
|
|
reportedTime: "14:30",
|
|
uncertaintyBeforeMinutes: 30,
|
|
uncertaintyAfterMinutes: 30,
|
|
location: { lat: 31.2304, lon: 121.4737, tz: 8 },
|
|
});
|
|
|
|
export function scanWithSigns(signs: readonly string[]) {
|
|
const samples = signs.map((sign) => ({
|
|
ascendantSign: sign,
|
|
d4Sign: null,
|
|
d9Sign: sign,
|
|
d10Sign: sign,
|
|
d24Sign: null,
|
|
d30Sign: null,
|
|
}));
|
|
return {
|
|
questionnaire: {
|
|
questions: [{ id: "education_environment_shift", prompt: "是否有明显学业变化?" }],
|
|
samples,
|
|
raw: { candidate_scan: { samples } },
|
|
},
|
|
};
|
|
}
|
|
|
|
export const unusedJourneyEngine: LegacyBirthTimeJourneyEngine = {
|
|
async scan() { throw new UnexpectedTestCallError(); },
|
|
async score() { throw new UnexpectedTestCallError(); },
|
|
async scoreEvents() { throw new UnexpectedTestCallError(); },
|
|
};
|
|
|
|
export function preserveLegacyResumeStore(store: BirthTimeJourneyStore): BirthTimeJourneyStore {
|
|
return {
|
|
...store,
|
|
async upgradeLegacyActiveCase(value) {
|
|
return value;
|
|
},
|
|
};
|
|
}
|
|
|
|
export function evidenceQuestion(
|
|
phase: "baseline" | "adaptive",
|
|
domain: EvidenceDomain,
|
|
round: number,
|
|
) {
|
|
return {
|
|
questionId: `${phase}_${domain}_${round}`,
|
|
phase,
|
|
domain,
|
|
requestedPrecision: ["year", "month"] as const,
|
|
allowUnknown: true as const,
|
|
purposeCode: `candidate_difference_${domain}`,
|
|
plannerVersion: "candidate-difference-v1",
|
|
};
|
|
}
|
|
|
|
export const existingEducationEvent = lifeEventSchema.parse({
|
|
id: "1d8ee348-61a3-433d-8907-ff6d281b9992",
|
|
domain: "education",
|
|
precision: "year",
|
|
date: "2011",
|
|
});
|
|
|
|
export const existingCareerEvent = lifeEventSchema.parse({
|
|
id: "12dc56f0-1f17-4a2f-86bf-1056ab78def9",
|
|
domain: "career",
|
|
precision: "month",
|
|
date: "2019-07",
|
|
});
|
|
|
|
export const lowCandidate = candidateResultSchema.parse({
|
|
resultId: "a485f35d-bfb3-4c9b-9151-d812510b9e80",
|
|
confidence: "low",
|
|
canApply: false,
|
|
winningSegment: null,
|
|
eventCount: 3,
|
|
domainCount: 3,
|
|
topScore: 8,
|
|
secondScore: 7,
|
|
marginPercent: 12.5,
|
|
reasons: ["Candidate scores remain close."],
|
|
evidence: [],
|
|
algorithmVersion: "birth-time-event-scoring-v1",
|
|
});
|
|
|
|
type GuidedCaseInput = {
|
|
readonly version?: number;
|
|
readonly phase?: "baseline" | "adaptive";
|
|
readonly domain?: EvidenceDomain;
|
|
readonly adaptiveRound?: number;
|
|
readonly askedDomains?: readonly EvidenceDomain[];
|
|
readonly lifeEvents?: readonly ReturnType<typeof lifeEventSchema.parse>[];
|
|
readonly candidateResult?: ReturnType<typeof candidateResultSchema.parse> | null;
|
|
};
|
|
|
|
export function guidedCase(input: GuidedCaseInput = {}): LegacyStoredRectificationCase {
|
|
const version = input.version ?? 0;
|
|
const phase = input.phase ?? "baseline";
|
|
const domain = input.domain ?? "career";
|
|
const adaptiveRound = input.adaptiveRound ?? 0;
|
|
const question = evidenceQuestion(phase, domain, phase === "adaptive" ? adaptiveRound : 1);
|
|
const lifeEvents = input.lifeEvents ?? [];
|
|
const candidateResult = input.candidateResult ?? null;
|
|
return {
|
|
id: journeyCaseId,
|
|
userId: "user-1",
|
|
journeyProtocol: "legacy-guided-v1",
|
|
snapshot: {
|
|
state: "rectifying",
|
|
assistantIntent: candidateResult?.confidence === "low"
|
|
? "explain_event_evidence_insufficient"
|
|
: "continue_rectification_questions",
|
|
input: "rectification_questions",
|
|
route: "rectification",
|
|
confidence: candidateResult?.confidence ?? null,
|
|
canApply: false,
|
|
activeTime: null,
|
|
reportedRange: { label: "14:00—15:00", startTime: "14:00", endTime: "15:00" },
|
|
},
|
|
questionnaire: scanWithSigns(["Cancer", "Leo", "Virgo"]).questionnaire,
|
|
answers: {},
|
|
eventContext: { birthDate: "1993-04-17", lat: 31.2304, lon: 121.4737, tz: 8 },
|
|
lifeEvents,
|
|
candidateResult,
|
|
turnVersion: version,
|
|
turnState: {
|
|
turnVersion: version,
|
|
nextAction: phase === "baseline"
|
|
? { kind: "ask_baseline_evidence", question }
|
|
: { kind: "ask_adaptive_evidence", question },
|
|
progress: {
|
|
phase,
|
|
baselineDomainCount: new Set(lifeEvents.map((event) => event.domain)).size,
|
|
confirmedEvidenceCount: lifeEvents.length,
|
|
adaptiveRound,
|
|
maxAdaptiveRounds: 3,
|
|
},
|
|
permissions: { canConfirmCandidate: false },
|
|
evidenceDraft: null,
|
|
},
|
|
evidenceDraft: null,
|
|
processedActionIds: [],
|
|
persistedProgress: { adaptiveRound, askedDomains: input.askedDomains ?? [] },
|
|
};
|
|
}
|
|
|
|
export function progressionService(storedCase: LegacyStoredRectificationCase) {
|
|
let scoreEventsCalls = 0;
|
|
const memory = memoryStore(storedCase);
|
|
const service = createBirthTimeJourneyService({
|
|
store: preserveLegacyResumeStore(memory.store),
|
|
engine: {
|
|
...unusedJourneyEngine,
|
|
async scoreEvents() {
|
|
scoreEventsCalls += 1;
|
|
return lowCandidate;
|
|
},
|
|
},
|
|
});
|
|
return { memory, service, scoreEventsCalls: () => scoreEventsCalls };
|
|
}
|