@buzzr/dfs-engine
    Preparing search index...

    Type Alias LegLinkage

    @buzzr/dfs-engine — pure-functional DFS prop grading, payouts, and stat normalization for PrizePicks/Underdog-style contests.

    All exports are pure functions or types — no I/O, no React, no native deps. Feed it data, get grading decisions and payout math back.

    type LegLinkage = {
        candidates: PlayerCandidate[];
        gameDate: string | null;
        gameId: string | null;
        gameMatchSource: "exact-time" | "closest-time" | "manual" | null;
        gameStartsAt: string | null;
        gameStartTime: string | null;
        resolvedAt: string;
        resolvedAthleteId: string | null;
        resolvedName: string | null;
        source: "directory" | "espn-api" | "manual" | null;
        status: LegLinkageStatus;
    }
    Index

    Properties

    candidates: PlayerCandidate[]

    Candidate set surfaced to the user. Empty when status === 'resolved' or 'manual'.

    gameDate: string | null

    Calendar date of the game in the league's local timezone, e.g. "2026-05-07". Set from slip text ("Thu, 6pm" → next Thursday in device TZ) or from a user-supplied date picker. Anchors the resolveLegGame query and the settlement watcher's grading window.

    NULL when the parser couldn't infer a day-of-week and the user hasn't supplied one yet — verify card surfaces a prompt so it can be filled in retroactively.

    gameId: string | null

    Game-id linkage. Populated by resolveLegGame against the public.games table on team+date+league match. NULL when the game can't be located — settlement still works without it (we fall back to the player's gamelog entries by date), but linking enables reactive UI on game state changes.

    gameMatchSource: "exact-time" | "closest-time" | "manual" | null

    How gameId was resolved. Surfaces in the verify card so the user knows whether we matched their game cleanly or just made a best guess by closest-time within the same date.

    • 'exact-time' — slip-time is within ~30 min of games.starts_at
    • 'closest-time' — same teams + same date matched, but time delta was larger (likely TZ slop or rounding)
    • 'manual' — user picked the game directly via verify card
    • null — gameId is null (no match found, or query not run yet)
    gameStartsAt: string | null

    ISO UTC timestamp of when the game starts. Derived from (gameDate + gameStartTime + device TZ) at save time, then cross-validated against public.games.starts_at when the game is resolved. Authoritative for the watcher's grading window — replaces placed_at as the dateHint when set, eliminating the futures-bet failure mode of the placed_at-anchored ±36h window.

    Watcher behavior keyed on this:

    • NULL → fall back to placed_at + asymmetric window (legacy bets).
    • Set, in the future → skip with skipped_game_not_final cheaply (no ESPN call until the game has actually been played).
    • Set, in the past → tight ±12h window around this for gamelog entry matching.
    gameStartTime: string | null

    Game tipoff/first-pitch time in 24h format ("18:00", "20:40") as displayed on the slip in the user's device timezone. Combined with gameDate and the device TZ at parse time to derive gameStartsAt. Stored separately so the verify card can show the user what they originally saw on the slip even if gameStartsAt later gets corrected from public.games.

    NULL when not extractable; user can fill in via the verify card.

    resolvedAt: string

    ISO timestamp of the most recent resolver run for this leg.

    resolvedAthleteId: string | null

    The chosen athleteId once resolved. Mirrors leg.playerAthleteId — the leg-level field stays as a denormalized convenience for the settlement watcher path (which only reads playerAthleteId), while this field is the canonical resolver output. NULL when status === 'ambiguous' / 'unmatched' / 'pending'.

    resolvedName: string | null

    Display name attached to the resolved athlete, or NULL while unresolved.

    source: "directory" | "espn-api" | "manual" | null

    Where the resolved athleteId came from.