fix: rebuild incompatible birth-time candidate models
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import { z } from "zod";
|
||||
import type {
|
||||
DifferencePacketInput,
|
||||
DynamicChoiceScoreInput,
|
||||
@@ -6,6 +7,14 @@ import type {
|
||||
import type { ServerChoiceEvidence } from "./birth-time-dynamic-choice-internal.ts";
|
||||
import type { TimeRange } from "./birth-time-dynamic-choice.ts";
|
||||
|
||||
const reusableCandidateModelSchema = z.object({
|
||||
opportunity_model_version: z.literal("birth-time-opportunity-model-v2"),
|
||||
range: z.object({ start_time: z.string(), end_time: z.string() }),
|
||||
windows: z.array(z.object({
|
||||
activations: z.record(z.string(), z.number().finite().nonnegative()),
|
||||
}).passthrough()),
|
||||
}).passthrough();
|
||||
|
||||
export class BirthTimeDynamicEngineInputError extends Error {
|
||||
readonly name = "BirthTimeDynamicEngineInputError";
|
||||
}
|
||||
@@ -51,11 +60,10 @@ function candidateModelForRange(
|
||||
range: TimeRange,
|
||||
): Readonly<Record<string, unknown>> | null {
|
||||
if (model === null) return null;
|
||||
if (model.opportunity_model_version !== "birth-time-opportunity-model-v2") return null;
|
||||
const persistedRange = model.range;
|
||||
if (typeof persistedRange !== "object" || persistedRange === null) return model;
|
||||
const value = persistedRange as Readonly<Record<string, unknown>>;
|
||||
return value.start_time === range.startTime && value.end_time === range.endTime
|
||||
const parsed = reusableCandidateModelSchema.safeParse(model);
|
||||
if (!parsed.success) return null;
|
||||
return parsed.data.range.start_time === range.startTime
|
||||
&& parsed.data.range.end_time === range.endTime
|
||||
? model
|
||||
: null;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,15 @@ import {
|
||||
} from "../src/lib/birth-time-dynamic-engine-input.ts";
|
||||
import { dynamicCase } from "./birth-time-dynamic-persistence-fixture.ts";
|
||||
|
||||
function matchingCandidateModel(activation = 0) {
|
||||
return {
|
||||
version: "birth-time-choice-scoring-v2",
|
||||
opportunity_model_version: "birth-time-opportunity-model-v2",
|
||||
range: { start_time: "05:02", end_time: "05:03" },
|
||||
windows: [{ activations: { "05:02": activation, "05:03": 1 } }],
|
||||
};
|
||||
}
|
||||
|
||||
function narrowedCase(startTime = "05:02", endTime = "05:03") {
|
||||
const stored = dynamicCase();
|
||||
return {
|
||||
@@ -65,16 +74,22 @@ test("narrowed question generation rebuilds a candidate model for the current ra
|
||||
|
||||
test("matching candidate models remain reusable", () => {
|
||||
const stored = narrowedCase();
|
||||
const matchingModel = {
|
||||
version: "birth-time-choice-scoring-v2",
|
||||
opportunity_model_version: "birth-time-opportunity-model-v2",
|
||||
range: { start_time: "05:02", end_time: "05:03" },
|
||||
};
|
||||
const matchingModel = matchingCandidateModel();
|
||||
const input = dynamicDifferenceInput({ ...stored, candidateModel: matchingModel });
|
||||
|
||||
assert.equal(input.candidateModel, matchingModel);
|
||||
});
|
||||
|
||||
test("persisted candidate models with legacy negative activations are rebuilt", () => {
|
||||
const stored = narrowedCase();
|
||||
const input = dynamicDifferenceInput({
|
||||
...stored,
|
||||
candidateModel: matchingCandidateModel(-0.2),
|
||||
});
|
||||
|
||||
assert.equal(input.candidateModel, null);
|
||||
});
|
||||
|
||||
test("evidence projection preserves cross-midnight candidate chronology", () => {
|
||||
const stored = narrowedCase("23:59", "00:00");
|
||||
const input = dynamicChoiceScoreInput({
|
||||
|
||||
Reference in New Issue
Block a user