mlb-opponent-profiler

$npx mdskill add lyndonkl/claude/mlb-opponent-profiler

Scans Yahoo pages to classify opposing managers into behavioral archetypes.

  • Extracts draft, FAAB, roster, and activity data from Yahoo pages.
  • Depends on the opponent-archetype-classifier API for taxonomy mapping.
  • Updates context files while preserving any manual notes provided.
  • Outputs weekly summaries and individual team profile markdown files.

SKILL.md

.github/skills/mlb-opponent-profilerView on GitHub ↗
---
name: mlb-opponent-profiler
description: Weekly refresh of per-opponent archetype + behavioral profiles for the 11 opposing teams in the user's Yahoo Fantasy Baseball league (ID 23756). Thin baseball-specific wrapper around the domain-neutral `opponent-archetype-classifier` -- provides the 10-archetype MLB taxonomy (balanced, stars_and_scrubs, punt_sv, punt_sb, punt_wins_qs, hitter_heavy, pitcher_heavy, inactive, frustrated_active, unknown), extracts MLB features from Yahoo pages (draft distribution, FAAB spend, waiver pattern, roster composition, lineup consistency, trade activity, recent record, activity recency), invokes the classifier, and writes/updates `context/opponents/<team-slug>.md` files per `opponent-profile-schema.md`. Read-modify-write preserves manual notes. Emits a weekly summary signal at `signals/wkNN-opponent-profiles.md`. Use when user says "opponent profiling", "classify opposing manager", "update opponent profiles", "refresh opponents", "weekly opponent scout", or "MLB fantasy opponent archetype".
---
# MLB Opponent Profiler

