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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
if (value === undefined || value.trim() === "") return null;
|
||||
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
|
||||
// config means each simulator shows exactly the knobs it actually reads.
|
||||
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]);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -254,14 +254,11 @@ export function resolveSourceElos(
|
|||
);
|
||||
}
|
||||
if (oddsInputs.length >= 2) {
|
||||
// Map onto the configured Elo band so the policy's floor/ceiling set the
|
||||
// odds-derived *spread* (the real flattening dial), not just a post-hoc clamp.
|
||||
const converted = convertFuturesToElo(oddsInputs, "american", {
|
||||
exponent: 0.33,
|
||||
eloMin: policy.eloMin,
|
||||
eloMax: policy.eloMax,
|
||||
});
|
||||
for (const [participantId, elo] of converted) {
|
||||
// Odds map onto the calibrated band (probability-engine DEFAULT_CALIBRATION);
|
||||
// the policy's eloMin/eloMax then clamp the result via clampDerived below, so
|
||||
// a season's Elo floor/ceiling still narrows the odds-derived spread without
|
||||
// widening it (and without clamping a high directly-entered base Elo's blend).
|
||||
for (const [participantId, elo] of convertFuturesToElo(oddsInputs)) {
|
||||
oddsElo.set(participantId, elo);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue