Improve price lookup and display

This commit is contained in:
Correl Roush 2023-01-11 23:56:20 -05:00
parent 20e8c024ce
commit 10e239d65e
8 changed files with 89 additions and 42 deletions

View file

@ -149,8 +149,17 @@ SELECT "oracle"."oracle_id"
, "scryfall"."collector_number" , "scryfall"."collector_number"
, "scryfall"."release_date" , "scryfall"."release_date"
, "scryfall"."rarity" , "scryfall"."rarity"
, "sets"."name" AS "set_name"
, "card_prices"."usd" AS "price_usd"
, "card_prices"."usd_foil" AS "price_usd_foil"
, "card_prices"."eur" AS "price_eur"
, "card_prices"."eur_foil" AS "price_eur_foil"
, "card_prices"."tix" AS "price_tix"
FROM "oracle" FROM "oracle"
JOIN "scryfall" USING ("oracle_id"); JOIN "scryfall" USING ("oracle_id")
JOIN "card_prices" ON ("scryfall"."scryfall_id" = "card_prices"."scryfall_id"
AND "card_prices"."date" = (SELECT VALUE FROM "vars" WHERE "key" = 'last_update'))
JOIN "sets" USING ("set_code");
DROP MATERIALIZED VIEW IF EXISTS "oracle_latest"; DROP MATERIALIZED VIEW IF EXISTS "oracle_latest";
CREATE MATERIALIZED VIEW "oracle_latest" AS CREATE MATERIALIZED VIEW "oracle_latest" AS
@ -167,9 +176,18 @@ SELECT "oracle"."oracle_id"
, "scryfall"."collector_number" , "scryfall"."collector_number"
, "scryfall"."release_date" , "scryfall"."release_date"
, "scryfall"."rarity" , "scryfall"."rarity"
, "sets"."name" AS "set_name"
, "card_prices"."usd" AS "price_usd"
, "card_prices"."usd_foil" AS "price_usd_foil"
, "card_prices"."eur" AS "price_eur"
, "card_prices"."eur_foil" AS "price_eur_foil"
, "card_prices"."tix" AS "price_tix"
FROM "oracle" FROM "oracle"
JOIN ( JOIN (
SELECT DISTINCT ON ("oracle_id") * SELECT DISTINCT ON ("oracle_id") *
FROM "scryfall" FROM "scryfall"
ORDER BY "oracle_id", "release_date" DESC ORDER BY "oracle_id", "release_date" DESC
) "scryfall" USING ("oracle_id"); ) "scryfall" USING ("oracle_id")
JOIN "card_prices" ON ("scryfall"."scryfall_id" = "card_prices"."scryfall_id"
AND "card_prices"."date" = (SELECT VALUE FROM "vars" WHERE "key" = 'last_update'))
JOIN "sets" USING ("set_code");

View file

