skills/mlb-category-state-analyzer/SKILL.md
Computes the weekly category state for a Yahoo H2H Categories matchup across all 10 scoring categories (R, HR, RBI, SB, OBP, K, ERA, WHIP, QS, SV). Pulls current totals from Yahoo, builds rest-of-week per-cat projections from roster + schedule, then DELEGATES matchup/per-cat win-probability math to `matchup-win-probability-sim`. Consumes the sim's `per_cat_win_probability` and `matchup_win_probability` to derive cat_position, cat_pressure, cat_reachability, and cat_punt_score, and emits a "push 6, punt N" plan that drives waiver, streaming, and lineup decisions. Use when user asks about "category state", "where am I winning", "should I punt", "matchup score", "cat pressure", weekly category planning, or which cats to push vs. concede.
npx skillsauth add lyndonkl/claude mlb-category-state-analyzerInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Scenario: Week 3, K L's Boomers (Team 5) vs. Los Doyers. Wednesday AM (mid-week). 4 scoring days remain.
Raw matchup scores (pulled from Yahoo matchup page):
| Cat | Us | Opp | Margin | Games left (us/opp) | |---|---|---|---|---| | R | 28 | 31 | -3 | 26 / 22 | | HR | 9 | 7 | +2 | 26 / 22 | | RBI | 30 | 29 | +1 | 26 / 22 | | SB | 4 | 6 | -2 | 26 / 22 | | OBP | .342 (82 PA) | .336 (78 PA) | +.006 | 26 / 22 GP | | K | 42 | 38 | +4 | 9 SP starts / 7 SP starts | | ERA | 3.80 (21 IP) | 4.12 (19 IP) | -0.32 (better) | 9 / 7 | | WHIP | 1.18 (21 IP) | 1.25 (19 IP) | -0.07 (better) | 9 / 7 | | QS | 2 | 1 | +1 | 9 / 7 | | SV | 3 | 5 | -2 | ~8 RP days / ~8 RP days |
Projections built for the sim (rest-of-week {mean, stddev} — see resources/methodology.md):
| Cat | Our projection | Opp projection | |---|---|---| | R | final 52 ± 9 | final 57 ± 8 | | HR | final 15 ± 3.5 | final 13 ± 3.2 | | RBI | final 52 ± 9 | final 55 ± 8 | | SB | final 6 ± 2.3 | final 10 ± 2.5 | | OBP | .346 ± .015 | .341 ± .014 | | K | final 96 ± 11 | final 85 ± 10 | | ERA | 3.88 ± 0.40 | 4.05 ± 0.45 | | WHIP | 1.20 ± 0.07 | 1.25 ± 0.08 | | QS | final 6.1 ± 1.5 | final 3.8 ± 1.4 | | SV | final 4.8 ± 1.4 | final 7.7 ± 1.5 |
Delegate to matchup-win-probability-sim with:
cat_list = [R, HR, RBI, SB, OBP, K, ERA, WHIP, QS, SV]cat_inverse_list = [ERA, WHIP]cat_win_threshold = 6our_per_cat_projection / opp_per_cat_projection from the table abovesim_mode = "monte_carlo", n_simulations = 10000, random_seed = 42Sim output (consumed by this skill):
matchup_win_probability = 0.58per_cat_win_probability: R 0.36, HR 0.65, RBI 0.42, SB 0.16, OBP 0.60, K 0.74, ERA 0.62, WHIP 0.68, QS 0.85, SV 0.10expected_cats_won = 5.18Per-cat signals (derived here from sim output + baseball state — see resources/methodology.md):
| Cat | Position (from state) | Pressure (state + pace) | Reachability (= round(100 × p_cat)) | Punt Score (= f(1 − p_cat) + volatility) | Verdict | |---|---|---|---|---|---| | R | losing | 72 | 36 | 44 | push (contested) | | HR | winning | 48 | 65 | 21 | maintain | | RBI | winning (thin) | 65 | 42 | 35 | push | | SB | losing | 55 | 16 | 58 | evaluate punt | | OBP | winning (thin) | 70 | 60 | 24 | push | | K | winning | 55 | 74 | 16 | push | | ERA | winning | 62 | 62 | 23 | push | | WHIP | winning | 60 | 68 | 19 | maintain | | QS | winning | 78 | 85 | 9 | push hard | | SV | losing | 38 | 10 | 84 | punt |
Overall recommendation: Push 6, maintain 2, punt 2. Matchup win prob 58% (neutral favorite).
per_cat_win_probability ≥ 0.60. Plus RBI as the contested-but-reachable 6th.Downstream implications for other agents:
matchup_win_probability = 0.58 → neutral-to-favorite, standard daily_quality optimization (no variance tilt).Copy this checklist and track progress:
MLB Category State Analysis Progress:
- [ ] Step 1: Pull current matchup scores from Yahoo
- [ ] Step 2: Count remaining games/PAs/IP for both rosters
- [ ] Step 3: Build per-cat projection dicts ({mean, stddev}) for both rosters
- [ ] Step 4: Delegate to matchup-win-probability-sim (pass cat_list, projections, threshold=6, inverse=[ERA,WHIP])
- [ ] Step 5: Derive cat_position (from state), cat_pressure, cat_reachability, cat_punt_score from sim output
- [ ] Step 6: Rank cats and emit push/maintain/punt plan
- [ ] Step 7: Write signal file with YAML frontmatter (include matchup_win_probability from sim)
Step 1: Pull current matchup scores
Web-fetch the Yahoo matchup page: https://baseball.fantasysports.yahoo.com/b1/23756/5/matchup?week=N. Extract current totals for both teams in each of the 10 cats. For ratio cats (OBP, ERA, WHIP), also capture the denominator (PAs for OBP, IP for ERA/WHIP). This is required — you cannot build a ratio-cat projection without the volume underlying the ratio.
See resources/methodology.md for scrape procedure and fallback if Yahoo is unreachable.
Step 2: Count remaining games/PAs/IP
For each roster, count the number of MLB games its players will play for the rest of the scoring period, and project PAs (hitters) and IP (pitchers).
per_cat_win_probability)Use MLB.com schedules + probable pitcher grids. See resources/methodology.md.
Step 3: Build per-cat projection dicts
For each team, build a dict {cat: {mean, stddev}} where mean is the projected final (or remaining, consistently used across both teams — pick one convention) and stddev reflects uncertainty given remaining volume.
mean = current_total + Σ(per-player per-game rate × games remaining × daily_quality). stddev ≈ 0.35 × expected_remaining as a default CV.mean = (current_ratio × current_volume + projected_remaining_ratio × remaining_volume) / total_volume. stddev ≈ σ_per_obs / sqrt(total_volume) — shrinks as total IP/PA grows.cat_list.qs_probability (not W) from upstream mlb-player-analyzer signals — see Guardrails.See resources/methodology.md.
Step 4: Delegate to matchup-win-probability-sim
Invoke the sibling skill with a well-formed input payload:
inputs to matchup-win-probability-sim:
cat_list: [R, HR, RBI, SB, OBP, K, ERA, WHIP, QS, SV]
cat_inverse_list: [ERA, WHIP]
cat_win_threshold: 6
our_per_cat_projection: <dict from Step 3>
opp_per_cat_projection: <dict from Step 3>
sim_mode: "monte_carlo"
n_simulations: 10000
random_seed: 42
tie_rule: "half"
outputs consumed:
matchup_win_probability (float in [0,1])
per_cat_win_probability (dict[cat, float])
expected_cats_won (float)
variance_estimate (float)
cat_inverse_list = [ERA, WHIP] (lower-is-better)cat_win_threshold = 6 (Yahoo 10-cat majority)Step 5: Derive per-cat signals from sim output + state
Apply the formulas in resources/methodology.md. The sim owns the probability math; this skill owns the baseball-state interpretation.
cat_position ∈ {winning, tied, losing} — computed locally from current totals (not sim). Ratio-cat direction handled (OBP higher = winning; ERA/WHIP lower = winning).cat_pressure (0–100) — simple arithmetic from position + close-margin + volume-edge + locked-in flags. See pressure formula in Quick Reference.cat_reachability (0–100) — now = round(100 × per_cat_win_probability[cat]), taken directly from the sim.cat_punt_score (0–100) — (100 × (1 − per_cat_win_probability[cat])) × 0.6 + 30 × is_volatile + 20 × below_min_threshold − 10 × has_spillover, clamped.Step 6: Rank and emit plan
Rank all 10 cats by cat_pressure × cat_reachability / 100:
cat_punt_score > 60, confirm punt; otherwise holdGoal in H2H Cats is 6-of-10. A defensible plan is "push 6, concede up to 4." See resources/template.md for the output signal format.
Step 7: Write signal file
Write to signals/YYYY-MM-DD-cat-state.md with YAML frontmatter (type: cat-state). Include matchup_win_probability from the sim as a top-level field. Validate with mlb-signal-emitter before persisting.
matchup_win_probability and expected_cats_won recorded in frontmattersim_meta block (sim_mode, n_simulations, random_seed) recorded for reproducibilitysource_urls includes Yahoo matchup page + MLB.com schedule pages + a reference to the sim skillValidate output using resources/evaluators/rubric_mlb_category_state_analyzer.json. Minimum: average score of 3.5 or above.
Pattern 1: Balanced mid-week state
p ∈ [0.40, 0.65] are the contested ones.Pattern 2: Volume-imbalanced matchup
per_cat_win_probability for R/HR/RBI/SB rises accordingly.Pattern 3: Two-start ace incoming (us or them)
per_cat_win_probability deltas.Pattern 4: Save-category volatility
per_cat_win_probability near 0.1–0.25 when behind by 2+.cat_punt_score (applied here, not in the sim) pushes SV to punt when sim reachability agrees.Pattern 5: Ratio-cat "freeze"
per_cat_win_probability ≈ 1.0 for those cats.Never compute OBP/ERA/WHIP from rates alone — always include volume (PA/IP). A .400 OBP in 10 PAs is not better than .342 in 82 PAs. The projection-dict mean/stddev for ratio cats must come from the weighted-average formula; the sim takes those as truth.
QS is the #1 category, not Wins. This league uses Quality Starts (6+ IP, ≤3 ER). A 5-inning outing scores zero. When projecting remaining QS, multiply each SP start by its QS probability (from mlb-player-analyzer's qs_probability signal) — don't just count scheduled starts.
OBP is the #5 category, not AVG. Walks count. When projecting OBP contribution, use players' OBP (not AVG). A high-BB, low-AVG player like Juan Soto is worth more in this league than his raw hit rate suggests.
SV is volatile — trust the punt when signals agree. Unlike counting batting cats, a 2-save deficit with 3 days left has low per_cat_win_probability regardless of roster. Don't fight for saves if the closer role on your roster isn't locked (check save_role_certainty < 70 → automatic punt candidate). The volatility bonus in cat_punt_score is applied here, not in the sim — the sim returns raw probability.
cat_reachability comes from the sim — don't recompute. This is a delegation. If the sim returns per_cat_win_probability[R] = 0.36, then cat_reachability[R] = 36. Do not apply z-score shortcuts or best/worst-case buckets here — those lived in the old heuristic and are now owned by the sim skill.
Locked-in cats get pressure adjustments, not zero. A locked-in win still has cat_pressure ≈ 40 (it's banked). A locked-in loss still has cat_pressure ≈ 20 (stop investing). Don't set them to zero — downstream agents use non-zero values to decide bench vs. drop.
Ratio cats need the minimum-IP/PA rule. Yahoo enforces minimums for pitcher ratio cats (usually 20 IP for the week). If either roster is tracking below the minimum late in the week, the ratio cat may auto-loss. Encode this in the projection dict (stddev → 0, mean → punitive) before calling the sim, AND add +20 below_min_threshold to cat_punt_score.
Never re-derive upstream signals. qs_probability, sb_opportunity, obp_contribution, save_role_certainty come from mlb-player-analyzer. Read them from the signal directory; do not recompute.
Always pass a random_seed to the sim. Without it, two runs of this skill produce slightly different cat_reachability values, which will confuse downstream agents doing diff comparisons. Default seed: 42.
Where the math lives now:
| Signal | Owner | Formula |
|---|---|---|
| cat_position | this skill | enum from current totals (ratio-direction aware) |
| cat_pressure | this skill | baseline 50 + 20 × close + 15 × vol-edge − 10 × locked_win − 30 × locked_loss |
| cat_reachability | delegated to matchup-win-probability-sim | = round(100 × per_cat_win_probability[cat]) |
| cat_punt_score | this skill (uses sim output) | (100 × (1 − p_cat)) × 0.6 + 30 × volatile + 20 × below_min − 10 × spillover |
| matchup_win_probability | delegated to matchup-win-probability-sim | Monte Carlo P(cats_won ≥ 6) |
cat_pressure =
50 # neutral baseline
+ 20 × (is_close_margin: deficit/lead ≤ 10% of total)
+ 15 × (opponent_volume_exhausted: we have more games left)
- 10 × (locked_in_win)
- 30 × (locked_in_loss)
clamp(0, 100)
cat_reachability = round(100 × per_cat_win_probability[cat]) # from sim
cat_punt_score =
(100 - cat_reachability) × 0.6 # base: if we can't reach, consider punting
+ 30 × (cat is traditionally volatile: SV)
+ 20 × (below min-PA/IP threshold)
- 10 × (cat has spillover: K→QS, OBP→R, HR→R+RBI)
clamp(0, 100)
League constants (from context/league-config.md):
cat_inverse_list to the sim)cat_win_threshold = 6)Signal file output schema (from context/frameworks/signal-framework.md):
---
type: cat-state
date: YYYY-MM-DD
emitted_by: mlb-category-state-analyzer
week: N
matchup_opponent: <team name>
scoring_days_remaining: N
matchup_win_probability: 0.58 # from matchup-win-probability-sim
expected_cats_won: 5.18 # from matchup-win-probability-sim
sim_meta:
sim_mode: monte_carlo
n_simulations: 10000
random_seed: 42
synthesis_confidence: 0.0-1.0
source_urls:
- https://baseball.fantasysports.yahoo.com/b1/23756/5/matchup?week=N
---
Body: per-cat table + overall push/maintain/punt recommendation + red-team findings.
Thresholds used downstream:
| Agent | Threshold | Effect |
|---|---|---|
| Waiver analyst | cat_pressure ≥ 60 | Prioritize targets that fill that cat |
| Streaming strategist | cat_pressure (ERA/WHIP) < 30 | Allow riskier streamers (we're punting) |
| Lineup optimizer | matchup_win_probability < 0.4 / > 0.6 | Variance-seek as underdog / damp as favorite |
| Trade analyzer | weights trade_cat_delta | Multiplied by cat_pressure / 50 |
Key resources:
matchup-win-probability-sim — owns per-cat and matchup-level win-probability math via Monte Carlo / Poisson-binomialInputs required:
qs_probability, save_role_certainty, obp_contribution, sb_opportunity, daily_qualitycat_win_threshold)Outputs produced:
signals/YYYY-MM-DD-cat-state.md — signal file with 10-cat table, overall plan, matchup_win_probability, confidence, source URLscat_position, cat_pressure, cat_reachability, cat_punt_score per catmatchup_win_probability (from sim delegate) for lineup-optimizer variance decisionstesting
Cluster a conference's event records into a small set of coarse themes with finer sub-clusters, an explicit outlier bucket, and soft (multi-membership) affinities — using the hybrid embed-then-label pipeline (embed abstracts, reduce, density-cluster, then LLM-label the clusters) when embedding libraries are available, and an LLM-reasoned hierarchical fallback when they are not. Embeddings do the grouping; the LLM only names the groups. Conference-agnostic. Use when turning structured event records into a navigable theme map for preference elicitation and scheduling, when you need 6-8 reasonable themes rather than 20 muddy ones, or when overlapping talks must belong to more than one theme. Trigger keywords - theme clustering, cluster talks, embed then label, soft membership, outlier talks, conference themes, topic map.
development
Build a personal conference schedule as a constraint-optimization problem — hard constraints (no time overlap, room-to-room travel time, capacity/registration, the attendee's own must-attends and blackouts) plus a user-owned weighted objective trading interest against breadth, pacing (maximize contiguous free time), and serendipity. Surfaces unbreakable conflicts (two high-value overlapping talks the model cannot rank) as decisions for the human rather than silently picking, and reports what each choice traded away. Conference-agnostic. Use to turn a preference profile plus a theme map into a day-by-day plan, to resolve overlapping sessions, or to balance a packed vs paced schedule. Trigger keywords - schedule optimization, conference schedule, constraint optimization, overlapping talks, contiguous free time, conflict surfacing, packed vs paced.
development
Parse a heterogeneous conference program (markdown, HTML, PDF-derived text, or JSON) into normalized event records with per-field confidence scores and independent classification axes (topic, depth, format, prerequisites, recorded, capacity). Detects the program's format before extracting, treats every inferred field as uncertain (present vs inferred vs missing), and flags thin or missing abstracts so downstream enrichment can target them. Conference-agnostic. Use when ingesting a conference or event schedule into a structured store, normalizing a talk/session list, or extracting per-session metadata with calibrated confidence. Trigger keywords - program ingestion, parse schedule, session extraction, event records, conference program, talk metadata, per-field confidence.
development
Build a personalized preference profile from a small number of well-chosen, cluster-grounded questions instead of a long survey. Represents the person's interests as an uncertainty region over the theme map, picks the single highest-information-gain choice-based question (contrasting real talks from different clusters), balances exploiting known interests against exploring uncertain ones, deliberately injects outlier probes to fight selection bias, and stops as soon as the schedule would be stable. Also elicits the user-owned objective weights and hard constraints. Interactive — runs where it can actually ask the person. Conference-agnostic. Use to turn a theme map into a preference profile, to decide what to ask a conference attendee, or to elicit scheduling priorities. Trigger keywords - preference elicitation, ask few questions, information gain, choice-based questions, selection bias probe, objective weights, attendee preferences.