Skip to content

Add fantasy football auction draft tracker Firefox add-on - #1

Open
RaphaelLikesCoding wants to merge 2 commits into
mainfrom
claude/fantasy-football-auction-tracker-wplpy7
Open

Add fantasy football auction draft tracker Firefox add-on#1
RaphaelLikesCoding wants to merge 2 commits into
mainfrom
claude/fantasy-football-auction-tracker-wplpy7

Conversation

@RaphaelLikesCoding

Copy link
Copy Markdown
Owner

A read-only Firefox sidebar add-on that watches a live fantasy football auction draft and turns it into an analytical layer: who was taken, for how much, what every team has left, what they still need, and what the player on the block is actually worth in this market.

Lands in a new top-level fantasy-auction-tracker/ directory. Nothing in the existing BLIS-Net codebase is touched.

Design decisions worth reviewing

It reads the DOM and the WebSocket, not the screen. The original ask was framed as "look at a browser screen," but a content script already lives inside the page, so pixel-scraping is a last resort rather than the design. Three layers, best first:

Layer Mechanism Confidence
ws page-world shim wrapping window.WebSocket, mirroring frames 1.00
dom MutationObserver over the draft board 0.85
ocr canvas fallback (designed for, not implemented) 0.55

The WS tap is strictly passive — it never sends, blocks, or alters a frame. Both implemented layers run simultaneously; the store deduplicates, so the DOM layer silently covers whatever the WS mapping misses instead of leaving a hole you notice only after the draft.

State is an append-only event log. Budgets, rosters and analytics are a pure function of that log (src/core/reducer.js — pure, total, deterministic). That is what makes replay, undo, and mid-draft page-refresh recovery work at all. Low-confidence observations are quarantined in a review queue rather than applied, and unresolved problems surface as alerts — silence never means "fine."

The platform seam is data, not code. Site knowledge lives in selector profiles, so repairing a redesign is a config change rather than a rewrite. That matters when a platform can reskin the week before your draft.

Analytics

  • Inflation — remaining money ÷ remaining par value, with the $1-per-open-slot floor removed from both sides (the naive form overstates it, since pinned dollars can never chase value)
  • Max bid per teammoney left − $1 × other open slots; the real ceiling a rival can reach, usually well below their raw budget
  • Positional scarcity — startable players left vs. starting slots still to fill, with replacement level derived from roster settings and flex demand split across eligible positions
  • Tier cliffs — the drop to the next player at a position, which is what justifies paying over par
  • Budget pressure — which teams are nearly locked into $1 bids
  • Bid advicewalk-away (par × inflation) and ceiling (+ a configurable slice of the tier cliff), both hard-capped by your own max bid so it never advises a bid you cannot legally make

Valuations are imported from CSV (alias-matched headers; FantasyPros exports work as-is) and rescaled to the league's total money, so a sheet built for a $200/12-team league does not silently bias every suggestion in a $300/10-team one.

Testing

src/core/ has no browser dependency, so the whole analytical layer runs under node --test with no DOM shim — 57 tests, all passing.

tools/simulate.js generates a deterministic 12-team $200 auction (192 sales, every team landing on exactly the cap) plus a matching valuation CSV. tools/replay.js rehearses the full pipeline against it. The replay tests assert end-to-end that no player is drafted twice, no team overspends, every dollar is accounted for, and reducing a prefix of the log equals replaying to that point — the property page-refresh recovery depends on.

Indexing the valuation lookups (they were linear finds inside a reduce, i.e. O(sales × players) on every event) took the suite from 14.1s to 1.8s.

What is not finished

  • The NFL.com and CBS selector profiles are provisional. They were written without access to a live auction draft room and are marked as such in profiles.js. tools/calibrate.js derives the real ones from a mock draft.
  • The WS field mappings need a real capture, so the WebSocket layer ships in record-only mode: it observes and captures frames, and the DOM layer does the work until a mapping is confirmed.
  • OCR is designed for but not implemented — only needed if a platform renders its draft board to <canvas>.

The platform was given as "NFL.com / CBS / other" without a specific pick, so the core and adapter layer were built platform-agnostic; narrowing to one platform is a profile + mapping change, not a code change.

Scope

Read-only and local. It observes your own draft and advises; it does not place bids, automate any interaction, or send anything anywhere — all state stays in browser.storage.local. Auto-bidding would put the user crosswise with platform terms of service, so it is deliberately out of scope.


Generated by Claude Code

claude added 2 commits August 17, 2026 17:52
A read-only Firefox sidebar add-on that observes a live auction draft and
layers analytics on top: who was taken, for how much, what every team has
left, what they still need, and what the player on the block is worth in
the current market.

Design decisions worth calling out:

- Reads the DOM/WebSocket, not the screen. A content script already lives
  inside the page, so OCR is a last resort rather than the design. A
  page-world shim wraps window.WebSocket and mirrors frames (passively --
  it never sends, blocks or alters one); a MutationObserver layer runs
  alongside it so the DOM silently covers whatever the WS mapping misses.

- State is an append-only event log. Budgets, rosters and analytics are a
  pure function of that log, which is what makes replay, undo, and
  mid-draft page-refresh recovery work at all. Low-confidence observations
  are quarantined for review rather than applied.

- The platform seam is data, not code. Site knowledge lives in selector
  profiles so repairing a redesign is a config change, not a rewrite.

Analytics: market inflation (discretionary form, with the $1-per-slot
floor removed from both sides), per-team max bid, positional scarcity
against replacement level, tier cliffs, budget pressure, and bid advice
capped by what you can legally bid.

Valuations are imported from CSV (alias-matched headers, FantasyPros
exports work as-is) and rescaled to the league's total money, so a sheet
built for a different league size does not silently bias every suggestion.

