mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-13 18:32:12 +03:00
Two of the fifteen factors calculateScore applies could not be set by anyone. scoringWeightsSchema is a plain z.object, so zod strips what it does not name: PUT a combo with connectionDensity and you get a 200 back with nothing saved, and normalizeScoringWeights then reads the gap as a deliberate zero — switching off anti-concentration and the quality signal. DEFAULT_INTELLIGENT_WEIGHTS, the dashboard's own copy, missed the same two and every non-zero value differed from the engine's; summing to 1.05, validateWeights rejected them outright. This adds the two keys to both lists and takes the dashboard defaults from DEFAULT_WEIGHTS. The scorer is not touched. One behaviour change, and it is the point: a combo whose stored weights omitted the two keys was running with them at zero and the other thirteen renormalized upward. It now uses the engine's distribution (quota 0.1549 → 0.1429, health 0.1740 → 0.1605) and a test pins those numbers. Left alone and documented rather than widened: rounded percentages now total 101% (six factors at 4.76% each render as 5%), and five stale .default() values in the schema that only bite when a config omits the key. Verified in a combined batch worktree: 174/174 focused tests across all 11 PRs of this batch, typecheck:core clean, complexity 2706/3218, cognitive-complexity 1221/1437, check:cycles and check:docs-counts green. Thanks @maxmad64bis — the red-before-green note (7 of 8 failing, and naming the one that passes on purpose) is exactly the evidence that makes a behaviour change reviewable.
115 lines
4.8 KiB
TypeScript
115 lines
4.8 KiB
TypeScript
import assert from "node:assert/strict";
|
|
import test from "node:test";
|
|
|
|
import {
|
|
DEFAULT_WEIGHTS,
|
|
normalizeScoringWeights,
|
|
validateWeights,
|
|
} from "../../open-sse/services/autoCombo/scoring.ts";
|
|
import { DEFAULT_INTELLIGENT_WEIGHTS } from "../../src/lib/combos/intelligentRouting.ts";
|
|
import { scoringWeightsSchema } from "../../src/shared/validation/schemas/combo.ts";
|
|
|
|
// `scoringWeightsSchema` is `.optional()` at the point of use; parse through the
|
|
// inner object so a missing schema key surfaces as a dropped property rather than
|
|
// as `undefined` for the whole value.
|
|
function parseWeights(input: Record<string, number>): Record<string, number> {
|
|
const parsed = scoringWeightsSchema.parse(input);
|
|
assert.ok(parsed, "the weights schema returned nothing for a valid payload");
|
|
return parsed as Record<string, number>;
|
|
}
|
|
|
|
test("the weights schema accepts exactly the factors the scorer declares", () => {
|
|
const declared = Object.keys(DEFAULT_WEIGHTS).sort();
|
|
const accepted = Object.keys(parseWeights(DEFAULT_WEIGHTS as Record<string, number>)).sort();
|
|
assert.deepEqual(
|
|
accepted,
|
|
declared,
|
|
"a factor the scorer weighs is missing from the schema (or vice versa) — " +
|
|
"adding a factor to DEFAULT_WEIGHTS means adding it here too"
|
|
);
|
|
});
|
|
|
|
test("saving the default weights gives back the default weights", () => {
|
|
const saved = parseWeights(DEFAULT_WEIGHTS as Record<string, number>);
|
|
for (const [factor, weight] of Object.entries(DEFAULT_WEIGHTS)) {
|
|
assert.equal(saved[factor], weight, `weight for "${factor}" did not survive validation`);
|
|
}
|
|
});
|
|
|
|
test("an explicit anti-concentration weight survives validation", () => {
|
|
const saved = parseWeights({
|
|
...(DEFAULT_WEIGHTS as Record<string, number>),
|
|
connectionDensity: 0.2,
|
|
});
|
|
assert.equal(saved.connectionDensity, 0.2);
|
|
});
|
|
|
|
test("an explicit quality weight survives validation", () => {
|
|
const saved = parseWeights({ ...(DEFAULT_WEIGHTS as Record<string, number>), quality: 0.1 });
|
|
assert.equal(saved.quality, 0.1);
|
|
});
|
|
|
|
test("an unknown key is still dropped — the schema does not become permissive", () => {
|
|
const saved = parseWeights({
|
|
...(DEFAULT_WEIGHTS as Record<string, number>),
|
|
notAFactor: 0.5,
|
|
});
|
|
assert.ok(
|
|
!("notAFactor" in saved),
|
|
"the schema must name the factors it accepts, not accept anything"
|
|
);
|
|
});
|
|
|
|
// The dashboard keeps its own copy of the weight table: it is imported by a client
|
|
// component, and the scorer's module pulls the tier resolver and per-provider cost
|
|
// data behind it. The copy is deliberate; these three tests are what keeps it honest.
|
|
test("the dashboard sliders offer exactly the factors the scorer weighs", () => {
|
|
assert.deepEqual(
|
|
Object.keys(DEFAULT_INTELLIGENT_WEIGHTS).sort(),
|
|
Object.keys(DEFAULT_WEIGHTS).sort(),
|
|
"a factor the scorer weighs has no slider (or vice versa)"
|
|
);
|
|
});
|
|
|
|
test("the dashboard defaults are the engine defaults", () => {
|
|
for (const [factor, weight] of Object.entries(DEFAULT_WEIGHTS)) {
|
|
assert.equal(
|
|
(DEFAULT_INTELLIGENT_WEIGHTS as Record<string, number>)[factor],
|
|
weight,
|
|
`the slider default for "${factor}" is not the engine's default`
|
|
);
|
|
}
|
|
});
|
|
|
|
test("the dashboard defaults are a distribution", () => {
|
|
const total = Object.values(DEFAULT_INTELLIGENT_WEIGHTS).reduce((a, b) => a + b, 0);
|
|
assert.ok(
|
|
validateWeights(DEFAULT_INTELLIGENT_WEIGHTS as never),
|
|
`slider defaults sum to ${total.toFixed(4)}, so the percentages shown to the operator do not add up to 100%`
|
|
);
|
|
});
|
|
|
|
// This is the one behaviour change in the fix, pinned rather than described.
|
|
// A config stored while the schema still stripped the two keys came back with
|
|
// them absent; `normalizeScoringWeights` read that as a deliberate zero and
|
|
// renormalized the remaining thirteen upward. Now the schema supplies the
|
|
// engine's own defaults, so the distribution is the intended one. Anyone who
|
|
// wants the old numbers back has to change this test on purpose.
|
|
test("a config saved without the two keys now scores with the engine's distribution", () => {
|
|
const savedUnderTheOldSchema: Record<string, number> = { ...DEFAULT_WEIGHTS };
|
|
delete savedUnderTheOldSchema.connectionDensity;
|
|
delete savedUnderTheOldSchema.quality;
|
|
|
|
const before = normalizeScoringWeights(savedUnderTheOldSchema as never);
|
|
const after = normalizeScoringWeights(parseWeights(savedUnderTheOldSchema) as never);
|
|
|
|
assert.equal(
|
|
Number((before.quota ?? 0).toFixed(4)),
|
|
0.1549,
|
|
"before the fix the thirteen surviving weights were renormalized upward"
|
|
);
|
|
assert.equal(Number((after.quota ?? 0).toFixed(4)), 0.1429, "now they are the engine's values");
|
|
assert.equal(before.connectionDensity ?? 0, 0, "anti-concentration used to be silently off");
|
|
assert.ok((after.connectionDensity ?? 0) > 0, "and now it votes, which is the point");
|
|
});
|