Published Agent Skill package. Follow the instructions in SKILL.md to complete the user's task.
dota2-analyzer
Analyze Dota 2 matches for single-map and series (Bo1/Bo2/Bo3/Bo5) win probabilities using rating-based logistic models strengthened by recent form, H2H, patch meta and optional Poisson kill or net-worth lead simulation. Extract team rankings, form and stats from DLTV, Dotabuff, OpenDota, STRATZ and Liquipedia via browse_page. Supports pre-draft strength estimates and post-draft adjustments. Use for pre-match predictions, series score distributions, value spotting vs bookmaker or Polymarket odds
This page contains 4 files from the original skill zip. Supporting markdown and scripts are included below so you do not need extra downloads.
Files in this package
- SKILL.md
- references/data-extraction-prompts.md
- references/strength-estimation.md
- scripts/calculate_dota2_probs.py
SKILL.md
Dota 2 Win Probability Analyzer (Strengthened)
Use for any professional Dota 2 match analysis involving win rates or probabilities. Trigger words include Dota 2, dota2, TI, The International, DreamLeague, ESL One, PGL, Bo3, Bo5, map win, series prediction, Radiant vs Dire, etc.
Core Model (Why This Is Stronger)
- Primary: Rating-difference logistic model calibrated on pro data. Convert DLTV ranking points, recent win rates and form into a single-map win probability. Far more stable than raw win-rate conversion for sparse samples.
- Strengthened upgrade: Optional Poisson (or Normal) model of expected kill difference / GPM difference drawn from team averages. Map the resulting lead distribution through an empirical logistic curve (gold or kill lead → win probability) for a second independent estimate. Average or use as sensitivity check.
- Series: Exact dynamic programming for Bo1–Bo5 score distributions treating maps as independent (standard practical approximation; real series have mild draft-adaptation dependence).
- Side note: Small Radiant advantage (patch-dependent, typically +0.5–2.5%) can be applied as a final adjustment when side is known or randomly assigned.
- Prefer the logistic + form model for most pre-match work. Use the Poisson-lead path when kill/GPM averages are reliable and you want a second view.
Typical calibrated range: even match map_p ≈ 0.48–0.53; clear favorite (large ranking + form gap) map_p ≈ 0.58–0.68. Extreme gaps rarely exceed 0.75 without roster collapse or patch mismatch.
Step-by-Step Workflow (Follow Strictly)
1. Clarify the Query
- Exact teams (full or short names / orgs), series format (Bo1, Bo2, Bo3 most common, Bo5 for majors/TI), event/stage, date, current patch if known.
- Ask whether draft/veto is complete or heroes are known (post-draft analysis is higher value).
- Past match (validation / review) vs upcoming (prediction). Multiple matches → prioritize high-profile or user-specified.
- Note any known absences, stand-ins, or recent roster changes.
2. Locate Data Pages
- Rankings (primary strength signal): https://dltv.org/ranking or web_search
DLTV Dota 2 ranking. - Team pages: web_search
site:dotabuff.com/esports [team]orOpenDota [team],STRATZ [team]. - Match / series page: Liquipedia (
site:liquipedia.net [team1] vs [team2]), Dotabuff esports match, or tournament bracket pages. - Recent form / H2H: team recent matches on Dotabuff or OpenDota; Liquipedia H2H section.
- Patch meta context when needed: Spectral Stats or STRATZ meta pages.
3. Extract Data with browse_page
Use the ready prompts in references/data-extraction-prompts.md.
- Ranking page → current points / rank for both teams.
- Each team page → recent win rate (last 20–40 games or 3 months), average duration, average kills / GPM / XPM if available, roster stability, notable absences.
- Match / Liquipedia page → series format, H2H history, previous results on current patch, odds if listed, draft notes.
- Customize extraction to focus on current patch window and relevant opponents.
- If data sparse (new roster, stand-ins, low sample) → shrink heavily toward 50% and state low confidence.
4. Estimate Inputs (see references/strength-estimation.md)
- Start from DLTV points difference or ranking gap → base rating_diff.
- Convert recent win-rate difference and form (last 5–10 series) into additive adjustment.
- Apply H2H, motivation (must-win, lower-bracket), roster continuity, patch fit tweaks (±0.02–0.05 typical on map_p).
- When draft known: manually adjust map_p by hero win-rate edges, known counters, or lane matchups (document the delta).
- Clamp extreme values (rarely outside 0.38–0.72). Always shrink toward 0.50 when samples < 10–12 recent games.
- For unknown draft: prepare base + optimistic + pessimistic scenarios or sensitivity range.
5. Run the Calculation Script
# Preferred — direct map win probability
python3 /home/workdir/.grok/skills/dota2-analyzer/scripts/calculate_dota2_probs.py \
--map_p 0.58 --format bo3
# From rating difference (Elo-style logistic)
python3 ... --rating_diff 120 --format bo3
# Strengthened Poisson kill-lead path
python3 ... --kills_a 28.5 --kills_b 24.0 --format map --poisson_lead
# Series with different per-map probabilities (post-draft or scenarios)
python3 ... --map_ps "0.62,0.55,0.48" --format bo3
# Bo5 example
python3 ... --map_p 0.57 --format bo5 --n_sims 100000
- Always run sensitivity: ±0.03–0.05 on map_p and observe series win % movement.
- If maps/drafts unknown, present base + optimistic + pessimistic series win ranges.
6. Present Results Clearly
- Match context: teams, format, event, patch, data timestamp, key extracted stats (ranking points, recent form, standout numbers).
- Estimated inputs: map_p (or rating_diff / expected kill lead) with one-sentence justification; note any draft adjustments.
- Probability table (from script):
- Single-map win % for A / B (Radiant/Dire if relevant)
- Series win % for A / B
- Score distribution (2-0, 2-1, 1-2, 0-2 for Bo3; fuller for Bo5)
- Key insights: biggest edges, upset potential, important maps or draft priorities if known.
- Market comparison (if odds available): model vs implied probability, rough EV flags.
- Caveats & confidence: sample sizes, roster uncertainty, unmodeled draft variance, High/Med/Low confidence.
7. Iteration & Extra Context
- Update live when draft completes, absences confirmed, or new form appears.
- For betting / Polymarket angle: after model probs, compare to sharp books or prediction-market prices; flag potential +EV.
- Back-test on finished matches when useful (estimate pre-match inputs from then-available data only).
Important Rules & Limitations
- Analytical estimates only — not betting advice. User is responsible for decisions and local regulations.
- Dota 2 has extremely high variance (draft, individual performance, Roshan timing, buyback economy). Even a 65% series favorite loses often. Never treat model edge as certainty.
- Data quality varies sharply. Top teams have rich samples; mid/lower-tier or newly formed rosters require heavy shrinkage toward 50%.
- Draft is the single largest unmodeled factor pre-game. Post-draft estimates are meaningfully stronger.
- Script assumes map independence. Real series have sequential draft adaptation and psychological effects; effect is usually modest.
- Prefer the logistic + form path. Use Poisson-lead only as a cross-check or when kill/GPM data is high-quality.
- Always show sensitivity or a range when data is thin. State confidence level explicitly.
Bundled Resources
scripts/calculate_dota2_probs.py— core calculator (logistic / rating_diff, series DP, optional Poisson kill-lead path, score distributions).references/data-extraction-prompts.md— ready-to-use browse_page instructions for DLTV, Dotabuff, OpenDota, STRATZ, Liquipedia.references/strength-estimation.md— formulas, calibration notes, draft adjustment guidelines, limitations.
Load these via read_file when needed. Re-run the script whenever inputs change. This converts public Dota 2 data into transparent, defensible win probabilities.
Supporting file: references/data-extraction-prompts.md
This file is part of the dota2-analyzer skill package. Use it when SKILL.md references references/data-extraction-prompts.md.
Ready-to-use browse_page Instructions for Dota 2 Data
Copy-paste or adapt these instructions when calling the browse_page tool. Keep them focused so the summarizer returns structured, usable numbers.
1. DLTV Ranking Page (https://dltv.org/ranking)
Extract the current Dota 2 world ranking. For the two teams [TEAM_A] and [TEAM_B] (or the top 20 if general): list rank, team name, points, and any noted roster core players. Note the ranking date or last update if shown. Summarize how points are calculated if briefly described. Return clean structured list.
2. Dotabuff Team / Esports Page
Search first: site:dotabuff.com/esports [team name] or similar.
From this Dotabuff page extract for the team:
- Overall recent win rate and record (last 20–40 games or time window shown)
- Average match duration
- Average kills / deaths if available
- Recent series results (especially Bo3/Bo5)
- Current or most-played heroes / notable hero pool if listed
- Roster and any stand-in or recent changes mentioned
- Head-to-head notes if present
Focus on numbers with sample sizes. Ignore pure pub data unless pro section is empty.
3. OpenDota Team Page
Extract team performance metrics: win rate over recent matches, average duration, average kills, GPM/XPM if shown, ranking or MMR proxy, recent form (last 10–20 games), and roster. Note any league or pro filter that is active. Provide sample sizes.
4. STRATZ Team or Match Pages
Summarize team or match statistics relevant to win probability: recent win rate, hero performance, average net worth or kill differentials if available, and any AI or model predictions shown. Extract key numerical edges and sample sizes. Note current patch if indicated.
5. Liquipedia Match / Series / Team Page
Extract match or series details: format (Bo1/Bo3/Bo5), date, event, patch if listed, final or ongoing score, roster for both sides (note stand-ins), head-to-head history summary, previous results between these teams on the current or recent patches, and any preview notes about form or absences. List map/game results if series is finished. Keep structured and concise.
6. Tournament Bracket or Group Page (Liquipedia or official)
Identify the relevant matchup between [TEAM_A] and [TEAM_B]. Extract stage, format, scheduled time, current standings impact (must-win etc.), and any listed odds or power rankings. Note nearby results that affect motivation.
7. Meta / Patch Context (Spectral Stats, STRATZ meta, Dotabuff trends)
Summarize the current patch meta relevant to pro play: dominant strategies (push, late-game, specific cores), Radiant win rate if shown, average game length and kill totals, and any major hero win-rate outliers. Keep to 5–8 key bullet points with numbers.
Usage Tips
- Always include the exact team names the user cares about in the instructions so the summarizer filters correctly.
- Request sample sizes (number of games / series) — they determine how hard to shrink estimates.
- For H2H, ask specifically for results on the current patch or last 6–12 months.
- If a page is heavily JavaScript-rendered and returns little, fall back to web_search snippets or try the alternative site (Dotabuff ↔ OpenDota ↔ STRATZ).
- After extraction, cross-check ranking points vs recent form; large discrepancies often signal roster change or patch shift.
Supporting file: references/strength-estimation.md
This file is part of the dota2-analyzer skill package. Use it when SKILL.md references references/strength-estimation.md.
Estimating Team Strength and Map Win Probabilities for Dota 2
Translate public rankings and form into inputs for calculate_dota2_probs.py. Prefer the logistic / rating_diff path for most work. Use the Poisson kill-lead path as a second opinion when average kills or GPM are stable.
1. Baseline from Rankings (Primary Signal)
Primary source: DLTV World Ranking (https://dltv.org/ranking).
- Points difference is the strongest single pre-match signal for established rosters.
- Rough calibration (pro scene 2025–2026):
- Every ~80–100 DLTV points ≈ +0.03 to +0.04 map win probability.
- Or convert via Elo-style: treat points gap as rating_diff and use
--rating_diff.
- Simple formula (starting point):
base_map_p_A = 0.50 + (points_A - points_B) / 2500
Clamp later. For teams far outside top 20 or with brand-new cores, prefer recent form win-rate conversion instead.
Alternative ranking sources (cross-check): Dotabuff team rankings, OpenDota, community Elo lists.
2. Recent Form & Win-Rate Adjustment
From team pages (Dotabuff / OpenDota / STRATZ last 20–40 games or ~3 months):
- Overall win rate difference is useful but noisy.
- Prefer series results (Bo3/Bo5) over individual maps when available.
- Adjustment (add to base_map_p):
form_adj = (winrate_A - winrate_B) * 0.20 # conservative
- Weight more recent results higher (last 5–8 series matter most).
- Discount heavily if roster has changed (new mid, new offlane, stand-in support).
Typical total map_p after ranking + form: 0.47–0.58 for most competitive matches. Rarely go outside 0.40–0.70 without extreme evidence.
3. Head-to-Head, Motivation & Context Tweaks
Small additive adjustments (±0.01–0.04):
- Strong H2H on current patch → +0.02–0.03 for the historical winner.
- Must-win / lower-bracket pressure, home crowd (rare), fatigue after long travel.
- Patch fit: some teams thrive on specific patches (e.g., heavy push, late-game, specific hero pools). Note current patch number and recent meta shifts.
- Roster continuity: if 4–5 players have played together >30 games, confidence increases; stand-ins reduce it.
4. Draft / Post-Ban Adjustment (High Value When Available)
Pre-draft estimates ignore the largest source of variance. Once heroes are known:
- Look up recent hero win rates on the current patch (STRATZ, Dotabuff, Spectral).
- Known hard counters or lane dominance (e.g., offlane vs hard carry matchup).
- Typical draft edge: ±0.03–0.08 on map_p depending on how one-sided the draft looks.
- Document the delta explicitly: “Draft favors A by ~+0.05 (strong mid + offlane counter)”.
If only partial draft (bans only), use smaller adjustments.
5. Optional Poisson Kill / Net-Worth Lead Path
When team average kills or GPM are reliable:
- Extract recent average kills per game (or GPM) for both teams.
- Feed into script with
--kills_a--kills_b --poisson_lead. - The script models kills ~ Poisson(λ), computes lead distribution, then maps lead → win probability via logistic.
- Use the resulting map_p as a cross-check or average it with the ranking-based estimate when both are high quality.
Gold-lead version (manual): if you have expected net-worth difference at ~20–25 min, convert roughly:
- +5k ≈ ~60%, +10k ≈ ~72–75%, +15k ≈ ~85% (very approximate; varies by patch and game length).
6. Radiant / Dire Side
Radiant historically holds a small edge (patch-dependent, often 50.5–52.5%). When side is known or can be assigned:
final_map_p = clamp(map_p + radiant_adv, 0.01, 0.99)
Typical radiant_adv = +0.01 to +0.025. Script supports --radiant_adv.
7. Series Conversion
Once you have a map_p (or list of map_ps):
- Bo1 = map_p
- Bo2 / Bo3 / Bo5 → use the script’s exact DP (independent maps).
- Real series have sequential information and draft adaptation; independence is a practical upper-bound approximation on favorite’s win probability.
Always report score distribution (2-0 / 2-1 etc.) and run sensitivity (±0.03–0.05 on map_p).
8. Calibration & Shrinkage Rules
- Sample size < 12 recent games or new core → shrink map_p halfway toward 0.50.
- Conflicting signals (ranking favors A, recent form favors B) → average or widen the range.
- Extreme values (>0.70 or <0.30) require strong evidence (huge ranking gap + dominant form + favorable draft).
- Always state confidence: High (stable top teams, rich data, post-draft), Medium, Low (stand-ins, sparse samples, early patch).
Limitations
- Draft is the dominant unmodeled factor pre-game.
- Individual player variance and “hero pool depth” are only partially captured by form.
- Patch changes can invalidate older data within days.
- The logistic and Poisson-lead models are strong baselines, not oracles. Combine with qualitative scouting.
Supporting file: scripts/calculate_dota2_probs.py
This file is part of the dota2-analyzer skill package. Use it when SKILL.md references scripts/calculate_dota2_probs.py.
#!/usr/bin/env python3
"""
Dota 2 Map and Series Win Probability Calculator (Strengthened)
Core models:
- Direct map_p or rating-difference logistic (Elo-style) conversion to single-map win probability.
- Exact dynamic programming for Bo1 / Bo2 / Bo3 / Bo5 series score distributions
(maps treated independent — practical approximation).
- Optional Poisson kill-lead path: model expected kills as Poisson, derive lead
distribution, then map lead → win probability via calibrated logistic.
Use as sensitivity / cross-check only.
Examples:
# Direct map probability
python3 calculate_dota2_probs.py --map_p 0.58 --format bo3
# From rating difference (positive = team A stronger)
python3 calculate_dota2_probs.py --rating_diff 150 --format bo3
# Series with per-map probabilities
python3 calculate_dota2_probs.py --map_ps "0.62,0.55,0.48" --format bo3
# Poisson kill-lead strengthened path
python3 calculate_dota2_probs.py --kills_a 29.0 --kills_b 24.5 --format map --poisson_lead
# Bo5 with more sims
python3 calculate_dota2_probs.py --map_p 0.57 --format bo5 --n_sims 100000
"""
from __future__ import annotations
import argparse
import math
import sys
from collections import defaultdict
from typing import Dict, List, Optional, Tuple
import numpy as np
from scipy.stats import poisson
def logistic_from_rating_diff(rating_diff: float, scale: float = 400.0) -> float:
"""Classic Elo-style win probability. rating_diff > 0 means team A stronger."""
return 1.0 / (1.0 + 10.0 ** (-rating_diff / scale))
def logistic_from_lead(lead: float, midpoint: float = 0.0, steepness: float = 0.12) -> float:
"""
Empirical mapping from kill (or gold/10k) lead to win probability.
Calibrated roughly on pro data: ~+8–10 kill lead or +8–10k net worth mid-game
corresponds to ~70–75% win probability. Adjust steepness if needed.
"""
return 1.0 / (1.0 + math.exp(-steepness * (lead - midpoint)))
def poisson_lead_win_prob(
kills_a: float,
kills_b: float,
max_k: int = 60,
steepness: float = 0.11,
) -> float:
"""
Model team kills as independent Poisson, compute P(A wins) by summing
over possible kill outcomes weighted by the lead→win logistic.
Returns expected win probability for team A.
"""
if kills_a <= 0 or kills_b <= 0:
raise ValueError("kills_a and kills_b must be positive")
p_win = 0.0
# Truncate Poisson tails
for ka in range(0, max_k + 1):
pa = poisson.pmf(ka, kills_a)
if pa < 1e-9:
continue
for kb in range(0, max_k + 1):
pb = poisson.pmf(kb, kills_b)
if pb < 1e-9:
continue
lead = ka - kb
p_win += pa * pb * logistic_from_lead(lead, steepness=steepness)
return float(p_win)
def series_dp(map_ps: List[float], best_of: int) -> Tuple[float, Dict[str, float]]:
"""
Exact dynamic programming for series win probability and score distribution.
map_ps: list of P(A wins map i) for i = 0 .. best_of-1 (or shorter; cycles last if needed).
Returns (P(A wins series), score_dist) where score_diff keys are like "2-0", "2-1".
"""
if best_of not in (1, 2, 3, 5):
raise ValueError("best_of must be 1, 2, 3 or 5")
wins_needed = (best_of // 2) + 1
# state: (maps_played, wins_a, wins_b) → probability
# We only need up to wins_needed
from functools import lru_cache
@lru_cache(maxsize=None)
def prob(wa: int, wb: int, idx: int) -> float:
if wa >= wins_needed:
return 1.0
if wb >= wins_needed:
return 0.0
if idx >= len(map_ps) and len(map_ps) > 0:
# reuse last map_p if more maps than provided
p = map_ps[-1]
else:
p = map_ps[idx] if idx < len(map_ps) else map_ps[-1]
return p * prob(wa + 1, wb, idx + 1) + (1.0 - p) * prob(wa, wb + 1, idx + 1)
series_p = prob(0, 0, 0)
# Score distribution via enumeration of paths (small state space)
score_dist: Dict[str, float] = defaultdict(float)
# BFS / recursive count of terminating scores
def collect(wa: int, wb: int, idx: int, path_p: float):
if wa >= wins_needed or wb >= wins_needed:
key = f"{wa}-{wb}"
score_dist[key] += path_p
return
p = map_ps[idx] if idx < len(map_ps) else map_ps[-1]
collect(wa + 1, wb, idx + 1, path_p * p)
collect(wa, wb + 1, idx + 1, path_p * (1.0 - p))
collect(0, 0, 0, 1.0)
# Normalize just in case of floating error
total = sum(score_dist.values())
if total > 0:
for k in score_dist:
score_dist[k] /= total
return series_p, dict(score_dist)
def print_results(
map_p: float,
format_str: str,
series_p: Optional[float] = None,
score_dist: Optional[Dict[str, float]] = None,
extra: str = "",
):
print("=" * 60)
print("Dota 2 Win Probability Results")
print("=" * 60)
if extra:
print(extra)
print(f"Single-map P(A wins) : {map_p*100:.1f}%")
print(f"Single-map P(B wins) : {(1-map_p)*100:.1f}%")
if series_p is not None and score_dist is not None:
print(f"\nSeries format : {format_str.upper()}")
print(f"Series P(A wins) : {series_p*100:.1f}%")
print(f"Series P(B wins) : {(1-series_p)*100:.1f}%")
print("\nScore distribution:")
# Sort nicely: higher A scores first
def sort_key(s: str):
a, b = map(int, s.split("-"))
return (-a, b)
for score in sorted(score_dist.keys(), key=sort_key):
print(f" {score:>5} : {score_dist[score]*100:5.1f}%")
print("=" * 60)
def main():
parser = argparse.ArgumentParser(
description="Dota 2 map & series win probability calculator"
)
parser.add_argument("--map_p", type=float, help="Direct P(A wins a single map)")
parser.add_argument(
"--rating_diff",
type=float,
help="Rating difference (A - B). Converted via Elo logistic (scale=400)",
)
parser.add_argument(
"--map_ps",
type=str,
help='Comma-separated per-map probs e.g. "0.62,0.55,0.48"',
)
parser.add_argument(
"--kills_a", type=float, help="Expected kills for team A (Poisson path)"
)
parser.add_argument(
"--kills_b", type=float, help="Expected kills for team B (Poisson path)"
)
parser.add_argument(
"--poisson_lead",
action="store_true",
help="Use Poisson kill-lead model (requires --kills_a --kills_b)",
)
parser.add_argument(
"--format",
type=str,
default="map",
choices=["map", "bo1", "bo2", "bo3", "bo5"],
help="Output format: map only or series BoN",
)
parser.add_argument(
"--n_sims",
type=int,
default=50000,
help="(Reserved for future Monte-Carlo extensions)",
)
parser.add_argument(
"--radiant_adv",
type=float,
default=0.0,
help="Additive Radiant side advantage to apply to map_p (e.g. 0.015)",
)
args = parser.parse_args()
map_p: Optional[float] = None
extra_notes = []
if args.poisson_lead:
if args.kills_a is None or args.kills_b is None:
print("ERROR: --poisson_lead requires --kills_a and --kills_b", file=sys.stderr)
sys.exit(1)
map_p = poisson_lead_win_prob(args.kills_a, args.kills_b)
extra_notes.append(
f"Poisson kill-lead model: λA={args.kills_a:.1f}, λB={args.kills_b:.1f}"
)
elif args.map_ps:
try:
map_ps = [float(x.strip()) for x in args.map_ps.split(",")]
if not map_ps:
raise ValueError
for p in map_ps:
if not (0.0 < p < 1.0):
raise ValueError("each map_p must be in (0,1)")
except Exception as e:
print(f"ERROR: invalid --map_ps: {e}", file=sys.stderr)
sys.exit(1)
# For series we will use the list; for single map report average
map_p = float(np.mean(map_ps))
extra_notes.append(f"Per-map probabilities: {map_ps}")
elif args.rating_diff is not None:
map_p = logistic_from_rating_diff(args.rating_diff)
extra_notes.append(f"From rating_diff={args.rating_diff:.1f} (Elo scale=400)")
elif args.map_p is not None:
map_p = args.map_p
if not (0.0 < map_p < 1.0):
print("ERROR: --map_p must be in (0,1)", file=sys.stderr)
sys.exit(1)
else:
print(
"ERROR: provide one of --map_p, --rating_diff, --map_ps or --poisson_lead",
file=sys.stderr,
)
sys.exit(1)
# Optional Radiant advantage
if args.radiant_adv != 0.0:
map_p = min(max(map_p + args.radiant_adv, 0.01), 0.99)
extra_notes.append(f"Applied Radiant advantage {args.radiant_adv:+.3f}")
fmt = args.format.lower()
if fmt == "map" or fmt == "bo1":
print_results(map_p, "map", extra=" | ".join(extra_notes))
return
# Series
best_of = {"bo2": 2, "bo3": 3, "bo5": 5}[fmt]
if args.map_ps:
map_ps_list = [float(x.strip()) for x in args.map_ps.split(",")]
# Pad or trim to reasonable length
while len(map_ps_list) < best_of:
map_ps_list.append(map_ps_list[-1])
map_ps_list = map_ps_list[:best_of]
else:
map_ps_list = [map_p] * best_of
series_p, score_dist = series_dp(map_ps_list, best_of)
print_results(
map_p if not args.map_ps else float(np.mean(map_ps_list)),
fmt,
series_p=series_p,
score_dist=score_dist,
extra=" | ".join(extra_notes),
)
if __name__ == "__main__":
main()