slab-cli 0.18.0__tar.gz → 0.20.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.18.0 → slab_cli-0.20.0}/PKG-INFO +2 -2
- {slab_cli-0.18.0 → slab_cli-0.20.0}/pyproject.toml +5 -2
- {slab_cli-0.18.0 → slab_cli-0.20.0}/src/slab_cli/commands/pricing.py +5 -5
- {slab_cli-0.18.0 → slab_cli-0.20.0}/src/slab_cli/commands/registry.py +2 -2
- {slab_cli-0.18.0 → slab_cli-0.20.0}/src/slab_cli/display.py +83 -42
- {slab_cli-0.18.0 → slab_cli-0.20.0}/README.md +0 -0
- {slab_cli-0.18.0 → slab_cli-0.20.0}/src/slab_cli/__init__.py +0 -0
- {slab_cli-0.18.0 → slab_cli-0.20.0}/src/slab_cli/banner.py +0 -0
- {slab_cli-0.18.0 → slab_cli-0.20.0}/src/slab_cli/browse.py +0 -0
- {slab_cli-0.18.0 → slab_cli-0.20.0}/src/slab_cli/client.py +0 -0
- {slab_cli-0.18.0 → slab_cli-0.20.0}/src/slab_cli/commands/__init__.py +0 -0
- {slab_cli-0.18.0 → slab_cli-0.20.0}/src/slab_cli/commands/breaks.py +0 -0
- {slab_cli-0.18.0 → slab_cli-0.20.0}/src/slab_cli/commands/catalog.py +0 -0
- {slab_cli-0.18.0 → slab_cli-0.20.0}/src/slab_cli/commands/collection.py +0 -0
- {slab_cli-0.18.0 → slab_cli-0.20.0}/src/slab_cli/commands/custom_sets.py +0 -0
- {slab_cli-0.18.0 → slab_cli-0.20.0}/src/slab_cli/commands/export.py +0 -0
- {slab_cli-0.18.0 → slab_cli-0.20.0}/src/slab_cli/commands/lots.py +0 -0
- {slab_cli-0.18.0 → slab_cli-0.20.0}/src/slab_cli/commands/setup.py +0 -0
- {slab_cli-0.18.0 → slab_cli-0.20.0}/src/slab_cli/commands/update.py +0 -0
- {slab_cli-0.18.0 → slab_cli-0.20.0}/src/slab_cli/config.py +0 -0
- {slab_cli-0.18.0 → slab_cli-0.20.0}/src/slab_cli/context.py +0 -0
- {slab_cli-0.18.0 → slab_cli-0.20.0}/src/slab_cli/flags.py +0 -0
- {slab_cli-0.18.0 → slab_cli-0.20.0}/src/slab_cli/help.py +0 -0
- {slab_cli-0.18.0 → slab_cli-0.20.0}/src/slab_cli/main.py +0 -0
- {slab_cli-0.18.0 → slab_cli-0.20.0}/src/slab_cli/paging.py +0 -0
- {slab_cli-0.18.0 → slab_cli-0.20.0}/src/slab_cli/picker.py +0 -0
- {slab_cli-0.18.0 → slab_cli-0.20.0}/src/slab_cli/platforms/__init__.py +0 -0
- {slab_cli-0.18.0 → slab_cli-0.20.0}/src/slab_cli/platforms/base.py +0 -0
- {slab_cli-0.18.0 → slab_cli-0.20.0}/src/slab_cli/platforms/posix.py +0 -0
- {slab_cli-0.18.0 → slab_cli-0.20.0}/src/slab_cli/platforms/windows.py +0 -0
- {slab_cli-0.18.0 → slab_cli-0.20.0}/src/slab_cli/prompts.py +0 -0
- {slab_cli-0.18.0 → slab_cli-0.20.0}/src/slab_cli/sources.py +0 -0
- {slab_cli-0.18.0 → slab_cli-0.20.0}/src/slab_cli/theme.py +0 -0
- {slab_cli-0.18.0 → slab_cli-0.20.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.20.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.35.0
|
|
7
7
|
Requires-Dist: httpx>=0.27
|
|
8
8
|
Requires-Dist: rich>=13.0
|
|
9
9
|
Requires-Dist: inquirerpy>=0.3
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "slab-cli"
|
|
3
|
-
version = "0.
|
|
3
|
+
version = "0.20.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"}]
|
|
@@ -9,7 +9,10 @@ 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
11
|
# grading-desk models `slab card grade` / `slab collection grade` parse responses into.
|
|
12
|
-
|
|
12
|
+
# 0.34 reframes the grading desk from verdicts to break-even readings (BreakEvenRegion,
|
|
13
|
+
# GradingDesk.break_even) — the desk shows data, never tells the user to grade.
|
|
14
|
+
# 0.35 adds Liquidity/LiquidityLabel, which the market view's Sells column renders.
|
|
15
|
+
"slab-schemas>=0.35.0",
|
|
13
16
|
"httpx>=0.27",
|
|
14
17
|
"rich>=13.0",
|
|
15
18
|
"InquirerPy>=0.3",
|
|
@@ -105,9 +105,9 @@ def _split_desk_flags(args: list[str]) -> tuple[list[str], float | None, str | N
|
|
|
105
105
|
|
|
106
106
|
|
|
107
107
|
def cmd_grade(args: list[str]) -> None:
|
|
108
|
-
"""
|
|
109
|
-
what each grade nets after the fee
|
|
110
|
-
|
|
108
|
+
"""The grading math for a card. Same player → set → card flow as `price`, then the grading
|
|
109
|
+
desk: what each grade nets after the fee and where break-even sits — the data to decide with;
|
|
110
|
+
the decision is yours. `--fee 19` prices at your rate; `--company SGC` switches grader."""
|
|
111
111
|
args, fee, company = _split_desk_flags(args)
|
|
112
112
|
card = _pick_card(args)
|
|
113
113
|
if card is None:
|
|
@@ -116,8 +116,8 @@ def cmd_grade(args: list[str]) -> None:
|
|
|
116
116
|
|
|
117
117
|
|
|
118
118
|
def cmd_grade_sweep(args: list[str]) -> None:
|
|
119
|
-
"""
|
|
120
|
-
|
|
119
|
+
"""The grading math across your raw cards, biggest upside first — where the per-card
|
|
120
|
+
break-even bars become batch odds. `--fee 19` prices at your rate."""
|
|
121
121
|
args, fee, company = _split_desk_flags(args)
|
|
122
122
|
render_collection_grading_desk(
|
|
123
123
|
ctx.client.get_collection_grading_desk(ctx.collector, fee=fee, company=company)
|
|
@@ -59,7 +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", "
|
|
62
|
+
Command("grade", "The grading math: payoffs per grade + break-even", pricing.cmd_grade, "<query> [--fee N]"),
|
|
63
63
|
Command("comps", "Recent raw sales behind the price", pricing.cmd_comps, "<query>"),
|
|
64
64
|
Command("history", "Price history over time", pricing.cmd_history, "<query>"),
|
|
65
65
|
]),
|
|
@@ -80,7 +80,7 @@ GROUPS: list[Group] = [
|
|
|
80
80
|
Command("export", "Export a set to a printable checklist", export.cmd_export),
|
|
81
81
|
Command("dashboard", "Overview: counts, value, composition", collection.cmd_dashboard),
|
|
82
82
|
Command("portfolio", "Portfolio: cost basis vs FMV + top movers", pricing.cmd_portfolio),
|
|
83
|
-
Command("grade", "
|
|
83
|
+
Command("grade", "Grading math for every raw copy, best upside first", pricing.cmd_grade_sweep, "[--fee N]"),
|
|
84
84
|
]),
|
|
85
85
|
Group("break", "case / box breaks", [
|
|
86
86
|
Command("list", "Your breaks — pick one to see value vs cost + top pulls", breaks.cmd_break_list),
|
|
@@ -20,7 +20,8 @@ from slab_schemas.community import (
|
|
|
20
20
|
from slab_schemas.custom_sets import CustomSetDetail, CustomSetOut, CustomSetSearchResult
|
|
21
21
|
from slab_schemas.dashboard import CatalogStats, DashboardStats, HighlightCard, LabeledCount
|
|
22
22
|
from slab_schemas.grading_desk import CollectionGradingDesk, GradingDesk
|
|
23
|
-
from slab_schemas.enums import Grade,
|
|
23
|
+
from slab_schemas.enums import BreakEvenRegion, Grade, LiquidityLabel, SealedFormat
|
|
24
|
+
from slab_schemas.liquidity import Liquidity
|
|
24
25
|
from slab_schemas.pricing import CardComps, CardMarket, PortfolioSummary, SetTopCards
|
|
25
26
|
from slab_schemas.sealed import SealedPriceHistory, SealedProductOut
|
|
26
27
|
from slab_schemas.timeseries import CardPriceHistory
|
|
@@ -971,7 +972,8 @@ def render_card_market(m: CardMarket) -> None:
|
|
|
971
972
|
|
|
972
973
|
# The Δ columns are FMV (appraisal) drift, not the window's sales — FMV smooths 90 days of
|
|
973
974
|
# comps, so these lag the live market (two-lane rule, CLAUDE.md). Headers say FMV so a
|
|
974
|
-
# "+$5" cell reads as the appraisal moving, not as last week's market.
|
|
975
|
+
# "+$5" cell reads as the appraisal moving, not as last week's market. "Sells" is the other
|
|
976
|
+
# lane: real sale cadence from comps (liquidity), so the price and its pace sit side by side.
|
|
975
977
|
table = slab_table([
|
|
976
978
|
("Grade", {"style": "slab.value"}),
|
|
977
979
|
("FMV", {"justify": "right"}),
|
|
@@ -979,6 +981,7 @@ def render_card_market(m: CardMarket) -> None:
|
|
|
979
981
|
("FMV Δ7d", {"justify": "right"}),
|
|
980
982
|
("FMV Δ30d", {"justify": "right"}),
|
|
981
983
|
("Comps", {"justify": "right", "style": "slab.dim"}),
|
|
984
|
+
("Sells", {"style": "slab.dim"}),
|
|
982
985
|
])
|
|
983
986
|
|
|
984
987
|
for pp in sorted(m.price_points, key=lambda p: (p.finish or "", p.grade_key)):
|
|
@@ -990,14 +993,39 @@ def render_card_market(m: CardMarket) -> None:
|
|
|
990
993
|
if pp.price_low is not None and pp.price_high is not None:
|
|
991
994
|
rng = f"${pp.price_low:,.0f} – ${pp.price_high:,.0f}"
|
|
992
995
|
table.add_row(label, fmv, rng, signed_money(pp.price_change_7d),
|
|
993
|
-
signed_money(pp.price_change_30d), str(pp.sample_size)
|
|
996
|
+
signed_money(pp.price_change_30d), str(pp.sample_size),
|
|
997
|
+
_liquidity_text(pp.liquidity))
|
|
994
998
|
|
|
995
999
|
window = m.price_points[0].window_days
|
|
996
1000
|
console.print(heading("Market Price", f"{window}-day window, as of {m.as_of_date}"))
|
|
997
1001
|
console.print(table)
|
|
1002
|
+
if any(pp.liquidity for pp in m.price_points):
|
|
1003
|
+
console.print(
|
|
1004
|
+
" [slab.dim]Sells = how often this exact printing and grade actually sells — the "
|
|
1005
|
+
"typical gap between real sales in the last 90 days, or the past year's count for "
|
|
1006
|
+
"slow movers. A rare seller isn't bad; it means the price is an estimate you may "
|
|
1007
|
+
"wait to realize.[/]"
|
|
1008
|
+
)
|
|
998
1009
|
console.print()
|
|
999
1010
|
|
|
1000
1011
|
|
|
1012
|
+
def _liquidity_text(liq: Liquidity | None) -> str:
|
|
1013
|
+
"""A liquidity reading as one short table cell — the pace, then the datum behind it.
|
|
1014
|
+
Data statements only, mirroring the `liquidity.*` glossary."""
|
|
1015
|
+
if liq is None:
|
|
1016
|
+
return "—"
|
|
1017
|
+
L = LiquidityLabel
|
|
1018
|
+
if liq.median_days_between_sales is not None:
|
|
1019
|
+
gap = liq.median_days_between_sales
|
|
1020
|
+
word = {L.sells_daily: "~daily", L.sells_weekly: "~weekly"}.get(liq.label, "~monthly")
|
|
1021
|
+
return f"{word} (every {gap}d)" if gap > 0 else f"{word} (several/day)"
|
|
1022
|
+
if liq.label == L.too_new_to_say:
|
|
1023
|
+
return f"too new to say ({liq.observed_days}d watched)"
|
|
1024
|
+
if liq.label == L.no_recent_sales:
|
|
1025
|
+
return "none this year"
|
|
1026
|
+
return f"{liq.sales_365d}×/year"
|
|
1027
|
+
|
|
1028
|
+
|
|
1001
1029
|
def _format_label(fmt: str) -> str:
|
|
1002
1030
|
"""SealedFormat value -> display label: hobby_box -> Hobby Box."""
|
|
1003
1031
|
return fmt.replace("_", " ").title()
|
|
@@ -1142,36 +1170,41 @@ def render_comps(c: CardComps) -> None:
|
|
|
1142
1170
|
console.print()
|
|
1143
1171
|
|
|
1144
1172
|
|
|
1145
|
-
def
|
|
1146
|
-
"""
|
|
1147
|
-
|
|
1148
|
-
`grading.
|
|
1173
|
+
def _desk_reading_line(d: GradingDesk) -> str:
|
|
1174
|
+
"""Where the math lands, as one plain DATA sentence — never an instruction. slab's principle:
|
|
1175
|
+
grading is subjective and the user is holding the card, so the desk states what the prices
|
|
1176
|
+
show and leaves the send/keep decision with them. Wording mirrors the `grading.break_even`
|
|
1177
|
+
glossary entry."""
|
|
1149
1178
|
def payoff_of(grade_key: str | None):
|
|
1150
1179
|
return next((l.payoff for l in d.lanes if l.grade_key == grade_key), None)
|
|
1151
1180
|
|
|
1152
1181
|
gem = next((l for l in d.lanes if l.grade_key.startswith(f"{d.grading_company}-")), None)
|
|
1153
|
-
if d.
|
|
1182
|
+
if d.break_even == BreakEvenRegion.not_enough_data:
|
|
1154
1183
|
return ("[slab.dim]No graded market to read yet — nobody has sold graded copies of this "
|
|
1155
1184
|
"card recently. Not a no; an honest shrug.[/]")
|
|
1156
|
-
if d.
|
|
1157
|
-
top = f"even a {gem.grade_key}
|
|
1158
|
-
return f"[slab.loss]
|
|
1185
|
+
if d.break_even == BreakEvenRegion.pays_at_no_grade:
|
|
1186
|
+
top = f"even a {gem.grade_key} returns {signed_money(gem.payoff)}" if gem and gem.payoff is not None else "no grade covers the fee"
|
|
1187
|
+
return (f"[slab.loss]No grade covers the fee[/] — at today's prices {top} "
|
|
1188
|
+
f"after the {money(d.fee)} fee.")
|
|
1159
1189
|
miss_pay = payoff_of(d.miss_grade)
|
|
1160
|
-
if d.
|
|
1190
|
+
if d.break_even == BreakEvenRegion.pays_at_any_grade:
|
|
1161
1191
|
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
|
|
1163
|
-
return f"[slab.gain]
|
|
1192
|
+
if miss_pay is not None else "even a realistic miss nets more than staying raw")
|
|
1193
|
+
return (f"[slab.gain]Every grade pays[/] — at today's prices {miss_txt} "
|
|
1194
|
+
f"after the {money(d.fee)} fee.")
|
|
1164
1195
|
pct = f"{d.ten_confidence_needed:.0%}"
|
|
1165
1196
|
gem_txt = f"a {gem.grade_key} nets {signed_money(gem.payoff)}" if gem else "a gem pays"
|
|
1166
1197
|
miss_txt = (f"a {d.miss_grade} comes back {signed_money(miss_pay)}"
|
|
1167
1198
|
if miss_pay is not None else f"a miss holds roughly raw value")
|
|
1168
|
-
return (f"[slab.foil]
|
|
1169
|
-
f"{
|
|
1199
|
+
return (f"[slab.foil]Depends on the grade[/] — {gem_txt}, {miss_txt}. Break-even sits at "
|
|
1200
|
+
f"{pct}: below that chance of a gem, grading loses; above it, grading wins. "
|
|
1201
|
+
f"You can see the card; that read is yours.")
|
|
1170
1202
|
|
|
1171
1203
|
|
|
1172
1204
|
def render_grading_desk(d: GradingDesk) -> None:
|
|
1173
|
-
"""One card's grading
|
|
1174
|
-
|
|
1205
|
+
"""One card's grading math: raw baseline, payoff per graded lane, and where break-even lands.
|
|
1206
|
+
Data to decide with, never a recommendation. Every sentence traces to a `grading.*` glossary
|
|
1207
|
+
entry."""
|
|
1175
1208
|
subjects = ", ".join(d.subjects) or "—"
|
|
1176
1209
|
slot = f"{d.subset or '—'} · {d.finish}" if d.finish else (d.subset or "base")
|
|
1177
1210
|
console.print()
|
|
@@ -1196,54 +1229,60 @@ def render_grading_desk(d: GradingDesk) -> None:
|
|
|
1196
1229
|
("Sells for", {"justify": "right", "style": "slab.foil"}),
|
|
1197
1230
|
("You clear", {"justify": "right"}),
|
|
1198
1231
|
("Sales", {"justify": "right", "style": "slab.dim"}),
|
|
1232
|
+
("Sells", {"style": "slab.dim"}),
|
|
1199
1233
|
])
|
|
1200
1234
|
for l in d.lanes:
|
|
1201
1235
|
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
|
|
1236
|
+
table.add_row(l.grade_key, money(l.fair_market_value), signed_money(l.payoff), n,
|
|
1237
|
+
_liquidity_text(l.liquidity))
|
|
1203
1238
|
console.print(heading("Payoff per Grade", "after the fee, vs staying raw"))
|
|
1204
1239
|
console.print(table)
|
|
1205
1240
|
if any(l.low_confidence for l in d.lanes):
|
|
1206
1241
|
console.print(" [slab.dim]! = priced off very few sales — an anecdote, "
|
|
1207
1242
|
"not a market.[/]")
|
|
1243
|
+
if any(l.liquidity for l in d.lanes):
|
|
1244
|
+
console.print(" [slab.dim]Sells = how often that grade actually trades — a payoff "
|
|
1245
|
+
"at a grade that sells twice a year is a longer wait than one that "
|
|
1246
|
+
"sells weekly.[/]")
|
|
1208
1247
|
|
|
1209
1248
|
console.print()
|
|
1210
|
-
console.print(f" {
|
|
1211
|
-
if d.
|
|
1249
|
+
console.print(f" {_desk_reading_line(d)}")
|
|
1250
|
+
if d.break_even in (BreakEvenRegion.pays_at_any_grade, BreakEvenRegion.depends_on_grade) \
|
|
1212
1251
|
and d.ten_confidence_needed_bad_day is not None and d.bad_day_grade:
|
|
1213
1252
|
console.print(
|
|
1214
1253
|
f" [slab.dim]Cautious read: if misses come back {d.bad_day_grade} instead, "
|
|
1215
|
-
f"
|
|
1254
|
+
f"break-even moves to {d.ten_confidence_needed_bad_day:.0%}.[/]"
|
|
1216
1255
|
)
|
|
1217
1256
|
if d.thin_data:
|
|
1218
|
-
console.print(" [slab.warn]Thin data[/][slab.dim] — part of this
|
|
1257
|
+
console.print(" [slab.warn]Thin data[/][slab.dim] — part of this reading rests on "
|
|
1219
1258
|
"1-2 sales. Treat it as a hint, not a market reading.[/]")
|
|
1220
1259
|
console.print()
|
|
1221
1260
|
|
|
1222
1261
|
|
|
1223
1262
|
def render_collection_grading_desk(sweep: CollectionGradingDesk) -> None:
|
|
1224
|
-
"""The batch view: raw copies
|
|
1225
|
-
odds — graded as a group, wins at these bars pay for the losses (the rip-EV
|
|
1263
|
+
"""The batch view: raw copies with their grading math, biggest upside first. Here the per-card
|
|
1264
|
+
bars become real odds — graded as a group, wins at these bars pay for the losses (the rip-EV
|
|
1265
|
+
logic). Every cell is data; which cards to send, if any, stays the user's call."""
|
|
1226
1266
|
console.print()
|
|
1227
1267
|
covered = f"{sweep.deskable} of your {sweep.total_raw_copies} raw copies have a graded market to read"
|
|
1228
1268
|
console.print(heading("Grading Desk", covered))
|
|
1229
1269
|
if not sweep.entries:
|
|
1230
1270
|
console.print("\n [slab.dim]None of your raw cards have graded sales to compare "
|
|
1231
|
-
"against yet — no
|
|
1271
|
+
"against yet — no market to read. That's the honest answer, not an "
|
|
1232
1272
|
"error.[/]\n")
|
|
1233
1273
|
return
|
|
1234
1274
|
|
|
1235
1275
|
table = slab_table([
|
|
1236
|
-
("Verdict", {}),
|
|
1237
1276
|
("Card", {"no_wrap": False}),
|
|
1238
1277
|
("Raw", {"justify": "right", "style": "slab.dim"}),
|
|
1239
1278
|
("Gem pays", {"justify": "right"}),
|
|
1240
|
-
("
|
|
1279
|
+
("Miss pays", {"justify": "right"}),
|
|
1280
|
+
("Break-even", {"justify": "right", "style": "slab.foil"}),
|
|
1241
1281
|
("Qty", {"justify": "right", "style": "slab.dim"}),
|
|
1242
1282
|
])
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
GradingVerdict.easy_no: "[slab.loss]keep raw[/]",
|
|
1283
|
+
BREAK_EVEN_TXT = {
|
|
1284
|
+
BreakEvenRegion.pays_at_any_grade: "[slab.gain]any grade[/]",
|
|
1285
|
+
BreakEvenRegion.pays_at_no_grade: "[slab.loss]no grade[/]",
|
|
1247
1286
|
}
|
|
1248
1287
|
for e in sweep.entries:
|
|
1249
1288
|
d = e.desk
|
|
@@ -1252,23 +1291,25 @@ def render_collection_grading_desk(sweep: CollectionGradingDesk) -> None:
|
|
|
1252
1291
|
gem = next((l for l in d.lanes
|
|
1253
1292
|
if l.grade_key.startswith(f"{d.grading_company}-") and l.payoff is not None),
|
|
1254
1293
|
None)
|
|
1255
|
-
|
|
1256
|
-
if d.
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1294
|
+
miss = next((l for l in d.lanes if l.grade_key == d.miss_grade), None)
|
|
1295
|
+
if d.break_even == BreakEvenRegion.depends_on_grade and d.ten_confidence_needed is not None:
|
|
1296
|
+
be = f"{d.ten_confidence_needed:.0%} gem"
|
|
1297
|
+
else:
|
|
1298
|
+
be = BREAK_EVEN_TXT.get(d.break_even, d.break_even.value)
|
|
1260
1299
|
thin = " [slab.warn]!" if d.thin_data else ""
|
|
1261
1300
|
table.add_row(
|
|
1262
|
-
|
|
1301
|
+
card,
|
|
1263
1302
|
money(d.raw.fair_market_value) if d.raw else "—",
|
|
1264
1303
|
signed_money(gem.payoff) if gem else "[slab.dim]—[/]",
|
|
1265
|
-
|
|
1304
|
+
signed_money(miss.payoff) if miss and miss.payoff is not None else "[slab.dim]—[/]",
|
|
1305
|
+
be + thin, str(e.quantity),
|
|
1266
1306
|
)
|
|
1267
1307
|
console.print(table)
|
|
1268
1308
|
console.print(f" [slab.dim]{sweep.fee_note}[/]")
|
|
1269
|
-
console.print(" [slab.dim]
|
|
1270
|
-
"
|
|
1271
|
-
"
|
|
1309
|
+
console.print(" [slab.dim]Break-even = the chance of a gem at which grading and staying raw "
|
|
1310
|
+
"pay the same — below it grading loses, above it grading wins; 'any grade' = "
|
|
1311
|
+
"even a miss pays, 'no grade' = even a gem doesn't. You can see each card's "
|
|
1312
|
+
"corners; slab can't — the call is yours. ! = rests on very few sales.[/]")
|
|
1272
1313
|
console.print()
|
|
1273
1314
|
|
|
1274
1315
|
|
|
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
|
|
File without changes
|