@ -16,6 +16,13 @@ import tutor.search
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def convert_price(price: typing.Optional[str]) -> typing.Optional[decimal.Decimal]:
if price:
return decimal.Decimal(price)
else:
return None
async def search( async def search(
db: psycopg.Cursor, db: psycopg.Cursor,
name: typing.Optional[str] = None, name: typing.Optional[str] = None,
@ -171,10 +178,6 @@ async def advanced_search(
else: else:
joins.append("LEFT JOIN copies ON (cards.scryfall_id = copies.scryfall_id)") joins.append("LEFT JOIN copies ON (cards.scryfall_id = copies.scryfall_id)")
joins.append("JOIN sets ON (cards.set_code = sets.set_code)") joins.append("JOIN sets ON (cards.set_code = sets.set_code)")
joins.append(
"JOIN card_prices ON (cards.scryfall_id = card_prices.scryfall_id "
"AND card_prices.date = (select value from vars where key = %(last_update_key)s))"
)
orderings = [ orderings = [
"cards.rarity DESC", "cards.rarity DESC",
"length(cards.color_identity) DESC", "length(cards.color_identity) DESC",
@ -185,15 +188,15 @@ async def advanced_search(
] ]
if sort_by == "price": if sort_by == "price":
orderings = [ orderings = [
'CAST(COALESCE(CASE WHEN "copies"."isFoil" THEN card_prices.usd_foil ELSE card_prices.usd END, 0) as decimal) DESC', 'CAST(COALESCE(CASE WHEN "copies"."isFoil" THEN cards.price_usd_foil ELSE cards.price_usd END, 0) as decimal) DESC',
*orderings, *orderings,
] ]
params["last_update_key"] = "last_update" params["last_update_key"] = "last_update"
query = " ".join( query = " ".join(
[ [
"SELECT cards.*, card_prices.*, copies.*", "SELECT cards.*, copies.*",
', CASE WHEN "copies"."isFoil" THEN card_prices.usd_foil', ', CASE WHEN "copies"."isFoil" THEN cards.price_usd_foil',
" ELSE card_prices.usd END AS usd", " ELSE cards.price_usd END AS usd",
"FROM cards", "FROM cards",
" ".join(joins), " ".join(joins),
"WHERE" if constraints else "", "WHERE" if constraints else "",
@ -207,12 +210,6 @@ async def advanced_search(
await db.execute(query, params) await db.execute(query, params)
rows = await db.fetchall() rows = await db.fetchall()
def convert_price(price: typing.Optional[str]) -> typing.Optional[decimal.Decimal]:
if price:
return decimal.Decimal(price)
else:
return None
return [ return [
tutor.models.CardCopy( tutor.models.CardCopy(
card=tutor.models.Card( card=tutor.models.Card(
@ -220,6 +217,7 @@ async def advanced_search(
oracle_id=row["oracle_id"], oracle_id=row["oracle_id"],
name=row["name"], name=row["name"],
set_code=row["set_code"], set_code=row["set_code"],
set_name=row["set_name"],
collector_number=row["collector_number"], collector_number=row["collector_number"],
rarity=tutor.models.Rarity.from_string(row["rarity"]), rarity=tutor.models.Rarity.from_string(row["rarity"]),
color_identity=tutor.models.Color.from_string(row["color_identity"]), color_identity=tutor.models.Color.from_string(row["color_identity"]),
@ -231,11 +229,11 @@ async def advanced_search(
legalities={}, legalities={},
edhrec_rank=row["edhrec_rank"], edhrec_rank=row["edhrec_rank"],
oracle_text=row["oracle_text"], oracle_text=row["oracle_text"],
price_usd=convert_price(row["usd"]), price_usd=convert_price(row["price_usd"]),
price_usd_foil=convert_price(row["usd_foil"]), price_usd_foil=convert_price(row["price_usd_foil"]),
price_eur=convert_price(row["eur"]), price_eur=convert_price(row["price_eur"]),
price_eur_foil=convert_price(row["eur_foil"]), price_eur_foil=convert_price(row["price_eur_foil"]),
price_tix=convert_price(row["tix"]), price_tix=convert_price(row["price_tix"]),
), ),
foil=row["isFoil"] if row["isFoil"] is not None else False, foil=row["isFoil"] if row["isFoil"] is not None else False,
collection=row["collection"] or "Default", collection=row["collection"] or "Default",
@ -380,9 +378,15 @@ async def get_decks(
'oracle_text', "oracle_latest"."oracle_text", 'oracle_text', "oracle_latest"."oracle_text",
'scryfall_id', "oracle_latest"."scryfall_id", 'scryfall_id', "oracle_latest"."scryfall_id",
'set_code', "oracle_latest"."set_code", 'set_code', "oracle_latest"."set_code",
'set_name', "oracle_latest"."set_name",
'collector_number', "oracle_latest"."collector_number", 'collector_number', "oracle_latest"."collector_number",
'rarity', "oracle_latest"."rarity", 'rarity', "oracle_latest"."rarity",
'release_date', "oracle_latest"."release_date", 'release_date', "oracle_latest"."release_date",
'price_usd', "oracle_latest"."price_usd",
'price_usd_foil', "oracle_latest"."price_usd_foil",
'price_eur', "oracle_latest"."price_eur",
'price_eur_foil', "oracle_latest"."price_eur_foil",
'price_tix', "oracle_latest"."price_tix",
'quantity', "deck_list"."quantity" 'quantity', "deck_list"."quantity"
))) AS "cards" ))) AS "cards"
FROM "decks" FROM "decks"
@ -417,9 +421,15 @@ async def get_decks(
oracle_text=card.get("oracle_text"), oracle_text=card.get("oracle_text"),
scryfall_id=card["scryfall_id"], scryfall_id=card["scryfall_id"],
set_code=card["set_code"], set_code=card["set_code"],
set_name=card["set_name"],
collector_number=card["collector_number"], collector_number=card["collector_number"],
rarity=card["rarity"], rarity=card["rarity"],
release_date=card["release_date"], release_date=card["release_date"],
price_usd=convert_price(card.get("price_usd")),
price_usd_foil=convert_price(card.get("price_usd_foil")),
price_eur=convert_price(card.get("price_eur")),
price_eur_foil=convert_price(card.get("price_eur_foil")),
price_tix=convert_price(card.get("price_tix")),
), ),
quantity=card["quantity"], quantity=card["quantity"],
) )
@ -450,9 +460,15 @@ async def get_deck(
'oracle_text', "oracle_latest"."oracle_text", 'oracle_text', "oracle_latest"."oracle_text",
'scryfall_id', "oracle_latest"."scryfall_id", 'scryfall_id', "oracle_latest"."scryfall_id",
'set_code', "oracle_latest"."set_code", 'set_code', "oracle_latest"."set_code",
'set_name', "oracle_latest"."set_name",
'collector_number', "oracle_latest"."collector_number", 'collector_number', "oracle_latest"."collector_number",
'rarity', "oracle_latest"."rarity", 'rarity', "oracle_latest"."rarity",
'release_date', "oracle_latest"."release_date", 'release_date', "oracle_latest"."release_date",
'price_usd', "oracle_latest"."price_usd",
'price_usd_foil', "oracle_latest"."price_usd_foil",
'price_eur', "oracle_latest"."price_eur",
'price_eur_foil', "oracle_latest"."price_eur_foil",
'price_tix', "oracle_latest"."price_tix",
'quantity', "deck_list"."quantity" 'quantity', "deck_list"."quantity"
))) AS "cards" ))) AS "cards"
FROM "decks" FROM "decks"
@ -486,9 +502,15 @@ async def get_deck(
oracle_text=card.get("oracle_text"), oracle_text=card.get("oracle_text"),
scryfall_id=card["scryfall_id"], scryfall_id=card["scryfall_id"],
set_code=card["set_code"], set_code=card["set_code"],
set_name=card["set_name"],
collector_number=card["collector_number"], collector_number=card["collector_number"],
rarity=card["rarity"], rarity=card["rarity"],
release_date=card["release_date"], release_date=card["release_date"],
price_usd=convert_price(card.get("price_usd")),
price_usd_foil=convert_price(card.get("price_usd_foil")),
price_eur=convert_price(card.get("price_eur")),
price_eur_foil=convert_price(card.get("price_eur_foil")),
price_tix=convert_price(card.get("price_tix")),
), ),
quantity=card["quantity"], quantity=card["quantity"],
) )
@ -517,16 +539,13 @@ async def collection_stats(db: psycopg.Cursor) -> dict:
SELECT COUNT("copies"."id") AS cards SELECT COUNT("copies"."id") AS cards
, SUM( , SUM(
CASE WHEN "copies"."isFoil" CASE WHEN "copies"."isFoil"
THEN "card_prices"."usd_foil" THEN "cards"."price_usd_foil"
ELSE "card_prices"."usd" ELSE "cards"."price_usd"
END END
) AS value ) AS value
, COUNT(DISTINCT cards.set_code) AS sets , COUNT(DISTINCT cards.set_code) AS sets
FROM "copies" FROM "copies"
JOIN "cards" USING ("scryfall_id") JOIN "cards" USING ("scryfall_id")
LEFT JOIN "card_prices" USING ("scryfall_id")
WHERE "card_prices"."date" =
(SELECT "value" FROM "vars" where "key" = 'last_update')
""" """
) )
return await db.fetchone() return await db.fetchone()

View file

@ -82,6 +82,7 @@ class Card:
oracle_id: uuid.UUID oracle_id: uuid.UUID
name: str name: str
set_code: str set_code: str
set_name: str
collector_number: str collector_number: str
rarity: str rarity: str
color_identity: typing.List[Color] color_identity: typing.List[Color]

View file

@ -65,9 +65,9 @@ class JSONEncoder(json.JSONEncoder):
} }
def _card(self, card: tutor.models.Card) -> dict: def _card(self, card: tutor.models.Card) -> dict:
def price(amount: typing.Optional[decimal.Decimal]) -> typing.Optional[str]: def price(amount: typing.Optional[decimal.Decimal]) -> typing.Optional[float]:
if amount is not None: if amount is not None:
return str(amount) return float(amount)
else: else:
return None return None
@ -75,6 +75,7 @@ class JSONEncoder(json.JSONEncoder):
"scryfall_id": str(card.scryfall_id), "scryfall_id": str(card.scryfall_id),
"name": card.name, "name": card.name,
"set_code": card.set_code, "set_code": card.set_code,
"set_name": card.set_name,
"collector_number": card.collector_number, "collector_number": card.collector_number,
"rarity": str(card.rarity), "rarity": str(card.rarity),
"color_identity": tutor.models.Color.to_string(card.color_identity), "color_identity": tutor.models.Color.to_string(card.color_identity),