## Table of Contents
- [Example](#example)
- [Workflow](#workflow)
- [Common Patterns](#common-patterns)
- [Guardrails](#guardrails)
- [Quick Reference](#quick-reference)

## Example

**Scenario**: Monday morning of Week 5. Refresh profile for `Springfield Isotopes` (manager Nikolay) after observing another high-activity week (7 moves, $14 spent on adds, 4-1 matchup win).

**Inputs**:
```yaml
team_name: "Springfield Isotopes"
yahoo_session: <authenticated chrome context>
prior_profile:                       # from context/opponents/springfield-isotopes.md (Week 4)
  archetype: balanced
  archetype_confidence: 0.50
  posterior: {balanced: 0.42, stars_and_scrubs: 0.18, punt_sv: 0.02, punt_sb: 0.04,
              punt_wins_qs: 0.03, hitter_heavy: 0.12, pitcher_heavy: 0.08,
              inactive: 0.00, frustrated_active: 0.11, unknown: 0.00}
```

**Step 1 -- Yahoo scrape (4 pages)**: teams index, team page for `team_id=8`, draftresults, transactions filtered by `tid=8`. URLs in [methodology.md](resources/methodology.md#yahoo-scrape-flow).

**Step 2 -- MLB feature extraction** yields:
```yaml
sp_roster_share: 0.35; closer_count: 2; sb_speed_count: 4; power_bat_count: 6
moves_per_week: 3.3; faab_spent_pct: 0.32; faab_avg_bid: 2.5
lineup_set_daily: true; trade_offers_sent: 1; trade_offers_received: 0
record_last_2_weeks: "8-2-0"; days_since_last_login: 0
```

**Step 3 -- Invoke `opponent-archetype-classifier`**:
```yaml
archetype_taxonomy:            # see resources/template.md -- 10 archetypes
  balanced: {...}
  stars_and_scrubs: {...}
  ...
observed_features: <from step 2>
archetype_prior: <prior_profile.posterior>     # sequential: last week's posterior is this week's prior
observation_weight: 0.65        # Week 5 -- see methodology.md observation_weight_calibration
correlated_feature_pairs:
  - [moves_per_week, faab_spent_pct]            # active managers do both
  - [sp_roster_share, closer_count]             # they trade off
```

**Step 4 -- Classifier returns**:
```yaml
posterior:
  balanced:            0.58    # up from 0.42 -- evidence accumulating
  stars_and_scrubs:    0.12
  hitter_heavy:        0.10
  frustrated_active:   0.08    # record is winning, so frustrated_active down
  pitcher_heavy:       0.07
  punt_sb:             0.03
  punt_sv:             0.01
  punt_wins_qs:        0.01
  inactive:            0.00
  unknown:             0.00

map_archetype: balanced
classification_confidence: 37.7      # 0.58 * 0.65 * 100 -- still just under 40
best_response_hints:
  - "Match cat-for-cat; decided on execution"
  - "Include in N-estimate for every common-value FAAB target"
  - "Active manager -- probe for fair consolidation trades"
```

**Step 5 -- Read existing `context/opponents/springfield-isotopes.md`, preserve manual notes, update machine-generated sections**:

The "Summary", "Apparent weaknesses", "Best response" and "Open questions" sections contain hand-authored user notes -- **keep verbatim**. Only these get refreshed:
- YAML frontmatter: `last_updated`, `confidence`, `source_urls`
- Section 2 (Archetype): archetype, archetype_confidence, archetype_evidence
- Section 3 (Category strength): cat_strength, presumed_punts, likely_pushes
- Section 4 (Behavioral): activity_level, last_active, waiver_aggression, faab_remaining, faab_avg_bid_pct, trade_propensity

**Step 6 -- Emit weekly signal** at `signals/wk05-opponent-profiles.md`:
```markdown
---
type: opponent_profile_summary
date: 2026-04-20
week: 5
emitted_by: mlb-opponent-profiler
confidence: 0.65
source_urls: [<11 team pages + transactions + teams>]
---
## Archetype shifts since Week 4
- Springfield Isotopes: balanced 0.42 -> 0.58 (confidence 0.50 -> 0.66). Nikolay continues active pattern, record confirms execution.
- Jennys Team: inactive 0.78 -> 0.84 (confidence 0.80 -> 0.85). Still zero moves. Profile hardening.
- ...9 more

## New reactivity triggers fired
- Team 7 (Kenyi) -- outbid us on Wade Miley at $14; shift `faab_avg_bid_pct` up
```

## Workflow

Copy this checklist and track progress:

```
Opponent Profiler Progress (per team, x 11 for all_opponents):
- [ ] Step 1: Resolve team identity (team_name -> team_id, manager_alias)
- [ ] Step 2: Scrape 4 Yahoo pages for this team
- [ ] Step 3: Extract MLB-specific features (9 features; see template.md)
- [ ] Step 4: Load prior_profile.posterior as archetype_prior (sequential update)
- [ ] Step 5: Invoke opponent-archetype-classifier with 10-archetype MLB taxonomy
- [ ] Step 6: Compute cat_strength (0-100 per cat) from roster composition
- [ ] Step 7: Read existing context/opponents/<slug>.md; identify manual-notes sections
- [ ] Step 8: Write updated file atomically (preserve manual notes)
- [ ] Step 9: (at end of batch) Emit signals/wkNN-opponent-profiles.md summary
```

**Step 1: Resolve team identity**

Input is either a single `team_name` (case-insensitive, fuzzy match against canonical Yahoo names) or `all_opponents: true` (iterate over team_ids 1-12, skipping user's own team_id). Map to `team_id` and `team-slug` (lowercased, hyphenated).

- [ ] If `team_name` does not match any Yahoo team, return error -- do not guess
- [ ] `team-slug` must match the filename on disk at `context/opponents/<slug>.md` (or create new file if absent)

**Step 2: Scrape 4 Yahoo pages**

Exact URL flow documented in [resources/methodology.md](resources/methodology.md#yahoo-scrape-flow). Pages needed:

- [ ] `https://baseball.fantasysports.yahoo.com/b1/23756/teams` -- manager, last-login, W-L
- [ ] `https://baseball.fantasysports.yahoo.com/b1/23756/<team_id>` -- roster + FAAB remaining
- [ ] `https://baseball.fantasysports.yahoo.com/b1/23756/<team_id>/draftresults` -- draft pick distribution
- [ ] `https://baseball.fantasysports.yahoo.com/b1/23756/transactions?tid=<team_id>` -- adds/drops/bids/trades
- [ ] Each scrape records the exact URL in `source_urls` (for citation)
- [ ] If any page returns 4xx/5xx or auth failure, degrade gracefully (see Guardrails #3)

**Step 3: Extract MLB features**

Computed from scraped pages. See [resources/methodology.md](resources/methodology.md#feature-extraction) for exact formulas.

- [ ] `sp_roster_share`, `closer_count`, `sb_speed_count`, `power_bat_count` (roster composition)
- [ ] `moves_per_week`, `faab_spent_pct`, `faab_avg_bid` (waiver activity)
- [ ] `lineup_set_daily` (bool -- any benched-but-MLB-starting players in last 7 days?)
- [ ] `trade_offers_sent`, `trade_offers_received`
- [ ] `record_last_2_weeks`, `days_since_last_login`

**Step 4: Sequential-update prior**

Read `prior_profile.posterior` (if supplied) and pass as `archetype_prior` to the classifier. This is the key sequential-Bayes move: last week's posterior becomes this week's prior.

- [ ] If no `prior_profile`, use taxonomy priors (Week 1 only)
- [ ] If `prior_profile.posterior` sums to != 1.0, normalize and flag in `assumptions_flagged`
- [ ] Observation weight rises with week number: Wk1-2 = 0.25, Wk3-4 = 0.45, Wk5-7 = 0.65, Wk8-11 = 0.80, Wk12+ = 0.90

**Step 5: Invoke `opponent-archetype-classifier`**

Pass the 10-archetype MLB taxonomy from [resources/template.md](resources/template.md#mlb-10-archetype-taxonomy) along with the observed features, prior, and observation_weight. **Do not re-implement Bayesian math in this skill.** The classifier returns `posterior`, `map_archetype`, `classification_confidence`, `best_response_hints`, `feature_contribution_breakdown`, `assumptions_flagged`.

- [ ] Pass `correlated_feature_pairs` (see Guardrails #1)
- [ ] If classifier returns `map_archetype: "inconclusive"`, write archetype as `unknown` with confidence as returned
- [ ] Do not override or reinterpret classifier output -- store it as-is

**Step 6: Compute `cat_strength` (0-100 per cat)**

Classifier returns the archetype; the 10 per-cat strength scores come from a separate roster-based estimator (this is MLB-specific and stays in this skill). Method in [resources/methodology.md](resources/methodology.md#cat-strength-estimation).

- [ ] Sum projected season totals for each cat across roster
- [ ] Normalize to 0-100 where 50 is league average
- [ ] Derive `presumed_punts`: cats where score < 35
- [ ] Derive `likely_pushes`: cats where score > 65

**Step 7: Read existing file, identify manual-notes sections**

**Critical: this skill never overwrites manually-authored notes.** See [resources/methodology.md](resources/methodology.md#read-modify-write-protocol).

- [ ] Open `context/opponents/<slug>.md`; if not present, emit new file from template
- [ ] Preserve Sections 1 (Summary), 5 (Apparent weaknesses / surpluses), 6 (Best response), 8 (Reactivity triggers), 9 (Open questions) verbatim
- [ ] Refresh only: frontmatter (last_updated, confidence, source_urls), Section 2 (Archetype), Section 3 (Category strength), Section 4 (Behavioral), Section 7 (Matchup history -- if we played them)

**Step 8: Write file atomically**

- [ ] Write to temp file `<slug>.md.tmp`, fsync, rename
- [ ] Validate output against [opponent-profile-schema.md](../../../yahoo-mlb/context/frameworks/opponent-profile-schema.md) frontmatter + section order before rename

**Step 9: Emit signal file**

After all 11 updates complete (for `all_opponents: true` mode), emit `signals/wkNN-opponent-profiles.md`. Format in [resources/template.md](resources/template.md#weekly-summary-signal-template).

- [ ] Include every archetype posterior that shifted by > 0.05 since last week
- [ ] Include every confidence change > 0.10
- [ ] List any reactivity-trigger events fired this week

## Common Patterns

**Pattern 1: `inactive` (dormant manager -- e.g. Jenny's Team)**
- **Signals**: `moves_per_week < 0.5`, `faab_spent_pct < 0.05`, `days_since_last_login > 3` or `lineup_set_daily = false`
- **Best-response**: Don't include in N-bidder FAAB estimates. Send consolidation trade offers. Expect roster atrophy -- exploit lineup-neglect advantage week-over-week.
- **Confidence grows fast**: inactivity is unambiguous; expect `archetype_confidence` above 0.80 by Week 4.

**Pattern 2: `punt_wins_qs` (Marmol strategy -- all-hitter + RP-only staff)**
- **Signals**: `sp_roster_share < 0.25`, `closer_count >= 3`, `moves_per_week > 3` (cycling for hitter production)
- **Best-response**: Concede K and QS (two free cat losses); lock 6 of remaining 8 cats. Do not stream SPs against them.

**Pattern 3: `punt_sv` (no closers, elite SP + hitting)**
- **Signals**: `closer_count = 0`, `sp_roster_share > 0.40`, high-end SP drafted in round 1-3
- **Best-response**: Concede SV; push all 5 hitting + K + QS + ERA + WHIP. Do not chase closers as streamers.

**Pattern 4: `frustrated_active` (active but losing -- motivated trader)**
- **Signals**: `moves_per_week > 4` AND `record_last_2_weeks` W% < 0.35
- **Best-response**: Prime trade partner. They'll respond to offers. Target their cooling stars (sell-high for them, buy-low for us).

**Pattern 5: `balanced` (the default)**
- **Signals**: No strong punts; roster composition within 1 std of league average across all features
- **Best-response**: Match cat-for-cat; matchup decided on weekly execution. Use variance-seeking when we're the underdog (`matchup_win_probability < 0.40`).

## Guardrails

1. **Classifier delegation purity.** This skill MUST NOT implement Bayesian math. If you find yourself computing posteriors, likelihoods, or normalizations, stop -- those belong in `opponent-archetype-classifier`. This skill contributes the taxonomy (MLB-specific), feature extraction (Yahoo-specific), and output formatting. Any math beyond "sum projected season totals for cat_strength" is a smell.

2. **Manual-notes preservation is non-negotiable.** Users hand-author Sections 1, 5, 6, 8, 9 with strategic notes this skill cannot reproduce (e.g. "Jennifer's email bounced, suggest probe via DM"). Blind-overwriting those sections destroys user work. Always read-modify-write. Validate diff before commit: only frontmatter + Sections 2, 3, 4, 7 should change.

3. **Graceful scrape failure.** If a Yahoo page returns 4xx/5xx or the session is unauthenticated: do NOT guess. Mark the corresponding features as `null`, record the failed URL in a `scrape_errors:` block in frontmatter, and drop `confidence` by 0.2. The profile should still emit, just with lowered confidence. A partial profile is better than a missing one.

4. **Sequential-update discipline.** Last week's posterior becomes this week's prior -- always. Do NOT restart from taxonomy priors each week (that throws away 1-4 weeks of evidence). The one exception: if `prior_profile.confidence < 0.20`, restart (the prior profile is essentially garbage).

5. **Correlated-feature handling.** `moves_per_week` and `faab_spent_pct` are strongly correlated (active managers do both). Pass `correlated_feature_pairs` to the classifier so it down-weights. Same for `sp_roster_share` and `closer_count` (they trade off by roster-slot constraint).

6. **Do not invent cat_strength from the archetype.** `cat_strength` is a separate estimator based on actual roster composition, NOT derived from `archetype`. A `balanced` team with a thin OF has below-average SB; don't paper over that by inheriting "balanced = 50s across the board". Compute from roster.

7. **Archetype `unknown` is valid.** When classification_confidence < 40, write `archetype: unknown` with `archetype_confidence` reflecting the classifier output. Do not force a MAP on thin evidence. Downstream agents handle `unknown` by falling back to broad heuristics.

8. **Citation requirement.** Every emitted profile lists exact Yahoo URLs in `source_urls`. The weekly signal file lists all URLs across all 11 teams (dedupe the teams-page URL).

9. **One-shot vs all-opponents mode.** Single-team refreshes (on trade-offer arrival, FAAB competition observation) emit only the profile file -- no weekly summary signal. Weekly summary signal is emitted only after all 11 refreshes complete.

10. **Team slugs stable.** Slugs are lowercased + hyphenated team name, stable across the season (e.g., `jennys-team`, `springfield-isotopes`). If a manager renames their team mid-season, keep the original slug and store the new `team_name` in frontmatter -- do not rename the file (breaks downstream agent references).

## Quick Reference

**Pipeline one-liner:**

```
scrape_yahoo(4_pages) -> extract_mlb_features -> invoke(opponent-archetype-classifier,
  taxonomy=mlb_10_archetype, prior=last_week_posterior, observation_weight=f(week))
  -> compute_cat_strength(roster) -> read_existing_md -> merge_preserve_notes
  -> atomic_write -> [if all_opponents] emit_weekly_signal
```

**MLB 10-archetype taxonomy (baked in):**

| Archetype | Core signal | Prior (Wk 1) |
|-----------|-------------|--------------|
| `balanced` | no punts, pushes all cats | 0.25 |
| `stars_and_scrubs` | 4-5 elite + bench scrubs | 0.10 |
| `punt_sv` | 0 closers, loaded elsewhere | 0.10 |
| `punt_sb` | power-only offense | 0.10 |
| `punt_wins_qs` | SP thin, RP-heavy (Marmol) | 0.05 |
| `hitter_heavy` | sp_roster_share < 0.30 | 0.10 |
| `pitcher_heavy` | sp_roster_share > 0.45 | 0.10 |
| `inactive` | zero moves, stale lineup | 0.10 |
| `frustrated_active` | high moves + losing record | 0.05 |
| `unknown` | inconclusive threshold | 0.05 |

Priors sum to 1.00. Informed (not uniform) per guardrail on 12-team base rates.

**Observation weight schedule:**

| Week | Weight | Rationale |
|------|--------|-----------|
| 1-2 | 0.25 | Draft-only evidence; noisy |
| 3-4 | 0.45 | First waivers visible |
| 5-7 | 0.65 | Patterns stabilizing |
| 8-11 | 0.80 | Behavior well-characterized |
| 12+ | 0.90 | Any remaining doubt is true ambiguity |

**Key resources:**

- **[resources/template.md](resources/template.md)**: 10-archetype MLB taxonomy YAML (passed to classifier), per-opponent `.md` output template (matches `opponent-profile-schema.md`), weekly summary signal template.
- **[resources/methodology.md](resources/methodology.md)**: Yahoo scrape URL flow, feature-extraction formulas, cat_strength estimator, read-modify-write protocol, sequential-update protocol, graceful-degradation handling.
- **[resources/evaluators/rubric_mlb_opponent_profiler.json](resources/evaluators/rubric_mlb_opponent_profiler.json)**: 10 quality criteria (Taxonomy Completeness, Feature Extraction Correctness, Yahoo Scrape Coverage, Classifier Delegation Purity, Output Schema Conformance, Preserve Manual Notes, Sequential-Update Correctness, Signal File Emission, Graceful Scrape Failure, Citations).

**Inputs required:**

- `team_name` (string, single team) OR `all_opponents: true`
- `yahoo_session` (authenticated Chrome context)
- `prior_profile` (optional; if missing, bootstrap from taxonomy priors)

**Outputs produced:**

- Updated `context/opponents/<team-slug>.md` (one per refreshed team) -- manual notes preserved
- `signals/wkNN-opponent-profiles.md` (only in `all_opponents` mode) -- summary of archetype shifts + confidence changes + reactivity events

**Downstream consumers (5 agents):**

- `mlb-lineup-optimizer` -- reads §3 cat_strength + §6 best_response for `leverage_vs_opponent`
- `mlb-category-state-analyzer` -- reads §2 archetype + §3 to anticipate opponent push/punt
- `mlb-faab-sizer` -- reads §4 (waiver_aggression, faab_remaining) across all 11 for N-bidder estimate
- `mlb-trade-analyzer` -- reads §4 (trade_propensity, trade_cooperation_score) + §5 weaknesses
- `mlb-fantasy-coach` -- reads §6 for the week's opponent for morning brief

More from lyndonkl/claude

SkillDescription
abstraction-concrete-examplesBuilds structured abstraction ladders that translate high-level principles into concrete, actionable examples across 3-5 levels. Bridges communication gaps, reveals hidden assumptions, and tests whether abstract ideas work in practice. Use when explaining concepts at different expertise levels, moving between abstract principles and concrete implementation, identifying edge cases by testing ideas against scenarios, designing layered documentation, decomposing complex problems into actionable steps, or bridging strategy-execution gaps.
academic-letter-architectGuides the creation of evidence-based academic recommendation letters, reference letters, and award nominations that combine concrete examples, meaningful comparisons, and genuine enthusiasm. Use when writing recommendation letters for students, postdocs, or colleagues, or when user mentions recommendation letter, reference, nomination, letter of support, endorsement, or needs help with strong advocacy and comparative statements.
adr-architectureDocuments significant architectural and technical decisions with full context, alternatives considered, trade-offs analyzed, and consequences understood. Creates a decision trail that helps teams understand why decisions were made. Use when choosing between technology options, making infrastructure decisions, establishing standards, migrating systems, or when user mentions ADR, architecture decision, technical decision record, or decision documentation.
adverse-selection-priorProduces a Bayesian prior probability that an offered transaction is +EV for the recipient, given that the counterparty chose to propose it. Applies Akerlof market-for-lemons logic -- if they offered it, they believe it is +EV for them, so the prior that it is +EV for us is materially below 50%. Reusable across trade evaluation, waiver drops (another team dropping a player is also adverse selection), job-offer analysis, M&A, and any "someone offered me this" situation. Use when you receive an unsolicited trade/offer/proposal, analyzing incoming trade prior, evaluating why a counterparty proposed a deal, or when user mentions adverse selection, market for lemons, why did they offer this, incoming trade prior, they proposed it, Bayesian adjustment on received offer.
alignment-values-north-starCreates actionable alignment frameworks that give teams a shared North Star (direction), values (guardrails), and decision tenets (behavioral standards). Enables autonomous decision-making while maintaining organizational coherence. Use when starting new teams, scaling organizations, defining culture, establishing product vision, resolving misalignment, creating strategic clarity, or when user mentions North Star, team values, mission, principles, guardrails, decision framework, or cultural alignment.
analogy-weight-checkFor every analogy in a substacker draft, verifies it carries mechanical weight — the analogy does real work explaining the mechanism, not merely decorates it. Cross-references analogy-catalog.md for novelty (is this analogy reused from a prior post?) and domain fit (biology > organizational > sports preferred; physics/military disfavored). Use whenever an analogy appears in the draft. Trigger keywords: analogy weight, decorative, mechanical weight, reused analogy, catalog check, metaphor check.
answer-uncomfortable-questionTakes one strategic question about substacker ("should we launch paid?", "is this section dead?", "are we writing for the wrong audience?") and produces the mandatory evidence + reasoning + downside triad plus a recommendation. Used 3 times per Growth Strategist review. Trigger keywords: uncomfortable question, strategic question, evidence reasoning downside, triad.
attribute-performanceFor each substacker post that materially over- or under-performs the rolling baseline (|z| ≥ 1.0), produces a plain-English attribution paragraph with calibrated confidence (high / medium / low / unexplained). Considers subject-line effect, topic zeitgeist, external share, day-of-week, length effect, and audience-notes signals. Labels unexplained outliers explicitly rather than fabricating a story. Use after compute-baseline when outlier posts exist. Trigger keywords: attribution, why did this post work, outlier explanation, performance analysis.
auction-first-price-shadingComputes the optimal shaded bid for a first-price sealed-bid auction given a true private value, an estimate of the number of competing bidders N, and a value-distribution assumption. Implements the `(N-1)/N` equilibrium shading rule for uniform private values, adjusts for log-normal or empirical value distributions, layers a risk-aversion adjustment, and caps output against the bidder's remaining budget. Domain-neutral auction theory reusable across fantasy sports (baseball FAAB, NBA/NHL waiver auctions), prediction-market limit sizing, sealed procurement bids, and any blind-bid context. Use when user mentions "first-price auction bid", "sealed bid shading", "(N-1)/N", "FAAB bid amount", "auction shading", "optimal bid first-price", "bid for sealed-bid", "blind bid sizing", or when downstream logic needs a principled shade factor rather than an ad-hoc heuristic.
auction-winners-curse-haircutApplies a Bayesian haircut to a bid valuation for common-value auctions where winning is itself evidence the bidder over-estimated. Takes a raw valuation, a value-type classification (common_value / private_value / mixed), the number of informed bidders N, and a signal-dispersion estimate, and returns an adjusted valuation. Domain-neutral and reusable across fantasy FAAB, prediction markets, M&A bids, ad-auction budgets, and any generic bidding context. Use when user mentions "winner's curse", "common value auction", "valuation haircut", "adverse valuation", "Bayesian bid adjustment", or "over-paying in auction".