fablazing-cli 0.3.0__tar.gz → 0.4.1__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.
Files changed (22) hide show
  1. {fablazing_cli-0.3.0 → fablazing_cli-0.4.1}/CHANGELOG.md +19 -0
  2. {fablazing_cli-0.3.0 → fablazing_cli-0.4.1}/PKG-INFO +8 -1
  3. {fablazing_cli-0.3.0 → fablazing_cli-0.4.1}/README.md +7 -0
  4. {fablazing_cli-0.3.0 → fablazing_cli-0.4.1}/pyproject.toml +1 -1
  5. fablazing_cli-0.4.1/src/fablazing_cli/__init__.py +1 -0
  6. {fablazing_cli-0.3.0 → fablazing_cli-0.4.1}/src/fablazing_cli/commands/hero.py +18 -6
  7. {fablazing_cli-0.3.0 → fablazing_cli-0.4.1}/src/fablazing_cli/commands/matchup.py +2 -0
  8. fablazing_cli-0.4.1/src/fablazing_cli/commands/packages.py +62 -0
  9. fablazing_cli-0.4.1/src/fablazing_cli/commands/player.py +52 -0
  10. {fablazing_cli-0.3.0 → fablazing_cli-0.4.1}/src/fablazing_cli/main.py +4 -1
  11. {fablazing_cli-0.3.0 → fablazing_cli-0.4.1}/src/fablazing_cli/reference.py +22 -3
  12. fablazing_cli-0.3.0/src/fablazing_cli/__init__.py +0 -1
  13. {fablazing_cli-0.3.0 → fablazing_cli-0.4.1}/.gitignore +0 -0
  14. {fablazing_cli-0.3.0 → fablazing_cli-0.4.1}/src/fablazing_cli/client.py +0 -0
  15. {fablazing_cli-0.3.0 → fablazing_cli-0.4.1}/src/fablazing_cli/commands/__init__.py +0 -0
  16. {fablazing_cli-0.3.0 → fablazing_cli-0.4.1}/src/fablazing_cli/commands/auth.py +0 -0
  17. {fablazing_cli-0.3.0 → fablazing_cli-0.4.1}/src/fablazing_cli/commands/card.py +0 -0
  18. {fablazing_cli-0.3.0 → fablazing_cli-0.4.1}/src/fablazing_cli/commands/meta.py +0 -0
  19. {fablazing_cli-0.3.0 → fablazing_cli-0.4.1}/src/fablazing_cli/commands/tournaments.py +0 -0
  20. {fablazing_cli-0.3.0 → fablazing_cli-0.4.1}/src/fablazing_cli/config.py +0 -0
  21. {fablazing_cli-0.3.0 → fablazing_cli-0.4.1}/src/fablazing_cli/output.py +0 -0
  22. {fablazing_cli-0.3.0 → fablazing_cli-0.4.1}/src/fablazing_cli/resolve.py +0 -0