src/core has no browser dependency, so the whole analytical layer runs
under node --test with no DOM shim: 57 tests, plus a deterministic draft
simulator and a replay harness that rehearses the full pipeline against a
recorded draft.

The NFL.com and CBS selector profiles are provisional -- written without
access to a live auction room -- and need calibration against a mock
draft; tools/calibrate.js does that. The WS field mappings need a real
capture, so the WS layer ships in record-only mode.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SjniQqz3FPpxjKzASrVNCR
Valuations say what a player is worth to the market; targets say what he
is worth to you, which is the number you actually bid off. Adds a Targets
tab and the core logic behind it.

- Tiers group players who are interchangeable to you, so losing one only
  matters when the tier runs dry. Tiers are flagged healthy, critical
  (<=2 left) or exhausted.
- Price targets are per-player and override the market-derived ceiling
  entirely -- set $42 and the Live tab says PASS at $43. Every price and
  tier cell is editable in place and saves on change (not on keystroke,
  so a half-typed number never briefly becomes the live ceiling).
- Optional inflation adjustment scales targets with the market, since a
  $40 target in a 1.2x market is really a $48 target. Off by default; the
  adjusted figure is shown beside the typed one so it is never a silent
  override.
- Plan feasibility fits open targets into remaining roster slots and
  compares the cost against money in hand, reporting the exact shortfall.
  Walking into "$12 left, plan needed $60" is the classic auction death
  and this makes it impossible.
- Seeding from the CSV uses the export's own tier column when present,
  otherwise bands by value, and merges rather than replaces so tuned
  prices survive.

Testing, toward the CBS Salary Cap target:

- The DOM adapter is now driven through a real DOM (linkedom, dev-only)
  against a CBS-shaped fixture built deliberately messy: a header row, a
  suffixed name, a D/ST alias, three price formats, and a row with no
  position. The full chain from DOM mutation to league state is asserted.
- tools/selftest.js verifies the profile inside a live draft room and is
  itself tested. It separates FAIL (container present, selector wrong)
  from EMPTY (nothing has happened yet) and NO-SCOPE (parent absent),
  because conflating those sends you chasing correct selectors.

Two bugs found and fixed along the way:

- LEAGUE_CONFIGURED merged its whole payload, so a config event carrying
  a null myTeamId silently erased your own team identity -- disabling the
  max-bid cap, the no-slot verdict and the entire target plan. Config
  merges now ignore null/undefined.
- The self-test scoped row-level selectors to the first matching row,
  which is usually the table header, and so reported correct selectors as
  broken while letting a genuinely wrong one pass as "waiting for a
  sale". It now scopes across every matching row.

124 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SjniQqz3FPpxjKzASrVNCR

Copy link
Copy Markdown
Owner Author

Pushed 188c87b — adds the tiered target board with adjustable price targets, plus the CBS testing work.

Target board

Tiers group players who are interchangeable to you, so losing one only matters when the tier runs dry. Each tier is flagged healthy, critical (≤2 names left) or exhausted.

Price targets are per-player and override the market-derived ceiling entirely — set $42 and the Live tab says PASS at $43 regardless of what the CSV thinks. Every price and tier is an editable cell that saves on change, not on keystroke, so a half-typed "4" of "45" never briefly becomes the live ceiling mid-bidding.

Inflation adjustment (optional, off by default) scales targets with the market, since a $40 target in a 1.2× market is really a $48 target. When on, the adjusted figure renders beside the typed one so it is never a silent override.

Plan feasibility is the number that earns the tab — it fits open targets into remaining roster slots and compares the cost against money in hand:

target board
  tier 1:  0/6 open  won 0 ($0)   lost 6   EXHAUSTED
  tier 2:  1/6 open  won 2 ($41)  lost 3   CRITICAL
  tier 3:  5/6 open  won 0 ($0)   lost 1

plan: $127 of targets for 6 of 13 open slots, $99 available -- SHORT BY $35

Walking into "$12 left, plan needed $60" is the classic auction death; this makes it impossible. Rehearsable via npm run replay -- ... --targets fixtures/sample-targets.json --me team-4.

On testing against CBS

I could not reach cbssports.com from this environment — the network policy returns 403 at the proxy on CONNECT — so live calibration did not happen and the CBS selectors remain unverified. Two things were done instead:

  1. The DOM adapter is now genuinely tested against a real DOM (linkedom, dev-dependency only — the add-on still ships dependency-free). fixtures/cbs-draft-room.html is a CBS-shaped room built deliberately messy: a header row that parses to nothing, $58 / 58 / $7 price formats, a suffixed name, a D/ST alias, and a row with no position. The full chain from DOM mutation through to league state is asserted end to end. This proves the adapter handles a room of that shape — it does not prove CBS uses those class names.

  2. tools/selftest.js closes that gap in the room, in one paste, and is itself tested. It separates FAIL (container present, selector genuinely wrong) from EMPTY (nothing has happened yet) from NO-SCOPE (parent absent) — conflating those is what sends you chasing selectors that are already correct. __auctionSelfTestLive() additionally confirms the room is observable at all, rather than canvas/shadow-DOM rendered.

Two bugs found while building this

Both were caught by the demo/tests rather than by inspection, and both were real:

  • LEAGUE_CONFIGURED erased your own team identity. It merged its entire payload, so a config event carrying myTeamId: null silently wiped the value you set — disabling your max-bid cap, the no-slot verdict, and the whole target plan. Config merges now ignore null/undefined. Regression tests added.
  • The self-test scoped row selectors to the first matching row, which is usually the table header. That reported correct selectors as broken and let a genuinely wrong one pass as "waiting for a sale" — precisely the false confidence the tool exists to prevent. It now scopes across every matching row.

124 tests pass (was 57).


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants