fablazing-cli 0.3.0__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {fablazing_cli-0.3.0 → fablazing_cli-0.4.0}/CHANGELOG.md +11 -0
- {fablazing_cli-0.3.0 → fablazing_cli-0.4.0}/PKG-INFO +1 -1
- {fablazing_cli-0.3.0 → fablazing_cli-0.4.0}/pyproject.toml +1 -1
- fablazing_cli-0.4.0/src/fablazing_cli/__init__.py +1 -0
- {fablazing_cli-0.3.0 → fablazing_cli-0.4.0}/src/fablazing_cli/commands/hero.py +18 -6
- fablazing_cli-0.4.0/src/fablazing_cli/commands/packages.py +62 -0
- fablazing_cli-0.4.0/src/fablazing_cli/commands/player.py +52 -0
- {fablazing_cli-0.3.0 → fablazing_cli-0.4.0}/src/fablazing_cli/main.py +4 -1
- {fablazing_cli-0.3.0 → fablazing_cli-0.4.0}/src/fablazing_cli/reference.py +16 -2
- fablazing_cli-0.3.0/src/fablazing_cli/__init__.py +0 -1
- {fablazing_cli-0.3.0 → fablazing_cli-0.4.0}/.gitignore +0 -0
- {fablazing_cli-0.3.0 → fablazing_cli-0.4.0}/README.md +0 -0
- {fablazing_cli-0.3.0 → fablazing_cli-0.4.0}/src/fablazing_cli/client.py +0 -0
- {fablazing_cli-0.3.0 → fablazing_cli-0.4.0}/src/fablazing_cli/commands/__init__.py +0 -0
- {fablazing_cli-0.3.0 → fablazing_cli-0.4.0}/src/fablazing_cli/commands/auth.py +0 -0
- {fablazing_cli-0.3.0 → fablazing_cli-0.4.0}/src/fablazing_cli/commands/card.py +0 -0
- {fablazing_cli-0.3.0 → fablazing_cli-0.4.0}/src/fablazing_cli/commands/matchup.py +0 -0
- {fablazing_cli-0.3.0 → fablazing_cli-0.4.0}/src/fablazing_cli/commands/meta.py +0 -0
- {fablazing_cli-0.3.0 → fablazing_cli-0.4.0}/src/fablazing_cli/commands/tournaments.py +0 -0
- {fablazing_cli-0.3.0 → fablazing_cli-0.4.0}/src/fablazing_cli/config.py +0 -0
- {fablazing_cli-0.3.0 → fablazing_cli-0.4.0}/src/fablazing_cli/output.py +0 -0
- {fablazing_cli-0.3.0 → fablazing_cli-0.4.0}/src/fablazing_cli/resolve.py +0 -0
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.0
|
|
4
|
+
|
|
5
|
+
- `fabz packages`: same-turn card pairs with lift, share, win rate and
|
|
6
|
+
typical assembly turn - new API endpoint
|
|
7
|
+
- `fabz player search` / `player results`: paper tournament players
|
|
8
|
+
- `fabz hero cards` now shows play/block/pitch usage rates and an
|
|
9
|
+
equipment table
|
|
10
|
+
- Documented sub-format ids ("Open") in the reference
|
|
11
|
+
- API: /llms.txt primer on the API domain; unhandled 500s are now
|
|
12
|
+
recorded server-side for review
|
|
13
|
+
|
|
3
14
|
## 0.3.0
|
|
4
15
|
|
|
5
16
|
- `fabz hero cards`: most-used deck cards for a hero vs the whole field
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "fablazing-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.0"
|
|
8
8
|
description = "Fablazing analytics from your terminal - Flesh and Blood matchups, meta, cards, and tournaments."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.4.0"
|
|
@@ -69,7 +69,9 @@ def cards(
|
|
|
69
69
|
pct(c.get("win_rate")),
|
|
70
70
|
pct(c.get("wilson_lower_bound")),
|
|
71
71
|
f"{c.get('copies_avg', 0):.1f}",
|
|
72
|
-
|
|
72
|
+
pct(c.get("play_rate")),
|
|
73
|
+
pct(c.get("block_rate")),
|
|
74
|
+
pct(c.get("pitch_rate")),
|
|
73
75
|
]
|
|
74
76
|
for c in data.get("cards", [])
|
|
75
77
|
]
|
|
@@ -79,11 +81,21 @@ def cards(
|
|
|
79
81
|
f"{format_type}, {days}d: {data.get('total_games', 0)} games, "
|
|
80
82
|
f"WR {pct(data.get('overall_win_rate'))} (95% CI {pct(ci[0])}-{pct(ci[1])})"
|
|
81
83
|
]
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
tables = [("Cards", ["Card", "Freq", "N", "WR", "Wilson LB", "Copies", "Play", "Block", "Pitch"], rows)]
|
|
85
|
+
equipment = data.get("equipment") or []
|
|
86
|
+
if equipment:
|
|
87
|
+
equipment_rows = [
|
|
88
|
+
[
|
|
89
|
+
e.get("card_id", ""),
|
|
90
|
+
pct(e.get("frequency")),
|
|
91
|
+
e.get("matches", ""),
|
|
92
|
+
pct(e.get("win_rate")),
|
|
93
|
+
pct(e.get("wilson_lower_bound")),
|
|
94
|
+
]
|
|
95
|
+
for e in equipment[:15]
|
|
96
|
+
]
|
|
97
|
+
tables.append(("Equipment", ["Item", "Freq", "N", "WR", "Wilson LB"], equipment_rows))
|
|
98
|
+
emit(payload, tables=tables, summary=summary)
|
|
87
99
|
|
|
88
100
|
|
|
89
101
|
@app.command()
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""the packages command: same-turn card pairs with lift"""
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
import typer
|
|
5
|
+
|
|
6
|
+
from .. import client, resolve
|
|
7
|
+
from ..output import emit, set_mode
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def packages(
|
|
11
|
+
hero: str = typer.Argument(..., help="Hero id or name", autocompletion=resolve.complete_hero),
|
|
12
|
+
format_type: str = typer.Option("cc", "--format"),
|
|
13
|
+
opponent: Optional[str] = typer.Option(None, "--opponent", help="Rank against this opponent only"),
|
|
14
|
+
seed: Optional[str] = typer.Option(None, "--seed", help="Only packages containing this card"),
|
|
15
|
+
min_lift: float = typer.Option(1.4, "--min-lift"),
|
|
16
|
+
min_games: int = typer.Option(10, "--min-games"),
|
|
17
|
+
sort: str = typer.Option("usage", "--sort", help="usage | lift | win_rate | turn"),
|
|
18
|
+
limit: int = typer.Option(20, "--limit", min=1, max=200),
|
|
19
|
+
as_json: bool = typer.Option(False, "--json"),
|
|
20
|
+
as_csv: bool = typer.Option(False, "--csv"),
|
|
21
|
+
):
|
|
22
|
+
"""Distinctive same-turn card pairs for a hero: how often they land
|
|
23
|
+
together, the lift over chance, and the win rate when they do."""
|
|
24
|
+
set_mode(as_json, as_csv)
|
|
25
|
+
hero_id = resolve.hero_id(hero)
|
|
26
|
+
params = {
|
|
27
|
+
"format": format_type,
|
|
28
|
+
"min_lift": min_lift,
|
|
29
|
+
"min_games": min_games,
|
|
30
|
+
"sort": sort,
|
|
31
|
+
"limit": limit,
|
|
32
|
+
}
|
|
33
|
+
if opponent:
|
|
34
|
+
params["opponent"] = resolve.hero_id(opponent)
|
|
35
|
+
if seed:
|
|
36
|
+
params["seed_card"] = resolve.card_id(seed)
|
|
37
|
+
payload = client.get(f"/packages/{hero_id}", params)
|
|
38
|
+
data = payload["data"]
|
|
39
|
+
|
|
40
|
+
rows = []
|
|
41
|
+
for p in data.get("packages", []):
|
|
42
|
+
pair = p.get("cards") or []
|
|
43
|
+
# the packages store reports pct_games/win_rate in 0-100 units,
|
|
44
|
+
# unlike the rest of the API's 0-1 fractions
|
|
45
|
+
rows.append([
|
|
46
|
+
" + ".join(pair),
|
|
47
|
+
p.get("games_together", ""),
|
|
48
|
+
f"{p.get('pct_games', 0):.1f}%",
|
|
49
|
+
f"{p.get('lift', 0):.2f}x",
|
|
50
|
+
f"{p.get('win_rate', 0):.1f}%",
|
|
51
|
+
p.get("typical_turn", ""),
|
|
52
|
+
])
|
|
53
|
+
corpus = data.get("corpus") or {}
|
|
54
|
+
summary = [
|
|
55
|
+
f"[bold]{data.get('hero_name', hero_id)}[/bold] packages - {format_type}, "
|
|
56
|
+
f"{corpus.get('games', '?')} games in corpus, sorted by {sort}"
|
|
57
|
+
]
|
|
58
|
+
emit(
|
|
59
|
+
payload,
|
|
60
|
+
tables=[("Packages", ["Pair", "Games", "Share", "Lift", "WR", "Turn"], rows)],
|
|
61
|
+
summary=summary,
|
|
62
|
+
)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""paper tournament players: search + results"""
|
|
2
|
+
import typer
|
|
3
|
+
|
|
4
|
+
from .. import client
|
|
5
|
+
from ..output import emit, set_mode
|
|
6
|
+
|
|
7
|
+
app = typer.Typer(help="Paper tournament players")
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@app.command()
|
|
11
|
+
def search(
|
|
12
|
+
query: str = typer.Argument(..., help="Part of a player name"),
|
|
13
|
+
limit: int = typer.Option(25, "--limit", min=1, max=100),
|
|
14
|
+
as_json: bool = typer.Option(False, "--json"),
|
|
15
|
+
as_csv: bool = typer.Option(False, "--csv"),
|
|
16
|
+
):
|
|
17
|
+
"""Search tournament players by name."""
|
|
18
|
+
set_mode(as_json, as_csv)
|
|
19
|
+
payload = client.get("/players/search", {"q": query, "limit": limit})
|
|
20
|
+
rows = [
|
|
21
|
+
[p.get("id", ""), p.get("player_name", ""), p.get("country", "")]
|
|
22
|
+
for p in payload["data"]
|
|
23
|
+
]
|
|
24
|
+
emit(payload, tables=[("Players", ["ID", "Name", "Country"], rows)])
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@app.command()
|
|
28
|
+
def results(
|
|
29
|
+
player_id: str = typer.Argument(..., help="Player id (from `player search`)"),
|
|
30
|
+
limit: int = typer.Option(25, "--limit", min=1, max=200),
|
|
31
|
+
as_json: bool = typer.Option(False, "--json"),
|
|
32
|
+
as_csv: bool = typer.Option(False, "--csv"),
|
|
33
|
+
):
|
|
34
|
+
"""A player's paper tournament results."""
|
|
35
|
+
set_mode(as_json, as_csv)
|
|
36
|
+
payload = client.get(f"/players/{player_id}/results", {"limit": limit})
|
|
37
|
+
rows = []
|
|
38
|
+
for r in payload["data"]:
|
|
39
|
+
wins, losses = r.get("wins"), r.get("losses")
|
|
40
|
+
record = f"{wins}-{losses}" if wins is not None and losses is not None else "-"
|
|
41
|
+
rows.append([
|
|
42
|
+
(r.get("created_at") or "")[:10],
|
|
43
|
+
r.get("tournament_name", ""),
|
|
44
|
+
r.get("format", ""),
|
|
45
|
+
r.get("hero_played") or "-",
|
|
46
|
+
record,
|
|
47
|
+
r.get("standing") or "-",
|
|
48
|
+
])
|
|
49
|
+
emit(
|
|
50
|
+
payload,
|
|
51
|
+
tables=[("Results", ["Date", "Tournament", "Format", "Hero", "W-L", "Standing"], rows)],
|
|
52
|
+
)
|
|
@@ -5,8 +5,9 @@ import sys
|
|
|
5
5
|
import typer
|
|
6
6
|
|
|
7
7
|
from . import __version__
|
|
8
|
-
from .commands import auth, card, hero, meta, tournaments
|
|
8
|
+
from .commands import auth, card, hero, meta, player, tournaments
|
|
9
9
|
from .commands.matchup import matchup
|
|
10
|
+
from .commands.packages import packages
|
|
10
11
|
from .config import load_config, save_config
|
|
11
12
|
from .output import console, err_console
|
|
12
13
|
from .reference import REFERENCE
|
|
@@ -46,7 +47,9 @@ app.add_typer(hero.app, name="hero")
|
|
|
46
47
|
app.add_typer(meta.app, name="meta")
|
|
47
48
|
app.add_typer(card.app, name="card")
|
|
48
49
|
app.add_typer(tournaments.app, name="tournaments")
|
|
50
|
+
app.add_typer(player.app, name="player")
|
|
49
51
|
app.command()(matchup)
|
|
52
|
+
app.command()(packages)
|
|
50
53
|
|
|
51
54
|
|
|
52
55
|
@app.command()
|
|
@@ -57,18 +57,32 @@ analytics and paper tournament results for competitive Flesh and Blood.
|
|
|
57
57
|
- `fablazing hero list` / `hero show EVO001`
|
|
58
58
|
- `fablazing hero aggregate EVO001 --period 14d --source online`
|
|
59
59
|
- `fablazing hero cards MON119 --days 30` - most-used deck cards for a
|
|
60
|
-
hero vs the whole field
|
|
61
|
-
|
|
60
|
+
hero vs the whole field: per-card WR, Wilson lower bound, copies, and
|
|
61
|
+
play/block/pitch usage rates, plus an equipment table (live
|
|
62
|
+
aggregation cached up to 4h)
|
|
62
63
|
- `fablazing card pov MON119 "Dread Screamer"` - does running the card
|
|
63
64
|
change the hero's win rate, overall and per opponent (cached up to 4h)
|
|
64
65
|
- `fablazing card search "sink"` / `card show sink_below_red`
|
|
65
66
|
- `fablazing card trends sink_below_red --days 30`
|
|
66
67
|
- `fablazing card synergies sink_below_red`
|
|
67
68
|
- `fablazing card popular --days 7`
|
|
69
|
+
- `fablazing packages EVO001 --min-lift 1.5 --sort lift` - distinctive
|
|
70
|
+
same-turn card pairs: games together, lift over chance, win rate,
|
|
71
|
+
typical assembly turn. `--opponent` ranks vs one matchup, `--seed`
|
|
72
|
+
filters to pairs containing a card.
|
|
73
|
+
- `fablazing player search "Emily"` / `player results <id>` - paper
|
|
74
|
+
tournament players and their event records.
|
|
68
75
|
- `fablazing tournaments list --type Calling`
|
|
69
76
|
- `fablazing tournaments show <id>` / `standings <id>` / `decklists <id>`
|
|
70
77
|
- `fablazing reference` prints this text.
|
|
71
78
|
|
|
79
|
+
## Sub-formats ("Open")
|
|
80
|
+
Each format family maps to internal ids: cc = 0,1 plus Open 4; sage =
|
|
81
|
+
14,15 plus Open 16; blitz = 2,3; ll = 8,11; gage = 18. All ids in a
|
|
82
|
+
family are included by default. The API's matchup endpoint accepts
|
|
83
|
+
`sub_formats` (repeatable int) to narrow, e.g. sub_formats=0&sub_formats=1
|
|
84
|
+
for cc without Open.
|
|
85
|
+
|
|
72
86
|
## Output
|
|
73
87
|
- Tables on a terminal, JSON when piped or with --json (the JSON is the
|
|
74
88
|
full API payload, a superset of the tables), CSV of the primary table
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.3.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|