Address code-review findings: odds band + inert UI knobs
- input-policy: revert the odds->Elo change that passed the policy's eloMin/eloMax into convertFuturesToElo. That widened the default odds-derived spread (1100/1900 vs the calibrated 1250/1750) — sharpening favorites for default-policy odds sims (UCL, World Cup, NCAA-FB, MLB) — and clamped the blend of a high directly-entered base Elo down to the ceiling. Odds now use the calibrated band; the policy's eloMin/eloMax still clamp the result (clampDerived), so a season's floor/ceiling narrows the spread without widening it or capping high base Elos. - admin simulator UI: restrict the structured Engine fields to a HONORED_ENGINE_KNOBS allowlist of keys actually read from config (by a simulator or the input-policy resolver). Bespoke per-sim constants present in a profile but not read from config (homeFieldElo, eloDivisor, srsEloScale, raceNoise, plBeta, fieldSize, bracketSize, ...) no longer render as editable controls that silently do nothing; the raw-JSON escape hatch still edits them. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PAFMogMkFJf52YpHyCDvuf
This commit is contained in:
parent
4142b21c55
commit
84b789db9b
2 changed files with 25 additions and 9 deletions
|
|
@ -69,6 +69,25 @@ interface ActionData {
|
||||||
message: string;
|
message: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Engine knobs that a simulator (or the shared input-policy resolver) actually
|
||||||
|
* reads from config. The structured Engine fields are limited to these so the UI
|
||||||
|
* never shows a control that silently does nothing — bespoke per-sim constants
|
||||||
|
* (e.g. homeFieldElo, eloDivisor, srsEloScale, raceNoise) that live in a profile
|
||||||
|
* but are not read from config stay editable only via the raw-JSON escape hatch.
|
||||||
|
*/
|
||||||
|
const HONORED_ENGINE_KNOBS = new Set([
|
||||||
|
"iterations",
|
||||||
|
"parityFactor",
|
||||||
|
"seasonGames",
|
||||||
|
"overtimeRate",
|
||||||
|
"matchParityFactor",
|
||||||
|
"averageOpponentElo",
|
||||||
|
"baseDrawRate",
|
||||||
|
"drawDecay",
|
||||||
|
"ratingScaleFactor",
|
||||||
|
]);
|
||||||
|
|
||||||
function parseOptionalNumber(value: string | undefined): number | null {
|
function parseOptionalNumber(value: string | undefined): number | null {
|
||||||
if (value === undefined || value.trim() === "") return null;
|
if (value === undefined || value.trim() === "") return null;
|
||||||
const parsed = Number(value);
|
const parsed = Number(value);
|
||||||
|
|
@ -335,7 +354,7 @@ export default function AdminSportsSeasonSimulator({ loaderData }: Route.Compone
|
||||||
// nested object edited in its own section). Driving the fields from the merged
|
// nested object edited in its own section). Driving the fields from the merged
|
||||||
// config means each simulator shows exactly the knobs it actually reads.
|
// config means each simulator shows exactly the knobs it actually reads.
|
||||||
const engineEntries = Object.entries(config.config)
|
const engineEntries = Object.entries(config.config)
|
||||||
.filter(([key, value]) => key !== "inputPolicy" && typeof value === "number")
|
.filter(([key, value]) => HONORED_ENGINE_KNOBS.has(key) && typeof value === "number")
|
||||||
.map(([key, value]) => [key, value as unknown as number] as [string, number]);
|
.map(([key, value]) => [key, value as unknown as number] as [string, number]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
||||||
|
|
@ -254,14 +254,11 @@ export function resolveSourceElos(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (oddsInputs.length >= 2) {
|
if (oddsInputs.length >= 2) {
|
||||||
// Map onto the configured Elo band so the policy's floor/ceiling set the
|
// Odds map onto the calibrated band (probability-engine DEFAULT_CALIBRATION);
|
||||||
// odds-derived *spread* (the real flattening dial), not just a post-hoc clamp.
|
// the policy's eloMin/eloMax then clamp the result via clampDerived below, so
|
||||||
const converted = convertFuturesToElo(oddsInputs, "american", {
|
// a season's Elo floor/ceiling still narrows the odds-derived spread without
|
||||||
exponent: 0.33,
|
// widening it (and without clamping a high directly-entered base Elo's blend).
|
||||||
eloMin: policy.eloMin,
|
for (const [participantId, elo] of convertFuturesToElo(oddsInputs)) {
|
||||||
eloMax: policy.eloMax,
|
|
||||||
});
|
|
||||||
for (const [participantId, elo] of converted) {
|
|
||||||
oddsElo.set(participantId, elo);
|
oddsElo.set(participantId, elo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue