:PROPERTIES: :ID: e79ad4ee-17d0-44f9-8270-2f19b65bc949 :header-args:sql: :engine postgresql :dbhost reason :dbuser tutor :dbpassword tutor :database tutor :exports both :header-args: :eval no-export :END: #+title: Tutor A [[id:cda9c620-fec5-4549-b979-22fc06819d77][Python]] application backed by a SQLite database for tracking my [[id:d5efa5bd-dac9-4ef5-b352-a2b794a37bd0][Magic: The Gathering]] card collection. https://git.phoenixinquis.net/correlr/tutor * Finding cards for a deck Decklist -- Alive -- Shared via TopDecked MTG Main (100) 1 Serra Ascendant 1 Soul's Attendant 1 Ajani's Pridemate 1 Alabaster Mage 1 Grateful Apparition 1 Suture Priest 1 Trelasarra, Moon Dancer 1 Angel of Vitality 1 Celestial Unicorn 1 Evolution Sage 1 Lurking Roper 1 Spike Feeder 1 Splendor Mare 1 Armorcraft Judge 1 Bloom Hulk 1 Core Prowler 1 Lathiel, the Bounteous Dawn 1 Lightkeeper of Emeria 1 Hamza, Guardian of Arashin 1 Ajani Goldmane 1 The Wanderer 1 Blossoming Wreath 1 Condemn 1 Fortifying Draught 1 Healing Salve 1 Light of Hope 1 Divine Offering 1 Revitalize 1 Abuna's Chant 1 Angel's Mercy 1 Blunt the Assault 1 Congregate 1 Refreshing Rain 1 War Report 1 Corrosive Gale 1 Bond of Flourishing 1 Courage in Crisis 1 Survival Cache 1 Hunters' Feast 1 Primal Command 1 Wanderer's Strike 1 Planewide Celebration 1 Fountain of Youth 1 Elixir of Immortality 1 Golden Urn 1 Sol Ring 1 Soul Net 1 The Ozolith 1 Contagion Clasp 1 Throne of Geth 1 Marble Chalice 1 Staff of Domination 1 Soul Conduit 1 Lifelink 1 Spirit Link 1 Ajani's Mantra 1 Inner Sanctum 1 Luminous Wake 1 Faith's Fetters 1 First Response 1 Vigil for the Lost 1 Noble Purpose 1 Noble Stand 1 Celestial Mantle 1 Blossoming Sands 1 Command Tower 14 Forest 1 Karn's Bastion 16 Plains 1 Razorverge Thicket 1 Sunpetal Grove 1 Yavimaya Hollow Shared via TopDecked MTG https://www.topdecked.com/decks/alive/833c308f-219f-4624-8b32-1adedcb477f0 #+caption: Adding the deck list to the database #+begin_src sql :exports code :eval never INSERT INTO decks (deck_id, name) VALUES (2, 'Alive'); INSERT INTO deck_cards (deck_id, oracle_id) SELECT DISTINCT 2, oracle_id FROM cards WHERE name IN ( 'Serra Ascendant', 'Soul''s Attendant', 'Ajani''s Pridemate', 'Alabaster Mage', 'Grateful Apparition', 'Suture Priest', 'Trelasarra, Moon Dancer', 'Angel of Vitality', 'Celestial Unicorn', 'Evolution Sage', 'Lurking Roper', 'Spike Feeder', 'Splendor Mare', 'Armorcraft Judge', 'Bloom Hulk', 'Core Prowler', 'Lathiel, the Bounteous Dawn', 'Lightkeeper of Emeria', 'Hamza, Guardian of Arashin', 'Ajani Goldmane', 'The Wanderer', 'Blossoming Wreath', 'Condemn', 'Fortifying Draught', 'Healing Salve', 'Light of Hope', 'Divine Offering', 'Revitalize', 'Abuna''s Chant', 'Angel''s Mercy', 'Blunt the Assault', 'Congregate', 'Refreshing Rain', 'War Report', 'Corrosive Gale', 'Bond of Flourishing', 'Courage in Crisis', 'Survival Cache', 'Hunters'' Feast', 'Primal Command', 'Wanderer''s Strike', 'Planewide Celebration', 'Fountain of Youth', 'Elixir of Immortality', 'Golden Urn', 'Sol Ring', 'Soul Net', 'The Ozolith', 'Contagion Clasp', 'Throne of Geth', 'Marble Chalice', 'Staff of Domination', 'Soul Conduit', 'Lifelink', 'Spirit Link', 'Ajani''s Mantra', 'Inner Sanctum', 'Luminous Wake', 'Faith''s Fetters', 'First Response', 'Vigil for the Lost', 'Noble Purpose', 'Noble Stand', 'Celestial Mantle', 'Blossoming Sands', 'Command Tower', 'Forest', 'Karn''s Bastion', 'Plains', 'Razorverge Thicket', 'Sunpetal Grove', 'Yavimaya Hollow' ); #+end_src #+caption: Finding cards in the collection #+begin_src sql :exports code :eval never select distinct set_code, color_identity, cards.name, sets.name, type_line, rarity from deck_cards join cards using(oracle_id) join copies using (scryfall_id) join rarities using (rarity) join sets using(set_code) where deck_id = 2 order by release_date desc, color_identity, rarity_ord desc, cards.name; #+end_src ** Questions - Where are the cards in my collection I need to build a deck? - Which cards do I want in one deck that are currently in another? - Which cards do I need more copies of? - Which cards do I need that would improve specific decks? ** Types : import Text -> (ValidCardNames, InvalidCardNames) : import Text -> (ValidCards, InvalidCards) : PreferredCard = PreferredCard { name: ValidCardName, card: Maybe ValidCard } : import Text -> ([PreferredCard], [InvalidCardNames]) : decklist DeckName -> ValidCardNames -> DeckList : commanderDeckList -> DeckList -> Either InvalidCommanderDeck CommanderDecklist : Deck = Deck [PreferredCard] * Collection stats #+begin_src sql SELECT COUNT("copies"."id") AS cards , SUM( CASE WHEN "copies"."isFoil" THEN "card_prices"."usd_foil" ELSE "card_prices"."usd" END ) AS value , COUNT(DISTINCT cards.set_code) AS sets FROM "copies" JOIN "cards" USING ("scryfall_id") LEFT JOIN "card_prices" USING ("scryfall_id") WHERE "card_prices"."date" = (SELECT "value" FROM "vars" where "key" = 'last_update') #+end_src #+RESULTS: | cards | value | sets | |-------+---------+------| | 12871 | 7289.44 | 161 | #+begin_src sql SELECT COUNT("copies"."id") AS cards FROM "copies" JOIN "cards" USING ("scryfall_id") LEFT JOIN "card_prices" USING ("scryfall_id") WHERE "card_prices"."date" = (SELECT "value" FROM "vars" where "key" = 'last_update') #+end_src #+RESULTS: | cards | |-------| | 12871 | #+begin_src sql select count(*) from copies #+end_src #+RESULTS: | count | |-------| | 12871 | * Identifying and ordering sets #+name: sets #+begin_src sql SELECT s.set_code , s.name , COUNT(co.id) AS cards , MIN(c.release_date) AS release_date FROM sets AS s JOIN cards AS c USING (set_code) JOIN copies AS co USING (scryfall_id) GROUP BY s.set_code, s.name ORDER BY min(c.release_date) DESC #+end_src #+RESULTS: sets | set_code | name | cards | release_date | |----------+-----------------------------------------------+-------+--------------| | TBOT | Transformers Tokens | 1 | 2022-11-18 | | BRC | The Brothers' War Commander | 209 | 2022-11-18 | | BRR | The Brothers' War Retro Artifacts | 56 | 2022-11-18 | | BOT | Transformers | 14 | 2022-11-18 | | BRO | The Brothers' War | 199 | 2022-11-18 | | UNF | Unfinity | 42 | 2022-10-07 | | DMU | Dominaria United | 219 | 2022-09-09 | | DMC | Dominaria United Commander | 35 | 2022-09-09 | | P30A | 30th Anniversary Promos | 2 | 2022-09-02 | | 2X2 | Double Masters 2022 | 459 | 2022-07-08 | | CLB | Commander Legends: Battle for Baldur's Gate | 94 | 2022-06-10 | | SNC | Streets of New Capenna | 203 | 2022-04-29 | | NCC | New Capenna Commander | 2 | 2022-04-29 | | SLX | Universes Within | 1 | 2022-03-03 | | NEO | Kamigawa: Neon Dynasty | 858 | 2022-02-18 | | NEC | Neon Dynasty Commander | 14 | 2022-02-18 | | VOC | Crimson Vow Commander | 1 | 2021-11-19 | | VOW | Innistrad: Crimson Vow | 105 | 2021-11-19 | | MID | Innistrad: Midnight Hunt | 13 | 2021-09-24 | | CMB2 | Mystery Booster Playtest Cards 2021 | 3 | 2021-08-20 | | AFR | Adventures in the Forgotten Realms | 213 | 2021-07-23 | | AFC | Forgotten Realms Commander | 70 | 2021-07-23 | | AAFR | Adventures in the Forgotten Realms Art Series | 3 | 2021-07-23 | | OAFC | Forgotten Realms Commander Display Commanders | 1 | 2021-07-23 | | PLG21 | Love Your LGS 2021 | 1 | 2021-06-22 | | MH2 | Modern Horizons 2 | 25 | 2021-06-18 | | STA | Strixhaven Mystical Archive | 29 | 2021-04-23 | | C21 | Commander 2021 | 74 | 2021-04-23 | | STX | Strixhaven: School of Mages | 369 | 2021-04-23 | | OC21 | Commander 2021 Display Commanders | 1 | 2021-04-23 | | TSTX | Strixhaven: School of Mages Tokens | 6 | 2021-04-23 | | TSR | Time Spiral Remastered | 1 | 2021-03-19 | | KHM | Kaldheim | 174 | 2021-02-05 | | TKHM | Kaldheim Tokens | 1 | 2021-02-05 | | KHC | Kaldheim Commander | 59 | 2021-02-05 | | CMR | Commander Legends | 481 | 2020-11-20 | | PLIST | The List | 8 | 2020-09-26 | | ZNR | Zendikar Rising | 54 | 2020-09-25 | | ZNC | Zendikar Rising Commander | 77 | 2020-09-25 | | JMP | Jumpstart | 1 | 2020-07-17 | | M21 | Core Set 2021 | 4 | 2020-07-03 | | PLG20 | Love Your LGS 2020 | 1 | 2020-05-18 | | IKO | Ikoria: Lair of Behemoths | 560 | 2020-04-24 | | C20 | Commander 2020 | 1 | 2020-04-17 | | THB | Theros Beyond Death | 93 | 2020-01-24 | | TTHB | Theros Beyond Death Tokens | 1 | 2020-01-24 | | SLD | Secret Lair Drop | 10 | 2019-12-16 | | MB1 | Mystery Booster | 42 | 2019-11-07 | | ELD | Throne of Eldraine | 147 | 2019-10-04 | | C19 | Commander 2019 | 101 | 2019-08-23 | | PM20 | Core Set 2020 Promos | 1 | 2019-07-12 | | M20 | Core Set 2020 | 600 | 2019-07-12 | | SS2 | Signature Spellbook: Gideon | 2 | 2019-06-28 | | TWAR | War of the Spark Tokens | 36 | 2019-05-03 | | WAR | War of the Spark | 544 | 2019-05-03 | | RNA | Ravnica Allegiance | 16 | 2019-01-25 | | PRNA | Ravnica Allegiance Promos | 1 | 2019-01-25 | | GRN | Guilds of Ravnica | 127 | 2018-10-05 | | C18 | Commander 2018 | 3 | 2018-08-09 | | M19 | Core Set 2019 | 1 | 2018-07-13 | | BBD | Battlebond | 1 | 2018-06-08 | | TDOM | Dominaria Tokens | 1 | 2018-04-27 | | DOM | Dominaria | 16 | 2018-04-27 | | RIX | Rivals of Ixalan | 29 | 2018-01-19 | | TRIX | Rivals of Ixalan Tokens | 1 | 2018-01-19 | | UST | Unstable | 119 | 2017-12-08 | | E02 | Explorers of Ixalan | 1 | 2017-11-24 | | XLN | Ixalan | 2 | 2017-09-29 | | C17 | Commander 2017 | 2 | 2017-08-25 | | THOU | Hour of Devastation Tokens | 1 | 2017-07-14 | | HOU | Hour of Devastation | 29 | 2017-07-14 | | E01 | Archenemy: Nicol Bolas | 1 | 2017-06-16 | | CMA | Commander Anthology | 1 | 2017-06-09 | | MM3 | Modern Masters 2017 | 1 | 2017-03-17 | | AER | Aether Revolt | 2 | 2017-01-20 | | C16 | Commander 2016 | 1 | 2016-11-11 | | PKLD | Kaladesh Promos | 1 | 2016-09-30 | | KLD | Kaladesh | 68 | 2016-09-30 | | EMN | Eldritch Moon | 1 | 2016-07-22 | | SOI | Shadows over Innistrad | 1 | 2016-04-08 | | C15 | Commander 2015 | 1 | 2015-11-13 | | ORI | Magic Origins | 1 | 2015-07-17 | | M15 | Magic 2015 | 44 | 2014-07-18 | | C13 | Commander 2013 | 1 | 2013-11-01 | | THS | Theros | 30 | 2013-09-27 | | M14 | Magic 2014 | 2 | 2013-07-19 | | GTC | Gatecrash | 1 | 2013-02-01 | | RTR | Return to Ravnica | 29 | 2012-10-05 | | M13 | Magic 2013 | 91 | 2012-07-13 | | AVR | Avacyn Restored | 47 | 2012-05-04 | | DKA | Dark Ascension | 52 | 2012-02-03 | | ISD | Innistrad | 354 | 2011-09-30 | | V11 | From the Vault: Legends | 1 | 2011-08-26 | | M12 | Magic 2012 | 244 | 2011-07-15 | | NPH | New Phyrexia | 244 | 2011-05-13 | | MBS | Mirrodin Besieged | 372 | 2011-02-04 | | PMBS | Mirrodin Besieged Promos | 1 | 2011-02-03 | | SOM | Scars of Mirrodin | 325 | 2010-10-01 | | PSOM | Scars of Mirrodin Promos | 1 | 2010-09-30 | | M11 | Magic 2011 | 320 | 2010-07-16 | | ROE | Rise of the Eldrazi | 329 | 2010-04-23 | | PWWK | Worldwake Promos | 2 | 2010-02-05 | | WWK | Worldwake | 149 | 2010-02-05 | | F10 | Friday Night Magic 2010 | 1 | 2010-01-01 | | ZEN | Zendikar | 76 | 2009-10-02 | | HOP | Planechase | 17 | 2009-09-04 | | M10 | Magic 2010 | 89 | 2009-07-17 | | CON | Conflux | 14 | 2009-02-06 | | PCON | Conflux Promos | 1 | 2009-01-31 | | PW09 | Wizards Play Network 2009 | 1 | 2009-01-01 | | DD2 | Duel Decks: Jace vs. Chandra | 120 | 2008-11-07 | | ALA | Shards of Alara | 22 | 2008-10-03 | | MOR | Morningtide | 1 | 2008-02-01 | | LRW | Lorwyn | 1 | 2007-10-12 | | 10E | Tenth Edition | 2 | 2007-07-13 | | PLC | Planar Chaos | 1 | 2007-02-02 | | CSP | Coldsnap | 2 | 2006-07-21 | | RAV | Ravnica: City of Guilds | 1 | 2005-10-07 | | 9ED | Ninth Edition | 1 | 2005-07-29 | | BOK | Betrayers of Kamigawa | 1 | 2005-02-04 | | CHK | Champions of Kamigawa | 5 | 2004-10-01 | | 5DN | Fifth Dawn | 46 | 2004-06-04 | | DST | Darksteel | 49 | 2004-02-06 | | MRD | Mirrodin | 102 | 2003-10-02 | | 8ED | Eighth Edition | 2 | 2003-07-28 | | SCG | Scourge | 53 | 2003-05-26 | | LGN | Legions | 37 | 2003-02-03 | | ONS | Onslaught | 32 | 2002-10-07 | | TOR | Torment | 29 | 2002-02-04 | | ODY | Odyssey | 230 | 2001-10-01 | | APC | Apocalypse | 672 | 2001-06-04 | | PAPC | Apocalypse Promos | 1 | 2001-05-26 | | 7ED | Seventh Edition | 196 | 2001-04-11 | | PLS | Planeshift | 42 | 2001-02-05 | | INV | Invasion | 141 | 2000-10-02 | | PCY | Prophecy | 32 | 2000-06-05 | | S00 | Starter 2000 | 6 | 2000-04-01 | | NEM | Nemesis | 98 | 2000-02-14 | | MMQ | Mercadian Masques | 165 | 1999-10-04 | | S99 | Starter 1999 | 3 | 1999-07-01 | | UDS | Urza's Destiny | 39 | 1999-06-07 | | 6ED | Classic Sixth Edition | 118 | 1999-04-21 | | ULG | Urza's Legacy | 38 | 1999-02-15 | | USG | Urza's Saga | 155 | 1998-10-12 | | EXO | Exodus | 21 | 1998-06-15 | | STH | Stronghold | 37 | 1998-03-02 | | TMP | Tempest | 35 | 1997-10-14 | | WTH | Weatherlight | 81 | 1997-06-09 | | 5ED | Fifth Edition | 73 | 1997-03-24 | | VIS | Visions | 4 | 1997-02-03 | | MIR | Mirage | 221 | 1996-10-08 | | ALL | Alliances | 13 | 1996-06-10 | | HML | Homelands | 45 | 1995-10-01 | | CHR | Chronicles | 2 | 1995-07-01 | | ICE | Ice Age | 116 | 1995-06-03 | | 4ED | Fourth Edition | 127 | 1995-04-01 | | FEM | Fallen Empires | 16 | 1994-11-01 | | DRK | The Dark | 3 | 1994-08-01 | | LEG | Legends | 2 | 1994-06-01 | | 3ED | Revised Edition | 43 | 1994-04-01 | | ATQ | Antiquities | 1 | 1994-03-04 | #+begin_src python :var data=sets :var filename="tutor-sets.png" :results file :exports results from datetime import datetime import matplotlib matplotlib.use('SVG') import matplotlib.pyplot as plt import pandas as pd data = [{'code': code, 'name': name, 'cards': int(cards), 'date': datetime.strptime(date, '%Y-%m-%d'), } for code, name, cards, date in data] data = pd.DataFrame(data).set_index('date').sort_index() fig, ax = plt.subplots(figsize=(14,8)) data['cumcards'] = data['cards'].cumsum() data.plot(y='cumcards', ax=ax) for date, row in data[data['cards'] > 300].iterrows(): ax.text(date, row['cumcards'], row['code']) plt.xlabel("Time") plt.ylabel("Cards") ax.get_legend().remove() plt.savefig(filename, transparent=True) return filename #+end_src #+RESULTS: [[file:tutor-sets.png]] * Identifying default cards for oracle entries #+begin_src sql SELECT oracle.oracle_id , oracle.name , scryfall.scryfall_id , scryfall.set_code , scryfall.collector_number FROM oracle JOIN ( SELECT DISTINCT ON (oracle_id) * FROM scryfall ORDER BY oracle_id, release_date DESC ) scryfall USING (oracle_id) WHERE oracle.name IN ('Rampant Growth', 'Counterspell', 'Demonic Tutor', 'Jin-Gitaxias, Progress Tyrant') #+end_src #+RESULTS: | oracle_id | name | scryfall_id | set_code | collector_number | |--------------------------------------+-------------------------------+--------------------------------------+----------+------------------| | 82004860-e589-4e38-8d61-8c0210e4ea39 | Demonic Tutor | 085ed548-a8c8-40b3-8183-51c060bc95cb | 30A | 398 | | 8539f295-5d58-4436-a73a-b9277c4c7795 | Rampant Growth | ba3026d2-334d-41bd-8c6b-7de7d78e54e0 | 40K | 220 | | cc187110-1148-4090-bbb8-e205694a39f5 | Counterspell | d14d5ff3-40c0-4f22-91ad-d6c8447cb9e0 | DMR | 281 | | f5daadc1-98ff-480a-82bb-fe7bfaa7b60e | Jin-Gitaxias, Progress Tyrant | 9a5ffb0d-1fc3-40da-ac1f-19465aa8412b | NEO | 514 |