@@ -1,5 +1,24 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.4.1
4
+
5
+ - `--sub-format` flag on matchup (repeatable) - the API capability was
6
+ documented but unreachable from the CLI
7
+ - Reference: honest per-command format coverage (meta and aggregate
8
+ commands don't cover gage; matchup and card commands do)
9
+ - README: troubleshooting note for interrupted pip upgrades
10
+
11
+ ## 0.4.0
12
+
13
+ - `fabz packages`: same-turn card pairs with lift, share, win rate and
14
+ typical assembly turn - new API endpoint
15
+ - `fabz player search` / `player results`: paper tournament players
16
+ - `fabz hero cards` now shows play/block/pitch usage rates and an
17
+ equipment table
18
+ - Documented sub-format ids ("Open") in the reference
19
+ - API: /llms.txt primer on the API domain; unhandled 500s are now
20
+ recorded server-side for review
21
+
3
22
  ## 0.3.0
4
23
 
5
24
  - `fabz hero cards`: most-used deck cards for a hero vs the whole field
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fablazing-cli
3
- Version: 0.3.0
3
+ Version: 0.4.1
4
4
  Summary: Fablazing analytics from your terminal - Flesh and Blood matchups, meta, cards, and tournaments.
5
5
  Project-URL: Homepage, https://fablazing.com
6
6
  Author: Fablazing
@@ -58,6 +58,13 @@ one. Copy count filters work on matchup includes:
58
58
  Tables on a terminal. JSON when piped or with `--json`, CSV with `--csv`.
59
59
  Exit codes: 0 ok, 1 api error, 2 usage error, 3 quota exceeded.
60
60
 
61
+ ## Troubleshooting
62
+
63
+ If an upgrade leaves `fabz` broken (interrupted pip uninstall can strand
64
+ an empty `fablazing_cli` directory and `~ablazing_cli` backup folders):
65
+
66
+ pip install --force-reinstall fablazing-cli
67
+
61
68
  ## Tab completion
62
69
 
63
70
  fablazing --install-completion
@@ -43,6 +43,13 @@ one. Copy count filters work on matchup includes:
43
43
  Tables on a terminal. JSON when piped or with `--json`, CSV with `--csv`.
44
44
  Exit codes: 0 ok, 1 api error, 2 usage error, 3 quota exceeded.
45
45
 
46
+ ## Troubleshooting
47
+
48
+ If an upgrade leaves `fabz` broken (interrupted pip uninstall can strand
49
+ an empty `fablazing_cli` directory and `~ablazing_cli` backup folders):
50
+
51
+ pip install --force-reinstall fablazing-cli
52
+
46
53
  ## Tab completion
47
54
 
48
55
  fablazing --install-completion
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "fablazing-cli"
7
- version = "0.3.0"
7
+ version = "0.4.1"
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.1"
@@ -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
- f"${c['tcg_market_price']:.2f}" if c.get("tcg_market_price") else "-",
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
- emit(
83
- payload,
84
- tables=[("Cards", ["Card", "Freq", "N", "WR", "Wilson LB", "Copies", "Price"], rows)],
85
- summary=summary,
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()
@@ -11,6 +11,7 @@ def matchup(
11
11
  hero1: str = typer.Argument(..., help="Deck 1 hero (id or name)", autocompletion=resolve.complete_hero),
12
12
  hero2: str = typer.Argument(..., help="Deck 2 hero (id or name)", autocompletion=resolve.complete_hero),
13
13
  format_type: str = typer.Option("cc", "--format"),
14
+ sub_formats: List[int] = typer.Option([], "--sub-format", help="Restrict to sub-format ids (repeatable), e.g. --sub-format 0 --sub-format 1 for cc without Open"),
14
15
  days: int = typer.Option(30, "--days", min=1, max=365),
15
16
  start_date: str = typer.Option(None, "--start", help="YYYY-MM-DD (overrides --days)"),
16
17
  end_date: str = typer.Option(None, "--end", help="YYYY-MM-DD"),
@@ -31,6 +32,7 @@ def matchup(
31
32
  h2 = resolve.hero_id(hero2)
32
33
  params = {
33
34
  "format": format_type,
35
+ "sub_formats": sub_formats or None,
34
36
  "days": days,
35
37
  "start_date": start_date,
36
38
  "end_date": end_date,
@@ -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()
@@ -23,7 +23,12 @@ analytics and paper tournament results for competitive Flesh and Blood.
23
23
  different cards and are counted separately everywhere.
24
24
  - Names are accepted anywhere an id is. Ambiguous names error out and
25
25
  list the matching ids (exit code 2).
26
- - Formats: `cc` (default), `blitz`, `ll`, `sage`, `gage`.
26
+ - Formats: `cc` (default), `blitz`, `ll`, `sage`, `gage` - but coverage
27
+ is per command. matchup, hero cards, card pov and card analytics
28
+ accept all five. meta snapshot/movers cover cc, blitz, ll, sage.
29
+ hero aggregate and meta matrix cover cc, blitz, ll only (their
30
+ pre-computed stores don't process the rest). gage is a tiny corpus
31
+ (a few hundred games total) - expect thin samples everywhere.
27
32
 
28
33
  ## Data notes
29
34
  - win_rate fields are fractions between 0 and 1.
@@ -57,18 +62,32 @@ analytics and paper tournament results for competitive Flesh and Blood.
57
62
  - `fablazing hero list` / `hero show EVO001`
58
63
  - `fablazing hero aggregate EVO001 --period 14d --source online`
59
64
  - `fablazing hero cards MON119 --days 30` - most-used deck cards for a
60
- hero vs the whole field, with per-card WR and Wilson lower bound
61
- (deck cards only, no equipment; live aggregation cached up to 4h)
65
+ hero vs the whole field: per-card WR, Wilson lower bound, copies, and
66
+ play/block/pitch usage rates, plus an equipment table (live
67
+ aggregation cached up to 4h)
62
68
  - `fablazing card pov MON119 "Dread Screamer"` - does running the card
63
69
  change the hero's win rate, overall and per opponent (cached up to 4h)
64
70
  - `fablazing card search "sink"` / `card show sink_below_red`
65
71
  - `fablazing card trends sink_below_red --days 30`
66
72
  - `fablazing card synergies sink_below_red`
67
73
  - `fablazing card popular --days 7`
74
+ - `fablazing packages EVO001 --min-lift 1.5 --sort lift` - distinctive
75
+ same-turn card pairs: games together, lift over chance, win rate,
76
+ typical assembly turn. `--opponent` ranks vs one matchup, `--seed`
77
+ filters to pairs containing a card.
78
+ - `fablazing player search "Emily"` / `player results <id>` - paper
79
+ tournament players and their event records.
68
80
  - `fablazing tournaments list --type Calling`
69
81
  - `fablazing tournaments show <id>` / `standings <id>` / `decklists <id>`
70
82
  - `fablazing reference` prints this text.
71
83
 
84
+ ## Sub-formats ("Open")
85
+ Each format family maps to internal ids: cc = 0,1 plus Open 4; sage =
86
+ 14,15 plus Open 16; blitz = 2,3; ll = 8,11; gage = 18. All ids in a
87
+ family are included by default. Narrow with repeatable `--sub-format`
88
+ on matchup (e.g. `--sub-format 0 --sub-format 1` for cc without Open);
89
+ the API parameter is `sub_formats`.
90
+
72
91
  ## Output
73
92
  - Tables on a terminal, JSON when piped or with --json (the JSON is the
74
93
  full API payload, a superset of the tables), CSV of the primary table
@@ -1 +0,0 @@
1
- __version__ = "0.3.0"
File without changes