← Index

Draft Agent

2026fastapi · llm · sseRepo

Two FastAPI servers that stream live bid advice during fantasy baseball drafts — one for Yahoo auctions, one for ESPN snake drafts. They share nothing but a projections loader, which is deliberate: an auction and a snake draft are different games and pretending otherwise produces advice that is wrong in both.

The valuations are the product

The language model is the interface, not the engine. What actually decides anything is auction_values.py, and the useful part is that it runs again after every single pick.

Values are recomputed against the remaining budget and the remaining player pool, with a $1 floor that guarantees the dollar values still sum to the money left in the room. So a player's price drifts as the draft empties out — the same outfielder is worth one number in round three and a different one once four teams have spent their stack. The model is always reasoning over current market prices instead of a preseason ranking that stopped being true twenty picks ago.

Points leagues get a separate path entirely: projected points above replacement rather than category z-scores, because roto value and points value disagree constantly and averaging them produces a number that describes no league at all.

Recommendations stream over SSE — tokens forwarded as they arrive, then a terminal done event carrying the parsed object, so the UI can show thinking without parsing half-formed JSON.

Self-healing someone else's OAuth bug

The yahoo_oauth library intermittently writes a valid JSON object followed by a stray closing brace. json.load raises Extra data, and every token refresh 500s until you hand-edit the file.

try:
    obj, _ = json.JSONDecoder().raw_decode(raw.lstrip())
except json.JSONDecodeError:
    logger.warning("token repair: %s is corrupt and unsalvageable — leaving as-is", token_path)
    return False
if not isinstance(obj, dict) or 'refresh_token' not in obj:
    return False  # don't clobber a genuinely-broken file

raw_decode returns the first valid object and the offset where it stopped, so the trailing garbage can be discarded without guessing at the file's structure.

The two guards are the point. A repair routine that rewrites the file whenever parsing fails will happily overwrite a genuinely broken token with something worse, on a path that only runs when things are already going badly. This one refuses to write unless what it recovered actually looks like a token.

It is called defensively at the top of the Yahoo connect path, because the failure shows up mid-draft, and mid-draft is the one time nobody is going to read a stack trace.

4,795 lines across 20 modules. 140 tests.