npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@buzzr/dfs-engine

v3.0.1

Published

Buzzr DFS Settlement OS core: extensible book policies, pure grading, and provider-ready settlement orchestration.

Readme

@buzzr/dfs-engine

npm version ci

Pure-functional DFS prop grading, payout math, stat normalization, and policy-aware settlement for DFS pick'em apps. PrizePicks and Underdog ship as stable built-ins, and v3 lets you register any custom book without forking. Drop-in TypeScript, zero runtime dependencies, ESM + CJS + .d.ts shipped.

Sports covered: NBA, WNBA, NCAAM/W, NFL, MLB, NHL, EPL, MLS, La Liga, NWSL, UEFA Champions League. ~70 props.

npm install @buzzr/dfs-engine

Why this exists

If you're building a DFS-adjacent tool — a bet tracker, parlay analyzer, EV calculator, social betting app, fantasy coaching tool — you eventually need code that answers:

  • Did this leg hit? Given a player's actual stat and a slip line, decide won / lost / push.
  • What does the slip pay out? Given the play type (Power / Flex / Standard), the pick count, the hits, and any boost, compute the multiplier and the withdrawable-vs-bonus split.
  • What happens when a player doesn't play? Demote a six-pick to a five-pick (PrizePicks) or scratch and rescale (Underdog).
  • What stat goes into a Pts + Rebs + Asts leg? Or Pass + Rush + Rec Yds? Or Hitter FS?

There's no good open-source TypeScript package for any of this. Everyone reinvents it from scratch, usually wrong. This is the version extracted from Buzzr, where it's been settling real money lines in production. ~1.6K LOC of pure functions, ~116 tests.

Quickstart

import { gradeLegFromActual } from '@buzzr/dfs-engine';

// Player scored 28 against a line of 24.5 over → leg won.
gradeLegFromActual(24.5, 'over', 28);  // 'won'

// Same line, only 20 → leg lost.
gradeLegFromActual(24.5, 'over', 20);  // 'lost'

// Game hasn't ended yet (no stat available) → leg pending.
gradeLegFromActual(24.5, 'over', null); // 'pending'

v3 Settlement OS

For full-entry settlement, create an isolated engine. Each engine owns its own book policies, payout overrides, league adapters, providers, clock, and audit metadata, so tests, apps, and plugins do not mutate a global registry.

import { createDfsEngine, defineBookPolicy, definePayoutTable } from '@buzzr/dfs-engine';

const myBook = defineBookPolicy({
  id: 'my-book',
  displayName: 'My Book',
  version: '2026-05',
  effectiveFrom: '2026-05-01',
  status: 'stable',
  sources: [{ label: 'Internal rules memo' }],
  playTypes: [
    {
      id: 'all-in',
      displayName: 'All-In',
      payoutModel: 'fixed-table',
      pickCount: { min: 2, max: 4 },
      allOrNothing: true,
    },
  ],
  tiePolicy: { type: 'push' },
  dnpPolicy: { type: 'remove_leg', voidIfNoSurvivors: true },
  pushPolicy: { type: 'remove_leg', refundIfNoSurvivors: true },
  payoutSplit: { type: 'all_withdrawable' },
  validation: { duplicatePlayers: 'error' },
});

const engine = createDfsEngine({
  bookPolicies: [myBook],
  payoutTables: [
    definePayoutTable({
      bookId: 'my-book',
      playTypeId: 'all-in',
      effectiveFrom: '2026-05-01',
      entries: [{ pickCount: 2, hits: 2, multiplier: 4 }],
    }),
  ],
});

const result = await engine.settleEntry(
  {
    entryId: 'slip-1',
    bookId: 'my-book',
    playTypeId: 'all-in',
    stake: 10,
    displayedMultiplier: 4,
    legs: [
      {
        legId: 'a',
        playerName: 'A. Example',
        playerId: 'athlete-1',
        league: 'NBA',
        propType: 'Points',
        line: 24.5,
        direction: 'over',
        gameDate: '2026-05-07',
      },
      {
        legId: 'b',
        playerName: 'B. Example',
        league: 'NBA',
        propType: 'Rebounds',
        line: 7.5,
        direction: 'over',
      },
    ],
  },
  { actualsByLegId: { a: 28, b: 9 } },
);

console.log(result.status, result.payout, result.policyVersion, result.explanationCodes);

Legacy app / playType inputs are intentionally not the main v3 model. Use adaptV2EntryInput(...) during migration:

import { adaptV2EntryInput } from '@buzzr/dfs-engine';

const v3Input = adaptV2EntryInput({
  entryId: 'legacy-slip',
  app: 'underdog',
  playType: 'underdog_flex',
  stake: 10,
  displayedMultiplier: 11.5,
  legs: [],
});

Optional SDK packages:

  • @buzzr/dfs-provider-espn wraps your ESPN-shaped loader as a stat provider.
  • @buzzr/dfs-testkit ships fixture builders and mock providers for settlement tests.

Examples

1. Look up the payout for a pick count + hit count

import { lookupStandardMultiplier } from '@buzzr/dfs-engine';

// PrizePicks 5-pick Power, all five hit → 20×.
lookupStandardMultiplier({ app: 'prizepicks', playType: 'power', pickCount: 5, hits: 5 });
// → 20

// PrizePicks 6-pick Flex, only 5 of 6 hit → 1.75×.
lookupStandardMultiplier({ app: 'prizepicks', playType: 'flex', pickCount: 6, hits: 5 });
// → 1.75

// Underdog 8-pick Standard, all hit → 100×.
lookupStandardMultiplier({ app: 'underdog', playType: 'underdog_standard', pickCount: 8, hits: 8 });
// → 100

2. Recompute the multiplier after a DNP

import { recalcMultiplierAfterDnp } from '@buzzr/dfs-engine';

// One leg on a 6-pick Power scratched. Demote to a 5-pick (all surviving
// must hit), scaling the slip's original multiplier proportionally so
// any boost flows through.
const { newMultiplier } = recalcMultiplierAfterDnp({
  app: 'prizepicks',
  playType: 'power',
  originalPickCount: 6,
  survivingPickCount: 5,
  survivingHits: 5,
  originalMultiplier: 37.5,   // slip-displayed multiplier (post-boost)
});
// newMultiplier ≈ 20 (37.5 × 20/37.5)

recalcMultiplierAfterDnp returns { newMultiplier, usedFallback }. usedFallback is true when the payout table doesn't cover the (app, playType, pickCount, hits) tuple — caller should warn the user that the recompute couldn't be verified.

3. Extract a stat from a gamelog entry

The grader needs a numeric value to compare against the line. extractStatForProp handles the prop-string → stat-value mapping across leagues:

import { extractStatForProp } from '@buzzr/dfs-engine';

const entry = {
  date: '2026-05-04',
  minutes: '38:21',
  points: '28',
  rebounds: '4',
  assists: '7',
  steals: '1',
  blocks: '0',
  turnovers: '2',
  threeP: '3',
};

extractStatForProp('Points', 'NBA', entry, 'prizepicks');          // 28
extractStatForProp('Pts+Rebs+Asts', 'NBA', entry, 'prizepicks');   // 39
extractStatForProp('3-Pointers Made', 'NBA', entry, 'prizepicks'); // 3
extractStatForProp('Rebounds', 'NBA', entry, 'prizepicks');        // 4

Slip-text aliases are normalized — "3PT Made", "3-pt made", "3ptm", "3pm", "threes" all resolve to '3-Pointers Made'. v0.3 adds 14 new props (Double-Double, Triple-Double, Pts+Stls, Longest Reception/Rush/Pass, MLB Singles/Doubles/Triples/Runs, Pitching Outs, NHL Plus/Minus). See DFS_PROP_TYPE_KEYS for the full canonical list (60+ props across NBA / WNBA / NCAAM/W / NFL / MLB / NHL).

4. Grade a full entry end-to-end

gradeDfsBetFromGraded rolls per-leg statuses into a bet-level result with the boost split:

import { gradeDfsBetFromGraded } from '@buzzr/dfs-engine';

const result = gradeDfsBetFromGraded({
  app: 'underdog',
  playType: 'underdog_flex',
  legs: [
    { legId: 'a', legStatus: 'won',  /* ...DfsBetLeg fields */ },
    { legId: 'b', legStatus: 'won',  /* ... */ },
    { legId: 'c', legStatus: 'lost', /* ... */ },
    { legId: 'd', legStatus: 'won',  /* ... */ },
    { legId: 'e', legStatus: 'won',  /* ... */ },
  ],
  stake: 10,
  displayedMultiplier: 11.5,      // boosted from base 10×
  baseMultiplier: 10,
  profitBoostPct: null,
});
// 4-of-5 Underdog Flex → standard 2×; scaled by displayed/base ratio.
// → { status: 'won', effectiveMultiplier: 2.3, totalPayout: 23,
//     withdrawablePayout: 20, bonusPayout: 3 }

Pending semantics: if any surviving leg is legStatus: 'pending', the whole bet returns status: 'pending' — you can call this every time a leg's actualValue updates without risk of premature settlement.

Add your own sport

Built-in coverage is NBA, WNBA, NCAAM/W, NFL, MLB, NHL. The plugin registry lets you add a sport without forking:

import {
  registerLeague,
  extractStatForProp,
  type AdapterTable,
} from '@buzzr/dfs-engine';

const SOCCER_ADAPTERS: AdapterTable = {
  Goals: (entry) => parseInt(entry.points, 10) || null,
  Assists: (entry) => parseInt(entry.rebounds, 10) || null,
};

registerLeague('EPL', SOCCER_ADAPTERS);
registerLeague('MLS', SOCCER_ADAPTERS);

extractStatForProp('Goals', 'EPL', someEntry, 'prizepicks'); // your value

getRegisteredLeagues() returns the current list; unregisterLeague(name) removes one (useful in tests).

Explained variants for richer error handling

When null isn't specific enough, use the *Explained variants — they return a discriminated union with a reason code so you can show the user why a leg can't be graded yet:

import {
  extractStatForPropExplained,
  gradeLegFromActualExplained,
} from '@buzzr/dfs-engine';

const stat = extractStatForPropExplained('Yellow Cards', 'EPL', entry, 'prizepicks');
if (!stat.ok) {
  console.log(stat.reason); // 'unknown_prop' | 'unsupported_league' | 'prop_not_supported_for_league' | 'adapter_returned_null'
  console.log(stat.detail); // human-readable context
}

const grade = gradeLegFromActualExplained(24.5, 'over', NaN);
if (!grade.ok) {
  console.log(grade.reason); // 'pending' | 'unparseable_actual'
}

What's in here

| Module | Highlights | |---|---| | payouts | lookupStandardMultiplier, recalcMultiplierAfterDnp, lookupBaseMultiplier — full PrizePicks (Power/Flex) and Underdog (Standard/Flex) payout schedules | | grading | gradeLegFromActual (+Explained), gradeDfsBetFromGraded, applyLegDnp, computeBoostSplit, detectMidGameDnp, reconcileMidGameDnpEntries, findGameLogCandidates, shouldRegradeLeg, extractStatForProp (+Explained) | | prop-normalizer | normalizeDfsPropType, asDfsPropTypeKey, DFS_PROP_TYPE_KEYS | | stat-adapters | getStatAdapter, extractStatForPropViaRegistry, registerLeague / unregisterLeague / getRegisteredLeagues, plus per-sport tables: BASKETBALL_ADAPTERS, NFL_ADAPTERS, MLB_ADAPTERS, NHL_ADAPTERS | | reconciliation-windows | isWithinReconciliationWindow, per-league stat-correction TTLs (NBA 2h, NFL 24h, MLB 6h) | | live-helpers | shouldWriteLiveActual, buildLiveSnapshot, buildLiveLegAlertTitle for live-watcher write-paths | | boxscore-shape | boxScorePlayerToGameLogShape for sources that only ship some stats on the boxscore (NHL Hits, Blocked Shots) | | types | DfsApp, DfsPlayType, DfsLegStatus, DfsBetLeg, DfsLegGameContext, DfsParseResult, LegLinkage, DfsPayoutSplit, BetslipParseMeta, …and ~15 more |

The PlayerGameLogEntryShape the adapters consume is intentionally minimal — define your own gamelog rows that satisfy the shape ({ date, minutes, points, ... }) and pipe them in.

See CHANGELOG.md for what's new in each release. Looking to contribute? Start at CONTRIBUTING.md. Copy-paste-runnable demos live in examples/README.md. Generated API docs: sarveshsea.github.io/dfs-engine.

Performance

Pure functions, zero deps, sub-microsecond on a Mac M-series (from npm run bench):

| Function | ops/sec | |---|---| | gradeLegFromActual | ~24M | | extractStatForPropViaRegistry (NBA Points) | ~7.5M | | gradeDfsBetFromGraded (5-pick Power) | ~11.5M | | recalcMultiplierAfterDnp | ~20M | | applyLegDnp (6-pick) | ~5.8M |

Floor numbers — every operation completes in microseconds. You will not be CPU-bound by this library.

Stability

Starting at 1.0, the public API is frozen. Breaking changes only at major versions. New sports, props, and *Explained failure reasons can ship in minor releases without breaking consumers. See CHANGELOG.md for the full stability contract.

Validating untrusted inputs

When an LLM, webhook, or cross-process source hands you a slip leg or gamelog entry, run it through the validator before grading:

import { validatePlayerGameLogEntryShape, validateDfsBetLeg } from '@buzzr/dfs-engine';

const v = validatePlayerGameLogEntryShape(maybeEntry);
if (!v.ok) {
  console.error('Bad gamelog entry:', v.errors);
  return;
}
// v.value is now typed as PlayerGameLogEntryShape

Status & caveats

  • Payout tables current as of 2026-05. PrizePicks and Underdog adjust their schedules periodically; if a recalc looks wrong, check whether the published schedule changed.
  • Slip-displayed multiplier always wins. Tables are only the demotion ratio baseline — Demon/Goblin/boost markups aren't enumerated.
  • Gamelog parsing is your problem. This package grades stats; it doesn't fetch them. Adapt ESPN, your own scraper, or a paid data feed to PlayerGameLogEntryShape upstream.
  • Sport coverage: NBA / WNBA / NCAAM (basketball), NFL, MLB (batters + pitchers), NHL (skaters + goalies). Adding a sport means a new AdapterTable plus extending DfsPropTypeKey.

Origin

Extracted from Buzzr, where it settles user bets placed on PrizePicks and Underdog. The Buzzr team has been iterating on this math against real slips and real stat-correction edge cases for two years. The npm package is the same code, just decoupled from the app.

License

MIT © Sarvesh Chidambaram