View file

@ -18,7 +18,8 @@
"elm/regex": "1.0.0", "elm/regex": "1.0.0",
"elm/url": "1.0.0", "elm/url": "1.0.0",
"elm-community/maybe-extra": "5.2.0", "elm-community/maybe-extra": "5.2.0",
"mdgriffith/elm-ui": "1.1.8" "mdgriffith/elm-ui": "1.1.8",
"myrho/elm-round": "1.0.5"
}, },
"indirect": { "indirect": {
"elm/bytes": "1.0.8", "elm/bytes": "1.0.8",

View file

@ -5,11 +5,11 @@ import Json.Decode.Pipeline as JDP
type alias Prices = type alias Prices =
{ usd : Maybe String { usd : Maybe Float
, usd_foil : Maybe String , usd_foil : Maybe Float
, eur : Maybe String , eur : Maybe Float
, eur_foil : Maybe String , eur_foil : Maybe Float
, tix : Maybe String , tix : Maybe Float
} }
@ -91,8 +91,8 @@ decodeCopy =
decodePrices : Json.Decode.Decoder Prices decodePrices : Json.Decode.Decoder Prices
decodePrices = decodePrices =
Json.Decode.succeed Prices Json.Decode.succeed Prices
|> JDP.required "usd" (Json.Decode.nullable Json.Decode.string) |> JDP.required "usd" (Json.Decode.nullable Json.Decode.float)
|> JDP.required "usd_foil" (Json.Decode.nullable Json.Decode.string) |> JDP.required "usd_foil" (Json.Decode.nullable Json.Decode.float)
|> JDP.required "eur" (Json.Decode.nullable Json.Decode.string) |> JDP.required "eur" (Json.Decode.nullable Json.Decode.float)
|> JDP.required "eur_foil" (Json.Decode.nullable Json.Decode.string) |> JDP.required "eur_foil" (Json.Decode.nullable Json.Decode.float)
|> JDP.required "tix" (Json.Decode.nullable Json.Decode.string) |> JDP.required "tix" (Json.Decode.nullable Json.Decode.float)

View file

@ -495,7 +495,13 @@ viewCardBrowser model =
[] []
) )
in in
UI.cardRow { foil = copy.foil, subtitle = copy.collection } cardAttrs model.symbols copy.card UI.cardRow
{ foil = copy.foil
, subtitle = String.join " " [ copy.collection ]
}
cardAttrs
model.symbols
copy.card
in in
E.column attrs E.column attrs
[ E.row [ E.row

View file

@ -20,6 +20,7 @@ import Element.Background as Background
import Element.Border as Border import Element.Border as Border
import Element.Font as Font import Element.Font as Font
import Maybe.Extra import Maybe.Extra
import Round
import Spinner import Spinner
import Symbol import Symbol
import Task import Task
@ -183,7 +184,7 @@ subtitle string =
E.el [ Font.size 16, Font.italic, Font.color colors.subtitle ] <| E.text string E.el [ Font.size 16, Font.italic, Font.color colors.subtitle ] <| E.text string
priceBadge : { currency : String, amount : String } -> E.Element msg priceBadge : { currency : String, amount : Float } -> E.Element msg
priceBadge { currency, amount } = priceBadge { currency, amount } =
E.el E.el
[ Border.rounded 5 [ Border.rounded 5
@ -196,7 +197,7 @@ priceBadge { currency, amount } =
<| <|
E.row [ E.width E.fill ] E.row [ E.width E.fill ]
[ E.el [ E.width <| E.fillPortion 1 ] <| E.text <| String.toUpper currency [ E.el [ E.width <| E.fillPortion 1 ] <| E.text <| String.toUpper currency
, E.el [ E.width <| E.fillPortion 2, Font.alignRight ] <| E.text amount , E.el [ E.width <| E.fillPortion 2, Font.alignRight ] <| E.text <| Round.round 2 amount
] ]