163 lines
5.9 KiB
TypeScript
163 lines
5.9 KiB
TypeScript
import fs from "fs";
|
|
import path from "path";
|
|
import type { AddressIntent, AddressResultMode } from "../types/addressQuery";
|
|
|
|
export type AddressRouteExpectationStatus = "matched" | "mismatch" | "not_found";
|
|
|
|
export interface AddressRouteExpectationEntry {
|
|
intent: AddressIntent;
|
|
expected_selected_recipes: string[];
|
|
expected_requested_result_modes?: AddressResultMode[];
|
|
expected_result_modes?: AddressResultMode[];
|
|
}
|
|
|
|
export interface AddressRouteExpectationsContract {
|
|
schema_version: "address_route_expectations_v1";
|
|
updated_at: string;
|
|
entries: AddressRouteExpectationEntry[];
|
|
}
|
|
|
|
export interface AddressRouteExpectationAudit {
|
|
status: AddressRouteExpectationStatus;
|
|
reason: string;
|
|
expected_selected_recipes: string[];
|
|
expected_requested_result_modes: AddressResultMode[];
|
|
expected_result_modes: AddressResultMode[];
|
|
}
|
|
|
|
const EXPECTATIONS_FILE = path.resolve(__dirname, "..", "..", "..", "..", "docs", "TECH", "address_route_expectations_v1.json");
|
|
|
|
function toObject(value: unknown): Record<string, unknown> | null {
|
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
return null;
|
|
}
|
|
return value as Record<string, unknown>;
|
|
}
|
|
|
|
function toNonEmptyString(value: unknown): string | null {
|
|
if (typeof value !== "string") {
|
|
return null;
|
|
}
|
|
const trimmed = value.trim();
|
|
return trimmed.length > 0 ? trimmed : null;
|
|
}
|
|
|
|
function toStringArray(value: unknown): string[] {
|
|
if (!Array.isArray(value)) {
|
|
return [];
|
|
}
|
|
return value.map((item) => toNonEmptyString(item)).filter((item): item is string => Boolean(item));
|
|
}
|
|
|
|
function parseResultModes(value: unknown): AddressResultMode[] {
|
|
const raw = toStringArray(value);
|
|
return raw.filter((mode): mode is AddressResultMode => mode === "heuristic_candidates" || mode === "confirmed_balance");
|
|
}
|
|
|
|
function parseEntry(value: unknown): AddressRouteExpectationEntry | null {
|
|
const object = toObject(value);
|
|
if (!object) {
|
|
return null;
|
|
}
|
|
const intent = toNonEmptyString(object.intent) as AddressIntent | null;
|
|
const expectedSelectedRecipes = toStringArray(object.expected_selected_recipes);
|
|
if (!intent || expectedSelectedRecipes.length === 0) {
|
|
return null;
|
|
}
|
|
const expectedRequestedResultModes = parseResultModes(object.expected_requested_result_modes);
|
|
const expectedResultModes = parseResultModes(object.expected_result_modes);
|
|
return {
|
|
intent,
|
|
expected_selected_recipes: expectedSelectedRecipes,
|
|
...(expectedRequestedResultModes.length > 0 ? { expected_requested_result_modes: expectedRequestedResultModes } : {}),
|
|
...(expectedResultModes.length > 0 ? { expected_result_modes: expectedResultModes } : {})
|
|
};
|
|
}
|
|
|
|
export function loadAddressRouteExpectationsContract(): AddressRouteExpectationsContract {
|
|
const raw = fs.readFileSync(EXPECTATIONS_FILE, "utf-8");
|
|
const parsed = JSON.parse(raw) as unknown;
|
|
const root = toObject(parsed);
|
|
if (!root) {
|
|
throw new Error("address_route_expectations_v1: invalid root payload");
|
|
}
|
|
const schemaVersion = toNonEmptyString(root.schema_version);
|
|
if (schemaVersion !== "address_route_expectations_v1") {
|
|
throw new Error(`address_route_expectations_v1: unexpected schema version '${schemaVersion ?? "null"}'`);
|
|
}
|
|
const updatedAt = toNonEmptyString(root.updated_at) ?? new Date().toISOString();
|
|
const entriesRaw = Array.isArray(root.entries) ? root.entries : [];
|
|
const entries = entriesRaw.map(parseEntry).filter((entry): entry is AddressRouteExpectationEntry => entry !== null);
|
|
if (entries.length === 0) {
|
|
throw new Error("address_route_expectations_v1: no valid entries");
|
|
}
|
|
return {
|
|
schema_version: "address_route_expectations_v1",
|
|
updated_at: updatedAt,
|
|
entries
|
|
};
|
|
}
|
|
|
|
export function evaluateAddressRouteExpectation(input: {
|
|
intent: AddressIntent;
|
|
selectedRecipe: string | null;
|
|
requestedResultMode?: AddressResultMode;
|
|
resultMode?: AddressResultMode;
|
|
}): AddressRouteExpectationAudit {
|
|
const contract = loadAddressRouteExpectationsContract();
|
|
const entry = contract.entries.find((item) => item.intent === input.intent);
|
|
if (!entry) {
|
|
return {
|
|
status: "not_found",
|
|
reason: "route_expectation_not_defined_for_intent",
|
|
expected_selected_recipes: [],
|
|
expected_requested_result_modes: [],
|
|
expected_result_modes: []
|
|
};
|
|
}
|
|
if (input.selectedRecipe && !entry.expected_selected_recipes.includes(input.selectedRecipe)) {
|
|
return {
|
|
status: "mismatch",
|
|
reason: "selected_recipe_mismatch",
|
|
expected_selected_recipes: entry.expected_selected_recipes,
|
|
expected_requested_result_modes: entry.expected_requested_result_modes ?? [],
|
|
expected_result_modes: entry.expected_result_modes ?? []
|
|
};
|
|
}
|
|
if (
|
|
input.requestedResultMode &&
|
|
Array.isArray(entry.expected_requested_result_modes) &&
|
|
entry.expected_requested_result_modes.length > 0 &&
|
|
!entry.expected_requested_result_modes.includes(input.requestedResultMode)
|
|
) {
|
|
return {
|
|
status: "mismatch",
|
|
reason: "requested_result_mode_mismatch",
|
|
expected_selected_recipes: entry.expected_selected_recipes,
|
|
expected_requested_result_modes: entry.expected_requested_result_modes,
|
|
expected_result_modes: entry.expected_result_modes ?? []
|
|
};
|
|
}
|
|
if (
|
|
input.resultMode &&
|
|
Array.isArray(entry.expected_result_modes) &&
|
|
entry.expected_result_modes.length > 0 &&
|
|
!entry.expected_result_modes.includes(input.resultMode)
|
|
) {
|
|
return {
|
|
status: "mismatch",
|
|
reason: "result_mode_mismatch",
|
|
expected_selected_recipes: entry.expected_selected_recipes,
|
|
expected_requested_result_modes: entry.expected_requested_result_modes ?? [],
|
|
expected_result_modes: entry.expected_result_modes
|
|
};
|
|
}
|
|
return {
|
|
status: "matched",
|
|
reason: "route_expectation_matched",
|
|
expected_selected_recipes: entry.expected_selected_recipes,
|
|
expected_requested_result_modes: entry.expected_requested_result_modes ?? [],
|
|
expected_result_modes: entry.expected_result_modes ?? []
|
|
};
|
|
}
|