slab-cli 0.16.0__tar.gz → 0.18.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.
- {slab_cli-0.16.0 → slab_cli-0.18.0}/PKG-INFO +2 -2
- {slab_cli-0.16.0 → slab_cli-0.18.0}/pyproject.toml +4 -2
- {slab_cli-0.16.0 → slab_cli-0.18.0}/src/slab_cli/client.py +26 -0
- {slab_cli-0.16.0 → slab_cli-0.18.0}/src/slab_cli/commands/pricing.py +47 -0
- {slab_cli-0.16.0 → slab_cli-0.18.0}/src/slab_cli/commands/registry.py +2 -0
- {slab_cli-0.16.0 → slab_cli-0.18.0}/src/slab_cli/display.py +143 -1
- {slab_cli-0.16.0 → slab_cli-0.18.0}/README.md +0 -0
- {slab_cli-0.16.0 → slab_cli-0.18.0}/src/slab_cli/__init__.py +0 -0
- {slab_cli-0.16.0 → slab_cli-0.18.0}/src/slab_cli/banner.py +0 -0
- {slab_cli-0.16.0 → slab_cli-0.18.0}/src/slab_cli/browse.py +0 -0
- {slab_cli-0.16.0 → slab_cli-0.18.0}/src/slab_cli/commands/__init__.py +0 -0
- {slab_cli-0.16.0 → slab_cli-0.18.0}/src/slab_cli/commands/breaks.py +0 -0
- {slab_cli-0.16.0 → slab_cli-0.18.0}/src/slab_cli/commands/catalog.py +0 -0
- {slab_cli-0.16.0 → slab_cli-0.18.0}/src/slab_cli/commands/collection.py +0 -0
- {slab_cli-0.16.0 → slab_cli-0.18.0}/src/slab_cli/commands/custom_sets.py +0 -0
- {slab_cli-0.16.0 → slab_cli-0.18.0}/src/slab_cli/commands/export.py +0 -0
- {slab_cli-0.16.0 → slab_cli-0.18.0}/src/slab_cli/commands/lots.py +0 -0
- {slab_cli-0.16.0 → slab_cli-0.18.0}/src/slab_cli/commands/setup.py +0 -0
- {slab_cli-0.16.0 → slab_cli-0.18.0}/src/slab_cli/commands/update.py +0 -0
- {slab_cli-0.16.0 → slab_cli-0.18.0}/src/slab_cli/config.py +0 -0
- {slab_cli-0.16.0 → slab_cli-0.18.0}/src/slab_cli/context.py +0 -0
- {slab_cli-0.16.0 → slab_cli-0.18.0}/src/slab_cli/flags.py +0 -0
- {slab_cli-0.16.0 → slab_cli-0.18.0}/src/slab_cli/help.py +0 -0
- {slab_cli-0.16.0 → slab_cli-0.18.0}/src/slab_cli/main.py +0 -0
- {slab_cli-0.16.0 → slab_cli-0.18.0}/src/slab_cli/paging.py +0 -0
- {slab_cli-0.16.0 → slab_cli-0.18.0}/src/slab_cli/picker.py +0 -0
- {slab_cli-0.16.0 → slab_cli-0.18.0}/src/slab_cli/platforms/__init__.py +0 -0
- {slab_cli-0.16.0 → slab_cli-0.18.0}/src/slab_cli/platforms/base.py +0 -0
- {slab_cli-0.16.0 → slab_cli-0.18.0}/src/slab_cli/platforms/posix.py +0 -0
- {slab_cli-0.16.0 → slab_cli-0.18.0}/src/slab_cli/platforms/windows.py +0 -0
- {slab_cli-0.16.0 → slab_cli-0.18.0}/src/slab_cli/prompts.py +0 -0
- {slab_cli-0.16.0 → slab_cli-0.18.0}/src/slab_cli/sources.py +0 -0
- {slab_cli-0.16.0 → slab_cli-0.18.0}/src/slab_cli/theme.py +0 -0
- {slab_cli-0.16.0 → slab_cli-0.18.0}/src/slab_cli/updates.py +0 -0
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: slab-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.18.0
|
|
4
4
|
Summary: CLI for the slab trading-card API — catalog, collect, and track your cards from the terminal.
|
|
5
5
|
Author: dev_jeb
|
|
6
|
-
Requires-Dist: slab-schemas>=0.
|
|
6
|
+
Requires-Dist: slab-schemas>=0.32.0
|
|
7
7
|
Requires-Dist: httpx>=0.27
|
|
8
8
|
Requires-Dist: rich>=13.0
|
|
9
9
|
Requires-Dist: inquirerpy>=0.3
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "slab-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.18.0"
|
|
4
4
|
description = "CLI for the slab trading-card API — catalog, collect, and track your cards from the terminal."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
authors = [{name = "dev_jeb"}]
|
|
7
7
|
requires-python = ">=3.13"
|
|
8
8
|
dependencies = [
|
|
9
9
|
# 0.31: CardSearchQuery.collapse_parallels — `card search` sends it on every query.
|
|
10
|
-
|
|
10
|
+
# 0.32 adds CompOut.grade_unconfirmed (the comps table's grade-sanity "?" marker) and the
|
|
11
|
+
# grading-desk models `slab card grade` / `slab collection grade` parse responses into.
|
|
12
|
+
"slab-schemas>=0.32.0",
|
|
11
13
|
"httpx>=0.27",
|
|
12
14
|
"rich>=13.0",
|
|
13
15
|
"InquirerPy>=0.3",
|
|
@@ -49,6 +49,7 @@ from slab_schemas.custom_sets import (
|
|
|
49
49
|
CustomSetUpdate,
|
|
50
50
|
)
|
|
51
51
|
from slab_schemas.dashboard import CatalogStats, DashboardStats
|
|
52
|
+
from slab_schemas.grading_desk import CollectionGradingDesk, GradingDesk
|
|
52
53
|
from slab_schemas.pricing import CardComps, CardMarket, SetTopCards
|
|
53
54
|
from slab_schemas.sealed import SealedPriceHistory, SealedProductOut
|
|
54
55
|
from slab_schemas.timeseries import CardPriceHistory
|
|
@@ -125,6 +126,31 @@ class SlabClient:
|
|
|
125
126
|
resp = self._request("GET", f"/cards/{card_uuid}/comps", params=params)
|
|
126
127
|
return CardComps.model_validate(resp.json())
|
|
127
128
|
|
|
129
|
+
def get_grading_desk(
|
|
130
|
+
self, card_uuid: str, fee: float | None = None, company: str | None = None
|
|
131
|
+
) -> GradingDesk:
|
|
132
|
+
params: dict[str, str] = {}
|
|
133
|
+
if fee is not None:
|
|
134
|
+
params["fee"] = str(fee)
|
|
135
|
+
if company:
|
|
136
|
+
params["company"] = company
|
|
137
|
+
resp = self._request("GET", f"/cards/{card_uuid}/grading-desk", params=params)
|
|
138
|
+
return GradingDesk.model_validate(resp.json())
|
|
139
|
+
|
|
140
|
+
def get_collection_grading_desk(
|
|
141
|
+
self, collector_uuid: str, fee: float | None = None,
|
|
142
|
+
company: str | None = None, limit: int = 50,
|
|
143
|
+
) -> CollectionGradingDesk:
|
|
144
|
+
params: dict[str, str] = {"limit": str(limit)}
|
|
145
|
+
if fee is not None:
|
|
146
|
+
params["fee"] = str(fee)
|
|
147
|
+
if company:
|
|
148
|
+
params["company"] = company
|
|
149
|
+
resp = self._request(
|
|
150
|
+
"GET", f"/collectors/{collector_uuid}/collection/grading-desk", params=params
|
|
151
|
+
)
|
|
152
|
+
return CollectionGradingDesk.model_validate(resp.json())
|
|
153
|
+
|
|
128
154
|
def get_card_price_history(
|
|
129
155
|
self,
|
|
130
156
|
card_uuid: str,
|
|
@@ -11,7 +11,9 @@ from ..context import MissingCollector, ctx
|
|
|
11
11
|
from ..display import (
|
|
12
12
|
console,
|
|
13
13
|
render_card_market,
|
|
14
|
+
render_collection_grading_desk,
|
|
14
15
|
render_comps,
|
|
16
|
+
render_grading_desk,
|
|
15
17
|
render_portfolio,
|
|
16
18
|
render_price_history,
|
|
17
19
|
render_sealed_price_history,
|
|
@@ -77,6 +79,51 @@ def cmd_comps(args: list[str]) -> None:
|
|
|
77
79
|
render_comps(ctx.client.get_card_comps(card.uuid))
|
|
78
80
|
|
|
79
81
|
|
|
82
|
+
def _split_desk_flags(args: list[str]) -> tuple[list[str], float | None, str | None]:
|
|
83
|
+
"""Pull `--fee <amount>` and `--company <name>` out of a grading query. Hand-rolled (the
|
|
84
|
+
shared flag grammar is search-specific); an unparseable fee errors loudly rather than
|
|
85
|
+
silently pricing off the default."""
|
|
86
|
+
rest: list[str] = []
|
|
87
|
+
fee: float | None = None
|
|
88
|
+
company: str | None = None
|
|
89
|
+
i = 0
|
|
90
|
+
while i < len(args):
|
|
91
|
+
a = args[i]
|
|
92
|
+
if a == "--fee" and i + 1 < len(args):
|
|
93
|
+
try:
|
|
94
|
+
fee = float(args[i + 1].lstrip("$"))
|
|
95
|
+
except ValueError:
|
|
96
|
+
raise SystemExit(f"--fee expects a number, got {args[i + 1]!r}")
|
|
97
|
+
i += 2
|
|
98
|
+
elif a == "--company" and i + 1 < len(args):
|
|
99
|
+
company = args[i + 1]
|
|
100
|
+
i += 2
|
|
101
|
+
else:
|
|
102
|
+
rest.append(a)
|
|
103
|
+
i += 1
|
|
104
|
+
return rest, fee, company
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def cmd_grade(args: list[str]) -> None:
|
|
108
|
+
"""Should I grade this card? Same player → set → card flow as `price`, then the grading desk:
|
|
109
|
+
what each grade nets after the fee, and how sure you'd need to be it gems before grading
|
|
110
|
+
beats keeping it raw. `--fee 19` prices at your rate; `--company SGC` switches grader."""
|
|
111
|
+
args, fee, company = _split_desk_flags(args)
|
|
112
|
+
card = _pick_card(args)
|
|
113
|
+
if card is None:
|
|
114
|
+
return
|
|
115
|
+
render_grading_desk(ctx.client.get_grading_desk(card.uuid, fee=fee, company=company))
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def cmd_grade_sweep(args: list[str]) -> None:
|
|
119
|
+
"""Which of your raw cards are worth grading? Ranks every ungraded copy best-case first —
|
|
120
|
+
where the per-card bars become batch odds. `--fee 19` prices at your rate."""
|
|
121
|
+
args, fee, company = _split_desk_flags(args)
|
|
122
|
+
render_collection_grading_desk(
|
|
123
|
+
ctx.client.get_collection_grading_desk(ctx.collector, fee=fee, company=company)
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
|
|
80
127
|
def _fetch_all_copies(page: int = 200, cap: int = 5000) -> tuple[list, PortfolioSummary | None]:
|
|
81
128
|
"""Page through the whole collection for the per-set/mover/break breakdowns, and grab the
|
|
82
129
|
server's summary block (full-result aggregates, identical on every page) from the first page —
|
|
@@ -59,6 +59,7 @@ GROUPS: list[Group] = [
|
|
|
59
59
|
Command("search", "Search the catalog (player, set, team…)", catalog.cmd_search, "[query] [flags]"),
|
|
60
60
|
Command("parallels", "Every parallel (the rainbow) of a card", catalog.cmd_rainbow, "<query>"),
|
|
61
61
|
Command("price", "Market price + 7d/30d trend", pricing.cmd_price, "<query>"),
|
|
62
|
+
Command("grade", "Should I grade it? Payoffs, the bar, a verdict", pricing.cmd_grade, "<query> [--fee N]"),
|
|
62
63
|
Command("comps", "Recent raw sales behind the price", pricing.cmd_comps, "<query>"),
|
|
63
64
|
Command("history", "Price history over time", pricing.cmd_history, "<query>"),
|
|
64
65
|
]),
|
|
@@ -79,6 +80,7 @@ GROUPS: list[Group] = [
|
|
|
79
80
|
Command("export", "Export a set to a printable checklist", export.cmd_export),
|
|
80
81
|
Command("dashboard", "Overview: counts, value, composition", collection.cmd_dashboard),
|
|
81
82
|
Command("portfolio", "Portfolio: cost basis vs FMV + top movers", pricing.cmd_portfolio),
|
|
83
|
+
Command("grade", "Which of your raw cards are worth grading", pricing.cmd_grade_sweep, "[--fee N]"),
|
|
82
84
|
]),
|
|
83
85
|
Group("break", "case / box breaks", [
|
|
84
86
|
Command("list", "Your breaks — pick one to see value vs cost + top pulls", breaks.cmd_break_list),
|
|
@@ -19,7 +19,8 @@ from slab_schemas.community import (
|
|
|
19
19
|
)
|
|
20
20
|
from slab_schemas.custom_sets import CustomSetDetail, CustomSetOut, CustomSetSearchResult
|
|
21
21
|
from slab_schemas.dashboard import CatalogStats, DashboardStats, HighlightCard, LabeledCount
|
|
22
|
-
from slab_schemas.
|
|
22
|
+
from slab_schemas.grading_desk import CollectionGradingDesk, GradingDesk
|
|
23
|
+
from slab_schemas.enums import Grade, GradingVerdict, SealedFormat
|
|
23
24
|
from slab_schemas.pricing import CardComps, CardMarket, PortfolioSummary, SetTopCards
|
|
24
25
|
from slab_schemas.sealed import SealedPriceHistory, SealedProductOut
|
|
25
26
|
from slab_schemas.timeseries import CardPriceHistory
|
|
@@ -1116,10 +1117,16 @@ def render_comps(c: CardComps) -> None:
|
|
|
1116
1117
|
("Title", {}),
|
|
1117
1118
|
])
|
|
1118
1119
|
|
|
1120
|
+
flagged = 0
|
|
1119
1121
|
for comp in c.comps:
|
|
1120
1122
|
when = comp.sold_date.isoformat() if comp.sold_date else "—"
|
|
1121
1123
|
price = money(comp.sale_price) if comp.sale_price is not None else "—"
|
|
1122
1124
|
grade = comp.grade_key or "—"
|
|
1125
|
+
# Grade sanity: "RAW" only means the title named no grade. A raw-labeled sale priced
|
|
1126
|
+
# like this card's graded copies is probably a graded sale with a terse title.
|
|
1127
|
+
if getattr(comp, "grade_unconfirmed", False):
|
|
1128
|
+
grade = f"[slab.warn]{grade}?[/]"
|
|
1129
|
+
flagged += 1
|
|
1123
1130
|
market = comp.marketplace or "—"
|
|
1124
1131
|
sale_type = comp.sale_type or "—"
|
|
1125
1132
|
title = comp.title if len(comp.title) <= 50 else comp.title[:49] + "…"
|
|
@@ -1127,6 +1134,141 @@ def render_comps(c: CardComps) -> None:
|
|
|
1127
1134
|
|
|
1128
1135
|
console.print(heading("Recent Comps", f"showing {len(c.comps)} of {c.total}"))
|
|
1129
1136
|
console.print(table)
|
|
1137
|
+
if flagged:
|
|
1138
|
+
console.print(
|
|
1139
|
+
f" [slab.dim]? = grade unconfirmed: no grade in the listing title, but priced like "
|
|
1140
|
+
f"this card's graded copies — likely a graded sale, so don't read it as raw value.[/]"
|
|
1141
|
+
)
|
|
1142
|
+
console.print()
|
|
1143
|
+
|
|
1144
|
+
|
|
1145
|
+
def _desk_verdict_line(d: GradingDesk) -> str:
|
|
1146
|
+
"""The verdict as one plain sentence — the confidence-BAR framing (you grade a card once, so
|
|
1147
|
+
the number is how good YOUR copy has to look, never "how often"). Wording mirrors the
|
|
1148
|
+
`grading.verdict` glossary entry."""
|
|
1149
|
+
def payoff_of(grade_key: str | None):
|
|
1150
|
+
return next((l.payoff for l in d.lanes if l.grade_key == grade_key), None)
|
|
1151
|
+
|
|
1152
|
+
gem = next((l for l in d.lanes if l.grade_key.startswith(f"{d.grading_company}-")), None)
|
|
1153
|
+
if d.verdict == GradingVerdict.not_enough_data:
|
|
1154
|
+
return ("[slab.dim]No graded market to read yet — nobody has sold graded copies of this "
|
|
1155
|
+
"card recently. Not a no; an honest shrug.[/]")
|
|
1156
|
+
if d.verdict == GradingVerdict.easy_no:
|
|
1157
|
+
top = f"even a {gem.grade_key} only returns {signed_money(gem.payoff)}" if gem and gem.payoff is not None else "no grade covers the fee"
|
|
1158
|
+
return f"[slab.loss]Keep it raw[/] — {top} after the {money(d.fee)} fee."
|
|
1159
|
+
miss_pay = payoff_of(d.miss_grade)
|
|
1160
|
+
if d.verdict == GradingVerdict.easy_yes:
|
|
1161
|
+
miss_txt = (f"even a {d.miss_grade} nets {signed_money(miss_pay)}"
|
|
1162
|
+
if miss_pay is not None else "even a realistic miss beats staying raw")
|
|
1163
|
+
return f"[slab.gain]Send it[/] — {miss_txt} after the {money(d.fee)} fee."
|
|
1164
|
+
pct = f"{d.ten_confidence_needed:.0%}"
|
|
1165
|
+
gem_txt = f"a {gem.grade_key} nets {signed_money(gem.payoff)}" if gem else "a gem pays"
|
|
1166
|
+
miss_txt = (f"a {d.miss_grade} comes back {signed_money(miss_pay)}"
|
|
1167
|
+
if miss_pay is not None else f"a miss holds roughly raw value")
|
|
1168
|
+
return (f"[slab.foil]Your call[/] — grade it only if you're more than {pct} sure it's a gem: "
|
|
1169
|
+
f"{gem_txt}, {miss_txt}.")
|
|
1170
|
+
|
|
1171
|
+
|
|
1172
|
+
def render_grading_desk(d: GradingDesk) -> None:
|
|
1173
|
+
"""One card's grading decision: raw baseline, payoff per graded lane, and the verdict in the
|
|
1174
|
+
bar framing. Every sentence traces to a `grading.*` glossary entry."""
|
|
1175
|
+
subjects = ", ".join(d.subjects) or "—"
|
|
1176
|
+
slot = f"{d.subset or '—'} · {d.finish}" if d.finish else (d.subset or "base")
|
|
1177
|
+
console.print()
|
|
1178
|
+
console.print(_card_headline(d.card_number, subjects, d.set_name, slot))
|
|
1179
|
+
|
|
1180
|
+
if d.raw is None:
|
|
1181
|
+
console.print("\n [slab.dim]This card has no raw market price yet, so there is no "
|
|
1182
|
+
"baseline to grade against.[/]\n")
|
|
1183
|
+
return
|
|
1184
|
+
|
|
1185
|
+
console.print(
|
|
1186
|
+
f"\n Raw value: [slab.value]{money(d.raw.fair_market_value)}[/] "
|
|
1187
|
+
f"[slab.dim](from {d.raw.sample_size} sales) · {d.grading_company} fee assumed: "
|
|
1188
|
+
f"{money(d.fee)}"
|
|
1189
|
+
+ (" — pass --fee to use your rate" if d.fee_source == "default" else " (your rate)")
|
|
1190
|
+
+ "[/]"
|
|
1191
|
+
)
|
|
1192
|
+
|
|
1193
|
+
if d.lanes:
|
|
1194
|
+
table = slab_table([
|
|
1195
|
+
("If it grades", {}),
|
|
1196
|
+
("Sells for", {"justify": "right", "style": "slab.foil"}),
|
|
1197
|
+
("You clear", {"justify": "right"}),
|
|
1198
|
+
("Sales", {"justify": "right", "style": "slab.dim"}),
|
|
1199
|
+
])
|
|
1200
|
+
for l in d.lanes:
|
|
1201
|
+
n = f"{l.sample_size}!" if l.low_confidence else str(l.sample_size)
|
|
1202
|
+
table.add_row(l.grade_key, money(l.fair_market_value), signed_money(l.payoff), n)
|
|
1203
|
+
console.print(heading("Payoff per Grade", "after the fee, vs staying raw"))
|
|
1204
|
+
console.print(table)
|
|
1205
|
+
if any(l.low_confidence for l in d.lanes):
|
|
1206
|
+
console.print(" [slab.dim]! = priced off very few sales — an anecdote, "
|
|
1207
|
+
"not a market.[/]")
|
|
1208
|
+
|
|
1209
|
+
console.print()
|
|
1210
|
+
console.print(f" {_desk_verdict_line(d)}")
|
|
1211
|
+
if d.verdict in (GradingVerdict.easy_yes, GradingVerdict.judgment_call) \
|
|
1212
|
+
and d.ten_confidence_needed_bad_day is not None and d.bad_day_grade:
|
|
1213
|
+
console.print(
|
|
1214
|
+
f" [slab.dim]Cautious read: if misses come back {d.bad_day_grade} instead, "
|
|
1215
|
+
f"the bar is {d.ten_confidence_needed_bad_day:.0%}.[/]"
|
|
1216
|
+
)
|
|
1217
|
+
if d.thin_data:
|
|
1218
|
+
console.print(" [slab.warn]Thin data[/][slab.dim] — part of this verdict rests on "
|
|
1219
|
+
"1-2 sales. Treat it as a hint, not a market reading.[/]")
|
|
1220
|
+
console.print()
|
|
1221
|
+
|
|
1222
|
+
|
|
1223
|
+
def render_collection_grading_desk(sweep: CollectionGradingDesk) -> None:
|
|
1224
|
+
"""The batch view: raw copies ranked best-case first. Here the per-card bars become real
|
|
1225
|
+
odds — graded as a group, wins at these bars pay for the losses (the rip-EV logic)."""
|
|
1226
|
+
console.print()
|
|
1227
|
+
covered = f"{sweep.deskable} of your {sweep.total_raw_copies} raw copies have a graded market to read"
|
|
1228
|
+
console.print(heading("Grading Desk", covered))
|
|
1229
|
+
if not sweep.entries:
|
|
1230
|
+
console.print("\n [slab.dim]None of your raw cards have graded sales to compare "
|
|
1231
|
+
"against yet — no verdicts to give. That's the honest answer, not an "
|
|
1232
|
+
"error.[/]\n")
|
|
1233
|
+
return
|
|
1234
|
+
|
|
1235
|
+
table = slab_table([
|
|
1236
|
+
("Verdict", {}),
|
|
1237
|
+
("Card", {"no_wrap": False}),
|
|
1238
|
+
("Raw", {"justify": "right", "style": "slab.dim"}),
|
|
1239
|
+
("Gem pays", {"justify": "right"}),
|
|
1240
|
+
("Bar", {"justify": "right", "style": "slab.foil"}),
|
|
1241
|
+
("Qty", {"justify": "right", "style": "slab.dim"}),
|
|
1242
|
+
])
|
|
1243
|
+
VERDICT_TXT = {
|
|
1244
|
+
GradingVerdict.easy_yes: "[slab.gain]send it[/]",
|
|
1245
|
+
GradingVerdict.judgment_call: "[slab.foil]your call[/]",
|
|
1246
|
+
GradingVerdict.easy_no: "[slab.loss]keep raw[/]",
|
|
1247
|
+
}
|
|
1248
|
+
for e in sweep.entries:
|
|
1249
|
+
d = e.desk
|
|
1250
|
+
who = ", ".join(d.subjects) or f"#{d.card_number}"
|
|
1251
|
+
card = f"{who} · {d.set_name or '—'}" + (f" · {d.finish}" if d.finish else "")
|
|
1252
|
+
gem = next((l for l in d.lanes
|
|
1253
|
+
if l.grade_key.startswith(f"{d.grading_company}-") and l.payoff is not None),
|
|
1254
|
+
None)
|
|
1255
|
+
bar = "—"
|
|
1256
|
+
if d.verdict == GradingVerdict.judgment_call and d.ten_confidence_needed is not None:
|
|
1257
|
+
bar = f"{d.ten_confidence_needed:.0%}"
|
|
1258
|
+
elif d.verdict == GradingVerdict.easy_yes:
|
|
1259
|
+
bar = "any"
|
|
1260
|
+
thin = " [slab.warn]!" if d.thin_data else ""
|
|
1261
|
+
table.add_row(
|
|
1262
|
+
VERDICT_TXT.get(d.verdict, d.verdict.value) + thin, card,
|
|
1263
|
+
money(d.raw.fair_market_value) if d.raw else "—",
|
|
1264
|
+
signed_money(gem.payoff) if gem else "[slab.dim]—[/]",
|
|
1265
|
+
bar, str(e.quantity),
|
|
1266
|
+
)
|
|
1267
|
+
console.print(table)
|
|
1268
|
+
console.print(f" [slab.dim]{sweep.fee_note}[/]")
|
|
1269
|
+
console.print(" [slab.dim]Bar = how sure you'd need to be the card gems before grading "
|
|
1270
|
+
"beats keeping it raw. You can see its corners; slab can't. "
|
|
1271
|
+
"! = verdict rests on very few sales.[/]")
|
|
1130
1272
|
console.print()
|
|
1131
1273
|
|
|
1132
1274
|
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|