/** * Pure helpers for the Simulator Setup page, split out so they can be unit tested * without pulling the route's server-only imports into the test. */ import type { BaseEloKey, ResolvedRating, ResolvedSourceElo, } from "~/services/simulations/input-policy"; /** * Short badge text for how a participant's Elo or rating was produced, or null for a * directly entered one — the unremarkable case, which needs no badge. * * The preview table needs this because a generated value is deliberately hidden from * `getParticipantSimulatorInputs`, so without the resolved value plus this label the * row reads as "nothing saved" and a projection losing to a raw Elo is invisible. * * Every remaining method is a missing-input fallback (`fallbackElo`, * `fallbackRating`, `averageKnown`, `worstKnownMinus`, `block`), which all read the * same way to an admin: this participant had nothing usable of its own. */ export function resolvedInputMethodLabel( method: ResolvedSourceElo["method"] | ResolvedRating["method"] ): string | null { switch (method) { case "direct": return null; case "projectedWins": case "projectedTablePoints": return "from projections"; case "sourceOdds": return "from futures"; case "blend": return "blended"; default: return "fallback"; } } /** * Method flag for a bulk-input row that carries a projection instead of an Elo, or * undefined when the row says nothing about how its Elo was produced. * * A row supplying a projection but no explicit Elo means "derive the Elo from this * projection". Stamping the flag marks whatever Elo is already stored as generated, * so `getParticipantSimulatorInputs` hides it and `resolveSourceElos` re-derives * from the projection — without it, the non-destructive upsert leaves a stale * hand-entered Elo in place, and that Elo wins the `baseEloPriority` race so the * projection is written to the database and then ignored on every run. * * Returning undefined (rather than an empty object) matters: the upsert only * preserves existing metadata, and clears a stale flag for a fresh direct Elo, when * the incoming metadata is null. */ export function projectionMethodMetadata( sourceElo: number | undefined, projectedWins: number | undefined, projectedTablePoints: number | undefined ): Record | undefined { if (sourceElo !== undefined) return undefined; if (projectedWins !== undefined) return { sourceEloMethod: "projectedWins" }; if (projectedTablePoints !== undefined) return { sourceEloMethod: "projectedTablePoints" }; return undefined; } /** * Translate the Base Elo Source select into a full `baseEloPriority` list. Only the * head of the list is user-facing (raw Elo vs. projections); the remaining keys keep * their existing relative order so a season that already has a custom ordering is * not silently flattened. */ export function parseBaseEloPriorityChoice( value: FormDataEntryValue | null, current: BaseEloKey[] ): BaseEloKey[] { // The select only renders for simulators that can derive Elo from a projection. // When it was not on the form there is no choice to apply, so keep what is stored // rather than silently rewriting the season's ordering. if (value === null) return current; const projections = current.filter((key) => key !== "sourceElo"); return value === "projectionsFirst" ? [...projections, "sourceElo"] : ["sourceElo", ...projections]; }