Compare commits

...

10 Commits

Author SHA1 Message Date
dca11b0ede asdf 2025-05-30 17:34:40 -04:00
f2c2b69d63 asdf 2025-05-30 17:31:59 -04:00
5c85411c69 we are so back 2025-05-05 14:05:12 -04:00
11aa4cda16 pricing 2025-04-29 00:04:29 -04:00
c9bba8a26e PRICING 2025-04-29 00:00:47 -04:00
d75e20ff2c Delete app.log 2025-04-25 00:03:39 -04:00
56ba750aad i think most of this works lole 2025-04-24 23:34:13 -04:00
210a033695 more inventory management work 2025-04-22 16:44:47 -04:00
d8ae45c025 kinda doesnt work yet 2025-04-20 00:32:25 -04:00
34eac3d954 god help me 2025-04-19 22:54:07 -04:00
2921 changed files with 6047 additions and 4794 deletions

View File

@ -67,7 +67,8 @@ def run_migrations_online() -> None:
with connectable.connect() as connection:
context.configure(
connection=connection, target_metadata=target_metadata
connection=connection,
target_metadata=target_metadata,
)
with context.begin_transaction():

View File

@ -0,0 +1,36 @@
"""i literally hate sql
Revision ID: 0f534237fc90
Revises: cf61f006db46
Create Date: 2025-04-25 16:59:07.177958
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '0f534237fc90'
down_revision: Union[str, None] = 'cf61f006db46'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint('uq_sku_mtgjson_uuid', 'mtgjson_skus', type_='unique')
op.drop_index('ix_mtgjson_skus_mtgjson_uuid', table_name='mtgjson_skus')
op.create_index(op.f('ix_mtgjson_skus_mtgjson_uuid'), 'mtgjson_skus', ['mtgjson_uuid'], unique=False)
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_mtgjson_skus_mtgjson_uuid'), table_name='mtgjson_skus')
op.create_index('ix_mtgjson_skus_mtgjson_uuid', 'mtgjson_skus', ['mtgjson_uuid'], unique=True)
op.create_unique_constraint('uq_sku_mtgjson_uuid', 'mtgjson_skus', ['mtgjson_uuid'])
# ### end Alembic commands ###

View File

@ -1,42 +0,0 @@
"""tcg product update again
Revision ID: 1746d35187a2
Revises: 9775314e337b
Create Date: 2025-04-17 22:02:35.492726
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '1746d35187a2'
down_revision: Union[str, None] = '9775314e337b'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('tcgplayer_products', sa.Column('ext_subtype', sa.String(), nullable=True))
op.add_column('tcgplayer_products', sa.Column('ext_oracle_text', sa.String(), nullable=True))
op.add_column('tcgplayer_products', sa.Column('ext_flavor_text', sa.String(), nullable=True))
op.drop_column('tcgplayer_products', 'ext_mana_cost')
op.drop_column('tcgplayer_products', 'ext_loyalty')
op.drop_column('tcgplayer_products', 'ext_mana_value')
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('tcgplayer_products', sa.Column('ext_mana_value', sa.VARCHAR(), autoincrement=False, nullable=True))
op.add_column('tcgplayer_products', sa.Column('ext_loyalty', sa.VARCHAR(), autoincrement=False, nullable=True))
op.add_column('tcgplayer_products', sa.Column('ext_mana_cost', sa.VARCHAR(), autoincrement=False, nullable=True))
op.drop_column('tcgplayer_products', 'ext_flavor_text')
op.drop_column('tcgplayer_products', 'ext_oracle_text')
op.drop_column('tcgplayer_products', 'ext_subtype')
# ### end Alembic commands ###

View File

@ -0,0 +1,40 @@
"""asdf
Revision ID: 236605bcac6e
Revises: d13600612a8f
Create Date: 2025-04-28 21:44:28.030202
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '236605bcac6e'
down_revision: Union[str, None] = 'd13600612a8f'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('marketplace_listings', sa.Column('recommended_price_id', sa.Integer(), nullable=True))
op.add_column('marketplace_listings', sa.Column('listed_price_id', sa.Integer(), nullable=True))
op.create_foreign_key(None, 'marketplace_listings', 'pricing_events', ['recommended_price_id'], ['id'])
op.create_foreign_key(None, 'marketplace_listings', 'pricing_events', ['listed_price_id'], ['id'])
op.drop_column('marketplace_listings', 'listed_price')
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('marketplace_listings', sa.Column('listed_price', sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=True))
op.drop_constraint(None, 'marketplace_listings', type_='foreignkey')
op.drop_constraint(None, 'marketplace_listings', type_='foreignkey')
op.drop_column('marketplace_listings', 'listed_price_id')
op.drop_column('marketplace_listings', 'recommended_price_id')
# ### end Alembic commands ###

View File

@ -1,32 +0,0 @@
"""tcg prices again
Revision ID: 2fcce9c8883a
Revises: b45c43900b56
Create Date: 2025-04-17 22:48:53.378544
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '2fcce9c8883a'
down_revision: Union[str, None] = 'b45c43900b56'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###

View File

@ -1,369 +0,0 @@
"""i hate alembic so goddamn much
Revision ID: 479003fbead7
Revises:
Create Date: 2025-04-17 12:08:13.714276
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '479003fbead7'
down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('cards',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(), nullable=True),
sa.Column('rarity', sa.String(), nullable=True),
sa.Column('set_name', sa.String(), nullable=True),
sa.Column('price', sa.Float(), nullable=True),
sa.Column('quantity', sa.Integer(), nullable=True),
sa.Column('tcgplayer_sku', sa.String(), nullable=True),
sa.Column('product_line', sa.String(), nullable=True),
sa.Column('product_name', sa.String(), nullable=True),
sa.Column('title', sa.String(), nullable=True),
sa.Column('number', sa.String(), nullable=True),
sa.Column('condition', sa.String(), nullable=True),
sa.Column('tcg_market_price', sa.Float(), nullable=True),
sa.Column('tcg_direct_low', sa.Float(), nullable=True),
sa.Column('tcg_low_price_with_shipping', sa.Float(), nullable=True),
sa.Column('tcg_low_price', sa.Float(), nullable=True),
sa.Column('total_quantity', sa.Integer(), nullable=True),
sa.Column('add_to_quantity', sa.Integer(), nullable=True),
sa.Column('tcg_marketplace_price', sa.Float(), nullable=True),
sa.Column('photo_url', sa.String(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_cards_id'), 'cards', ['id'], unique=False)
op.create_index(op.f('ix_cards_name'), 'cards', ['name'], unique=False)
op.create_index(op.f('ix_cards_set_name'), 'cards', ['set_name'], unique=False)
op.create_index(op.f('ix_cards_tcgplayer_sku'), 'cards', ['tcgplayer_sku'], unique=True)
op.create_table('files',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(), nullable=True),
sa.Column('file_type', sa.String(), nullable=True),
sa.Column('content_type', sa.String(), nullable=True),
sa.Column('path', sa.String(), nullable=True),
sa.Column('size', sa.Integer(), nullable=True),
sa.Column('file_metadata', sa.JSON(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_files_id'), 'files', ['id'], unique=False)
op.create_table('games',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(), nullable=True),
sa.Column('description', sa.String(), nullable=True),
sa.Column('image_url', sa.String(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_games_id'), 'games', ['id'], unique=False)
op.create_table('inventory',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('tcgplayer_id', sa.String(), nullable=True),
sa.Column('product_line', sa.String(), nullable=True),
sa.Column('set_name', sa.String(), nullable=True),
sa.Column('product_name', sa.String(), nullable=True),
sa.Column('title', sa.String(), nullable=True),
sa.Column('number', sa.String(), nullable=True),
sa.Column('rarity', sa.String(), nullable=True),
sa.Column('condition', sa.String(), nullable=True),
sa.Column('tcg_market_price', sa.Float(), nullable=True),
sa.Column('tcg_direct_low', sa.Float(), nullable=True),
sa.Column('tcg_low_price_with_shipping', sa.Float(), nullable=True),
sa.Column('tcg_low_price', sa.Float(), nullable=True),
sa.Column('total_quantity', sa.Integer(), nullable=True),
sa.Column('add_to_quantity', sa.Integer(), nullable=True),
sa.Column('tcg_marketplace_price', sa.Float(), nullable=True),
sa.Column('photo_url', sa.String(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_inventory_id'), 'inventory', ['id'], unique=False)
op.create_index(op.f('ix_inventory_tcgplayer_id'), 'inventory', ['tcgplayer_id'], unique=True)
op.create_table('mtgjson_cards',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('card_id', sa.String(), nullable=True),
sa.Column('name', sa.String(), nullable=True),
sa.Column('set_code', sa.String(), nullable=True),
sa.Column('uuid', sa.String(), nullable=True),
sa.Column('abu_id', sa.String(), nullable=True),
sa.Column('card_kingdom_etched_id', sa.String(), nullable=True),
sa.Column('card_kingdom_foil_id', sa.String(), nullable=True),
sa.Column('card_kingdom_id', sa.String(), nullable=True),
sa.Column('cardsphere_id', sa.String(), nullable=True),
sa.Column('cardsphere_foil_id', sa.String(), nullable=True),
sa.Column('cardtrader_id', sa.String(), nullable=True),
sa.Column('csi_id', sa.String(), nullable=True),
sa.Column('mcm_id', sa.String(), nullable=True),
sa.Column('mcm_meta_id', sa.String(), nullable=True),
sa.Column('miniaturemarket_id', sa.String(), nullable=True),
sa.Column('mtg_arena_id', sa.String(), nullable=True),
sa.Column('mtgjson_foil_version_id', sa.String(), nullable=True),
sa.Column('mtgjson_non_foil_version_id', sa.String(), nullable=True),
sa.Column('mtgjson_v4_id', sa.String(), nullable=True),
sa.Column('mtgo_foil_id', sa.String(), nullable=True),
sa.Column('mtgo_id', sa.String(), nullable=True),
sa.Column('multiverse_id', sa.String(), nullable=True),
sa.Column('scg_id', sa.String(), nullable=True),
sa.Column('scryfall_id', sa.String(), nullable=True),
sa.Column('scryfall_card_back_id', sa.String(), nullable=True),
sa.Column('scryfall_oracle_id', sa.String(), nullable=True),
sa.Column('scryfall_illustration_id', sa.String(), nullable=True),
sa.Column('tcgplayer_product_id', sa.String(), nullable=True),
sa.Column('tcgplayer_etched_product_id', sa.String(), nullable=True),
sa.Column('tnt_id', sa.String(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_mtgjson_cards_card_id'), 'mtgjson_cards', ['card_id'], unique=True)
op.create_index(op.f('ix_mtgjson_cards_id'), 'mtgjson_cards', ['id'], unique=False)
op.create_table('mtgjson_skus',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('sku_id', sa.String(), nullable=True),
sa.Column('product_id', sa.String(), nullable=True),
sa.Column('condition', sa.String(), nullable=True),
sa.Column('finish', sa.String(), nullable=True),
sa.Column('language', sa.String(), nullable=True),
sa.Column('printing', sa.String(), nullable=True),
sa.Column('card_id', sa.String(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_mtgjson_skus_id'), 'mtgjson_skus', ['id'], unique=False)
op.create_index(op.f('ix_mtgjson_skus_product_id'), 'mtgjson_skus', ['product_id'], unique=False)
op.create_index(op.f('ix_mtgjson_skus_sku_id'), 'mtgjson_skus', ['sku_id'], unique=False)
op.create_table('tcgplayer_categories',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('category_id', sa.Integer(), nullable=True),
sa.Column('name', sa.String(), nullable=False),
sa.Column('display_name', sa.String(), nullable=True),
sa.Column('seo_category_name', sa.String(), nullable=True),
sa.Column('category_description', sa.String(), nullable=True),
sa.Column('category_page_title', sa.String(), nullable=True),
sa.Column('sealed_label', sa.String(), nullable=True),
sa.Column('non_sealed_label', sa.String(), nullable=True),
sa.Column('condition_guide_url', sa.String(), nullable=True),
sa.Column('is_scannable', sa.Boolean(), nullable=True),
sa.Column('popularity', sa.Integer(), nullable=True),
sa.Column('is_direct', sa.Boolean(), nullable=True),
sa.Column('modified_on', sa.DateTime(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_tcgplayer_categories_category_id'), 'tcgplayer_categories', ['category_id'], unique=True)
op.create_index(op.f('ix_tcgplayer_categories_id'), 'tcgplayer_categories', ['id'], unique=False)
op.create_table('tcgplayer_groups',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('group_id', sa.Integer(), nullable=True),
sa.Column('name', sa.String(), nullable=False),
sa.Column('abbreviation', sa.String(), nullable=True),
sa.Column('is_supplemental', sa.Boolean(), nullable=True),
sa.Column('published_on', sa.DateTime(), nullable=True),
sa.Column('modified_on', sa.DateTime(), nullable=True),
sa.Column('category_id', sa.Integer(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_tcgplayer_groups_group_id'), 'tcgplayer_groups', ['group_id'], unique=True)
op.create_index(op.f('ix_tcgplayer_groups_id'), 'tcgplayer_groups', ['id'], unique=False)
op.create_table('tcgplayer_order_products',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('order_number', sa.String(), nullable=True),
sa.Column('product_name', sa.String(), nullable=True),
sa.Column('unit_price', sa.Float(), nullable=True),
sa.Column('extended_price', sa.Float(), nullable=True),
sa.Column('quantity', sa.Integer(), nullable=True),
sa.Column('url', sa.String(), nullable=True),
sa.Column('product_id', sa.String(), nullable=True),
sa.Column('sku_id', sa.String(), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_tcgplayer_order_products_id'), 'tcgplayer_order_products', ['id'], unique=False)
op.create_index(op.f('ix_tcgplayer_order_products_order_number'), 'tcgplayer_order_products', ['order_number'], unique=False)
op.create_table('tcgplayer_order_refunds',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('order_number', sa.String(), nullable=True),
sa.Column('refund_created_at', sa.DateTime(), nullable=True),
sa.Column('type', sa.String(), nullable=True),
sa.Column('amount', sa.Float(), nullable=True),
sa.Column('description', sa.String(), nullable=True),
sa.Column('origin', sa.String(), nullable=True),
sa.Column('shipping_amount', sa.Float(), nullable=True),
sa.Column('products', sa.JSON(), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_tcgplayer_order_refunds_id'), 'tcgplayer_order_refunds', ['id'], unique=False)
op.create_index(op.f('ix_tcgplayer_order_refunds_order_number'), 'tcgplayer_order_refunds', ['order_number'], unique=False)
op.create_table('tcgplayer_order_transactions',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('order_number', sa.String(), nullable=True),
sa.Column('product_amount', sa.Float(), nullable=True),
sa.Column('shipping_amount', sa.Float(), nullable=True),
sa.Column('gross_amount', sa.Float(), nullable=True),
sa.Column('fee_amount', sa.Float(), nullable=True),
sa.Column('net_amount', sa.Float(), nullable=True),
sa.Column('direct_fee_amount', sa.Float(), nullable=True),
sa.Column('taxes', sa.JSON(), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_tcgplayer_order_transactions_id'), 'tcgplayer_order_transactions', ['id'], unique=False)
op.create_index(op.f('ix_tcgplayer_order_transactions_order_number'), 'tcgplayer_order_transactions', ['order_number'], unique=False)
op.create_table('tcgplayer_orders',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('order_number', sa.String(), nullable=True),
sa.Column('order_created_at', sa.DateTime(), nullable=True),
sa.Column('status', sa.String(), nullable=True),
sa.Column('channel', sa.String(), nullable=True),
sa.Column('fulfillment', sa.String(), nullable=True),
sa.Column('seller_name', sa.String(), nullable=True),
sa.Column('buyer_name', sa.String(), nullable=True),
sa.Column('payment_type', sa.String(), nullable=True),
sa.Column('pickup_status', sa.String(), nullable=True),
sa.Column('shipping_type', sa.String(), nullable=True),
sa.Column('estimated_delivery_date', sa.DateTime(), nullable=True),
sa.Column('recipient_name', sa.String(), nullable=True),
sa.Column('address_line_1', sa.String(), nullable=True),
sa.Column('address_line_2', sa.String(), nullable=True),
sa.Column('city', sa.String(), nullable=True),
sa.Column('state', sa.String(), nullable=True),
sa.Column('zip_code', sa.String(), nullable=True),
sa.Column('country', sa.String(), nullable=True),
sa.Column('tracking_numbers', sa.JSON(), nullable=True),
sa.Column('created_at', sa.DateTime(), nullable=True),
sa.Column('updated_at', sa.DateTime(), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_tcgplayer_orders_id'), 'tcgplayer_orders', ['id'], unique=False)
op.create_index(op.f('ix_tcgplayer_orders_order_number'), 'tcgplayer_orders', ['order_number'], unique=False)
op.create_table('boxes',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('product_id', sa.Integer(), nullable=True),
sa.Column('type', sa.String(), nullable=True),
sa.Column('set_code', sa.String(), nullable=True),
sa.Column('sku', sa.Integer(), nullable=True),
sa.Column('name', sa.String(), nullable=True),
sa.Column('game_id', sa.Integer(), nullable=True),
sa.Column('expected_number_of_cards', sa.Integer(), nullable=True),
sa.Column('description', sa.String(), nullable=True),
sa.Column('image_url', sa.String(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
sa.ForeignKeyConstraint(['game_id'], ['games.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_boxes_id'), 'boxes', ['id'], unique=False)
op.create_table('tcgplayer_products',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('product_id', sa.Integer(), nullable=True),
sa.Column('name', sa.String(), nullable=False),
sa.Column('clean_name', sa.String(), nullable=True),
sa.Column('image_url', sa.String(), nullable=True),
sa.Column('category_id', sa.Integer(), nullable=True),
sa.Column('group_id', sa.Integer(), nullable=True),
sa.Column('url', sa.String(), nullable=True),
sa.Column('modified_on', sa.DateTime(), nullable=True),
sa.Column('image_count', sa.Integer(), nullable=True),
sa.Column('ext_rarity', sa.String(), nullable=True),
sa.Column('ext_number', sa.String(), nullable=True),
sa.Column('low_price', sa.Float(), nullable=True),
sa.Column('mid_price', sa.Float(), nullable=True),
sa.Column('high_price', sa.Float(), nullable=True),
sa.Column('market_price', sa.Float(), nullable=True),
sa.Column('direct_low_price', sa.Float(), nullable=True),
sa.Column('sub_type_name', sa.String(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
sa.ForeignKeyConstraint(['group_id'], ['tcgplayer_groups.group_id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_tcgplayer_products_id'), 'tcgplayer_products', ['id'], unique=False)
op.create_index(op.f('ix_tcgplayer_products_product_id'), 'tcgplayer_products', ['product_id'], unique=False)
op.create_table('open_boxes',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('box_id', sa.Integer(), nullable=True),
sa.Column('number_of_cards', sa.Integer(), nullable=True),
sa.Column('date_opened', sa.DateTime(timezone=True), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
sa.ForeignKeyConstraint(['box_id'], ['boxes.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_open_boxes_id'), 'open_boxes', ['id'], unique=False)
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_open_boxes_id'), table_name='open_boxes')
op.drop_table('open_boxes')
op.drop_index(op.f('ix_tcgplayer_products_product_id'), table_name='tcgplayer_products')
op.drop_index(op.f('ix_tcgplayer_products_id'), table_name='tcgplayer_products')
op.drop_table('tcgplayer_products')
op.drop_index(op.f('ix_boxes_id'), table_name='boxes')
op.drop_table('boxes')
op.drop_index(op.f('ix_tcgplayer_orders_order_number'), table_name='tcgplayer_orders')
op.drop_index(op.f('ix_tcgplayer_orders_id'), table_name='tcgplayer_orders')
op.drop_table('tcgplayer_orders')
op.drop_index(op.f('ix_tcgplayer_order_transactions_order_number'), table_name='tcgplayer_order_transactions')
op.drop_index(op.f('ix_tcgplayer_order_transactions_id'), table_name='tcgplayer_order_transactions')
op.drop_table('tcgplayer_order_transactions')
op.drop_index(op.f('ix_tcgplayer_order_refunds_order_number'), table_name='tcgplayer_order_refunds')
op.drop_index(op.f('ix_tcgplayer_order_refunds_id'), table_name='tcgplayer_order_refunds')
op.drop_table('tcgplayer_order_refunds')
op.drop_index(op.f('ix_tcgplayer_order_products_order_number'), table_name='tcgplayer_order_products')
op.drop_index(op.f('ix_tcgplayer_order_products_id'), table_name='tcgplayer_order_products')
op.drop_table('tcgplayer_order_products')
op.drop_index(op.f('ix_tcgplayer_groups_id'), table_name='tcgplayer_groups')
op.drop_index(op.f('ix_tcgplayer_groups_group_id'), table_name='tcgplayer_groups')
op.drop_table('tcgplayer_groups')
op.drop_index(op.f('ix_tcgplayer_categories_id'), table_name='tcgplayer_categories')
op.drop_index(op.f('ix_tcgplayer_categories_category_id'), table_name='tcgplayer_categories')
op.drop_table('tcgplayer_categories')
op.drop_index(op.f('ix_mtgjson_skus_sku_id'), table_name='mtgjson_skus')
op.drop_index(op.f('ix_mtgjson_skus_product_id'), table_name='mtgjson_skus')
op.drop_index(op.f('ix_mtgjson_skus_id'), table_name='mtgjson_skus')
op.drop_table('mtgjson_skus')
op.drop_index(op.f('ix_mtgjson_cards_id'), table_name='mtgjson_cards')
op.drop_index(op.f('ix_mtgjson_cards_card_id'), table_name='mtgjson_cards')
op.drop_table('mtgjson_cards')
op.drop_index(op.f('ix_inventory_tcgplayer_id'), table_name='inventory')
op.drop_index(op.f('ix_inventory_id'), table_name='inventory')
op.drop_table('inventory')
op.drop_index(op.f('ix_games_id'), table_name='games')
op.drop_table('games')
op.drop_index(op.f('ix_files_id'), table_name='files')
op.drop_table('files')
op.drop_index(op.f('ix_cards_tcgplayer_sku'), table_name='cards')
op.drop_index(op.f('ix_cards_set_name'), table_name='cards')
op.drop_index(op.f('ix_cards_name'), table_name='cards')
op.drop_index(op.f('ix_cards_id'), table_name='cards')
op.drop_table('cards')
# ### end Alembic commands ###

View File

@ -1,51 +0,0 @@
"""tcg prices again 2
Revision ID: 493b2cb724d0
Revises: 2fcce9c8883a
Create Date: 2025-04-17 23:05:11.919652
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision: str = '493b2cb724d0'
down_revision: Union[str, None] = '2fcce9c8883a'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index('ix_tcgplayer_prices_date', table_name='tcgplayer_prices')
op.drop_index('ix_tcgplayer_prices_id', table_name='tcgplayer_prices')
op.drop_index('ix_tcgplayer_prices_product_id', table_name='tcgplayer_prices')
op.drop_table('tcgplayer_prices')
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('tcgplayer_prices',
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
sa.Column('product_id', sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column('date', postgresql.TIMESTAMP(), autoincrement=False, nullable=True),
sa.Column('low_price', sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=True),
sa.Column('mid_price', sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=True),
sa.Column('high_price', sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=True),
sa.Column('market_price', sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=True),
sa.Column('direct_low_price', sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=True),
sa.Column('sub_type_name', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('created_at', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'), autoincrement=False, nullable=True),
sa.Column('updated_at', postgresql.TIMESTAMP(timezone=True), autoincrement=False, nullable=True),
sa.PrimaryKeyConstraint('id', name='tcgplayer_prices_pkey')
)
op.create_index('ix_tcgplayer_prices_product_id', 'tcgplayer_prices', ['product_id'], unique=False)
op.create_index('ix_tcgplayer_prices_id', 'tcgplayer_prices', ['id'], unique=False)
op.create_index('ix_tcgplayer_prices_date', 'tcgplayer_prices', ['date'], unique=False)
# ### end Alembic commands ###

View File

@ -1,53 +0,0 @@
"""fuck foreign keys for real dog
Revision ID: 54cd251d13a3
Revises: e34bfa37db00
Create Date: 2025-04-17 23:10:59.010644
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision: str = '54cd251d13a3'
down_revision: Union[str, None] = 'e34bfa37db00'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index('ix_tcgplayer_price_history_date', table_name='tcgplayer_price_history')
op.drop_index('ix_tcgplayer_price_history_id', table_name='tcgplayer_price_history')
op.drop_index('ix_tcgplayer_price_history_product_id', table_name='tcgplayer_price_history')
op.drop_table('tcgplayer_price_history')
op.drop_constraint('tcgplayer_products_group_id_fkey', 'tcgplayer_products', type_='foreignkey')
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.create_foreign_key('tcgplayer_products_group_id_fkey', 'tcgplayer_products', 'tcgplayer_groups', ['group_id'], ['group_id'])
op.create_table('tcgplayer_price_history',
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
sa.Column('product_id', sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column('date', postgresql.TIMESTAMP(), autoincrement=False, nullable=True),
sa.Column('low_price', sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=True),
sa.Column('mid_price', sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=True),
sa.Column('high_price', sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=True),
sa.Column('market_price', sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=True),
sa.Column('direct_low_price', sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=True),
sa.Column('sub_type_name', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('created_at', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'), autoincrement=False, nullable=True),
sa.Column('updated_at', postgresql.TIMESTAMP(timezone=True), autoincrement=False, nullable=True),
sa.PrimaryKeyConstraint('id', name='tcgplayer_price_history_pkey')
)
op.create_index('ix_tcgplayer_price_history_product_id', 'tcgplayer_price_history', ['product_id'], unique=False)
op.create_index('ix_tcgplayer_price_history_id', 'tcgplayer_price_history', ['id'], unique=False)
op.create_index('ix_tcgplayer_price_history_date', 'tcgplayer_price_history', ['date'], unique=False)
# ### end Alembic commands ###

View File

@ -1,8 +1,8 @@
"""tcg prices
"""b
Revision ID: b45c43900b56
Revises: 1746d35187a2
Create Date: 2025-04-17 22:47:44.405906
Revision ID: 62eee00bae8e
Revises: 0f534237fc90
Create Date: 2025-04-28 11:01:28.564264
"""
from typing import Sequence, Union
@ -12,8 +12,8 @@ import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'b45c43900b56'
down_revision: Union[str, None] = '1746d35187a2'
revision: str = '62eee00bae8e'
down_revision: Union[str, None] = '0f534237fc90'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
@ -21,12 +21,12 @@ depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
pass
op.create_index('idx_product_subtype', 'tcgplayer_products', ['tcgplayer_product_id', 'normalized_sub_type_name'], unique=False)
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
pass
op.drop_index('idx_product_subtype', table_name='tcgplayer_products')
# ### end Alembic commands ###

View File

@ -1,32 +0,0 @@
"""fuck foreign keys for real dog
Revision ID: 7f309a891094
Revises: 54cd251d13a3
Create Date: 2025-04-17 23:11:55.027126
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '7f309a891094'
down_revision: Union[str, None] = '54cd251d13a3'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###

View File

@ -1,40 +0,0 @@
"""tcg product update
Revision ID: 9775314e337b
Revises: 479003fbead7
Create Date: 2025-04-17 21:58:17.637210
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = '9775314e337b'
down_revision: Union[str, None] = '479003fbead7'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column('tcgplayer_products', sa.Column('ext_power', sa.String(), nullable=True))
op.add_column('tcgplayer_products', sa.Column('ext_toughness', sa.String(), nullable=True))
op.add_column('tcgplayer_products', sa.Column('ext_loyalty', sa.String(), nullable=True))
op.add_column('tcgplayer_products', sa.Column('ext_mana_cost', sa.String(), nullable=True))
op.add_column('tcgplayer_products', sa.Column('ext_mana_value', sa.String(), nullable=True))
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column('tcgplayer_products', 'ext_mana_value')
op.drop_column('tcgplayer_products', 'ext_mana_cost')
op.drop_column('tcgplayer_products', 'ext_loyalty')
op.drop_column('tcgplayer_products', 'ext_toughness')
op.drop_column('tcgplayer_products', 'ext_power')
# ### end Alembic commands ###

View File

@ -1,51 +0,0 @@
"""recreate tcgplayer price history
Revision ID: 9fb73424598c
Revises: 7f309a891094
Create Date: 2025-04-17 23:13:55.027126
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision: str = '9fb73424598c'
down_revision: Union[str, None] = '7f309a891094'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('tcgplayer_price_history',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('product_id', sa.Integer(), nullable=True),
sa.Column('date', sa.DateTime(), nullable=True),
sa.Column('low_price', sa.Float(), nullable=True),
sa.Column('mid_price', sa.Float(), nullable=True),
sa.Column('high_price', sa.Float(), nullable=True),
sa.Column('market_price', sa.Float(), nullable=True),
sa.Column('direct_low_price', sa.Float(), nullable=True),
sa.Column('sub_type_name', sa.String(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_tcgplayer_price_history_id'), 'tcgplayer_price_history', ['id'], unique=False)
op.create_index(op.f('ix_tcgplayer_price_history_product_id'), 'tcgplayer_price_history', ['product_id'], unique=False)
op.create_index(op.f('ix_tcgplayer_price_history_date'), 'tcgplayer_price_history', ['date'], unique=False)
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_tcgplayer_price_history_date'), table_name='tcgplayer_price_history')
op.drop_index(op.f('ix_tcgplayer_price_history_product_id'), table_name='tcgplayer_price_history')
op.drop_index(op.f('ix_tcgplayer_price_history_id'), table_name='tcgplayer_price_history')
op.drop_table('tcgplayer_price_history')
# ### end Alembic commands ###

View File

@ -1,28 +0,0 @@
"""changing db bigly
Revision ID: cc7dd65bcdd9
Revises: 9fb73424598c
Create Date: 2025-04-19 13:36:41.784661
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'cc7dd65bcdd9'
down_revision: Union[str, None] = '9fb73424598c'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
pass
def downgrade() -> None:
"""Downgrade schema."""
pass

View File

@ -0,0 +1,553 @@
"""alembic is actually behaving so this message will be nice :)
Revision ID: cf61f006db46
Revises:
Create Date: 2025-04-25 14:34:28.206737
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'cf61f006db46'
down_revision: Union[str, None] = None
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('critical_error_logs',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('error_message', sa.String(), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_critical_error_logs_id'), 'critical_error_logs', ['id'], unique=False)
op.create_table('customers',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_customers_id'), 'customers', ['id'], unique=False)
op.create_index(op.f('ix_customers_name'), 'customers', ['name'], unique=False)
op.create_table('files',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(), nullable=True),
sa.Column('file_type', sa.String(), nullable=True),
sa.Column('content_type', sa.String(), nullable=True),
sa.Column('path', sa.String(), nullable=True),
sa.Column('size', sa.Integer(), nullable=True),
sa.Column('file_metadata', sa.JSON(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_files_id'), 'files', ['id'], unique=False)
op.create_table('marketplaces',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_marketplaces_id'), 'marketplaces', ['id'], unique=False)
op.create_index(op.f('ix_marketplaces_name'), 'marketplaces', ['name'], unique=False)
op.create_table('most_recent_tcgplayer_price',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('product_id', sa.Integer(), nullable=False),
sa.Column('sub_type_name', sa.String(), nullable=True),
sa.Column('date', sa.DateTime(), nullable=False),
sa.Column('low_price', sa.Float(), nullable=True),
sa.Column('mid_price', sa.Float(), nullable=True),
sa.Column('high_price', sa.Float(), nullable=True),
sa.Column('market_price', sa.Float(), nullable=True),
sa.Column('direct_low_price', sa.Float(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index('idx_most_recent_price_product_subtype', 'most_recent_tcgplayer_price', ['product_id', 'sub_type_name'], unique=True)
op.create_index(op.f('ix_most_recent_tcgplayer_price_product_id'), 'most_recent_tcgplayer_price', ['product_id'], unique=False)
op.create_index(op.f('ix_most_recent_tcgplayer_price_sub_type_name'), 'most_recent_tcgplayer_price', ['sub_type_name'], unique=False)
op.create_table('mtgjson_cards',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('mtgjson_uuid', sa.String(), nullable=True),
sa.Column('name', sa.String(), nullable=True),
sa.Column('set_code', sa.String(), nullable=True),
sa.Column('abu_id', sa.String(), nullable=True),
sa.Column('card_kingdom_etched_id', sa.String(), nullable=True),
sa.Column('card_kingdom_foil_id', sa.String(), nullable=True),
sa.Column('card_kingdom_id', sa.String(), nullable=True),
sa.Column('cardsphere_id', sa.String(), nullable=True),
sa.Column('cardsphere_foil_id', sa.String(), nullable=True),
sa.Column('cardtrader_id', sa.String(), nullable=True),
sa.Column('csi_id', sa.String(), nullable=True),
sa.Column('mcm_id', sa.String(), nullable=True),
sa.Column('mcm_meta_id', sa.String(), nullable=True),
sa.Column('miniaturemarket_id', sa.String(), nullable=True),
sa.Column('mtg_arena_id', sa.String(), nullable=True),
sa.Column('mtgjson_foil_version_id', sa.String(), nullable=True),
sa.Column('mtgjson_non_foil_version_id', sa.String(), nullable=True),
sa.Column('mtgjson_v4_id', sa.String(), nullable=True),
sa.Column('mtgo_foil_id', sa.String(), nullable=True),
sa.Column('mtgo_id', sa.String(), nullable=True),
sa.Column('multiverse_id', sa.String(), nullable=True),
sa.Column('scg_id', sa.String(), nullable=True),
sa.Column('scryfall_id', sa.String(), nullable=True),
sa.Column('scryfall_card_back_id', sa.String(), nullable=True),
sa.Column('scryfall_oracle_id', sa.String(), nullable=True),
sa.Column('scryfall_illustration_id', sa.String(), nullable=True),
sa.Column('tcgplayer_product_id', sa.Integer(), nullable=True),
sa.Column('tcgplayer_etched_product_id', sa.Integer(), nullable=True),
sa.Column('tnt_id', sa.String(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
sa.ForeignKeyConstraint(['mtgjson_uuid'], ['mtgjson_cards.mtgjson_uuid'], ),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('mtgjson_uuid', name='uq_card_mtgjson_uuid')
)
op.create_index(op.f('ix_mtgjson_cards_id'), 'mtgjson_cards', ['id'], unique=False)
op.create_index(op.f('ix_mtgjson_cards_mtgjson_uuid'), 'mtgjson_cards', ['mtgjson_uuid'], unique=True)
op.create_table('sealed_expected_values',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('tcgplayer_product_id', sa.Integer(), nullable=False),
sa.Column('expected_value', sa.Float(), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index('idx_sealed_expected_value_product_id_deleted_at', 'sealed_expected_values', ['tcgplayer_product_id', 'deleted_at'], unique=True)
op.create_index(op.f('ix_sealed_expected_values_id'), 'sealed_expected_values', ['id'], unique=False)
op.create_table('tcgplayer_categories',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('category_id', sa.Integer(), nullable=True),
sa.Column('name', sa.String(), nullable=False),
sa.Column('display_name', sa.String(), nullable=True),
sa.Column('seo_category_name', sa.String(), nullable=True),
sa.Column('category_description', sa.String(), nullable=True),
sa.Column('category_page_title', sa.String(), nullable=True),
sa.Column('sealed_label', sa.String(), nullable=True),
sa.Column('non_sealed_label', sa.String(), nullable=True),
sa.Column('condition_guide_url', sa.String(), nullable=True),
sa.Column('is_scannable', sa.Boolean(), nullable=True),
sa.Column('popularity', sa.Integer(), nullable=True),
sa.Column('is_direct', sa.Boolean(), nullable=True),
sa.Column('modified_on', sa.DateTime(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_tcgplayer_categories_category_id'), 'tcgplayer_categories', ['category_id'], unique=True)
op.create_index(op.f('ix_tcgplayer_categories_id'), 'tcgplayer_categories', ['id'], unique=False)
op.create_table('tcgplayer_order_products',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('order_number', sa.String(), nullable=True),
sa.Column('product_name', sa.String(), nullable=True),
sa.Column('unit_price', sa.Float(), nullable=True),
sa.Column('extended_price', sa.Float(), nullable=True),
sa.Column('quantity', sa.Integer(), nullable=True),
sa.Column('url', sa.String(), nullable=True),
sa.Column('product_id', sa.Integer(), nullable=True),
sa.Column('sku_id', sa.Integer(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_tcgplayer_order_products_id'), 'tcgplayer_order_products', ['id'], unique=False)
op.create_index(op.f('ix_tcgplayer_order_products_order_number'), 'tcgplayer_order_products', ['order_number'], unique=False)
op.create_table('tcgplayer_order_refunds',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('order_number', sa.String(), nullable=True),
sa.Column('refund_created_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('type', sa.String(), nullable=True),
sa.Column('amount', sa.Float(), nullable=True),
sa.Column('description', sa.String(), nullable=True),
sa.Column('origin', sa.String(), nullable=True),
sa.Column('shipping_amount', sa.Float(), nullable=True),
sa.Column('products', sa.JSON(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_tcgplayer_order_refunds_id'), 'tcgplayer_order_refunds', ['id'], unique=False)
op.create_index(op.f('ix_tcgplayer_order_refunds_order_number'), 'tcgplayer_order_refunds', ['order_number'], unique=False)
op.create_table('tcgplayer_order_transactions',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('order_number', sa.String(), nullable=True),
sa.Column('product_amount', sa.Float(), nullable=True),
sa.Column('shipping_amount', sa.Float(), nullable=True),
sa.Column('gross_amount', sa.Float(), nullable=True),
sa.Column('fee_amount', sa.Float(), nullable=True),
sa.Column('net_amount', sa.Float(), nullable=True),
sa.Column('direct_fee_amount', sa.Float(), nullable=True),
sa.Column('taxes', sa.JSON(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_tcgplayer_order_transactions_id'), 'tcgplayer_order_transactions', ['id'], unique=False)
op.create_index(op.f('ix_tcgplayer_order_transactions_order_number'), 'tcgplayer_order_transactions', ['order_number'], unique=False)
op.create_table('tcgplayer_orders',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('order_number', sa.String(), nullable=True),
sa.Column('order_created_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('status', sa.String(), nullable=True),
sa.Column('channel', sa.String(), nullable=True),
sa.Column('fulfillment', sa.String(), nullable=True),
sa.Column('seller_name', sa.String(), nullable=True),
sa.Column('buyer_name', sa.String(), nullable=True),
sa.Column('payment_type', sa.String(), nullable=True),
sa.Column('pickup_status', sa.String(), nullable=True),
sa.Column('shipping_type', sa.String(), nullable=True),
sa.Column('estimated_delivery_date', sa.DateTime(timezone=True), nullable=True),
sa.Column('recipient_name', sa.String(), nullable=True),
sa.Column('address_line_1', sa.String(), nullable=True),
sa.Column('address_line_2', sa.String(), nullable=True),
sa.Column('city', sa.String(), nullable=True),
sa.Column('state', sa.String(), nullable=True),
sa.Column('zip_code', sa.String(), nullable=True),
sa.Column('country', sa.String(), nullable=True),
sa.Column('tracking_numbers', sa.JSON(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_tcgplayer_orders_id'), 'tcgplayer_orders', ['id'], unique=False)
op.create_index(op.f('ix_tcgplayer_orders_order_number'), 'tcgplayer_orders', ['order_number'], unique=False)
op.create_table('tcgplayer_price_history',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('product_id', sa.Integer(), nullable=False),
sa.Column('sub_type_name', sa.String(), nullable=True),
sa.Column('date', sa.DateTime(), nullable=True),
sa.Column('low_price', sa.Float(), nullable=True),
sa.Column('mid_price', sa.Float(), nullable=True),
sa.Column('high_price', sa.Float(), nullable=True),
sa.Column('market_price', sa.Float(), nullable=True),
sa.Column('direct_low_price', sa.Float(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index('idx_price_history_product_subtype_date', 'tcgplayer_price_history', ['product_id', 'sub_type_name', 'date'], unique=False)
op.create_index(op.f('ix_tcgplayer_price_history_date'), 'tcgplayer_price_history', ['date'], unique=False)
op.create_index(op.f('ix_tcgplayer_price_history_id'), 'tcgplayer_price_history', ['id'], unique=False)
op.create_index(op.f('ix_tcgplayer_price_history_product_id'), 'tcgplayer_price_history', ['product_id'], unique=False)
op.create_index(op.f('ix_tcgplayer_price_history_sub_type_name'), 'tcgplayer_price_history', ['sub_type_name'], unique=False)
op.create_table('vendors',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_vendors_id'), 'vendors', ['id'], unique=False)
op.create_index(op.f('ix_vendors_name'), 'vendors', ['name'], unique=False)
op.create_table('manabox_import_staging',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('file_id', sa.Integer(), nullable=True),
sa.Column('tcgplayer_product_id', sa.Integer(), nullable=True),
sa.Column('tcgplayer_sku_id', sa.Integer(), nullable=True),
sa.Column('quantity', sa.Integer(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True),
sa.ForeignKeyConstraint(['file_id'], ['files.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('tcgplayer_groups',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('group_id', sa.Integer(), nullable=True),
sa.Column('name', sa.String(), nullable=False),
sa.Column('abbreviation', sa.String(), nullable=True),
sa.Column('is_supplemental', sa.Boolean(), nullable=True),
sa.Column('published_on', sa.DateTime(), nullable=True),
sa.Column('modified_on', sa.DateTime(), nullable=True),
sa.Column('category_id', sa.Integer(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
sa.ForeignKeyConstraint(['category_id'], ['tcgplayer_categories.category_id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_tcgplayer_groups_group_id'), 'tcgplayer_groups', ['group_id'], unique=True)
op.create_index(op.f('ix_tcgplayer_groups_id'), 'tcgplayer_groups', ['id'], unique=False)
op.create_table('transactions',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('vendor_id', sa.Integer(), nullable=True),
sa.Column('customer_id', sa.Integer(), nullable=True),
sa.Column('marketplace_id', sa.Integer(), nullable=True),
sa.Column('transaction_type', sa.String(), nullable=True),
sa.Column('transaction_date', sa.DateTime(timezone=True), nullable=True),
sa.Column('transaction_total_amount', sa.Float(), nullable=True),
sa.Column('transaction_notes', sa.String(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True),
sa.ForeignKeyConstraint(['customer_id'], ['customers.id'], ),
sa.ForeignKeyConstraint(['marketplace_id'], ['marketplaces.id'], ),
sa.ForeignKeyConstraint(['vendor_id'], ['vendors.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_transactions_id'), 'transactions', ['id'], unique=False)
op.create_table('tcgplayer_products',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('tcgplayer_product_id', sa.Integer(), nullable=False),
sa.Column('normalized_sub_type_name', sa.String(), nullable=False),
sa.Column('sub_type_name', sa.String(), nullable=True),
sa.Column('name', sa.String(), nullable=False),
sa.Column('clean_name', sa.String(), nullable=True),
sa.Column('image_url', sa.String(), nullable=True),
sa.Column('category_id', sa.Integer(), nullable=True),
sa.Column('group_id', sa.Integer(), nullable=True),
sa.Column('url', sa.String(), nullable=True),
sa.Column('modified_on', sa.DateTime(), nullable=True),
sa.Column('image_count', sa.Integer(), nullable=True),
sa.Column('ext_rarity', sa.String(), nullable=True),
sa.Column('ext_subtype', sa.String(), nullable=True),
sa.Column('ext_oracle_text', sa.String(), nullable=True),
sa.Column('ext_number', sa.String(), nullable=True),
sa.Column('low_price', sa.Float(), nullable=True),
sa.Column('mid_price', sa.Float(), nullable=True),
sa.Column('high_price', sa.Float(), nullable=True),
sa.Column('market_price', sa.Float(), nullable=True),
sa.Column('direct_low_price', sa.Float(), nullable=True),
sa.Column('ext_power', sa.String(), nullable=True),
sa.Column('ext_toughness', sa.String(), nullable=True),
sa.Column('ext_flavor_text', sa.String(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
sa.ForeignKeyConstraint(['category_id'], ['tcgplayer_categories.category_id'], ),
sa.ForeignKeyConstraint(['group_id'], ['tcgplayer_groups.group_id'], ),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('tcgplayer_product_id', 'normalized_sub_type_name', name='uq_product_subtype')
)
op.create_index(op.f('ix_tcgplayer_products_id'), 'tcgplayer_products', ['id'], unique=False)
op.create_table('mtgjson_skus',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('mtgjson_uuid', sa.String(), nullable=True),
sa.Column('tcgplayer_sku_id', sa.Integer(), nullable=True),
sa.Column('tcgplayer_product_id', sa.Integer(), nullable=False),
sa.Column('normalized_printing', sa.String(), nullable=False),
sa.Column('condition', sa.String(), nullable=True),
sa.Column('finish', sa.String(), nullable=True),
sa.Column('language', sa.String(), nullable=True),
sa.Column('printing', sa.String(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
sa.ForeignKeyConstraint(['mtgjson_uuid'], ['mtgjson_cards.mtgjson_uuid'], ),
sa.ForeignKeyConstraint(['tcgplayer_product_id', 'normalized_printing'], ['tcgplayer_products.tcgplayer_product_id', 'tcgplayer_products.normalized_sub_type_name'], name='fk_sku_to_product_composite'),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('mtgjson_uuid', name='uq_sku_mtgjson_uuid')
)
op.create_index('idx_sku_product_printing', 'mtgjson_skus', ['tcgplayer_product_id', 'normalized_printing'], unique=False)
op.create_index(op.f('ix_mtgjson_skus_id'), 'mtgjson_skus', ['id'], unique=False)
op.create_index(op.f('ix_mtgjson_skus_mtgjson_uuid'), 'mtgjson_skus', ['mtgjson_uuid'], unique=True)
op.create_index(op.f('ix_mtgjson_skus_tcgplayer_sku_id'), 'mtgjson_skus', ['tcgplayer_sku_id'], unique=True)
op.create_table('physical_items',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('item_type', sa.String(), nullable=True),
sa.Column('tcgplayer_product_id', sa.Integer(), nullable=True),
sa.Column('tcgplayer_sku_id', sa.Integer(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True),
sa.CheckConstraint('(tcgplayer_sku_id IS NOT NULL OR tcgplayer_product_id IS NOT NULL)', name='ck_physical_items_sku_or_product_not_null'),
sa.ForeignKeyConstraint(['tcgplayer_sku_id'], ['mtgjson_skus.tcgplayer_sku_id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('tcgplayer_inventory',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('tcgplayer_sku_id', sa.Integer(), nullable=True),
sa.Column('product_line', sa.String(), nullable=True),
sa.Column('set_name', sa.String(), nullable=True),
sa.Column('product_name', sa.String(), nullable=True),
sa.Column('title', sa.String(), nullable=True),
sa.Column('number', sa.String(), nullable=True),
sa.Column('rarity', sa.String(), nullable=True),
sa.Column('condition', sa.String(), nullable=True),
sa.Column('tcg_market_price', sa.Float(), nullable=True),
sa.Column('tcg_direct_low', sa.Float(), nullable=True),
sa.Column('tcg_low_price_with_shipping', sa.Float(), nullable=True),
sa.Column('tcg_low_price', sa.Float(), nullable=True),
sa.Column('total_quantity', sa.Integer(), nullable=True),
sa.Column('add_to_quantity', sa.Integer(), nullable=True),
sa.Column('tcg_marketplace_price', sa.Float(), nullable=True),
sa.Column('photo_url', sa.String(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.ForeignKeyConstraint(['tcgplayer_sku_id'], ['mtgjson_skus.tcgplayer_sku_id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_tcgplayer_inventory_id'), 'tcgplayer_inventory', ['id'], unique=False)
op.create_index(op.f('ix_tcgplayer_inventory_tcgplayer_sku_id'), 'tcgplayer_inventory', ['tcgplayer_sku_id'], unique=True)
op.create_table('boxes',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('expected_value', sa.Float(), nullable=True),
sa.ForeignKeyConstraint(['id'], ['physical_items.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('cards',
sa.Column('id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['id'], ['physical_items.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('cases',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('expected_value', sa.Float(), nullable=True),
sa.Column('num_boxes', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['id'], ['physical_items.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('inventory_items',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('physical_item_id', sa.Integer(), nullable=True),
sa.Column('cost_basis', sa.Float(), nullable=True),
sa.Column('parent_id', sa.Integer(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True),
sa.ForeignKeyConstraint(['parent_id'], ['inventory_items.id'], ),
sa.ForeignKeyConstraint(['physical_item_id'], ['physical_items.id'], ),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('physical_item_id')
)
op.create_index(op.f('ix_inventory_items_id'), 'inventory_items', ['id'], unique=False)
op.create_table('open_events',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('source_item_id', sa.Integer(), nullable=True),
sa.Column('open_date', sa.DateTime(timezone=True), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True),
sa.ForeignKeyConstraint(['source_item_id'], ['physical_items.id'], ),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('source_item_id', name='uq_openevent_one_per_source')
)
op.create_index(op.f('ix_open_events_id'), 'open_events', ['id'], unique=False)
op.create_table('marketplace_listings',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('inventory_item_id', sa.Integer(), nullable=False),
sa.Column('marketplace_id', sa.Integer(), nullable=False),
sa.Column('listing_date', sa.DateTime(timezone=True), nullable=True),
sa.Column('delisting_date', sa.DateTime(timezone=True), nullable=True),
sa.Column('listed_price', sa.Float(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True),
sa.ForeignKeyConstraint(['inventory_item_id'], ['inventory_items.id'], ),
sa.ForeignKeyConstraint(['marketplace_id'], ['marketplaces.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_marketplace_listings_id'), 'marketplace_listings', ['id'], unique=False)
op.create_table('open_event_resulting_items',
sa.Column('event_id', sa.Integer(), nullable=False),
sa.Column('item_id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['event_id'], ['open_events.id'], ),
sa.ForeignKeyConstraint(['item_id'], ['physical_items.id'], ),
sa.PrimaryKeyConstraint('event_id', 'item_id')
)
op.create_table('transaction_items',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('transaction_id', sa.Integer(), nullable=True),
sa.Column('inventory_item_id', sa.Integer(), nullable=True),
sa.Column('unit_price', sa.Float(), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), server_default=sa.text('now()'), nullable=True),
sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True),
sa.ForeignKeyConstraint(['inventory_item_id'], ['inventory_items.id'], ),
sa.ForeignKeyConstraint(['transaction_id'], ['transactions.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_transaction_items_id'), 'transaction_items', ['id'], unique=False)
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_transaction_items_id'), table_name='transaction_items')
op.drop_table('transaction_items')
op.drop_table('open_event_resulting_items')
op.drop_index(op.f('ix_marketplace_listings_id'), table_name='marketplace_listings')
op.drop_table('marketplace_listings')
op.drop_index(op.f('ix_open_events_id'), table_name='open_events')
op.drop_table('open_events')
op.drop_index(op.f('ix_inventory_items_id'), table_name='inventory_items')
op.drop_table('inventory_items')
op.drop_table('cases')
op.drop_table('cards')
op.drop_table('boxes')
op.drop_index(op.f('ix_tcgplayer_inventory_tcgplayer_sku_id'), table_name='tcgplayer_inventory')
op.drop_index(op.f('ix_tcgplayer_inventory_id'), table_name='tcgplayer_inventory')
op.drop_table('tcgplayer_inventory')
op.drop_table('physical_items')
op.drop_index(op.f('ix_mtgjson_skus_tcgplayer_sku_id'), table_name='mtgjson_skus')
op.drop_index(op.f('ix_mtgjson_skus_mtgjson_uuid'), table_name='mtgjson_skus')
op.drop_index(op.f('ix_mtgjson_skus_id'), table_name='mtgjson_skus')
op.drop_index('idx_sku_product_printing', table_name='mtgjson_skus')
op.drop_table('mtgjson_skus')
op.drop_index(op.f('ix_tcgplayer_products_id'), table_name='tcgplayer_products')
op.drop_table('tcgplayer_products')
op.drop_index(op.f('ix_transactions_id'), table_name='transactions')
op.drop_table('transactions')
op.drop_index(op.f('ix_tcgplayer_groups_id'), table_name='tcgplayer_groups')
op.drop_index(op.f('ix_tcgplayer_groups_group_id'), table_name='tcgplayer_groups')
op.drop_table('tcgplayer_groups')
op.drop_table('manabox_import_staging')
op.drop_index(op.f('ix_vendors_name'), table_name='vendors')
op.drop_index(op.f('ix_vendors_id'), table_name='vendors')
op.drop_table('vendors')
op.drop_index(op.f('ix_tcgplayer_price_history_sub_type_name'), table_name='tcgplayer_price_history')
op.drop_index(op.f('ix_tcgplayer_price_history_product_id'), table_name='tcgplayer_price_history')
op.drop_index(op.f('ix_tcgplayer_price_history_id'), table_name='tcgplayer_price_history')
op.drop_index(op.f('ix_tcgplayer_price_history_date'), table_name='tcgplayer_price_history')
op.drop_index('idx_price_history_product_subtype_date', table_name='tcgplayer_price_history')
op.drop_table('tcgplayer_price_history')
op.drop_index(op.f('ix_tcgplayer_orders_order_number'), table_name='tcgplayer_orders')
op.drop_index(op.f('ix_tcgplayer_orders_id'), table_name='tcgplayer_orders')
op.drop_table('tcgplayer_orders')
op.drop_index(op.f('ix_tcgplayer_order_transactions_order_number'), table_name='tcgplayer_order_transactions')
op.drop_index(op.f('ix_tcgplayer_order_transactions_id'), table_name='tcgplayer_order_transactions')
op.drop_table('tcgplayer_order_transactions')
op.drop_index(op.f('ix_tcgplayer_order_refunds_order_number'), table_name='tcgplayer_order_refunds')
op.drop_index(op.f('ix_tcgplayer_order_refunds_id'), table_name='tcgplayer_order_refunds')
op.drop_table('tcgplayer_order_refunds')
op.drop_index(op.f('ix_tcgplayer_order_products_order_number'), table_name='tcgplayer_order_products')
op.drop_index(op.f('ix_tcgplayer_order_products_id'), table_name='tcgplayer_order_products')
op.drop_table('tcgplayer_order_products')
op.drop_index(op.f('ix_tcgplayer_categories_id'), table_name='tcgplayer_categories')
op.drop_index(op.f('ix_tcgplayer_categories_category_id'), table_name='tcgplayer_categories')
op.drop_table('tcgplayer_categories')
op.drop_index(op.f('ix_sealed_expected_values_id'), table_name='sealed_expected_values')
op.drop_index('idx_sealed_expected_value_product_id_deleted_at', table_name='sealed_expected_values')
op.drop_table('sealed_expected_values')
op.drop_index(op.f('ix_mtgjson_cards_mtgjson_uuid'), table_name='mtgjson_cards')
op.drop_index(op.f('ix_mtgjson_cards_id'), table_name='mtgjson_cards')
op.drop_table('mtgjson_cards')
op.drop_index(op.f('ix_most_recent_tcgplayer_price_sub_type_name'), table_name='most_recent_tcgplayer_price')
op.drop_index(op.f('ix_most_recent_tcgplayer_price_product_id'), table_name='most_recent_tcgplayer_price')
op.drop_index('idx_most_recent_price_product_subtype', table_name='most_recent_tcgplayer_price')
op.drop_table('most_recent_tcgplayer_price')
op.drop_index(op.f('ix_marketplaces_name'), table_name='marketplaces')
op.drop_index(op.f('ix_marketplaces_id'), table_name='marketplaces')
op.drop_table('marketplaces')
op.drop_index(op.f('ix_files_id'), table_name='files')
op.drop_table('files')
op.drop_index(op.f('ix_customers_name'), table_name='customers')
op.drop_index(op.f('ix_customers_id'), table_name='customers')
op.drop_table('customers')
op.drop_index(op.f('ix_critical_error_logs_id'), table_name='critical_error_logs')
op.drop_table('critical_error_logs')
# ### end Alembic commands ###

View File

@ -0,0 +1,34 @@
"""there is literally no point to ever using foreign keys
Revision ID: d13600612a8f
Revises: 62eee00bae8e
Create Date: 2025-04-28 11:37:11.023788
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'd13600612a8f'
down_revision: Union[str, None] = '62eee00bae8e'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_constraint('tcgplayer_inventory_tcgplayer_sku_id_fkey', 'tcgplayer_inventory', type_='foreignkey')
op.drop_constraint('unmanaged_tcgplayer_inventory_tcgplayer_sku_id_fkey', 'unmanaged_tcgplayer_inventory', type_='foreignkey')
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.create_foreign_key('unmanaged_tcgplayer_inventory_tcgplayer_sku_id_fkey', 'unmanaged_tcgplayer_inventory', 'mtgjson_skus', ['tcgplayer_sku_id'], ['tcgplayer_sku_id'])
op.create_foreign_key('tcgplayer_inventory_tcgplayer_sku_id_fkey', 'tcgplayer_inventory', 'mtgjson_skus', ['tcgplayer_sku_id'], ['tcgplayer_sku_id'])
# ### end Alembic commands ###

View File

@ -1,339 +0,0 @@
"""changing db bigly
Revision ID: d4d3f43ce86a
Revises: cc7dd65bcdd9
Create Date: 2025-04-19 13:46:27.330261
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import postgresql
# revision identifiers, used by Alembic.
revision: str = 'd4d3f43ce86a'
down_revision: Union[str, None] = 'cc7dd65bcdd9'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('customers',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_customers_id'), 'customers', ['id'], unique=False)
op.create_index(op.f('ix_customers_name'), 'customers', ['name'], unique=True)
op.create_table('products',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(), nullable=True),
sa.Column('tcgplayer_id', sa.String(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_table('tcgplayer_inventory',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('tcgplayer_id', sa.String(), nullable=True),
sa.Column('product_line', sa.String(), nullable=True),
sa.Column('set_name', sa.String(), nullable=True),
sa.Column('product_name', sa.String(), nullable=True),
sa.Column('title', sa.String(), nullable=True),
sa.Column('number', sa.String(), nullable=True),
sa.Column('rarity', sa.String(), nullable=True),
sa.Column('condition', sa.String(), nullable=True),
sa.Column('tcg_market_price', sa.Float(), nullable=True),
sa.Column('tcg_direct_low', sa.Float(), nullable=True),
sa.Column('tcg_low_price_with_shipping', sa.Float(), nullable=True),
sa.Column('tcg_low_price', sa.Float(), nullable=True),
sa.Column('total_quantity', sa.Integer(), nullable=True),
sa.Column('add_to_quantity', sa.Integer(), nullable=True),
sa.Column('tcg_marketplace_price', sa.Float(), nullable=True),
sa.Column('photo_url', sa.String(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), server_default=sa.text('CURRENT_TIMESTAMP'), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_tcgplayer_inventory_id'), 'tcgplayer_inventory', ['id'], unique=False)
op.create_index(op.f('ix_tcgplayer_inventory_tcgplayer_id'), 'tcgplayer_inventory', ['tcgplayer_id'], unique=True)
op.create_table('vendors',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_vendors_id'), 'vendors', ['id'], unique=False)
op.create_index(op.f('ix_vendors_name'), 'vendors', ['name'], unique=True)
op.create_table('physical_items',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('item_type', sa.String(), nullable=True),
sa.Column('product_id', sa.Integer(), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True),
sa.ForeignKeyConstraint(['product_id'], ['products.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('transactions',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('vendor_id', sa.Integer(), nullable=True),
sa.Column('customer_id', sa.Integer(), nullable=True),
sa.Column('transaction_type', sa.String(), nullable=True),
sa.Column('transaction_date', sa.DateTime(timezone=True), nullable=True),
sa.Column('transaction_total_amount', sa.Float(), nullable=True),
sa.Column('transaction_notes', sa.String(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True),
sa.ForeignKeyConstraint(['customer_id'], ['customers.id'], ),
sa.ForeignKeyConstraint(['vendor_id'], ['vendors.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_transactions_id'), 'transactions', ['id'], unique=False)
op.create_table('inventory_items',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('physical_item_id', sa.Integer(), nullable=True),
sa.Column('cost_basis', sa.Float(), nullable=True),
sa.Column('parent_id', sa.Integer(), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True),
sa.ForeignKeyConstraint(['parent_id'], ['inventory_items.id'], ),
sa.ForeignKeyConstraint(['physical_item_id'], ['physical_items.id'], ),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('physical_item_id')
)
op.create_index(op.f('ix_inventory_items_id'), 'inventory_items', ['id'], unique=False)
op.create_table('sealed_cases',
sa.Column('id', sa.Integer(), nullable=False),
sa.ForeignKeyConstraint(['id'], ['physical_items.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('transaction_items',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('transaction_id', sa.Integer(), nullable=True),
sa.Column('physical_item_id', sa.Integer(), nullable=True),
sa.Column('unit_price', sa.Float(), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True),
sa.ForeignKeyConstraint(['physical_item_id'], ['physical_items.id'], ),
sa.ForeignKeyConstraint(['transaction_id'], ['transactions.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_transaction_items_id'), 'transaction_items', ['id'], unique=False)
op.create_table('sealed_boxes',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('case_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['case_id'], ['sealed_cases.id'], ),
sa.ForeignKeyConstraint(['id'], ['physical_items.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('open_events',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('sealed_case_id', sa.Integer(), nullable=True),
sa.Column('sealed_box_id', sa.Integer(), nullable=True),
sa.Column('open_date', sa.DateTime(timezone=True), nullable=True),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True),
sa.ForeignKeyConstraint(['sealed_box_id'], ['sealed_boxes.id'], ),
sa.ForeignKeyConstraint(['sealed_case_id'], ['sealed_cases.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_open_events_id'), 'open_events', ['id'], unique=False)
op.create_table('open_cards',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('open_event_id', sa.Integer(), nullable=True),
sa.Column('box_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['box_id'], ['open_boxes.id'], ),
sa.ForeignKeyConstraint(['id'], ['physical_items.id'], ),
sa.ForeignKeyConstraint(['open_event_id'], ['open_events.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_table('cost_basis',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('transaction_item_id', sa.Integer(), nullable=True),
sa.Column('sealed_case_id', sa.Integer(), nullable=True),
sa.Column('sealed_box_id', sa.Integer(), nullable=True),
sa.Column('open_box_id', sa.Integer(), nullable=True),
sa.Column('open_card_id', sa.Integer(), nullable=True),
sa.Column('quantity', sa.Integer(), nullable=False),
sa.Column('unit_cost', sa.Float(), nullable=False),
sa.Column('created_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('updated_at', sa.DateTime(timezone=True), nullable=True),
sa.Column('deleted_at', sa.DateTime(timezone=True), nullable=True),
sa.ForeignKeyConstraint(['open_box_id'], ['open_boxes.id'], ),
sa.ForeignKeyConstraint(['open_card_id'], ['open_cards.id'], ),
sa.ForeignKeyConstraint(['sealed_box_id'], ['sealed_boxes.id'], ),
sa.ForeignKeyConstraint(['sealed_case_id'], ['sealed_cases.id'], ),
sa.ForeignKeyConstraint(['transaction_item_id'], ['transaction_items.id'], ),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_cost_basis_id'), 'cost_basis', ['id'], unique=False)
# Drop tables in correct dependency order
# First drop foreign key constraints
op.execute('DROP TABLE IF EXISTS open_cards CASCADE')
op.execute('DROP TABLE IF EXISTS cost_basis CASCADE')
op.execute('DROP TABLE IF EXISTS open_boxes CASCADE')
op.execute('DROP TABLE IF EXISTS boxes CASCADE')
op.execute('DROP TABLE IF EXISTS games CASCADE')
op.drop_index('ix_inventory_id', table_name='inventory')
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
# Create tables in correct dependency order
op.create_table('games',
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
sa.Column('name', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('description', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('image_url', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('created_at', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'), autoincrement=False, nullable=True),
sa.Column('updated_at', postgresql.TIMESTAMP(timezone=True), autoincrement=False, nullable=True),
sa.PrimaryKeyConstraint('id', name='games_pkey')
)
op.create_index('ix_games_id', 'games', ['id'], unique=False)
op.create_table('boxes',
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
sa.Column('product_id', sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column('type', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('set_code', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('sku', sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column('name', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('game_id', sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column('expected_number_of_cards', sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column('description', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('image_url', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('created_at', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'), autoincrement=False, nullable=True),
sa.Column('updated_at', postgresql.TIMESTAMP(timezone=True), autoincrement=False, nullable=True),
sa.ForeignKeyConstraint(['game_id'], ['games.id'], name='boxes_game_id_fkey'),
sa.PrimaryKeyConstraint('id', name='boxes_pkey')
)
op.create_index('ix_boxes_id', 'boxes', ['id'], unique=False)
op.create_table('open_boxes',
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
sa.Column('box_id', sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column('date_opened', postgresql.TIMESTAMP(timezone=True), autoincrement=False, nullable=True),
sa.Column('number_of_cards', sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column('created_at', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'), autoincrement=False, nullable=True),
sa.Column('updated_at', postgresql.TIMESTAMP(timezone=True), autoincrement=False, nullable=True),
sa.ForeignKeyConstraint(['box_id'], ['boxes.id'], name='open_boxes_box_id_fkey'),
sa.PrimaryKeyConstraint('id', name='open_boxes_pkey')
)
op.create_index('ix_open_boxes_id', 'open_boxes', ['id'], unique=False)
op.create_table('open_cards',
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
sa.Column('box_id', sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column('created_at', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'), autoincrement=False, nullable=True),
sa.Column('updated_at', postgresql.TIMESTAMP(timezone=True), autoincrement=False, nullable=True),
sa.ForeignKeyConstraint(['box_id'], ['open_boxes.id'], name='open_cards_box_id_fkey'),
sa.PrimaryKeyConstraint('id', name='open_cards_pkey')
)
op.create_index('ix_open_cards_id', 'open_cards', ['id'], unique=False)
op.create_table('cost_basis',
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
sa.Column('open_box_id', sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column('created_at', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'), autoincrement=False, nullable=True),
sa.Column('updated_at', postgresql.TIMESTAMP(timezone=True), autoincrement=False, nullable=True),
sa.ForeignKeyConstraint(['open_box_id'], ['open_boxes.id'], name='cost_basis_open_box_id_fkey'),
sa.PrimaryKeyConstraint('id', name='cost_basis_pkey')
)
op.create_index('ix_cost_basis_id', 'cost_basis', ['id'], unique=False)
op.create_table('cards',
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
sa.Column('name', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('rarity', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('set_name', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('price', sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=True),
sa.Column('quantity', sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column('tcgplayer_sku', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('product_line', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('product_name', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('title', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('number', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('condition', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('tcg_market_price', sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=True),
sa.Column('tcg_direct_low', sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=True),
sa.Column('tcg_low_price_with_shipping', sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=True),
sa.Column('tcg_low_price', sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=True),
sa.Column('total_quantity', sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column('add_to_quantity', sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column('tcg_marketplace_price', sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=True),
sa.Column('photo_url', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('created_at', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'), autoincrement=False, nullable=True),
sa.Column('updated_at', postgresql.TIMESTAMP(timezone=True), autoincrement=False, nullable=True),
sa.PrimaryKeyConstraint('id', name='cards_pkey')
)
op.create_index('ix_cards_tcgplayer_sku', 'cards', ['tcgplayer_sku'], unique=True)
op.create_index('ix_cards_set_name', 'cards', ['set_name'], unique=False)
op.create_index('ix_cards_name', 'cards', ['name'], unique=False)
op.create_index('ix_cards_id', 'cards', ['id'], unique=False)
op.create_table('inventory',
sa.Column('id', sa.INTEGER(), autoincrement=True, nullable=False),
sa.Column('tcgplayer_id', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('product_line', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('set_name', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('product_name', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('title', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('number', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('rarity', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('condition', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('tcg_market_price', sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=True),
sa.Column('tcg_direct_low', sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=True),
sa.Column('tcg_low_price_with_shipping', sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=True),
sa.Column('tcg_low_price', sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=True),
sa.Column('total_quantity', sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column('add_to_quantity', sa.INTEGER(), autoincrement=False, nullable=True),
sa.Column('tcg_marketplace_price', sa.DOUBLE_PRECISION(precision=53), autoincrement=False, nullable=True),
sa.Column('photo_url', sa.VARCHAR(), autoincrement=False, nullable=True),
sa.Column('created_at', postgresql.TIMESTAMP(timezone=True), server_default=sa.text('now()'), autoincrement=False, nullable=True),
sa.Column('updated_at', postgresql.TIMESTAMP(timezone=True), autoincrement=False, nullable=True),
sa.PrimaryKeyConstraint('id', name='inventory_pkey')
)
op.create_index('ix_inventory_tcgplayer_id', 'inventory', ['tcgplayer_id'], unique=True)
op.create_index('ix_inventory_id', 'inventory', ['id'], unique=False)
op.drop_index(op.f('ix_cost_basis_id'), table_name='cost_basis')
op.drop_table('cost_basis')
op.drop_table('open_cards')
op.drop_index(op.f('ix_open_events_id'), table_name='open_events')
op.drop_table('open_events')
op.drop_table('sealed_boxes')
op.drop_index(op.f('ix_transaction_items_id'), table_name='transaction_items')
op.drop_table('transaction_items')
op.drop_table('sealed_cases')
op.drop_index(op.f('ix_inventory_items_id'), table_name='inventory_items')
op.drop_table('inventory_items')
op.drop_index(op.f('ix_transactions_id'), table_name='transactions')
op.drop_table('transactions')
op.drop_table('physical_items')
op.drop_index(op.f('ix_vendors_name'), table_name='vendors')
op.drop_index(op.f('ix_vendors_id'), table_name='vendors')
op.drop_table('vendors')
op.drop_index(op.f('ix_tcgplayer_inventory_tcgplayer_id'), table_name='tcgplayer_inventory')
op.drop_index(op.f('ix_tcgplayer_inventory_id'), table_name='tcgplayer_inventory')
op.drop_table('tcgplayer_inventory')
op.drop_table('products')
op.drop_index(op.f('ix_customers_name'), table_name='customers')
op.drop_index(op.f('ix_customers_id'), table_name='customers')
op.drop_table('customers')
# ### end Alembic commands ###

View File

@ -1,32 +0,0 @@
"""tcg prices again 3
Revision ID: e34bfa37db00
Revises: 493b2cb724d0
Create Date: 2025-04-17 23:05:40.805511
"""
from typing import Sequence, Union
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision: str = 'e34bfa37db00'
down_revision: Union[str, None] = '493b2cb724d0'
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
pass
# ### end Alembic commands ###

28
app.log
View File

@ -1,28 +0,0 @@
2025-04-19 13:56:40,410 - INFO - app.main - Application starting up...
2025-04-19 13:56:40,492 - INFO - app.main - Database initialized successfully
2025-04-19 13:56:40,492 - INFO - app.services.service_manager - Service OrderManagementService registered
2025-04-19 13:56:40,492 - INFO - app.services.service_manager - Service TCGPlayerInventoryService registered
2025-04-19 13:56:40,492 - INFO - app.services.service_manager - Service LabelPrinterService registered
2025-04-19 13:56:40,492 - INFO - app.services.service_manager - Service RegularPrinterService registered
2025-04-19 13:56:40,495 - INFO - app.services.service_manager - Service AddressLabelService registered
2025-04-19 13:56:40,497 - INFO - app.services.service_manager - Service PullSheetService registered
2025-04-19 13:56:40,497 - INFO - app.services.service_manager - Service SetLabelService registered
2025-04-19 13:56:40,497 - INFO - app.services.service_manager - Service DataInitializationService registered
2025-04-19 13:56:40,498 - INFO - app.services.service_manager - Service SchedulerService registered
2025-04-19 13:56:40,498 - INFO - app.services.service_manager - Service FileService registered
2025-04-19 13:56:40,498 - INFO - app.services.service_manager - Service TCGCSVService registered
2025-04-19 13:56:40,498 - INFO - app.services.service_manager - Service MTGJSONService registered
2025-04-19 13:56:40,499 - INFO - app.services.service_manager - All services initialized successfully
2025-04-19 13:56:40,499 - INFO - app.services.data_initialization - Starting data initialization process
2025-04-19 13:56:40,499 - INFO - app.services.data_initialization - Data initialization completed
2025-04-19 13:56:40,499 - INFO - app.main - Data initialization results: {}
2025-04-19 13:56:40,499 - INFO - apscheduler.scheduler - Adding job tentatively -- it will be properly scheduled when the scheduler starts
2025-04-19 13:56:40,499 - INFO - app.services.scheduler.base_scheduler - Scheduled task update_open_orders_hourly to run every 3600 seconds
2025-04-19 13:56:40,499 - INFO - apscheduler.scheduler - Adding job tentatively -- it will be properly scheduled when the scheduler starts
2025-04-19 13:56:40,499 - INFO - app.services.scheduler.base_scheduler - Scheduled task update_all_orders_daily to run every 86400 seconds
2025-04-19 13:56:40,499 - INFO - apscheduler.scheduler - Added job "SchedulerService.start_scheduled_tasks.<locals>.<lambda>" to job store "default"
2025-04-19 13:56:40,500 - INFO - apscheduler.scheduler - Added job "SchedulerService.start_scheduled_tasks.<locals>.<lambda>" to job store "default"
2025-04-19 13:56:40,500 - INFO - apscheduler.scheduler - Scheduler started
2025-04-19 13:56:40,500 - INFO - app.services.scheduler.base_scheduler - Scheduler started
2025-04-19 13:56:40,500 - INFO - app.services.scheduler.scheduler_service - All scheduled tasks started
2025-04-19 13:56:40,500 - INFO - app.main - Scheduler started successfully

View File

@ -0,0 +1,87 @@
from app.models.inventory_management import InventoryItem
from app.contexts.inventory_product import InventoryProductContext
from sqlalchemy.orm import Session
from datetime import datetime
from app.models.tcgplayer_products import TCGPlayerProduct
class InventoryItemContext:
def __init__(self, item: InventoryItem, db: Session):
self.item = item
self.physical_item = item.physical_item
self.marketplace_listing = item.marketplace_listing
self.parent = item.parent
self.children = item.children
self.product = item.products
self.db = db
@property
def cost_basis(self) -> float:
return self.item.cost_basis
@property
def product_id(self) -> int:
return self.physical_item.product_id
@property
def product_name(self) -> str:
return self.product.name if self.product else None
@property
def item_type(self) -> str:
return self.physical_item.item_type
@property
def market_price(self) -> float:
if not self.product or not self.product.most_recent_tcgplayer_price:
return 0.0
return self.product.most_recent_tcgplayer_price.market_price
@property
def tcg_low_price(self) -> float:
if not self.product or not self.product.most_recent_tcgplayer_price:
return 0.0
return self.product.most_recent_tcgplayer_price.low_price
@property
def listed_price(self) -> float:
if not self.marketplace_listing:
return 0.0
return self.marketplace_listing[0].listed_price if self.marketplace_listing else 0.0
def top_level_parent(self) -> "InventoryItemContext":
if self.parent:
return InventoryItemContext(self.parent, self.db)
return self
def box_expected_value(self) -> float:
top_level_parent = self.top_level_parent()
if 'case' in top_level_parent.item_type:
return top_level_parent.physical_item.open_event.sealed_case.expected_value
elif 'box' in top_level_parent.item_type:
return top_level_parent.physical_item.open_event.sealed_box.expected_value
else:
raise ValueError("Unknown top level parent item type")
def box_acquisition_cost(self) -> float:
if self.physical_item.transaction_items:
return self.physical_item.transaction_items[0].unit_price
elif self.parent:
return InventoryItemContext(self.parent, self.db).box_acquisition_cost()
else:
raise ValueError("Cannot find transaction unit price for this item")
def age_on_marketplace(self) -> int:
if not self.marketplace_listing:
return 0
return (datetime.now() - self.marketplace_listing[0].listing_date).days
class InventoryItemContextFactory:
def __init__(self, db: Session):
self.db = db
def get_context(self, item: InventoryItem) -> InventoryItemContext:
return InventoryItemContext(item, self.db)
def get_context_for_product(self, product: TCGPlayerProduct) -> InventoryProductContext:
return InventoryProductContext(product, self.db)

View File

@ -0,0 +1,27 @@
from sqlalchemy.orm import Session
from app.models.tcgplayer_products import TCGPlayerProduct
class InventoryProductContext:
def __init__(self, product: TCGPlayerProduct, db: Session):
self.product = product
self.prices = product.most_recent_tcgplayer_price
self.db = db
@property
def market_price(self) -> float:
return self.product.most_recent_tcgplayer_price.market_price
@property
def low_price(self) -> float:
return self.product.most_recent_tcgplayer_price.low_price
@property
def name(self) -> str:
return self.product.name
@property
def image_url(self) -> str:
return self.product.image_url
@property
def id(self) -> int:
return self.product.id

View File

@ -0,0 +1,94 @@
Name,Set code,Set name,Collector number,Foil,Rarity,Quantity,ManaBox ID,Scryfall ID,Purchase price,Misprint,Altered,Condition,Language,Purchase price currency
Undergrowth Leopard,TDM,Tarkir: Dragonstorm,165,foil,common,1,104307,67ab8f9a-b17c-452f-b4ef-a3f91909e3de,0.08,false,false,near_mint,en,USD
Gurmag Nightwatch,TDM,Tarkir: Dragonstorm,190,foil,common,1,104369,de731430-6bbf-4782-953e-b69c46353959,0.03,false,false,near_mint,en,USD
Mystic Monastery,TDM,Tarkir: Dragonstorm,262,foil,uncommon,1,104945,c7b8a01c-c400-47c7-8270-78902efe850e,0.21,false,false,near_mint,en,USD
Stormshriek Feral // Flush Out,TDM,Tarkir: Dragonstorm,124,foil,common,1,104447,0ec92c44-7cf0-48a5-a3ca-bc633496d887,0.1,false,false,near_mint,en,USD
Reigning Victor,TDM,Tarkir: Dragonstorm,216,foil,common,1,104334,a394112a-032b-4047-887a-6522cf7b83d5,0.02,false,false,near_mint,en,USD
Dragonbroods' Relic,TDM,Tarkir: Dragonstorm,140,foil,uncommon,1,104569,3d634087-77ba-4543-aa7a-8a3774d69cd7,0.13,false,false,near_mint,en,USD
Sagu Wildling // Roost Seek,TDM,Tarkir: Dragonstorm,306,foil,common,1,104903,b72ee8f9-5e79-4f77-ae7e-e4c274f78187,0.13,false,false,near_mint,en,USD
Sibsig Appraiser,TDM,Tarkir: Dragonstorm,56,foil,common,1,105135,670c5b96-bac6-449b-a2bd-cb43750d3911,0.05,false,false,near_mint,en,USD
Sage of the Fang,TDM,Tarkir: Dragonstorm,155,foil,uncommon,1,105123,1ebf4a9d-d90c-4017-9f00-fca89899f301,0.09,false,false,near_mint,en,USD
Snowmelt Stag,TDM,Tarkir: Dragonstorm,57,foil,common,1,104869,a6b3b131-704a-4586-84f8-db465cd4a277,0.04,false,false,near_mint,en,USD
Tranquil Cove,TDM,Tarkir: Dragonstorm,270,foil,common,1,104249,1c4efa6c-4f29-41cd-a728-bf0e479ace05,0.07,false,false,near_mint,en,USD
Rally the Monastery,TDM,Tarkir: Dragonstorm,19,foil,uncommon,1,104136,b56e0037-8143-4c13-83e1-0c3f44e685ea,0.22,false,false,near_mint,en,USD
Dragon's Prey,TDM,Tarkir: Dragonstorm,79,foil,common,1,104754,7a6004ff-4180-4332-8b51-960f8c7521d9,0.03,false,false,near_mint,en,USD
Ambling Stormshell,TDM,Tarkir: Dragonstorm,37,foil,rare,1,104942,c74d4a57-0f66-4965-9ed7-f88a08aa1d15,0.45,false,false,near_mint,en,USD
Mountain,TDM,Tarkir: Dragonstorm,275,foil,common,1,104397,fe0865ba-47c0-40bc-b0c6-e1ea5ae08a98,1.83,false,false,near_mint,en,USD
Mardu Devotee,TDM,Tarkir: Dragonstorm,16,foil,common,1,104366,da45e9b0-a4f6-413b-9e62-666c511eb5b0,0.09,false,false,near_mint,en,USD
Swiftwater Cliffs,TDM,Tarkir: Dragonstorm,268,foil,common,1,104361,ca53fb19-b8ca-485b-af1a-5117ae54bfe3,0.11,false,false,near_mint,en,USD
Adorned Crocodile,TDM,Tarkir: Dragonstorm,69,foil,common,1,105159,bb13a34b-6ac8-47cb-9e91-47106a585fc1,0.05,false,false,near_mint,en,USD
Dusyut Earthcarver,TDM,Tarkir: Dragonstorm,141,foil,common,1,104352,b98ecc96-f557-479a-8685-2b5487d5b407,0.02,false,false,near_mint,en,USD
Knockout Maneuver,TDM,Tarkir: Dragonstorm,147,foil,uncommon,1,105149,9d218831-2a41-46a3-8e9d-93462cae5cab,0.07,false,false,near_mint,en,USD
Roiling Dragonstorm,TDM,Tarkir: Dragonstorm,55,foil,uncommon,1,104280,455f4c96-684b-4b14-bd21-6799da2e1fa7,0.22,false,false,near_mint,en,USD
Dragonclaw Strike,TDM,Tarkir: Dragonstorm,180,foil,uncommon,1,105161,bc7692ef-7091-4365-85a8-1edbd374f279,0.12,false,false,near_mint,en,USD
Seize Opportunity,TDM,Tarkir: Dragonstorm,119,foil,common,1,104391,f7818d28-b9a5-4341-9adc-666070b8878d,0.03,false,false,near_mint,en,USD
Shock Brigade,TDM,Tarkir: Dragonstorm,120,foil,common,1,104700,66940466-8e9d-4a85-bfb0-e92189b7a121,0.11,false,false,near_mint,en,USD
Hardened Tactician,TDM,Tarkir: Dragonstorm,191,foil,uncommon,1,104780,86b225cb-5c45-4da1-a64e-b04091e483e8,0.43,false,false,near_mint,en,USD
Stormplain Detainment,TDM,Tarkir: Dragonstorm,28,foil,common,1,104135,39f3aab5-7b54-4b55-8114-c6f9f79c255d,0.04,false,false,near_mint,en,USD
Formation Breaker,TDM,Tarkir: Dragonstorm,143,foil,uncommon,1,105136,67ab8e8f-3ef6-4339-8c66-68c5aca4867a,0.08,false,false,near_mint,en,USD
Duty Beyond Death,TDM,Tarkir: Dragonstorm,10,foil,uncommon,1,104265,2e92640d-768b-4357-905f-bea017d351cc,1.11,false,false,near_mint,en,USD
Piercing Exhale,TDM,Tarkir: Dragonstorm,151,foil,common,1,104891,b2a0deb9-5bc3-42d5-9e1e-5f463d176aef,0.04,false,false,near_mint,en,USD
Trade Route Envoy,TDM,Tarkir: Dragonstorm,163,foil,common,1,105174,f0c89d95-d697-4cfa-9dfa-52d7adb96176,0.05,false,false,near_mint,en,USD
Thornwood Falls,TDM,Tarkir: Dragonstorm,269,foil,common,1,104376,ebb502c2-5fd0-46a9-b77d-010f4a942056,0.07,false,false,near_mint,en,USD
Kin-Tree Nurturer,TDM,Tarkir: Dragonstorm,83,foil,common,1,105124,2177ef64-28bf-4acf-b1f1-c1408f03c411,0.03,false,false,near_mint,en,USD
Rebellious Strike,TDM,Tarkir: Dragonstorm,20,foil,common,1,104949,c9bafe19-3bd6-4da0-b3e5-e0b89262504c,0.06,false,false,near_mint,en,USD
Scoured Barrens,TDM,Tarkir: Dragonstorm,267,foil,common,1,104346,b4b47b80-69ed-44b0-afa0-ca90206dc16d,0.06,false,false,near_mint,en,USD
Avenger of the Fallen,TDM,Tarkir: Dragonstorm,73,foil,rare,1,104984,d5397366-151f-46b0-b9b2-fa4d5bd892d8,0.68,false,false,near_mint,en,USD
Dismal Backwater,TDM,Tarkir: Dragonstorm,254,foil,common,1,104238,082b52c9-c46e-44d3-b723-546ba528e07b,0.07,false,false,near_mint,en,USD
Rediscover the Way,TDM,Tarkir: Dragonstorm,215,normal,rare,1,104313,79d6decf-afd5-4e96-b87e-fd7ab7e3c068,0.19,false,false,near_mint,en,USD
Auroral Procession,TDM,Tarkir: Dragonstorm,169,normal,uncommon,1,104701,672f94ad-65d6-4c7d-925d-165ef264626f,0.22,false,false,near_mint,en,USD
Runescale Stormbrood // Chilling Screech,TDM,Tarkir: Dragonstorm,316,normal,uncommon,1,104733,72e8f916-5a01-4918-bcb5-7fd69fe32785,0.31,false,false,near_mint,en,USD
Stadium Headliner,TDM,Tarkir: Dragonstorm,122,normal,rare,1,104552,37d4ab2a-a06a-4768-b5e1-e1def957d7f4,0.44,false,false,near_mint,en,USD
Nature's Rhythm,TDM,Tarkir: Dragonstorm,150,normal,rare,1,104460,1397d904-c51d-451e-8505-7f3118acc1f6,3.08,false,false,near_mint,en,USD
Karakyk Guardian,TDM,Tarkir: Dragonstorm,198,normal,uncommon,1,104859,a4c77b08-c3f6-4458-8636-f226f9843b6d,0.08,false,false,near_mint,en,USD
"Anafenza, Unyielding Lineage",TDM,Tarkir: Dragonstorm,2,normal,rare,1,104258,29957f49-9a6b-42f6-b2fb-b48f653ab725,0.22,false,false,near_mint,en,USD
"Narset, Jeskai Waymaster",TDM,Tarkir: Dragonstorm,209,normal,rare,1,103995,6b77cbc1-dbc8-44d9-aa29-15cbb19afecd,0.22,false,false,near_mint,en,USD
Stillness in Motion,TDM,Tarkir: Dragonstorm,59,normal,rare,1,104864,a6289251-17e4-4987-96b9-2fb1a8f90e2a,0.17,false,false,near_mint,en,USD
Thunder of Unity,TDM,Tarkir: Dragonstorm,231,normal,rare,1,104671,5c953b36-f5e4-4258-91cb-f07e799321f7,0.14,false,false,near_mint,en,USD
The Sibsig Ceremony,TDM,Tarkir: Dragonstorm,340,normal,rare,1,104719,6daa156c-478f-47dd-9284-b95e82ccfd68,0.67,false,false,near_mint,en,USD
Tersa Lightshatter,TDM,Tarkir: Dragonstorm,127,normal,rare,1,104825,99e96b34-b1c4-4647-a38e-2cf1aedaaace,2.31,false,false,near_mint,en,USD
Forest,TDM,Tarkir: Dragonstorm,286,normal,common,1,104324,8e3e83d2-96ba-4d5c-a1ed-6c08a90b339c,0.07,false,false,near_mint,en,USD
Windcrag Siege,TDM,Tarkir: Dragonstorm,235,normal,rare,1,104534,31a8329b-23a1-4c49-a579-a5da8d01435a,1.68,false,false,near_mint,en,USD
Mountain,TDM,Tarkir: Dragonstorm,284,normal,common,1,104274,3df7c206-97b6-49d7-ba01-7a35fd8c61d9,0.05,false,false,near_mint,en,USD
Inevitable Defeat,TDM,Tarkir: Dragonstorm,194,normal,rare,1,103997,9d677980-b608-407e-9f17-790a81263f15,0.28,false,false,near_mint,en,USD
Dalkovan Encampment,TDM,Tarkir: Dragonstorm,394,normal,rare,1,104670,5af006f6-135e-4ea0-8ce4-7824934e87da,0.72,false,false,near_mint,en,USD
Host of the Hereafter,TDM,Tarkir: Dragonstorm,193,normal,uncommon,1,104448,0f182957-8133-45a7-80a3-1944bead4d43,0.14,false,false,near_mint,en,USD
"Sarkhan, Dragon Ascendant",TDM,Tarkir: Dragonstorm,118,normal,rare,1,104003,c2200646-7b7c-489d-bbae-16b03e1d7fb2,0.32,false,false,near_mint,en,USD
Stormscale Scion,TDM,Tarkir: Dragonstorm,123,normal,mythic,1,103987,0ac43386-bd32-425c-8776-cec00b064cbc,6.78,false,false,near_mint,en,USD
Dragon Sniper,TDM,Tarkir: Dragonstorm,139,normal,uncommon,1,105120,074b1e00-45bb-4436-8f5e-058512b2d08a,0.25,false,false,near_mint,en,USD
Island,TDM,Tarkir: Dragonstorm,273,normal,common,1,104276,4208e66c-8c98-4c48-ab07-8523c0b26ca4,1.02,false,false,near_mint,en,USD
Yathan Roadwatcher,TDM,Tarkir: Dragonstorm,236,normal,rare,1,104800,8e77339b-dd82-481c-9ee2-4156ca69ad35,0.14,false,false,near_mint,en,USD
Nomad Outpost,TDM,Tarkir: Dragonstorm,263,normal,uncommon,1,104868,a68fbeaa-941f-4d53-becd-f93ed22b9a54,0.12,false,false,near_mint,en,USD
Sage of the Skies,TDM,Tarkir: Dragonstorm,22,normal,rare,1,104710,6ade6918-6d1d-448d-ab56-93996051e9a9,0.21,false,false,near_mint,en,USD
Kheru Goldkeeper,TDM,Tarkir: Dragonstorm,199,normal,uncommon,1,104798,8d11183a-57f5-4ddb-8a6e-15fff704b114,0.18,false,false,near_mint,en,USD
All-Out Assault,TDM,Tarkir: Dragonstorm,167,normal,mythic,1,104348,b74876d8-f6a6-4b47-b960-b01a331bab01,4.11,false,false,near_mint,en,USD
Winternight Stories,TDM,Tarkir: Dragonstorm,67,normal,rare,1,104693,64d9367c-f50c-4568-aa63-6760c44ecaeb,0.44,false,false,near_mint,en,USD
New Way Forward,TDM,Tarkir: Dragonstorm,211,normal,rare,1,104996,d9d48f9e-79f0-478c-9db0-ff7ac4a8f401,0.17,false,false,near_mint,en,USD
Strategic Betrayal,TDM,Tarkir: Dragonstorm,94,normal,uncommon,1,105145,95617742-548d-464a-bb89-a858ffa9018f,0.18,false,false,near_mint,en,USD
Opulent Palace,TDM,Tarkir: Dragonstorm,264,normal,uncommon,1,104491,21cb3b3b-0738-4c2e-a3fc-927fd6b9d3fb,0.14,false,false,near_mint,en,USD
United Battlefront,TDM,Tarkir: Dragonstorm,32,normal,rare,1,104370,dff398be-4ba4-4976-9acc-be99d2e07a61,0.51,false,false,near_mint,en,USD
Temur Battlecrier,TDM,Tarkir: Dragonstorm,228,normal,rare,1,104309,72184791-0767-4108-920c-763e92dae2d4,0.68,false,false,near_mint,en,USD
"Kotis, the Fangkeeper",TDM,Tarkir: Dragonstorm,362,normal,rare,1,104388,f70098f2-e5a8-4056-b5b3-1229fc290c51,0.48,false,false,near_mint,en,USD
Forest,TDM,Tarkir: Dragonstorm,285,normal,common,1,104317,8100bceb-ffba-487a-bb45-4fe2a156a8dc,0.06,false,false,near_mint,en,USD
Dragonfire Blade,TDM,Tarkir: Dragonstorm,240,normal,rare,1,104427,031afea3-fbfb-4663-a8cc-9b7eb7b16020,0.64,false,false,near_mint,en,USD
Great Arashin City,TDM,Tarkir: Dragonstorm,257,normal,rare,1,105033,ecba23b6-9f3a-431e-bc22-f1fb04d27b68,0.33,false,false,near_mint,en,USD
Smile at Death,TDM,Tarkir: Dragonstorm,24,normal,mythic,1,104000,ae2da18f-0d7d-446c-b463-8bf170ed95da,3.51,false,false,near_mint,en,USD
Maelstrom of the Spirit Dragon,TDM,Tarkir: Dragonstorm,260,normal,rare,1,104359,c4e90bfb-d9a5-48a9-9ff9-b0f50a813eee,1.31,false,false,near_mint,en,USD
Eshki Dragonclaw,TDM,Tarkir: Dragonstorm,182,normal,rare,1,104445,0d369c44-78ee-4f3c-bf2b-cddba7fe26d4,0.19,false,false,near_mint,en,USD
Skirmish Rhino,TDM,Tarkir: Dragonstorm,224,normal,uncommon,1,103992,4a2e9ba1-c254-41e3-9845-4e81f9fec38d,0.15,false,false,near_mint,en,USD
"Teval, Arbiter of Virtue",TDM,Tarkir: Dragonstorm,373,normal,mythic,1,104332,a19c38bc-946c-438a-ac8b-f59ff0b4c613,7.06,false,false,near_mint,en,USD
"Ureni, the Song Unending",TDM,Tarkir: Dragonstorm,233,normal,mythic,1,104253,227802c0-4ff6-43a8-a850-ed0f546dc5ac,3.79,false,false,near_mint,en,USD
Hardened Tactician,TDM,Tarkir: Dragonstorm,191,normal,uncommon,1,104780,86b225cb-5c45-4da1-a64e-b04091e483e8,1.0,false,false,near_mint,en,USD
Sandsteppe Citadel,TDM,Tarkir: Dragonstorm,266,normal,uncommon,1,104603,47f47e7f-39ba-4807-8e32-7262a61dfbba,0.13,false,false,near_mint,en,USD
"Kotis, the Fangkeeper",TDM,Tarkir: Dragonstorm,202,normal,rare,1,104364,d3736f17-f80b-4b2c-b919-2c963bc14682,0.28,false,false,near_mint,en,USD
Magmatic Hellkite,TDM,Tarkir: Dragonstorm,111,normal,rare,1,104895,b3b3aec8-d931-4c7f-86b5-1e7dfb717b59,0.56,false,false,near_mint,en,USD
Dalkovan Encampment,TDM,Tarkir: Dragonstorm,253,normal,rare,1,104822,98ad5f0c-8775-4e89-8e92-84a6ade93e35,0.38,false,false,near_mint,en,USD
Ambling Stormshell,TDM,Tarkir: Dragonstorm,37,normal,rare,1,104942,c74d4a57-0f66-4965-9ed7-f88a08aa1d15,0.18,false,false,near_mint,en,USD
Hollowmurk Siege,TDM,Tarkir: Dragonstorm,192,normal,rare,1,104668,5ac0e136-8877-4bfc-a831-2bf7b7b5ad1e,0.53,false,false,near_mint,en,USD
Avenger of the Fallen,TDM,Tarkir: Dragonstorm,73,normal,rare,1,104984,d5397366-151f-46b0-b9b2-fa4d5bd892d8,0.34,false,false,near_mint,en,USD
Mystic Monastery,TDM,Tarkir: Dragonstorm,262,normal,uncommon,1,104945,c7b8a01c-c400-47c7-8270-78902efe850e,0.11,false,false,near_mint,en,USD
Songcrafter Mage,TDM,Tarkir: Dragonstorm,225,normal,rare,1,104813,9523bc07-49e5-409c-ae6b-b28e305eef36,0.35,false,false,near_mint,en,USD
Misty Rainforest,SPG,Special Guests,111,normal,mythic,1,104321,894105c4-d3ce-4d38-855b-24aa47b112c1,32.31,false,false,near_mint,en,USD
Tempest Hawk,TDM,Tarkir: Dragonstorm,31,normal,common,3,104587,422f9453-ab12-4e3c-8c51-be87391395a1,0.92,false,false,near_mint,en,USD
Duty Beyond Death,TDM,Tarkir: Dragonstorm,10,normal,uncommon,2,104265,2e92640d-768b-4357-905f-bea017d351cc,0.33,false,false,near_mint,en,USD
Heritage Reclamation,TDM,Tarkir: Dragonstorm,145,normal,common,2,104636,4f8fee37-a050-4329-8b10-46d150e7a95e,0.2,false,false,near_mint,en,USD
1 Name Set code Set name Collector number Foil Rarity Quantity ManaBox ID Scryfall ID Purchase price Misprint Altered Condition Language Purchase price currency
2 Undergrowth Leopard TDM Tarkir: Dragonstorm 165 foil common 1 104307 67ab8f9a-b17c-452f-b4ef-a3f91909e3de 0.08 false false near_mint en USD
3 Gurmag Nightwatch TDM Tarkir: Dragonstorm 190 foil common 1 104369 de731430-6bbf-4782-953e-b69c46353959 0.03 false false near_mint en USD
4 Mystic Monastery TDM Tarkir: Dragonstorm 262 foil uncommon 1 104945 c7b8a01c-c400-47c7-8270-78902efe850e 0.21 false false near_mint en USD
5 Stormshriek Feral // Flush Out TDM Tarkir: Dragonstorm 124 foil common 1 104447 0ec92c44-7cf0-48a5-a3ca-bc633496d887 0.1 false false near_mint en USD
6 Reigning Victor TDM Tarkir: Dragonstorm 216 foil common 1 104334 a394112a-032b-4047-887a-6522cf7b83d5 0.02 false false near_mint en USD
7 Dragonbroods' Relic TDM Tarkir: Dragonstorm 140 foil uncommon 1 104569 3d634087-77ba-4543-aa7a-8a3774d69cd7 0.13 false false near_mint en USD
8 Sagu Wildling // Roost Seek TDM Tarkir: Dragonstorm 306 foil common 1 104903 b72ee8f9-5e79-4f77-ae7e-e4c274f78187 0.13 false false near_mint en USD
9 Sibsig Appraiser TDM Tarkir: Dragonstorm 56 foil common 1 105135 670c5b96-bac6-449b-a2bd-cb43750d3911 0.05 false false near_mint en USD
10 Sage of the Fang TDM Tarkir: Dragonstorm 155 foil uncommon 1 105123 1ebf4a9d-d90c-4017-9f00-fca89899f301 0.09 false false near_mint en USD
11 Snowmelt Stag TDM Tarkir: Dragonstorm 57 foil common 1 104869 a6b3b131-704a-4586-84f8-db465cd4a277 0.04 false false near_mint en USD
12 Tranquil Cove TDM Tarkir: Dragonstorm 270 foil common 1 104249 1c4efa6c-4f29-41cd-a728-bf0e479ace05 0.07 false false near_mint en USD
13 Rally the Monastery TDM Tarkir: Dragonstorm 19 foil uncommon 1 104136 b56e0037-8143-4c13-83e1-0c3f44e685ea 0.22 false false near_mint en USD
14 Dragon's Prey TDM Tarkir: Dragonstorm 79 foil common 1 104754 7a6004ff-4180-4332-8b51-960f8c7521d9 0.03 false false near_mint en USD
15 Ambling Stormshell TDM Tarkir: Dragonstorm 37 foil rare 1 104942 c74d4a57-0f66-4965-9ed7-f88a08aa1d15 0.45 false false near_mint en USD
16 Mountain TDM Tarkir: Dragonstorm 275 foil common 1 104397 fe0865ba-47c0-40bc-b0c6-e1ea5ae08a98 1.83 false false near_mint en USD
17 Mardu Devotee TDM Tarkir: Dragonstorm 16 foil common 1 104366 da45e9b0-a4f6-413b-9e62-666c511eb5b0 0.09 false false near_mint en USD
18 Swiftwater Cliffs TDM Tarkir: Dragonstorm 268 foil common 1 104361 ca53fb19-b8ca-485b-af1a-5117ae54bfe3 0.11 false false near_mint en USD
19 Adorned Crocodile TDM Tarkir: Dragonstorm 69 foil common 1 105159 bb13a34b-6ac8-47cb-9e91-47106a585fc1 0.05 false false near_mint en USD
20 Dusyut Earthcarver TDM Tarkir: Dragonstorm 141 foil common 1 104352 b98ecc96-f557-479a-8685-2b5487d5b407 0.02 false false near_mint en USD
21 Knockout Maneuver TDM Tarkir: Dragonstorm 147 foil uncommon 1 105149 9d218831-2a41-46a3-8e9d-93462cae5cab 0.07 false false near_mint en USD
22 Roiling Dragonstorm TDM Tarkir: Dragonstorm 55 foil uncommon 1 104280 455f4c96-684b-4b14-bd21-6799da2e1fa7 0.22 false false near_mint en USD
23 Dragonclaw Strike TDM Tarkir: Dragonstorm 180 foil uncommon 1 105161 bc7692ef-7091-4365-85a8-1edbd374f279 0.12 false false near_mint en USD
24 Seize Opportunity TDM Tarkir: Dragonstorm 119 foil common 1 104391 f7818d28-b9a5-4341-9adc-666070b8878d 0.03 false false near_mint en USD
25 Shock Brigade TDM Tarkir: Dragonstorm 120 foil common 1 104700 66940466-8e9d-4a85-bfb0-e92189b7a121 0.11 false false near_mint en USD
26 Hardened Tactician TDM Tarkir: Dragonstorm 191 foil uncommon 1 104780 86b225cb-5c45-4da1-a64e-b04091e483e8 0.43 false false near_mint en USD
27 Stormplain Detainment TDM Tarkir: Dragonstorm 28 foil common 1 104135 39f3aab5-7b54-4b55-8114-c6f9f79c255d 0.04 false false near_mint en USD
28 Formation Breaker TDM Tarkir: Dragonstorm 143 foil uncommon 1 105136 67ab8e8f-3ef6-4339-8c66-68c5aca4867a 0.08 false false near_mint en USD
29 Duty Beyond Death TDM Tarkir: Dragonstorm 10 foil uncommon 1 104265 2e92640d-768b-4357-905f-bea017d351cc 1.11 false false near_mint en USD
30 Piercing Exhale TDM Tarkir: Dragonstorm 151 foil common 1 104891 b2a0deb9-5bc3-42d5-9e1e-5f463d176aef 0.04 false false near_mint en USD
31 Trade Route Envoy TDM Tarkir: Dragonstorm 163 foil common 1 105174 f0c89d95-d697-4cfa-9dfa-52d7adb96176 0.05 false false near_mint en USD
32 Thornwood Falls TDM Tarkir: Dragonstorm 269 foil common 1 104376 ebb502c2-5fd0-46a9-b77d-010f4a942056 0.07 false false near_mint en USD
33 Kin-Tree Nurturer TDM Tarkir: Dragonstorm 83 foil common 1 105124 2177ef64-28bf-4acf-b1f1-c1408f03c411 0.03 false false near_mint en USD
34 Rebellious Strike TDM Tarkir: Dragonstorm 20 foil common 1 104949 c9bafe19-3bd6-4da0-b3e5-e0b89262504c 0.06 false false near_mint en USD
35 Scoured Barrens TDM Tarkir: Dragonstorm 267 foil common 1 104346 b4b47b80-69ed-44b0-afa0-ca90206dc16d 0.06 false false near_mint en USD
36 Avenger of the Fallen TDM Tarkir: Dragonstorm 73 foil rare 1 104984 d5397366-151f-46b0-b9b2-fa4d5bd892d8 0.68 false false near_mint en USD
37 Dismal Backwater TDM Tarkir: Dragonstorm 254 foil common 1 104238 082b52c9-c46e-44d3-b723-546ba528e07b 0.07 false false near_mint en USD
38 Rediscover the Way TDM Tarkir: Dragonstorm 215 normal rare 1 104313 79d6decf-afd5-4e96-b87e-fd7ab7e3c068 0.19 false false near_mint en USD
39 Auroral Procession TDM Tarkir: Dragonstorm 169 normal uncommon 1 104701 672f94ad-65d6-4c7d-925d-165ef264626f 0.22 false false near_mint en USD
40 Runescale Stormbrood // Chilling Screech TDM Tarkir: Dragonstorm 316 normal uncommon 1 104733 72e8f916-5a01-4918-bcb5-7fd69fe32785 0.31 false false near_mint en USD
41 Stadium Headliner TDM Tarkir: Dragonstorm 122 normal rare 1 104552 37d4ab2a-a06a-4768-b5e1-e1def957d7f4 0.44 false false near_mint en USD
42 Nature's Rhythm TDM Tarkir: Dragonstorm 150 normal rare 1 104460 1397d904-c51d-451e-8505-7f3118acc1f6 3.08 false false near_mint en USD
43 Karakyk Guardian TDM Tarkir: Dragonstorm 198 normal uncommon 1 104859 a4c77b08-c3f6-4458-8636-f226f9843b6d 0.08 false false near_mint en USD
44 Anafenza, Unyielding Lineage TDM Tarkir: Dragonstorm 2 normal rare 1 104258 29957f49-9a6b-42f6-b2fb-b48f653ab725 0.22 false false near_mint en USD
45 Narset, Jeskai Waymaster TDM Tarkir: Dragonstorm 209 normal rare 1 103995 6b77cbc1-dbc8-44d9-aa29-15cbb19afecd 0.22 false false near_mint en USD
46 Stillness in Motion TDM Tarkir: Dragonstorm 59 normal rare 1 104864 a6289251-17e4-4987-96b9-2fb1a8f90e2a 0.17 false false near_mint en USD
47 Thunder of Unity TDM Tarkir: Dragonstorm 231 normal rare 1 104671 5c953b36-f5e4-4258-91cb-f07e799321f7 0.14 false false near_mint en USD
48 The Sibsig Ceremony TDM Tarkir: Dragonstorm 340 normal rare 1 104719 6daa156c-478f-47dd-9284-b95e82ccfd68 0.67 false false near_mint en USD
49 Tersa Lightshatter TDM Tarkir: Dragonstorm 127 normal rare 1 104825 99e96b34-b1c4-4647-a38e-2cf1aedaaace 2.31 false false near_mint en USD
50 Forest TDM Tarkir: Dragonstorm 286 normal common 1 104324 8e3e83d2-96ba-4d5c-a1ed-6c08a90b339c 0.07 false false near_mint en USD
51 Windcrag Siege TDM Tarkir: Dragonstorm 235 normal rare 1 104534 31a8329b-23a1-4c49-a579-a5da8d01435a 1.68 false false near_mint en USD
52 Mountain TDM Tarkir: Dragonstorm 284 normal common 1 104274 3df7c206-97b6-49d7-ba01-7a35fd8c61d9 0.05 false false near_mint en USD
53 Inevitable Defeat TDM Tarkir: Dragonstorm 194 normal rare 1 103997 9d677980-b608-407e-9f17-790a81263f15 0.28 false false near_mint en USD
54 Dalkovan Encampment TDM Tarkir: Dragonstorm 394 normal rare 1 104670 5af006f6-135e-4ea0-8ce4-7824934e87da 0.72 false false near_mint en USD
55 Host of the Hereafter TDM Tarkir: Dragonstorm 193 normal uncommon 1 104448 0f182957-8133-45a7-80a3-1944bead4d43 0.14 false false near_mint en USD
56 Sarkhan, Dragon Ascendant TDM Tarkir: Dragonstorm 118 normal rare 1 104003 c2200646-7b7c-489d-bbae-16b03e1d7fb2 0.32 false false near_mint en USD
57 Stormscale Scion TDM Tarkir: Dragonstorm 123 normal mythic 1 103987 0ac43386-bd32-425c-8776-cec00b064cbc 6.78 false false near_mint en USD
58 Dragon Sniper TDM Tarkir: Dragonstorm 139 normal uncommon 1 105120 074b1e00-45bb-4436-8f5e-058512b2d08a 0.25 false false near_mint en USD
59 Island TDM Tarkir: Dragonstorm 273 normal common 1 104276 4208e66c-8c98-4c48-ab07-8523c0b26ca4 1.02 false false near_mint en USD
60 Yathan Roadwatcher TDM Tarkir: Dragonstorm 236 normal rare 1 104800 8e77339b-dd82-481c-9ee2-4156ca69ad35 0.14 false false near_mint en USD
61 Nomad Outpost TDM Tarkir: Dragonstorm 263 normal uncommon 1 104868 a68fbeaa-941f-4d53-becd-f93ed22b9a54 0.12 false false near_mint en USD
62 Sage of the Skies TDM Tarkir: Dragonstorm 22 normal rare 1 104710 6ade6918-6d1d-448d-ab56-93996051e9a9 0.21 false false near_mint en USD
63 Kheru Goldkeeper TDM Tarkir: Dragonstorm 199 normal uncommon 1 104798 8d11183a-57f5-4ddb-8a6e-15fff704b114 0.18 false false near_mint en USD
64 All-Out Assault TDM Tarkir: Dragonstorm 167 normal mythic 1 104348 b74876d8-f6a6-4b47-b960-b01a331bab01 4.11 false false near_mint en USD
65 Winternight Stories TDM Tarkir: Dragonstorm 67 normal rare 1 104693 64d9367c-f50c-4568-aa63-6760c44ecaeb 0.44 false false near_mint en USD
66 New Way Forward TDM Tarkir: Dragonstorm 211 normal rare 1 104996 d9d48f9e-79f0-478c-9db0-ff7ac4a8f401 0.17 false false near_mint en USD
67 Strategic Betrayal TDM Tarkir: Dragonstorm 94 normal uncommon 1 105145 95617742-548d-464a-bb89-a858ffa9018f 0.18 false false near_mint en USD
68 Opulent Palace TDM Tarkir: Dragonstorm 264 normal uncommon 1 104491 21cb3b3b-0738-4c2e-a3fc-927fd6b9d3fb 0.14 false false near_mint en USD
69 United Battlefront TDM Tarkir: Dragonstorm 32 normal rare 1 104370 dff398be-4ba4-4976-9acc-be99d2e07a61 0.51 false false near_mint en USD
70 Temur Battlecrier TDM Tarkir: Dragonstorm 228 normal rare 1 104309 72184791-0767-4108-920c-763e92dae2d4 0.68 false false near_mint en USD
71 Kotis, the Fangkeeper TDM Tarkir: Dragonstorm 362 normal rare 1 104388 f70098f2-e5a8-4056-b5b3-1229fc290c51 0.48 false false near_mint en USD
72 Forest TDM Tarkir: Dragonstorm 285 normal common 1 104317 8100bceb-ffba-487a-bb45-4fe2a156a8dc 0.06 false false near_mint en USD
73 Dragonfire Blade TDM Tarkir: Dragonstorm 240 normal rare 1 104427 031afea3-fbfb-4663-a8cc-9b7eb7b16020 0.64 false false near_mint en USD
74 Great Arashin City TDM Tarkir: Dragonstorm 257 normal rare 1 105033 ecba23b6-9f3a-431e-bc22-f1fb04d27b68 0.33 false false near_mint en USD
75 Smile at Death TDM Tarkir: Dragonstorm 24 normal mythic 1 104000 ae2da18f-0d7d-446c-b463-8bf170ed95da 3.51 false false near_mint en USD
76 Maelstrom of the Spirit Dragon TDM Tarkir: Dragonstorm 260 normal rare 1 104359 c4e90bfb-d9a5-48a9-9ff9-b0f50a813eee 1.31 false false near_mint en USD
77 Eshki Dragonclaw TDM Tarkir: Dragonstorm 182 normal rare 1 104445 0d369c44-78ee-4f3c-bf2b-cddba7fe26d4 0.19 false false near_mint en USD
78 Skirmish Rhino TDM Tarkir: Dragonstorm 224 normal uncommon 1 103992 4a2e9ba1-c254-41e3-9845-4e81f9fec38d 0.15 false false near_mint en USD
79 Teval, Arbiter of Virtue TDM Tarkir: Dragonstorm 373 normal mythic 1 104332 a19c38bc-946c-438a-ac8b-f59ff0b4c613 7.06 false false near_mint en USD
80 Ureni, the Song Unending TDM Tarkir: Dragonstorm 233 normal mythic 1 104253 227802c0-4ff6-43a8-a850-ed0f546dc5ac 3.79 false false near_mint en USD
81 Hardened Tactician TDM Tarkir: Dragonstorm 191 normal uncommon 1 104780 86b225cb-5c45-4da1-a64e-b04091e483e8 1.0 false false near_mint en USD
82 Sandsteppe Citadel TDM Tarkir: Dragonstorm 266 normal uncommon 1 104603 47f47e7f-39ba-4807-8e32-7262a61dfbba 0.13 false false near_mint en USD
83 Kotis, the Fangkeeper TDM Tarkir: Dragonstorm 202 normal rare 1 104364 d3736f17-f80b-4b2c-b919-2c963bc14682 0.28 false false near_mint en USD
84 Magmatic Hellkite TDM Tarkir: Dragonstorm 111 normal rare 1 104895 b3b3aec8-d931-4c7f-86b5-1e7dfb717b59 0.56 false false near_mint en USD
85 Dalkovan Encampment TDM Tarkir: Dragonstorm 253 normal rare 1 104822 98ad5f0c-8775-4e89-8e92-84a6ade93e35 0.38 false false near_mint en USD
86 Ambling Stormshell TDM Tarkir: Dragonstorm 37 normal rare 1 104942 c74d4a57-0f66-4965-9ed7-f88a08aa1d15 0.18 false false near_mint en USD
87 Hollowmurk Siege TDM Tarkir: Dragonstorm 192 normal rare 1 104668 5ac0e136-8877-4bfc-a831-2bf7b7b5ad1e 0.53 false false near_mint en USD
88 Avenger of the Fallen TDM Tarkir: Dragonstorm 73 normal rare 1 104984 d5397366-151f-46b0-b9b2-fa4d5bd892d8 0.34 false false near_mint en USD
89 Mystic Monastery TDM Tarkir: Dragonstorm 262 normal uncommon 1 104945 c7b8a01c-c400-47c7-8270-78902efe850e 0.11 false false near_mint en USD
90 Songcrafter Mage TDM Tarkir: Dragonstorm 225 normal rare 1 104813 9523bc07-49e5-409c-ae6b-b28e305eef36 0.35 false false near_mint en USD
91 Misty Rainforest SPG Special Guests 111 normal mythic 1 104321 894105c4-d3ce-4d38-855b-24aa47b112c1 32.31 false false near_mint en USD
92 Tempest Hawk TDM Tarkir: Dragonstorm 31 normal common 3 104587 422f9453-ab12-4e3c-8c51-be87391395a1 0.92 false false near_mint en USD
93 Duty Beyond Death TDM Tarkir: Dragonstorm 10 normal uncommon 2 104265 2e92640d-768b-4357-905f-bea017d351cc 0.33 false false near_mint en USD
94 Heritage Reclamation TDM Tarkir: Dragonstorm 145 normal common 2 104636 4f8fee37-a050-4329-8b10-46d150e7a95e 0.2 false false near_mint en USD

View File

@ -0,0 +1,504 @@
Name,Set code,Set name,Collector number,Foil,Rarity,Quantity,ManaBox ID,Scryfall ID,Purchase price,Misprint,Altered,Condition,Language,Purchase price currency
"Tinybones, Bauble Burglar",FDN,Foundations,72,normal,rare,1,101414,ff3d85bc-ef2d-4251-baf4-a14bd0cee61e,0.66,false,false,near_mint,en,USD
Scrawling Crawler,FDN,Foundations,132,normal,rare,1,100912,a1176dcf-40ee-4342-aa74-791b8352e99a,4.81,false,false,near_mint,en,USD
"Giada, Font of Hope",FDN,Foundations,141,normal,rare,1,100804,8ae6fc26-cfad-4da8-98d9-49c27c24d293,1.33,false,false,near_mint,en,USD
Blasphemous Edict,FDN,Foundations,57,normal,rare,1,100168,11040ecd-3153-4029-b42b-1441bc51ec34,6.9,false,false,near_mint,en,USD
"Drakuseth, Maw of Flames",FDN,Foundations,193,normal,rare,1,100092,029b1edb-e1de-4f1c-81df-8d17f4920318,0.33,false,false,near_mint,en,USD
"Koma, World-Eater",FDN,Foundations,347,normal,rare,1,100792,8889e1ca-eec1-408b-b11e-98cc0a357a97,4.69,false,false,near_mint,en,USD
"Ghalta, Primal Hunger",FDN,Foundations,222,normal,rare,1,100635,6a9c39e4-a8cf-42dd-8d0e-45634b335546,0.54,false,false,near_mint,en,USD
Sire of Seven Deaths,FDN,Foundations,1,normal,mythic,1,100812,8d8432a7-1c8a-4cfb-947c-ecf9791063eb,18.63,false,false,near_mint,en,USD
Hero's Downfall,FDN,Foundations,319,normal,uncommon,1,101639,10cedc6d-075a-4f9b-a858-e2c29809ee33,0.39,false,false,near_mint,en,USD
"Etali, Primal Storm",FDN,Foundations,194,normal,rare,1,101037,b6af9894-95b5-4c8e-902f-a9ba70f02e4a,0.32,false,false,near_mint,en,USD
High Fae Trickster,FDN,Foundations,307,normal,rare,1,100918,a21180a4-208f-4c13-a704-58403ddaf12f,3.39,false,false,near_mint,en,USD
Mocking Sprite,FDN,Foundations,159,foil,common,1,101624,f6792f63-b651-497d-8aa5-cddf4cedeca8,0.09,false,false,near_mint,en,USD
Bake into a Pie,FDN,Foundations,169,foil,common,1,101494,2ab0e660-86a3-4b92-82fa-77dcb5db947d,0.06,false,false,near_mint,en,USD
Boltwave,FDN,Foundations,79,foil,uncommon,1,100810,8d1ec351-5e70-4eb2-b590-6bff94ef8178,4.27,false,false,near_mint,en,USD
Jungle Hollow,FDN,Foundations,263,foil,common,1,101224,dc758e14-d370-45e4-bbc5-938fb4d21127,0.08,false,false,near_mint,en,USD
Ambush Wolf,FDN,Foundations,98,foil,common,1,101492,2903832c-318e-42ab-bf58-c682ec2f7afd,0.03,false,false,near_mint,en,USD
An Offer You Can't Refuse,FDN,Foundations,160,foil,uncommon,1,100948,a829747f-cf9b-4d81-ba66-9f0630ed4565,1.51,false,false,near_mint,en,USD
Sower of Chaos,FDN,Foundations,95,foil,common,1,101556,7ff50606-491c-4946-8d03-719b01cfad77,0.02,false,false,near_mint,en,USD
Guarded Heir,FDN,Foundations,14,foil,uncommon,1,100505,525ba5c7-3ce5-4e52-b8b5-96c9040a6738,0.06,false,false,near_mint,en,USD
Wind-Scarred Crag,FDN,Foundations,271,foil,common,1,100684,759e99df-11a8-4aee-b6bc-344e84e10d94,0.08,false,false,near_mint,en,USD
Think Twice,FDN,Foundations,165,foil,common,1,101202,d88faaa1-eb41-40f7-991c-5c06e1138f3d,0.03,false,false,near_mint,en,USD
Grow from the Ashes,FDN,Foundations,225,foil,common,1,101502,42525f8a-aee7-4811-8f05-471b559c2c4a,0.07,false,false,near_mint,en,USD
Spitfire Lagac,FDN,Foundations,208,foil,common,1,101496,30f600cd-b696-4f49-9cbc-5a33aa43d04c,0.05,false,false,near_mint,en,USD
Abyssal Harvester,FDN,Foundations,54,foil,rare,1,101342,f2e0f538-5825-47e9-883c-3ec6fd5b25ea,3.18,false,false,near_mint,en,USD
Sanguine Syphoner,FDN,Foundations,68,foil,common,1,101582,b1daf5bb-c8e9-4e79-a532-ca92a9a885cd,0.19,false,false,near_mint,en,USD
Goldvein Pick,FDN,Foundations,253,foil,common,1,101572,a241317d-2277-467e-a8f9-aa71c944e244,0.06,false,false,near_mint,en,USD
Goblin Negotiation,FDN,Foundations,88,foil,uncommon,1,101335,f2016585-e26c-4d13-b09f-af6383c192f7,0.14,false,false,near_mint,en,USD
Banishing Light,FDN,Foundations,138,foil,common,1,101613,e38dc3b3-1629-491b-8afd-0e7a9a857713,0.05,false,false,near_mint,en,USD
Dauntless Veteran,FDN,Foundations,8,foil,uncommon,1,100704,7a136f26-ac66-407f-b389-357222d2c4a2,0.06,false,false,near_mint,en,USD
Run Away Together,FDN,Foundations,162,foil,common,1,101614,e598eb7b-10dc-49e6-ac60-2fefa987173e,0.02,false,false,near_mint,en,USD
"Tatyova, Benthic Druid",FDN,Foundations,247,foil,uncommon,1,101301,eabc978a-0666-472d-bdc6-d4b29d29eca4,0.14,false,false,near_mint,en,USD
"Balmor, Battlemage Captain",FDN,Foundations,237,foil,uncommon,1,100142,0b45ab13-9bb6-48af-8b37-d97b25801ac8,0.13,false,false,near_mint,en,USD
Involuntary Employment,FDN,Foundations,203,foil,common,1,101622,f3ad3d62-2f24-4562-b3fa-809213dbc4a4,0.03,false,false,near_mint,en,USD
"Dwynen, Gilt-Leaf Daen",FDN,Foundations,217,foil,uncommon,1,100086,01c00d7b-7fac-4f8c-a1ea-de2cf4d06627,0.23,false,false,near_mint,en,USD
Swiftfoot Boots,FDN,Foundations,258,foil,uncommon,1,100414,41040541-b129-4cf4-9411-09b1d9d32c19,2.03,false,false,near_mint,en,USD
Soul-Shackled Zombie,FDN,Foundations,70,foil,common,1,101609,deea5690-6eb2-4353-b917-cbbf840e4e71,0.05,false,false,near_mint,en,USD
Fake Your Own Death,FDN,Foundations,174,foil,common,1,101539,693635a6-df50-44c5-9598-0c79b45d4df4,0.09,false,false,near_mint,en,USD
Gnarlid Colony,FDN,Foundations,224,foil,common,1,101508,47565d10-96bf-4fb0-820f-f20a44a76b6f,0.05,false,false,near_mint,en,USD
Apothecary Stomper,FDN,Foundations,99,foil,common,1,101537,680b7b0c-0e1b-46ce-9917-9fc6e05aa148,0.02,false,false,near_mint,en,USD
Rugged Highlands,FDN,Foundations,265,foil,common,1,101400,fd6eaf8e-8881-4d7b-bafc-75e4ca5cbef6,0.05,false,false,near_mint,en,USD
Firebrand Archer,FDN,Foundations,196,foil,common,1,101630,fe0312f1-4c98-4b7f-8a34-0059ea80edef,0.13,false,false,near_mint,en,USD
Scoured Barrens,FDN,Foundations,266,foil,common,1,100277,2632a4b2-9ca6-4b67-9a99-14f52ad3dc41,0.12,false,false,near_mint,en,USD
Courageous Goblin,FDN,Foundations,82,foil,common,1,101566,8db6819c-666a-409d-85a5-b9ac34d8dd2f,0.02,false,false,near_mint,en,USD
Jungle Hollow,FDN,Foundations,263,normal,common,1,101224,dc758e14-d370-45e4-bbc5-938fb4d21127,0.07,false,false,near_mint,en,USD
Wind-Scarred Crag,FDN,Foundations,271,normal,common,1,100684,759e99df-11a8-4aee-b6bc-344e84e10d94,0.04,false,false,near_mint,en,USD
Dismal Backwater,FDN,Foundations,261,normal,common,1,101220,dbb0df36-8467-4a41-8e1c-6c3584d4fd10,0.06,false,false,near_mint,en,USD
Bloodfell Caves,FDN,Foundations,259,normal,common,1,100806,8b90dc92-cb66-41d9-89f9-2b6e3cfc8082,0.05,false,false,near_mint,en,USD
Rugged Highlands,FDN,Foundations,265,normal,common,1,101400,fd6eaf8e-8881-4d7b-bafc-75e4ca5cbef6,0.05,false,false,near_mint,en,USD
Scavenging Ooze,FDN,Foundations,232,normal,rare,1,100808,8c504c23-1e9a-411b-9cfe-4180d0c744f6,0.15,false,false,near_mint,en,USD
"Kiora, the Rising Tide",FDN,Foundations,45,normal,rare,1,100762,83f20a32-9f5d-4a68-8995-549e57554da2,1.57,false,false,near_mint,en,USD
Curator of Destinies,FDN,Foundations,34,normal,rare,1,100908,9ff79da7-c3f7-4541-87a0-503544c699b5,0.12,false,false,near_mint,en,USD
"Loot, Exuberant Explorer",FDN,Foundations,106,normal,rare,1,100131,09980ce6-425b-4e03-94d0-0f02043cb361,4.8,false,false,near_mint,en,USD
Micromancer,FDN,Foundations,158,normal,uncommon,1,101274,e6af54ea-b57a-4e50-8e46-1747cca14430,0.07,false,false,near_mint,en,USD
"Ruby, Daring Tracker",FDN,Foundations,245,normal,uncommon,1,101405,fe3e7dd2-b66d-4218-9fde-f84bec26b7bf,0.05,false,false,near_mint,en,USD
Mild-Mannered Librarian,FDN,Foundations,228,normal,uncommon,1,100515,5389663a-fe25-41b9-8c92-1f4d7721ffc2,0.03,false,false,near_mint,en,USD
Guarded Heir,FDN,Foundations,14,normal,uncommon,1,100505,525ba5c7-3ce5-4e52-b8b5-96c9040a6738,0.05,false,false,near_mint,en,USD
Garruk's Uprising,FDN,Foundations,220,normal,uncommon,1,100447,4805c303-e73b-443b-a09f-49d2c2c88bb5,0.25,false,false,near_mint,en,USD
Vampire Nighthawk,FDN,Foundations,186,normal,uncommon,1,101474,0a1934ab-3171-4fc6-8033-ad998899ba73,0.12,false,false,near_mint,en,USD
Soulstone Sanctuary,FDN,Foundations,133,normal,rare,1,100596,642553a7-6d0f-483d-a873-3a703786db42,1.9,false,false,near_mint,en,USD
"Balmor, Battlemage Captain",FDN,Foundations,237,normal,uncommon,1,100142,0b45ab13-9bb6-48af-8b37-d97b25801ac8,0.07,false,false,near_mint,en,USD
Adventuring Gear,FDN,Foundations,249,normal,uncommon,1,100358,361f9b99-5b5d-40da-b4b9-5ad90f6280ee,0.06,false,false,near_mint,en,USD
Grappling Kraken,FDN,Foundations,39,normal,uncommon,1,101165,d1f5cab3-3fc0-448d-8252-cd55abf5b596,0.12,false,false,near_mint,en,USD
Quakestrider Ceratops,FDN,Foundations,110,normal,uncommon,1,100120,067f72c2-ead6-4879-bc9d-696c9f87c0b2,0.11,false,false,near_mint,en,USD
Genesis Wave,FDN,Foundations,221,normal,rare,1,101177,d46f7ddb-f986-4f1f-b096-ae1a02d0bdc8,0.29,false,false,near_mint,en,USD
"Lathril, Blade of the Elves",FDN,Foundations,242,normal,rare,1,100811,8d4e5480-a287-4a25-b855-a26dae555b1c,0.25,false,false,near_mint,en,USD
Elvish Archdruid,FDN,Foundations,219,normal,rare,1,100341,341da856-7414-403b-b2e3-4bebd58a5aa4,0.4,false,false,near_mint,en,USD
Imprisoned in the Moon,FDN,Foundations,156,normal,uncommon,1,101313,ee28e147-6622-4399-a314-c14a5c912dd0,0.18,false,false,near_mint,en,USD
Inspiring Call,FDN,Foundations,226,normal,uncommon,1,100400,3e241642-5172-4437-b694-f6aa159d5cd9,0.15,false,false,near_mint,en,USD
Essence Scatter,FDN,Foundations,153,normal,uncommon,1,101226,dd05c850-f91e-4ffb-b4cc-8418d49dad90,0.04,false,false,near_mint,en,USD
Exemplar of Light,FDN,Foundations,11,normal,rare,1,100832,920c8fc5-fdd2-446a-a676-5c363f96928f,2.82,false,false,near_mint,en,USD
Meteor Golem,FDN,Foundations,256,normal,uncommon,1,101167,d291ea1e-36bc-46b3-b3ae-084fa0ba69eb,0.05,false,false,near_mint,en,USD
Swiftfoot Boots,FDN,Foundations,258,normal,uncommon,1,100414,41040541-b129-4cf4-9411-09b1d9d32c19,1.19,false,false,near_mint,en,USD
Brazen Scourge,FDN,Foundations,191,normal,uncommon,1,101616,eb84b86c-3276-4fc1-a09d-47de388cb729,0.02,false,false,near_mint,en,USD
Sylvan Scavenging,FDN,Foundations,113,normal,rare,1,101100,c35b683c-d3b2-46a1-876a-81b34e8ba2fc,0.25,false,false,near_mint,en,USD
Claws Out,FDN,Foundations,6,normal,uncommon,1,100429,4396049c-b976-4b7f-8ecd-564e24ebd631,0.1,false,false,near_mint,en,USD
Snakeskin Veil,FDN,Foundations,233,normal,uncommon,1,100645,6cc4c21d-9bdc-4490-9203-17f51db0ddd1,0.08,false,false,near_mint,en,USD
Skyship Buccaneer,FDN,Foundations,50,normal,uncommon,1,100587,62958fc3-55dc-4b97-a070-490d6ed27820,0.02,false,false,near_mint,en,USD
Arcane Epiphany,FDN,Foundations,29,normal,uncommon,1,100116,06431793-5dfe-4cbf-990b-4bcc960d1f31,0.03,false,false,near_mint,en,USD
Brass's Bounty,FDN,Foundations,190,normal,rare,1,100610,65fe7127-b0ec-400f-97f1-6e17ab8e319d,0.14,false,false,near_mint,en,USD
Fiendish Panda,FDN,Foundations,120,normal,uncommon,1,100483,4e434d74-cad0-45f5-bc8d-f34aa5e1d879,0.09,false,false,near_mint,en,USD
Frenzied Goblin,FDN,Foundations,199,normal,uncommon,1,101602,d5592573-2889-40b1-b1d5-c2802482549a,0.03,false,false,near_mint,en,USD
Lunar Insight,FDN,Foundations,46,normal,rare,1,100958,a9a159f6-fecf-4bdd-b2f8-a9665a5cc32d,0.25,false,false,near_mint,en,USD
Twinblade Blessing,FDN,Foundations,26,normal,uncommon,1,101310,ecf01cbe-9fcb-4f35-bc6b-2280620b06ff,0.1,false,false,near_mint,en,USD
"Tatyova, Benthic Druid",FDN,Foundations,247,normal,uncommon,1,101301,eabc978a-0666-472d-bdc6-d4b29d29eca4,0.06,false,false,near_mint,en,USD
Dragon Trainer,FDN,Foundations,84,normal,uncommon,1,100830,91bd75a1-cb54-4e38-9ce1-e8f32a73c6eb,0.04,false,false,near_mint,en,USD
Raise the Past,FDN,Foundations,22,normal,rare,1,100641,6c6be129-56da-4fe7-a6bd-6a1d402c09e1,2.27,false,false,near_mint,en,USD
Divine Resilience,FDN,Foundations,10,normal,uncommon,1,101347,f3a08245-a535-4d24-b8c0-78759bb9c4b0,0.11,false,false,near_mint,en,USD
Bulk Up,FDN,Foundations,80,normal,uncommon,1,100857,977dcc50-da10-4281-b522-9240c1204f5d,0.2,false,false,near_mint,en,USD
Diregraf Ghoul,FDN,Foundations,171,normal,uncommon,1,100439,4682012c-d7e0-4257-b538-3de497507464,0.03,false,false,near_mint,en,USD
Drake Hatcher,FDN,Foundations,35,normal,rare,1,101071,bcaf4196-6bf3-47fa-b5c7-0e77f45cf820,0.12,false,false,near_mint,en,USD
Youthful Valkyrie,FDN,Foundations,149,normal,uncommon,1,100894,9d795f79-c3a5-4ea1-a5cf-1ce73d6837b6,0.14,false,false,near_mint,en,USD
Seeker's Folly,FDN,Foundations,69,normal,uncommon,1,101067,bc359da6-8b7f-45ec-b530-ce159fc35953,0.06,false,false,near_mint,en,USD
Heroic Reinforcements,FDN,Foundations,241,normal,uncommon,1,100631,6a05e8d5-c2ad-489a-888d-22622886b620,0.04,false,false,near_mint,en,USD
Inspiration from Beyond,FDN,Foundations,43,normal,uncommon,1,101033,b636fe95-664f-4fb1-aab9-28856edeccd6,0.04,false,false,near_mint,en,USD
"Dwynen, Gilt-Leaf Daen",FDN,Foundations,217,normal,uncommon,1,100086,01c00d7b-7fac-4f8c-a1ea-de2cf4d06627,0.14,false,false,near_mint,en,USD
Twinflame Tyrant,FDN,Foundations,97,normal,mythic,1,100228,1eb34f51-0bd2-43c3-af95-2ce8dabcc7bb,17.77,false,false,near_mint,en,USD
Sun-Blessed Healer,FDN,Foundations,25,normal,uncommon,1,100332,323d029e-9a88-4188-b3a4-38ef32cffc9f,0.09,false,false,near_mint,en,USD
Seismic Rupture,FDN,Foundations,205,normal,uncommon,1,100268,2519a51a-26a0-4884-9ba8-9db135c9ee49,0.02,false,false,near_mint,en,USD
Slumbering Cerberus,FDN,Foundations,94,normal,uncommon,1,100892,9d06faa8-201d-45db-b398-ad56f7b01848,0.03,false,false,near_mint,en,USD
Tragic Banshee,FDN,Foundations,73,normal,uncommon,1,100324,30df3e33-2f17-4067-99f1-5db6b0f41fd4,0.03,false,false,near_mint,en,USD
Stromkirk Bloodthief,FDN,Foundations,185,normal,uncommon,1,97176,485d6a5a-2054-47d5-91b8-71ce308ed4dc,0.04,false,false,near_mint,en,USD
Blanchwood Armor,FDN,Foundations,213,normal,uncommon,1,100237,1fd7ec1a-dafa-42ca-bc25-f6848fb03f60,0.07,false,false,near_mint,en,USD
Spectral Sailor,FDN,Foundations,164,normal,uncommon,1,100100,03a49535-c5f3-4a6f-b333-7ac7bffdc9ae,0.06,false,false,near_mint,en,USD
Extravagant Replication,FDN,Foundations,154,normal,rare,1,100634,6a41dfae-bc7e-4105-8f7e-fd0109197ad8,0.43,false,false,near_mint,en,USD
Electroduplicate,FDN,Foundations,85,normal,rare,1,100976,abb06b1c-5d4e-49b9-9c4a-e60ab656a257,0.3,false,false,near_mint,en,USD
Angel of Finality,FDN,Foundations,136,normal,uncommon,1,101057,baaabd52-3aa9-4e2f-9369-d4db8b405ba8,0.07,false,false,near_mint,en,USD
Battlesong Berserker,FDN,Foundations,78,normal,uncommon,1,100917,a1f8b199-5d62-485f-b1c3-b30aa550595b,0.03,false,false,near_mint,en,USD
Swiftblade Vindicator,FDN,Foundations,246,normal,rare,1,101372,f94618ec-000c-4371-b925-05ff82bfe221,0.12,false,false,near_mint,en,USD
Dauntless Veteran,FDN,Foundations,8,normal,uncommon,1,100704,7a136f26-ac66-407f-b389-357222d2c4a2,0.05,false,false,near_mint,en,USD
Hero's Downfall,FDN,Foundations,175,normal,uncommon,1,97185,ad2c01d9-8f54-46c0-9dc9-d4d4764ce1c9,0.1,false,false,near_mint,en,USD
Resolute Reinforcements,FDN,Foundations,145,normal,uncommon,1,100841,940f3989-77cc-49a9-92e0-095a75d80f0f,0.09,false,false,near_mint,en,USD
Zombify,FDN,Foundations,187,normal,uncommon,1,101225,dc798e6f-13c4-457c-b052-b7b65bc83cfe,0.09,false,false,near_mint,en,USD
Fiery Annihilation,FDN,Foundations,86,normal,uncommon,1,100523,54fe00aa-d284-48f9-b5a2-1bd4c5fa8e58,0.07,false,false,near_mint,en,USD
Clinquant Skymage,FDN,Foundations,33,normal,uncommon,1,100357,36012810-0e83-4640-8ba7-7262229f1b84,0.05,false,false,near_mint,en,USD
Consuming Aberration,FDN,Foundations,238,normal,rare,1,101066,bc2b28fd-66b0-457c-80ea-7caed2cc7926,0.16,false,false,near_mint,en,USD
Fishing Pole,FDN,Foundations,128,normal,uncommon,1,101128,c95ab836-3277-4223-9aaa-ef2c77256b65,0.07,false,false,near_mint,en,USD
Felling Blow,FDN,Foundations,105,normal,uncommon,1,100854,96948ae3-b15d-4d6d-aa73-9f52084cd903,0.05,false,false,near_mint,en,USD
Abrade,FDN,Foundations,188,normal,uncommon,1,100522,548947dc-a5ca-43b5-9531-bcef20fa4ae5,0.09,false,false,near_mint,en,USD
Spinner of Souls,FDN,Foundations,112,normal,rare,1,101358,f50a8dec-b079-4192-9098-6cdc1026c693,0.66,false,false,near_mint,en,USD
Vampire Gourmand,FDN,Foundations,74,normal,uncommon,1,100827,917514c0-9cd5-4b97-85b9-c4f753560ad4,0.09,false,false,near_mint,en,USD
Needletooth Pack,FDN,Foundations,108,normal,uncommon,1,100868,993c1679-e02b-44f2-b34e-12fd6b5142e9,0.05,false,false,near_mint,en,USD
Burnished Hart,FDN,Foundations,250,normal,uncommon,1,100609,65ebbff0-fbe6-4310-a33f-e00bb2534979,0.06,false,false,near_mint,en,USD
Arbiter of Woe,FDN,Foundations,55,normal,uncommon,1,101008,b2496c4a-df03-4583-bd76-f98ed5cb61ee,0.06,false,false,near_mint,en,USD
Good-Fortune Unicorn,FDN,Foundations,240,normal,uncommon,1,101300,eabbe163-2b15-42e3-89ce-7363e6250d3a,0.1,false,false,near_mint,en,USD
Reassembling Skeleton,FDN,Foundations,182,normal,uncommon,1,100291,28e84b1b-1c05-4e1b-93b8-9cc2ca73509d,0.08,false,false,near_mint,en,USD
Reclamation Sage,FDN,Foundations,231,normal,uncommon,1,100197,1918ea65-ab7f-4d40-97fd-a656c892a2a1,0.14,false,false,near_mint,en,USD
Leyline Axe,FDN,Foundations,129,normal,rare,1,101052,b9c03336-a321-4c06-94d1-809f328fabd8,3.17,false,false,near_mint,en,USD
An Offer You Can't Refuse,FDN,Foundations,160,normal,uncommon,1,100948,a829747f-cf9b-4d81-ba66-9f0630ed4565,0.99,false,false,near_mint,en,USD
Goblin Negotiation,FDN,Foundations,88,normal,uncommon,1,101335,f2016585-e26c-4d13-b09f-af6383c192f7,0.09,false,false,near_mint,en,USD
Empyrean Eagle,FDN,Foundations,239,normal,uncommon,1,100533,577e99a7-4a55-4314-8f08-2ae0c33b85c7,0.08,false,false,near_mint,en,USD
Solemn Simulacrum,FDN,Foundations,257,normal,rare,1,100514,5383f45e-3da2-40fb-beee-801448bbb60f,0.3,false,false,near_mint,en,USD
Crystal Barricade,FDN,Foundations,7,normal,rare,1,100822,905d3e02-ea06-45e7-9adb-c8e7583323a2,1.24,false,false,near_mint,en,USD
Hidetsugu's Second Rite,FDN,Foundations,202,normal,uncommon,1,100577,609421da-8d89-4365-b18b-778832d91482,0.04,false,false,near_mint,en,USD
Affectionate Indrik,FDN,Foundations,211,normal,uncommon,1,100310,2da8347d-06a4-46e0-a55e-cc2da4660263,0.02,false,false,near_mint,en,USD
Infernal Vessel,FDN,Foundations,63,normal,uncommon,1,101560,877b6330-2d0b-4f2f-a848-f10b06fb4ef5,0.06,false,false,near_mint,en,USD
"Zimone, Paradox Sculptor",FDN,Foundations,126,normal,mythic,1,100241,20ccbfdd-ddae-440c-9bc0-38b15a56fdd1,2.13,false,false,near_mint,en,USD
High-Society Hunter,FDN,Foundations,61,normal,rare,1,100501,51da4a4b-ea12-4169-a7cf-eb4427f13e84,0.64,false,false,near_mint,en,USD
Heraldic Banner,FDN,Foundations,254,normal,uncommon,1,100678,743ea709-dbb3-4db8-a2ce-544f47eb6339,0.24,false,false,near_mint,en,USD
Wardens of the Cycle,FDN,Foundations,125,normal,uncommon,1,100761,83ea9b2c-5723-4eff-88ac-6669975939e3,0.07,false,false,near_mint,en,USD
Preposterous Proportions,FDN,Foundations,109,normal,rare,1,100983,acb65189-60e4-42e0-9fb1-da6b716b91d7,0.94,false,false,near_mint,en,USD
Savannah Lions,FDN,Foundations,146,normal,uncommon,1,97184,9c9ac1bc-cdf3-4fa6-8319-a7ea164e9e47,0.04,false,false,near_mint,en,USD
Secluded Courtyard,FDN,Foundations,267,normal,uncommon,1,101161,d13373d2-139b-48c7-a8c9-828cefc4f150,0.12,false,false,near_mint,en,USD
Ajani's Pridemate,FDN,Foundations,135,normal,uncommon,1,100255,222c1a68-e34c-4103-b1be-17d4ceaef6ce,0.06,false,false,near_mint,en,USD
"Arahbo, the First Fang",FDN,Foundations,2,normal,rare,1,100503,524a5d93-26ed-436d-a437-dc9460acce98,1.0,false,false,near_mint,en,USD
Authority of the Consuls,FDN,Foundations,137,normal,rare,1,100425,42ce2d7f-5924-47c0-b5ed-dacf9f9617a0,5.3,false,false,near_mint,en,USD
Nine-Lives Familiar,FDN,Foundations,321,normal,rare,1,100060,6cc1623f-370d-42b5-88a2-039f31e9be0b,2.67,false,false,near_mint,en,USD
Ajani's Pridemate,FDN,Foundations,293,foil,uncommon,1,101180,d4cfb9bc-4273-4e5f-a7ac-2006a8345a4e,0.38,false,false,near_mint,en,USD
Helpful Hunter,FDN,Foundations,16,foil,common,1,97172,1b9a0e91-80b5-428f-8f08-931d0631be14,1.61,false,false,near_mint,en,USD
Felidar Savior,FDN,Foundations,12,foil,common,1,97191,cd092b14-d72f-4de0-8f19-1338661b9e3b,0.05,false,false,near_mint,en,USD
Thrill of Possibility,FDN,Foundations,210,normal,common,3,101561,882b348c-076b-41d8-b505-063480636669,0.03,false,false,near_mint,en,USD
Lightshell Duo,FDN,Foundations,157,normal,common,7,101063,bb75315c-ea8f-4eb0-899e-c73ef75fc396,0.04,false,false,near_mint,en,USD
Mischievous Pup,FDN,Foundations,144,normal,uncommon,2,100670,7214d984-6400-44d7-bde6-57d96b606e78,0.04,false,false,near_mint,en,USD
Swiftwater Cliffs,FDN,Foundations,268,normal,common,3,101389,fb88667d-7088-4889-960f-317486ebe856,0.03,false,false,near_mint,en,USD
Hare Apparent,FDN,Foundations,15,normal,common,3,100907,9fc6f0e9-eb5f-4bc0-b3d7-756644b66d12,3.62,false,false,near_mint,en,USD
Dazzling Angel,FDN,Foundations,9,normal,common,3,101468,027dc444-e544-4693-8653-3dcdda530162,0.1,false,false,near_mint,en,USD
Bigfin Bouncer,FDN,Foundations,31,normal,common,3,100882,9b1d5b76-b07e-45c6-800d-4cfce085164f,0.02,false,false,near_mint,en,USD
Ambush Wolf,FDN,Foundations,98,normal,common,4,101492,2903832c-318e-42ab-bf58-c682ec2f7afd,0.05,false,false,near_mint,en,USD
Healer's Hawk,FDN,Foundations,142,normal,common,3,101595,cc8e4563-04bb-46b5-835e-64ba11c0e972,0.09,false,false,near_mint,en,USD
Rune-Sealed Wall,FDN,Foundations,49,normal,uncommon,2,101212,da0f147b-95ed-4f32-9b46-6a633ae31976,0.15,false,false,near_mint,en,USD
Pilfer,FDN,Foundations,181,normal,common,4,101564,8c7c88b5-6d09-453b-b9c1-7dcbba8f1080,0.03,false,false,near_mint,en,USD
Stab,FDN,Foundations,71,normal,common,3,101538,6859a5ba-1c1c-4631-bba8-f9900b827178,0.04,false,false,near_mint,en,USD
Heartfire Immolator,FDN,Foundations,201,normal,uncommon,2,100390,3ca38f4d-01f5-4a02-9000-01261a440dbf,0.03,false,false,near_mint,en,USD
Marauding Blight-Priest,FDN,Foundations,178,normal,common,3,101528,5f70dafc-c638-4ec0-ab5b-62998f752720,0.12,false,false,near_mint,en,USD
Broken Wings,FDN,Foundations,214,normal,common,3,100584,61f9cbeb-cc9c-4562-be65-8a77053faefe,0.02,false,false,near_mint,en,USD
Firespitter Whelp,FDN,Foundations,197,normal,uncommon,2,100463,4b3a4c7d-3126-4bde-9dca-cb6a1e2f37c9,0.15,false,false,near_mint,en,USD
Make Your Move,FDN,Foundations,143,normal,common,3,101546,7368f861-3288-4645-90a7-ca35d6da3721,0.03,false,false,near_mint,en,USD
Treetop Snarespinner,FDN,Foundations,114,normal,common,4,101562,88e68fa3-159d-49a6-8ac6-afc9bd6f1718,0.06,false,false,near_mint,en,USD
Vengeful Bloodwitch,FDN,Foundations,76,normal,uncommon,2,97189,bd0c12dd-f138-45c0-9614-d83a1d8e8399,0.17,false,false,near_mint,en,USD
Evolving Wilds,FDN,Foundations,262,normal,common,4,100376,3a0b9356-5b91-4542-8802-f0f7275238e1,0.06,false,false,near_mint,en,USD
Bite Down,FDN,Foundations,212,normal,common,3,101625,f8d70b3b-f6f9-4b3c-ad70-0ce369e812b5,0.04,false,false,near_mint,en,USD
Elfsworn Giant,FDN,Foundations,103,normal,common,3,100497,5128a5be-ffa6-4998-8488-872d80b24cb2,0.06,false,false,near_mint,en,USD
Apothecary Stomper,FDN,Foundations,99,normal,common,3,101537,680b7b0c-0e1b-46ce-9917-9fc6e05aa148,0.05,false,false,near_mint,en,USD
Axgard Cavalry,FDN,Foundations,189,normal,common,3,101631,fe3cc41a-adae-4c9b-b4d3-03f3ca862fed,0.03,false,false,near_mint,en,USD
Wary Thespian,FDN,Foundations,235,normal,common,3,101574,a3d62d04-0974-4cb5-9a35-5e996c6456e2,0.01,false,false,near_mint,en,USD
Fleeting Flight,FDN,Foundations,13,normal,common,3,101513,55139100-9342-41fd-b10a-8e9932e605d4,0.04,false,false,near_mint,en,USD
Quick-Draw Katana,FDN,Foundations,130,normal,common,3,101540,69beec98-c89c-4673-953c-8b3ef3d81560,0.07,false,false,near_mint,en,USD
Goblin Surprise,FDN,Foundations,200,normal,common,3,101512,527dd5d4-5f72-40bb-8a9d-1f5ac3f81e2e,0.05,false,false,near_mint,en,USD
Sower of Chaos,FDN,Foundations,95,normal,common,4,101556,7ff50606-491c-4946-8d03-719b01cfad77,0.01,false,false,near_mint,en,USD
Involuntary Employment,FDN,Foundations,203,normal,common,4,101622,f3ad3d62-2f24-4562-b3fa-809213dbc4a4,0.06,false,false,near_mint,en,USD
Burst Lightning,FDN,Foundations,192,normal,common,3,100994,aec5d380-d354-4750-931a-6c91853e2edc,0.08,false,false,near_mint,en,USD
Banishing Light,FDN,Foundations,138,normal,common,4,101613,e38dc3b3-1629-491b-8afd-0e7a9a857713,0.03,false,false,near_mint,en,USD
Blossoming Sands,FDN,Foundations,260,normal,common,2,100364,37676ed8-588c-4bca-8065-874b74d84807,0.05,false,false,near_mint,en,USD
Felidar Savior,FDN,Foundations,12,normal,common,3,97191,cd092b14-d72f-4de0-8f19-1338661b9e3b,0.02,false,false,near_mint,en,USD
Revenge of the Rats,FDN,Foundations,67,normal,uncommon,2,100232,1f463c55-39a0-4f2f-aae3-0c5540bde5b7,0.12,false,false,near_mint,en,USD
Armasaur Guide,FDN,Foundations,3,normal,common,3,101591,c80fc380-0499-4499-8a60-c43844c02c9b,0.03,false,false,near_mint,en,USD
Campus Guide,FDN,Foundations,251,normal,common,3,101504,43c59814-3167-4b05-bb85-6c736f3956a4,0.02,false,false,near_mint,en,USD
Dreadwing Scavenger,FDN,Foundations,118,normal,uncommon,2,101252,e24d838b-ab48-410a-9a50-dbfea5da089b,0.04,false,false,near_mint,en,USD
Gleaming Barrier,FDN,Foundations,252,normal,common,3,101479,1b49b009-e6f2-494a-9235-f5c25c2d70a9,0.06,false,false,near_mint,en,USD
Scoured Barrens,FDN,Foundations,266,normal,common,2,100277,2632a4b2-9ca6-4b67-9a99-14f52ad3dc41,0.07,false,false,near_mint,en,USD
Erudite Wizard,FDN,Foundations,37,normal,common,3,100835,9273c417-0fcd-4273-b24e-afff76336d0c,0.01,false,false,near_mint,en,USD
Gorehorn Raider,FDN,Foundations,89,normal,common,3,101551,78ce6c40-3452-4aa0-a45b-dbfd70f8d220,0.02,false,false,near_mint,en,USD
Cackling Prowler,FDN,Foundations,101,normal,common,3,101481,1bd8e971-c075-4203-8d83-c28f22d4f9b9,0.03,false,false,near_mint,en,USD
Burglar Rat,FDN,Foundations,170,normal,common,4,101608,de1c8758-ce3d-49cf-8173-c0eb46f5e7bc,0.05,false,false,near_mint,en,USD
Mocking Sprite,FDN,Foundations,159,normal,common,3,101624,f6792f63-b651-497d-8aa5-cddf4cedeca8,0.03,false,false,near_mint,en,USD
Cathar Commando,FDN,Foundations,139,normal,common,3,100204,19cf024d-edb6-4a79-8676-73f8db0cdf1f,0.06,false,false,near_mint,en,USD
Hungry Ghoul,FDN,Foundations,62,normal,common,3,100701,790f9433-7565-4f7f-88e8-8af762ea0296,0.04,false,false,near_mint,en,USD
Vampire Soulcaller,FDN,Foundations,75,normal,common,3,101495,2d076293-3b45-4878-8f67-978927cc1f68,0.04,false,false,near_mint,en,USD
Exsanguinate,FDN,Foundations,173,normal,uncommon,1,101330,f11d7311-4066-4a5d-ba28-9857fa707a0b,0.4,false,false,near_mint,en,USD
Fanatical Firebrand,FDN,Foundations,195,normal,common,3,101598,d1296316-7781-4e98-95e6-7020648be6a5,0.03,false,false,near_mint,en,USD
Sanguine Syphoner,FDN,Foundations,68,normal,common,4,101582,b1daf5bb-c8e9-4e79-a532-ca92a9a885cd,0.07,false,false,near_mint,en,USD
Boltwave,FDN,Foundations,79,normal,uncommon,2,100810,8d1ec351-5e70-4eb2-b590-6bff94ef8178,4.08,false,false,near_mint,en,USD
Nessian Hornbeetle,FDN,Foundations,229,normal,uncommon,2,100395,3d4d93de-85c6-4653-8ddd-d8bf21516d44,0.05,false,false,near_mint,en,USD
Goldvein Pick,FDN,Foundations,253,normal,common,3,101572,a241317d-2277-467e-a8f9-aa71c944e244,0.06,false,false,near_mint,en,USD
Icewind Elemental,FDN,Foundations,42,normal,common,3,101629,fd0eba76-3829-408b-828f-0b223c884728,0.05,false,false,near_mint,en,USD
Fleeting Distraction,FDN,Foundations,155,normal,common,3,101587,c0b86a7b-4912-43a7-ab89-c3432385baa1,0.02,false,false,near_mint,en,USD
Faebloom Trick,FDN,Foundations,38,normal,uncommon,2,100148,0c3bee8f-f5be-4404-a696-c902637799c3,0.17,false,false,near_mint,en,USD
Brineborn Cutthroat,FDN,Foundations,152,normal,uncommon,2,100986,acf7aafb-931f-49e5-8691-eab8cb34b05e,0.02,false,false,near_mint,en,USD
Gutless Plunderer,FDN,Foundations,60,normal,common,3,101567,909d7778-c7f8-4fa4-89f2-8b32e86e96e4,0.05,false,false,near_mint,en,USD
Thornwood Falls,FDN,Foundations,269,normal,common,2,100424,42799f51-0f8c-444b-974e-dae281a5c697,0.05,false,false,near_mint,en,USD
Tranquil Cove,FDN,Foundations,270,normal,common,2,100719,7c9cabca-5bcc-4b97-b2ac-a345ad3ee43c,0.06,false,false,near_mint,en,USD
Fake Your Own Death,FDN,Foundations,174,normal,common,3,101539,693635a6-df50-44c5-9598-0c79b45d4df4,0.05,false,false,near_mint,en,USD
Crypt Feaster,FDN,Foundations,59,normal,common,4,100382,3b072811-998a-4a71-b59c-6afecc0dc4b6,0.03,false,false,near_mint,en,USD
Incinerating Blast,FDN,Foundations,90,normal,common,3,101603,d58e20ab-c5ca-4295-884d-78efdaa83243,0.03,false,false,near_mint,en,USD
Refute,FDN,Foundations,48,normal,common,3,100368,38806934-dd9c-4ad4-a59c-a16dce03a14a,0.06,false,false,near_mint,en,USD
Tolarian Terror,FDN,Foundations,167,normal,common,3,100270,2569d4f3-55ed-4f99-9592-34c7df0aab72,0.09,false,false,near_mint,en,USD
Joust Through,FDN,Foundations,19,normal,uncommon,2,100767,846adb38-f9bb-4fed-b8ed-36ec7885f989,0.05,false,false,near_mint,en,USD
Bake into a Pie,FDN,Foundations,169,normal,common,3,101494,2ab0e660-86a3-4b92-82fa-77dcb5db947d,0.03,false,false,near_mint,en,USD
Soul-Shackled Zombie,FDN,Foundations,70,normal,common,4,101609,deea5690-6eb2-4353-b917-cbbf840e4e71,0.04,false,false,near_mint,en,USD
Perforating Artist,FDN,Foundations,124,normal,uncommon,2,100674,72980409-53f0-43c1-965e-06f22e7bb608,0.1,false,false,near_mint,en,USD
Serra Angel,FDN,Foundations,147,normal,uncommon,2,100391,3cee9303-9d65-45a2-93d4-ef4aba59141b,0.05,false,false,near_mint,en,USD
Squad Rallier,FDN,Foundations,24,normal,common,3,101534,65e1ee86-6f08-4aa0-bf63-ae12028ef080,0.04,false,false,near_mint,en,USD
Elementalist Adept,FDN,Foundations,36,normal,common,3,101605,d9768cc6-8f53-4922-ae32-376a2f32d719,0.02,false,false,near_mint,en,USD
Elvish Regrower,FDN,Foundations,104,normal,uncommon,2,100278,2694e3cd-26ed-4a10-ae55-fb84d7800253,0.09,false,false,near_mint,en,USD
Infestation Sage,FDN,Foundations,64,normal,common,3,101601,d40c73de-7a5f-46f2-a70b-449bc8ecfe24,0.07,false,false,near_mint,en,USD
Inspiring Paladin,FDN,Foundations,18,normal,common,3,101472,0763be06-25b2-4d6b-ab33-a1af85aeb443,0.02,false,false,near_mint,en,USD
Luminous Rebuke,FDN,Foundations,20,normal,common,3,101529,621839e1-2756-4cdc-a25c-5f76ea98dd87,0.07,false,false,near_mint,en,USD
Gnarlid Colony,FDN,Foundations,224,normal,common,3,101508,47565d10-96bf-4fb0-820f-f20a44a76b6f,0.02,false,false,near_mint,en,USD
Sure Strike,FDN,Foundations,209,normal,common,3,101525,5de6a1e4-5c66-43e6-9f2a-2635bdab03f6,0.03,false,false,near_mint,en,USD
Helpful Hunter,FDN,Foundations,16,normal,common,3,97172,1b9a0e91-80b5-428f-8f08-931d0631be14,0.14,false,false,near_mint,en,USD
Goblin Boarders,FDN,Foundations,87,normal,common,3,101506,4409a063-bf2a-4a49-803e-3ce6bd474353,0.04,false,false,near_mint,en,USD
Macabre Waltz,FDN,Foundations,177,normal,common,3,101509,4d1f3c84-89ba-4426-a80b-d524f172c912,0.03,false,false,near_mint,en,USD
Grow from the Ashes,FDN,Foundations,225,normal,common,3,101502,42525f8a-aee7-4811-8f05-471b559c2c4a,0.03,false,false,near_mint,en,USD
Stroke of Midnight,FDN,Foundations,148,normal,uncommon,2,100970,ab135925-d924-456d-851a-6ccdaaf27271,0.17,false,false,near_mint,en,USD
Eaten Alive,FDN,Foundations,172,normal,common,3,100216,1c4f7b20-b2a8-498c-8c36-dc296863b0b9,0.02,false,false,near_mint,en,USD
Aetherize,FDN,Foundations,151,normal,uncommon,2,100225,1e5530fc-0291-4a17-b048-c5d24e6f51d8,0.17,false,false,near_mint,en,USD
Giant Growth,FDN,Foundations,223,normal,common,4,101073,bd0bf74e-14c1-4428-88d8-2181a080b5d0,0.03,false,false,near_mint,en,USD
Billowing Shriekmass,FDN,Foundations,56,normal,uncommon,2,100711,7b3587a9-0667-4d53-807b-c437bcb1d7b3,0.02,false,false,near_mint,en,USD
Think Twice,FDN,Foundations,165,normal,common,4,101202,d88faaa1-eb41-40f7-991c-5c06e1138f3d,0.05,false,false,near_mint,en,USD
Beast-Kin Ranger,FDN,Foundations,100,normal,common,3,100082,0102e0be-5783-4825-9489-713b1b1df0b2,0.05,false,false,near_mint,en,USD
Spitfire Lagac,FDN,Foundations,208,normal,common,4,101496,30f600cd-b696-4f49-9cbc-5a33aa43d04c,0.02,false,false,near_mint,en,USD
Aegis Turtle,FDN,Foundations,150,normal,common,3,101590,c7f2014a-fbc9-447c-a440-e06d01066bb9,0.08,false,false,near_mint,en,USD
Firebrand Archer,FDN,Foundations,196,normal,common,3,101630,fe0312f1-4c98-4b7f-8a34-0059ea80edef,0.05,false,false,near_mint,en,USD
Shivan Dragon,FDN,Foundations,206,normal,uncommon,2,100236,1fcff1e0-2745-448d-a27b-e31719e222e9,0.05,false,false,near_mint,en,USD
Cephalid Inkmage,FDN,Foundations,32,normal,uncommon,2,101040,b7e47680-18c7-4ffb-aac4-c5db6e7095ba,0.05,false,false,near_mint,en,USD
Prideful Parent,FDN,Foundations,21,normal,common,3,97188,b742117a-8a72-43b9-b05d-274829d138a2,0.04,false,false,near_mint,en,USD
Uncharted Voyage,FDN,Foundations,53,normal,common,4,101611,e0846820-e595-4743-8a28-29c57d728677,0.01,false,false,near_mint,en,USD
Eager Trufflesnout,FDN,Foundations,102,normal,uncommon,2,100940,a6e8433d-eb2a-43d1-b59b-7d70ff97c8e7,0.04,false,false,near_mint,en,USD
Juggernaut,FDN,Foundations,255,normal,uncommon,2,101351,f4468fff-cd6f-428c-b7a0-ff89f5bbea2e,0.07,false,false,near_mint,en,USD
Llanowar Elves,FDN,Foundations,227,normal,common,3,95583,6a0b230b-d391-4998-a3f7-7b158a0ec2cd,0.15,false,false,near_mint,en,USD
Overrun,FDN,Foundations,230,normal,uncommon,2,100220,1d8e9cbb-8bf4-4a48-a58e-79deb3abdf7f,0.14,false,false,near_mint,en,USD
Crackling Cyclops,FDN,Foundations,83,normal,common,3,101541,6e5b899a-52f7-471b-ad50-4fa6566758fd,0.01,false,false,near_mint,en,USD
Mischievous Mystic,FDN,Foundations,47,normal,uncommon,2,100242,20d89cec-528b-4b2a-87db-e11ce0000622,0.14,false,false,near_mint,en,USD
Witness Protection,FDN,Foundations,168,normal,common,3,101621,f231e981-0069-43ce-ac1c-c85ced613e93,0.08,false,false,near_mint,en,USD
Dwynen's Elite,FDN,Foundations,218,normal,common,3,100800,89d94c28-ea2e-4a3d-935f-6b2d9f2efc7a,0.05,false,false,near_mint,en,USD
Bushwhack,FDN,Foundations,215,normal,common,3,101469,03ebdb36-55e0-49dd-a514-785fbeb4ae19,0.1,false,false,near_mint,en,USD
Run Away Together,FDN,Foundations,162,normal,common,3,101614,e598eb7b-10dc-49e6-ac60-2fefa987173e,0.05,false,false,near_mint,en,USD
Strongbox Raider,FDN,Foundations,96,normal,uncommon,2,101006,b2223eb8-59f9-489b-a3f3-b6496218cb79,0.02,false,false,near_mint,en,USD
Vanguard Seraph,FDN,Foundations,28,normal,common,4,101503,4329c861-fc16-4a96-9c03-25af6ac2adc8,0.06,false,false,near_mint,en,USD
Self-Reflection,FDN,Foundations,163,normal,uncommon,2,101247,e1e6abc9-25b2-4d51-b519-2525079eab51,0.04,false,false,near_mint,en,USD
Strix Lookout,FDN,Foundations,52,normal,common,3,101627,fbd2422e-8e84-4c39-af29-3b4d38baee63,0.03,false,false,near_mint,en,USD
Cat Collector,FDN,Foundations,4,normal,uncommon,2,100507,526fe356-bff1-4211-9e88-bf913ac76b1d,0.1,false,false,near_mint,en,USD
Courageous Goblin,FDN,Foundations,82,normal,common,3,101566,8db6819c-666a-409d-85a5-b9ac34d8dd2f,0.03,false,false,near_mint,en,USD
"Ygra, Eater of All",BLB,Bloomburrow,241,normal,mythic,1,95825,b9ac7673-eae8-4c4b-889e-5025213a6151,11.58,false,false,near_mint,en,USD
Lifecreed Duo,BLB,Bloomburrow,20,normal,common,1,95968,ca543405-5e12-48a0-9a77-082ac9bcb2f2,0.06,false,false,near_mint,en,USD
Take Out the Trash,BLB,Bloomburrow,156,normal,common,1,95940,7a1c6f00-af4c-4d35-b682-6c0e759df9a5,0.04,false,false,near_mint,en,USD
Ravine Raider,BLB,Bloomburrow,106,normal,common,1,96370,874510be-7ecd-4eff-abad-b9594eb4821a,0.02,false,false,near_mint,en,USD
Longstalk Brawl,BLB,Bloomburrow,182,normal,common,1,95966,c7ef748c-b5e5-4e7d-bf2e-d3e6c08edb42,0.04,false,false,near_mint,en,USD
Valley Floodcaller,BLB,Bloomburrow,79,normal,rare,1,95876,90b12da0-f666-471d-95f5-15d8c9b31c92,2.65,false,false,near_mint,en,USD
Bandit's Talent,BLB,Bloomburrow,83,normal,uncommon,1,95917,485dc8d8-9e44-4a0f-9ff6-fa448e232290,0.47,false,false,near_mint,en,USD
Brambleguard Veteran,BLB,Bloomburrow,165,normal,uncommon,1,95880,bac9f6f8-6797-4580-9fc4-9a825872e017,0.09,false,false,near_mint,en,USD
Mouse Trapper,BLB,Bloomburrow,22,normal,uncommon,1,95948,8ba1bc5a-03e7-44ec-893e-44042cbc02ef,0.04,false,false,near_mint,en,USD
Bushy Bodyguard,BLB,Bloomburrow,166,normal,uncommon,1,95997,0de60cf7-fa82-4b6f-9f88-6590fba5c863,0.08,false,false,near_mint,en,USD
Valley Mightcaller,BLB,Bloomburrow,202,normal,rare,1,96057,7256451f-0122-452a-88e8-0fb0f6bea3f3,1.01,false,false,near_mint,en,USD
Druid of the Spade,BLB,Bloomburrow,170,normal,common,1,96054,6b485cf7-bad0-4824-9ba7-cb112ce4769f,0.02,false,false,near_mint,en,USD
Skyskipper Duo,BLB,Bloomburrow,71,normal,common,1,96476,d6844bad-ffbe-4c6e-b438-08562eccea52,0.04,false,false,near_mint,en,USD
Osteomancer Adept,BLB,Bloomburrow,103,normal,rare,1,95800,7d8238dd-858f-466c-96de-986bd66861d7,0.36,false,false,near_mint,en,USD
Tender Wildguide,BLB,Bloomburrow,196,normal,rare,1,95792,6b8bfa91-adb0-4596-8c16-d8bb64fdb26d,0.49,false,false,near_mint,en,USD
Huskburster Swarm,BLB,Bloomburrow,98,normal,uncommon,1,95978,ed2f61d7-4eb0-41c5-8a34-a0793c2abc51,0.13,false,false,near_mint,en,USD
Scrapshooter,BLB,Bloomburrow,191,normal,rare,1,96113,c42ab407-e72d-4c48-9a9e-2055b5e71c69,0.38,false,false,near_mint,en,USD
Scavenger's Talent,BLB,Bloomburrow,111,normal,rare,1,96084,9a52b7fe-87ae-425b-85fd-b24e6e0395f1,1.54,false,false,near_mint,en,USD
Valley Rotcaller,BLB,Bloomburrow,119,normal,rare,1,95781,4da80a9a-b1d5-4fc5-92f7-36946195d0c7,1.45,false,false,near_mint,en,USD
Thornplate Intimidator,BLB,Bloomburrow,117,normal,common,1,96019,42f66c4a-feaa-4ba6-aa56-955b43329a9e,0.02,false,false,near_mint,en,USD
Bakersbane Duo,BLB,Bloomburrow,163,normal,common,1,96035,5309354f-1ff4-4fa9-9141-01ea2f7588ab,0.1,false,false,near_mint,en,USD
Shore Up,BLB,Bloomburrow,69,normal,common,1,96277,4dc3b49e-3674-494c-bdea-4374cefd10f4,0.08,false,false,near_mint,en,USD
Emberheart Challenger,BLB,Bloomburrow,133,normal,rare,1,95888,0035082e-bb86-4f95-be48-ffc87fe5286d,4.13,false,false,near_mint,en,USD
"Gev, Scaled Scorch",BLB,Bloomburrow,214,normal,rare,1,96001,131ea976-289e-4f32-896d-27bbfd423ba9,0.37,false,false,near_mint,en,USD
Starfall Invocation,BLB,Bloomburrow,34,normal,rare,1,95904,2aea38e6-ec58-4091-b27c-2761bdd12b13,0.88,false,false,near_mint,en,USD
Tidecaller Mentor,BLB,Bloomburrow,236,normal,uncommon,1,95859,fa10ffac-7cc2-41ef-b8a0-9431923c0542,0.04,false,false,near_mint,en,USD
Jackdaw Savior,BLB,Bloomburrow,18,normal,rare,1,96000,121af600-6143-450a-9f87-12ce4833f1ec,0.27,false,false,near_mint,en,USD
"Helga, Skittish Seer",BLB,Bloomburrow,217,normal,mythic,1,95914,40339715-22d0-4f99-822b-a00d9824f27a,2.0,false,false,near_mint,en,USD
Long River Lurker,BLB,Bloomburrow,57,normal,uncommon,1,95941,7c267719-cd03-4003-b281-e732d5e42a1e,0.1,false,false,near_mint,en,USD
Thornvault Forager,BLB,Bloomburrow,197,normal,rare,1,95807,8c2d6b02-a453-40f9-992a-5c5542987cfb,0.65,false,false,near_mint,en,USD
Eddymurk Crab,BLB,Bloomburrow,48,normal,uncommon,1,96132,e6d45abe-4962-47d9-a54e-7e623ea8647c,0.18,false,false,near_mint,en,USD
Moonstone Harbinger,BLB,Bloomburrow,101,normal,uncommon,1,95922,59e4aa8d-1d06-48db-b205-aa2f1392bbcb,0.03,false,false,near_mint,en,USD
Brazen Collector,BLB,Bloomburrow,128,normal,uncommon,1,95873,78b55a58-c669-4dc6-aa63-5d9dff52e613,0.09,false,false,near_mint,en,USD
Brightblade Stoat,BLB,Bloomburrow,4,normal,uncommon,1,95882,df7fea2e-7414-4bc8-adb0-9342e174c009,0.07,false,false,near_mint,en,USD
Warren Warleader,BLB,Bloomburrow,38,normal,mythic,1,95849,eb5237a0-5ac3-4ded-9f92-5f782a7bbbd7,3.14,false,false,near_mint,en,USD
Kitnap,BLB,Bloomburrow,53,normal,rare,1,95739,085be5d1-fd85-46d1-ad39-a8aa75a06a96,0.14,false,false,near_mint,en,USD
Fountainport,BLB,Bloomburrow,253,normal,rare,1,96052,658cfcb7-81b7-48c6-9dd2-1663d06108cf,5.77,false,false,near_mint,en,USD
Whiskervale Forerunner,BLB,Bloomburrow,40,normal,rare,1,95927,60a78d59-af31-4af9-95aa-2573fe553925,0.17,false,false,near_mint,en,USD
Dreamdew Entrancer,BLB,Bloomburrow,211,normal,rare,1,95755,26bd6b0d-8606-4a37-8be3-a852f1a8e99c,0.28,false,false,near_mint,en,USD
Playful Shove,BLB,Bloomburrow,145,normal,uncommon,1,95993,07956edf-34c1-4218-9784-ddbca13e380c,0.1,false,false,near_mint,en,USD
Feed the Cycle,BLB,Bloomburrow,94,normal,uncommon,1,96067,7e017ff8-2936-4a1b-bece-00004cfbad06,0.12,false,false,near_mint,en,USD
Hoarder's Overflow,BLB,Bloomburrow,141,normal,uncommon,1,96112,c2ed5079-07b4-4575-a2c8-5f0cbff888c3,0.04,false,false,near_mint,en,USD
Sunspine Lynx,BLB,Bloomburrow,155,normal,rare,1,95875,8995ceaf-b7e0-423c-8f3e-25212d522502,1.8,false,false,near_mint,en,USD
Stormcatch Mentor,BLB,Bloomburrow,234,normal,uncommon,1,95813,99754055-6d67-4fde-aff3-41f6af6ea764,0.21,false,false,near_mint,en,USD
For the Common Good,BLB,Bloomburrow,172,normal,rare,1,95912,3ec72a27-b622-47d7-bdf3-970ccaef0d2a,0.87,false,false,near_mint,en,USD
Dawn's Truce,BLB,Bloomburrow,295,normal,rare,1,95893,0cce7aec-f9b0-461b-8245-5286b741409d,8.43,false,false,near_mint,en,USD
"Clement, the Worrywort",BLB,Bloomburrow,329,normal,rare,1,95835,d1a68d51-cd4e-4ee3-abc7-01435085aa26,0.55,false,false,near_mint,en,USD
Tender Wildguide,BLB,Bloomburrow,325,normal,rare,1,95760,2dc164c8-62ca-4d59-ae1c-ef273fde9d10,0.63,false,false,near_mint,en,USD
Valley Questcaller,BLB,Bloomburrow,299,normal,rare,1,95839,d9f25130-678d-4338-8eb4-b20d2da5bc74,1.0,false,false,near_mint,en,USD
Heirloom Epic,BLB,Bloomburrow,246,normal,uncommon,1,96061,7839ce48-0175-494a-ab89-9bdfb7a50cb1,0.06,false,false,near_mint,en,USD
Shrike Force,BLB,Bloomburrow,31,normal,uncommon,1,95763,306fec2c-d8b7-4f4b-8f58-10e3b9f3158f,0.14,false,false,near_mint,en,USD
Into the Flood Maw,BLB,Bloomburrow,52,normal,uncommon,1,95919,50b9575a-53d9-4df7-b86c-cda021107d3f,1.48,false,false,near_mint,en,USD
Salvation Swan,BLB,Bloomburrow,28,normal,rare,1,95635,b2656160-d319-4530-a6e5-c418596c3f12,0.27,false,false,near_mint,en,USD
Hired Claw,BLB,Bloomburrow,140,normal,rare,1,95897,1ae41080-0d67-4719-adb2-49bf2a268b6c,2.43,false,false,near_mint,en,USD
Starseer Mentor,BLB,Bloomburrow,233,normal,uncommon,1,95791,6b2f6dc5-9fe8-49c1-b24c-1d99ce1da619,0.05,false,false,near_mint,en,USD
Mistbreath Elder,BLB,Bloomburrow,184,normal,rare,1,95975,e5246540-5a84-41d8-9e30-8e7a6c0e84e1,0.37,false,false,near_mint,en,USD
Hivespine Wolverine,BLB,Bloomburrow,177,normal,uncommon,1,95943,821970a3-a291-4fe9-bb13-dfc54f9c3caf,0.06,false,false,near_mint,en,USD
Patchwork Banner,BLB,Bloomburrow,247,normal,uncommon,1,96097,a8a982c8-bc08-44ba-b3ed-9e4b124615d6,4.68,false,false,near_mint,en,USD
"Beza, the Bounding Spring",BLB,Bloomburrow,2,normal,mythic,1,95862,fc310a26-b6a0-4e42-98ab-bdfd7b06cb63,9.56,false,false,near_mint,en,USD
Essence Channeler,BLB,Bloomburrow,12,normal,rare,1,96042,5aaf7e4c-4d5d-4acc-a834-e6c4a7629408,1.27,false,false,near_mint,en,USD
Valley Questcaller,BLB,Bloomburrow,36,normal,rare,1,95826,ba629ca8-a368-4282-8a61-9bf6a5c217f0,1.12,false,false,near_mint,en,USD
Conduct Electricity,BLB,Bloomburrow,130,normal,common,1,95906,2f373dd6-2412-453c-85ba-10230dfe473a,0.02,false,false,near_mint,en,USD
Glidedive Duo,BLB,Bloomburrow,96,normal,common,1,96026,4831e7ae-54e3-4bd9-b5af-52dc29f81715,0.02,false,false,near_mint,en,USD
Mind Spiral,BLB,Bloomburrow,59,normal,common,1,96068,7e24fe6a-607b-49b8-9fca-cecb1e40de7f,0.01,false,false,near_mint,en,USD
Starforged Sword,BLB,Bloomburrow,249,normal,uncommon,1,96110,c23d8e96-b972-4c6c-b0c4-b6627621f048,0.03,false,false,near_mint,en,USD
Vinereap Mentor,BLB,Bloomburrow,238,normal,uncommon,1,95902,29b615ba-45c4-42a1-8525-1535f0b55300,0.16,false,false,near_mint,en,USD
Mindwhisker,BLB,Bloomburrow,60,normal,uncommon,1,96099,aaa10f34-5bfd-4d87-8f07-58de3b0f5663,0.08,false,false,near_mint,en,USD
Persistent Marshstalker,BLB,Bloomburrow,104,normal,uncommon,1,95947,8b900c71-713b-4b7e-b4be-ad9f4aa0c139,0.13,false,false,near_mint,en,USD
Portent of Calamity,BLB,Bloomburrow,66,normal,rare,1,96073,8599e2dd-9164-4da3-814f-adccef3b9497,0.14,false,false,near_mint,en,USD
Fabled Passage,BLB,Bloomburrow,252,normal,rare,1,96075,8809830f-d8e1-4603-9652-0ad8b00234e9,5.13,false,false,near_mint,en,USD
Stormsplitter,BLB,Bloomburrow,154,normal,mythic,1,96040,56f214d3-6b93-40db-a693-55e491c8a283,3.12,false,false,near_mint,en,USD
Stargaze,BLB,Bloomburrow,114,normal,uncommon,1,95939,777fc599-8de7-44d2-8fdd-9bddf5948a0c,0.14,false,false,near_mint,en,USD
Coruscation Mage,BLB,Bloomburrow,131,normal,uncommon,1,95972,dc2c1de0-6233-469a-be72-a050b97d2c8f,0.32,false,false,near_mint,en,USD
Dour Port-Mage,BLB,Bloomburrow,47,normal,rare,1,96049,6402133e-eed1-4a46-9667-8b7a310362c1,2.17,false,false,near_mint,en,USD
"Muerra, Trash Tactician",BLB,Bloomburrow,227,normal,rare,1,95821,b40e4658-fd68-46d0-9a89-25570a023d19,0.31,false,false,near_mint,en,USD
Stormchaser's Talent,BLB,Bloomburrow,75,normal,rare,1,96092,a36e682d-b43d-4e08-bf5b-70d7e924dbe5,13.62,false,false,near_mint,en,USD
Sinister Monolith,BLB,Bloomburrow,113,normal,uncommon,1,96012,2a15e06c-2608-4e7a-a16c-d35417669d86,0.08,false,false,near_mint,en,USD
Pawpatch Formation,BLB,Bloomburrow,186,normal,uncommon,1,95963,b82c20ad-0f69-4822-ae76-770832cccdf7,1.83,false,false,near_mint,en,USD
Plumecreed Mentor,BLB,Bloomburrow,228,normal,uncommon,1,95819,b1aa988f-547e-449a-9f1a-296c01d68d96,0.03,false,false,near_mint,en,USD
"Baylen, the Haymaker",BLB,Bloomburrow,205,normal,rare,1,95889,00e93be2-e06b-4774-8ba5-ccf82a6da1d8,1.04,false,false,near_mint,en,USD
Long River's Pull,BLB,Bloomburrow,58,normal,uncommon,1,95900,1c81d0fa-81a1-4f9b-a5fd-5a648fd01dea,0.23,false,false,near_mint,en,USD
Bonecache Overseer,BLB,Bloomburrow,85,normal,uncommon,1,95944,82defb87-237f-4b77-9673-5bf00607148f,0.08,false,false,near_mint,en,USD
Three Tree Scribe,BLB,Bloomburrow,199,normal,uncommon,1,95977,ea2ca1b3-4c1a-4be5-b321-f57db5ff0528,0.15,false,false,near_mint,en,USD
Cruelclaw's Heist,BLB,Bloomburrow,88,normal,rare,1,96121,cab4539a-0157-4cbe-b50f-6e2575df74e9,0.48,false,false,near_mint,en,USD
Manifold Mouse,BLB,Bloomburrow,143,normal,rare,1,95881,db3832b5-e83f-4569-bd49-fb7b86fa2d47,3.37,false,false,near_mint,en,USD
Iridescent Vinelasher,BLB,Bloomburrow,99,normal,rare,1,95877,b2bc854c-4e72-48e0-a098-e3451d6e511d,1.11,false,false,near_mint,en,USD
Daggerfang Duo,BLB,Bloomburrow,89,normal,common,1,96468,cea2bb34-e328-44fb-918a-72208c9457e4,0.03,false,false,near_mint,en,USD
Stickytongue Sentinel,BLB,Bloomburrow,193,normal,common,1,96105,b5fa9651-b217-4f93-9c46-9bdb11feedcb,0.03,false,false,near_mint,en,USD
Brave-Kin Duo,BLB,Bloomburrow,3,normal,common,1,95824,b8dd4693-424d-4d6e-86cf-24401a23d6b1,0.03,false,false,near_mint,en,USD
Driftgloom Coyote,BLB,Bloomburrow,11,normal,uncommon,1,95969,d7ab2de3-3aea-461a-a74f-fb742cf8a198,0.03,false,false,near_mint,en,USD
Rockface Village,BLB,Bloomburrow,259,normal,uncommon,1,95629,62799d24-39a6-4e66-8ac3-7cafa99e6e6d,0.48,false,false,near_mint,en,USD
Flamecache Gecko,BLB,Bloomburrow,135,normal,uncommon,1,96142,fb8e7c97-8393-41b8-bb0b-3983dcc5e7f4,0.08,false,false,near_mint,en,USD
Innkeeper's Talent,BLB,Bloomburrow,180,normal,rare,1,95954,941b0afc-0e8f-45f2-ae7f-07595e164611,19.36,false,false,near_mint,en,USD
Repel Calamity,BLB,Bloomburrow,27,foil,uncommon,1,95834,d068192a-6270-4981-819d-4945fa4a2b83,0.08,false,false,near_mint,en,USD
Galewind Moose,BLB,Bloomburrow,173,foil,uncommon,1,95871,58706bd8-558a-43b9-9f1e-c1ff0044203b,0.14,false,false,near_mint,en,USD
Brave-Kin Duo,BLB,Bloomburrow,3,foil,common,1,95824,b8dd4693-424d-4d6e-86cf-24401a23d6b1,0.06,false,false,near_mint,en,USD
Agate Assault,BLB,Bloomburrow,122,foil,common,1,96066,7dd9946b-515e-4e0d-9da2-711e126e9fa6,0.03,false,false,near_mint,en,USD
Flamecache Gecko,BLB,Bloomburrow,135,foil,uncommon,1,96142,fb8e7c97-8393-41b8-bb0b-3983dcc5e7f4,0.12,false,false,near_mint,en,USD
Rabid Gnaw,BLB,Bloomburrow,147,foil,uncommon,1,96014,2f815bae-820a-49f6-8eed-46f658e7b6ff,0.1,false,false,near_mint,en,USD
Pond Prophet,BLB,Bloomburrow,229,foil,common,1,95861,fb959e74-61ea-453d-bb9f-ad0183c0e1b1,0.16,false,false,near_mint,en,USD
Star Charter,BLB,Bloomburrow,33,foil,uncommon,1,95894,0e209237-00f7-4bf0-8287-ccde02ce8e8d,0.12,false,false,near_mint,en,USD
Kindlespark Duo,BLB,Bloomburrow,142,foil,common,1,96096,a839fba3-1b66-4dd1-bf43-9b015b44fc81,0.07,false,false,near_mint,en,USD
Crumb and Get It,BLB,Bloomburrow,8,foil,common,1,96259,3c7b3b25-d4b3-4451-9f5c-6eb369541175,0.04,false,false,near_mint,en,USD
Peerless Recycling,BLB,Bloomburrow,188,foil,uncommon,1,95925,5f72466c-505b-4371-9366-0fde525a37e6,0.23,false,false,near_mint,en,USD
Nocturnal Hunger,BLB,Bloomburrow,102,foil,common,1,96060,742c0409-9abd-4559-b52e-932cc90c531a,0.02,false,false,near_mint,en,USD
Seedpod Squire,BLB,Bloomburrow,232,foil,common,1,95852,f3684577-51ce-490e-9b59-b19c733be466,0.03,false,false,near_mint,en,USD
Nettle Guard,BLB,Bloomburrow,23,foil,common,1,95949,8c9c3cc3-2aa2-453e-a17c-2baeeaabe0a9,0.05,false,false,near_mint,en,USD
Sazacap's Brew,BLB,Bloomburrow,151,foil,common,1,96330,6d963080-b3ec-467d-82f7-39db6ecd6bbc,0.05,false,false,near_mint,en,USD
Waterspout Warden,BLB,Bloomburrow,80,foil,common,1,95909,35898b39-98e2-405b-8f18-0e054bd2c29e,0.04,false,false,near_mint,en,USD
Mindwhisker,BLB,Bloomburrow,60,foil,uncommon,1,96099,aaa10f34-5bfd-4d87-8f07-58de3b0f5663,0.12,false,false,near_mint,en,USD
Splash Portal,BLB,Bloomburrow,74,foil,uncommon,1,95958,adbaa356-28ba-487f-930a-a957d9960ab0,0.28,false,false,near_mint,en,USD
Festival of Embers,BLB,Bloomburrow,134,foil,rare,1,96023,4433ee12-2013-4fdc-979f-ae065f63a527,0.2,false,false,near_mint,en,USD
Brightblade Stoat,BLB,Bloomburrow,4,foil,uncommon,1,95882,df7fea2e-7414-4bc8-adb0-9342e174c009,0.11,false,false,near_mint,en,USD
Mind Spiral,BLB,Bloomburrow,59,foil,common,1,96068,7e24fe6a-607b-49b8-9fca-cecb1e40de7f,0.04,false,false,near_mint,en,USD
Rust-Shield Rampager,BLB,Bloomburrow,190,foil,common,1,96117,c96b01f5-83de-4237-a68d-f946c53e31a6,0.04,false,false,near_mint,en,USD
Barkform Harvester,BLB,Bloomburrow,243,foil,common,1,95984,f77049a6-0f22-415b-bc89-20bcb32accf6,0.11,false,false,near_mint,en,USD
Wax-Wane Witness,BLB,Bloomburrow,39,foil,common,1,95971,d90ea719-5320-46c6-a347-161853a14776,0.05,false,false,near_mint,en,USD
Warren Elder,BLB,Bloomburrow,37,foil,common,1,96030,4bf20069-5a20-4f95-976b-6af2b69f3ad0,0.04,false,false,near_mint,en,USD
Stickytongue Sentinel,BLB,Bloomburrow,193,foil,common,1,96105,b5fa9651-b217-4f93-9c46-9bdb11feedcb,0.05,false,false,near_mint,en,USD
"Vren, the Relentless",BLB,Bloomburrow,239,foil,rare,1,95930,6506277d-f031-4db5-9d16-bf2389094785,0.71,false,false,near_mint,en,USD
Three Tree Scribe,BLB,Bloomburrow,199,foil,uncommon,1,95977,ea2ca1b3-4c1a-4be5-b321-f57db5ff0528,0.2,false,false,near_mint,en,USD
Glidedive Duo,BLB,Bloomburrow,96,foil,common,1,96026,4831e7ae-54e3-4bd9-b5af-52dc29f81715,0.03,false,false,near_mint,en,USD
Bushy Bodyguard,BLB,Bloomburrow,166,foil,uncommon,1,95997,0de60cf7-fa82-4b6f-9f88-6590fba5c863,0.12,false,false,near_mint,en,USD
Conduct Electricity,BLB,Bloomburrow,130,foil,common,1,95906,2f373dd6-2412-453c-85ba-10230dfe473a,0.03,false,false,near_mint,en,USD
Daggerfang Duo,BLB,Bloomburrow,89,foil,common,1,96468,cea2bb34-e328-44fb-918a-72208c9457e4,0.07,false,false,near_mint,en,USD
Shore Up,BLB,Bloomburrow,69,foil,common,1,96277,4dc3b49e-3674-494c-bdea-4374cefd10f4,0.13,false,false,near_mint,en,USD
Hidden Grotto,BLB,Bloomburrow,254,foil,common,1,95918,4ba8f2e7-8357-4862-97dc-1942d066023a,0.17,false,false,near_mint,en,USD
Cindering Cutthroat,BLB,Bloomburrow,208,foil,common,1,95820,b2ea10dd-21ea-4622-be27-79d03a802b85,0.01,false,false,near_mint,en,USD
"Glarb, Calamity's Augur",BLB,Bloomburrow,215,foil,mythic,1,95864,ffc70b2d-5a3a-49ea-97db-175a62248302,4.3,false,false,near_mint,en,USD
Kindlespark Duo,BLB,Bloomburrow,142,normal,common,5,96096,a839fba3-1b66-4dd1-bf43-9b015b44fc81,0.04,false,false,near_mint,en,USD
Finch Formation,BLB,Bloomburrow,50,normal,common,2,95899,1c671eab-d1ef-4d79-94eb-8b85f0d18699,0.02,false,false,near_mint,en,USD
Builder's Talent,BLB,Bloomburrow,5,normal,uncommon,2,96002,15fa581a-724e-4196-a9a3-ff84c54bdb7d,0.08,false,false,near_mint,en,USD
Might of the Meek,BLB,Bloomburrow,144,normal,common,9,95627,509bf254-8a2b-4dfa-9ae5-386321b35e8b,0.09,false,false,near_mint,en,USD
Nightwhorl Hermit,BLB,Bloomburrow,62,normal,common,3,95994,0928e04f-2568-41e8-b603-7a25cf5f94d0,0.02,false,false,near_mint,en,USD
Fell,BLB,Bloomburrow,95,normal,uncommon,2,95830,c96ac326-de44-470b-a592-a4c2a052c091,0.3,false,false,near_mint,en,USD
Sunshower Druid,BLB,Bloomburrow,195,normal,common,6,95630,7740abc5-54e1-478d-966e-0fa64e727995,0.04,false,false,near_mint,en,USD
Wandertale Mentor,BLB,Bloomburrow,240,normal,uncommon,2,95808,8c399a55-d02e-41ed-b827-8784b738c118,0.09,false,false,near_mint,en,USD
Thought-Stalker Warlock,BLB,Bloomburrow,118,normal,uncommon,2,96018,42e80284-d489-493b-ae92-95b742d07cb3,0.12,false,false,near_mint,en,USD
Splash Portal,BLB,Bloomburrow,74,normal,uncommon,2,95958,adbaa356-28ba-487f-930a-a957d9960ab0,0.23,false,false,near_mint,en,USD
Alania's Pathmaker,BLB,Bloomburrow,123,normal,common,7,96123,d3871fe6-e26e-4ab4-bd81-7e3c7b8135c1,0.02,false,false,near_mint,en,USD
Head of the Homestead,BLB,Bloomburrow,216,normal,common,3,95762,2fc20157-edd3-484d-8864-925c071c0551,0.04,false,false,near_mint,en,USD
Hidden Grotto,BLB,Bloomburrow,254,normal,common,4,95918,4ba8f2e7-8357-4862-97dc-1942d066023a,0.08,false,false,near_mint,en,USD
Star Charter,BLB,Bloomburrow,33,normal,uncommon,3,95894,0e209237-00f7-4bf0-8287-ccde02ce8e8d,0.04,false,false,near_mint,en,USD
War Squeak,BLB,Bloomburrow,160,normal,common,4,95999,105964a7-88b7-4340-aa66-e908189a3638,0.02,false,false,near_mint,en,USD
Bellowing Crier,BLB,Bloomburrow,42,normal,common,2,96119,ca2215dd-6300-49cf-b9b2-3a840b786c31,0.04,false,false,near_mint,en,USD
Cindering Cutthroat,BLB,Bloomburrow,208,normal,common,4,95820,b2ea10dd-21ea-4622-be27-79d03a802b85,0.02,false,false,near_mint,en,USD
Intrepid Rabbit,BLB,Bloomburrow,17,normal,common,7,96276,4d70b99d-c8bf-4a56-8957-cf587fe60b81,0.03,false,false,near_mint,en,USD
Carrot Cake,BLB,Bloomburrow,7,normal,common,3,95636,eb03bb4f-8b4b-417e-bfc6-294cd2186b2e,0.06,false,false,near_mint,en,USD
Thought Shucker,BLB,Bloomburrow,77,normal,common,7,95916,44b0d83b-cc41-4f82-892c-ef6d3293228a,0.02,false,false,near_mint,en,USD
Seasoned Warrenguard,BLB,Bloomburrow,30,normal,uncommon,2,96081,90873995-876f-4e89-8bc7-41a74f4d931f,0.09,false,false,near_mint,en,USD
Junkblade Bruiser,BLB,Bloomburrow,220,normal,common,3,95810,918fd89b-5ab7-4ae2-920c-faca5e9da7b9,0.04,false,false,near_mint,en,USD
Cache Grab,BLB,Bloomburrow,167,normal,common,2,95842,dfd977dc-a7c3-4d0a-aca7-b25bd154e963,0.08,false,false,near_mint,en,USD
Lilypad Village,BLB,Bloomburrow,255,normal,uncommon,2,95631,7e95a7cc-ed77-4ca4-80db-61c0fc68bf50,0.14,false,false,near_mint,en,USD
Agate-Blade Assassin,BLB,Bloomburrow,82,normal,common,5,96017,39ebb84a-1c52-4b07-9bd0-b360523b3a5b,0.03,false,false,near_mint,en,USD
Repel Calamity,BLB,Bloomburrow,27,normal,uncommon,2,95834,d068192a-6270-4981-819d-4945fa4a2b83,0.07,false,false,near_mint,en,USD
Hazel's Nocturne,BLB,Bloomburrow,97,normal,uncommon,2,96009,239363df-4de8-4b64-80fc-a1f4b5c36027,0.07,false,false,near_mint,en,USD
Treeguard Duo,BLB,Bloomburrow,200,normal,common,4,96077,89c8456e-c971-42b7-abf3-ff5ae1320abe,0.01,false,false,near_mint,en,USD
Calamitous Tide,BLB,Bloomburrow,43,normal,uncommon,2,96003,178bc8b2-ffa0-4549-aead-aacb3db3cf19,0.03,false,false,near_mint,en,USD
Splash Lasher,BLB,Bloomburrow,73,normal,uncommon,2,95910,362ee125-35a0-46cd-a201-e6797d12d33a,0.04,false,false,near_mint,en,USD
Blooming Blast,BLB,Bloomburrow,126,normal,uncommon,2,95996,0cd92a83-cec3-4085-a929-3f204e3e0140,0.06,false,false,near_mint,en,USD
Sugar Coat,BLB,Bloomburrow,76,normal,uncommon,2,95887,fcacbe71-efb0-49e1-b2d0-3ee65ec6cf8b,0.05,false,false,near_mint,en,USD
Dazzling Denial,BLB,Bloomburrow,45,normal,common,6,96369,8739f1ac-2e57-4b52-a7ff-cc8df5936aad,0.04,false,false,near_mint,en,USD
Nettle Guard,BLB,Bloomburrow,23,normal,common,4,95949,8c9c3cc3-2aa2-453e-a17c-2baeeaabe0a9,0.03,false,false,near_mint,en,USD
Raccoon Rallier,BLB,Bloomburrow,148,normal,common,5,96104,b5b5180f-5a1c-4df8-9019-195e65a50ce3,0.04,false,false,near_mint,en,USD
High Stride,BLB,Bloomburrow,176,normal,common,8,96153,09c8cf4b-8e65-4a1c-b458-28b5ab56b390,0.04,false,false,near_mint,en,USD
Otterball Antics,BLB,Bloomburrow,63,normal,uncommon,2,95913,3ff83ff7-e428-4ccc-8341-f223dab76bd1,0.1,false,false,near_mint,en,USD
Frilled Sparkshooter,BLB,Bloomburrow,136,normal,common,7,95934,674bbd6d-e329-42cf-963d-88d1ce8fe51e,0.02,false,false,near_mint,en,USD
Moonrise Cleric,BLB,Bloomburrow,226,normal,common,3,95767,35f2a71f-31e8-4b51-9dd4-51a5336b3b86,0.04,false,false,near_mint,en,USD
Wax-Wane Witness,BLB,Bloomburrow,39,normal,common,3,95971,d90ea719-5320-46c6-a347-161853a14776,0.02,false,false,near_mint,en,USD
Pearl of Wisdom,BLB,Bloomburrow,64,normal,common,7,95625,13cb9575-1138-4f99-8e90-0eaf00bdf4a1,0.01,false,false,near_mint,en,USD
Run Away Together,BLB,Bloomburrow,67,normal,common,3,95799,7cb7ec70-a5a4-4188-ba1a-e88b81bdbad0,0.04,false,false,near_mint,en,USD
Early Winter,BLB,Bloomburrow,93,normal,common,2,95626,5030e6ac-211d-4145-8c87-998a8351a467,0.05,false,false,near_mint,en,USD
Three Tree Rootweaver,BLB,Bloomburrow,198,normal,common,2,96469,d1ab6e14-26e0-4174-b5c6-bc0f5c26b177,0.04,false,false,near_mint,en,USD
Mudflat Village,BLB,Bloomburrow,257,normal,uncommon,2,95628,53ec4ad3-9cf0-4f1b-a9db-d63feee594ab,0.24,false,false,near_mint,en,USD
Starlit Soothsayer,BLB,Bloomburrow,115,normal,common,6,95895,184c1eca-2991-438f-b5d2-cd2529b9c9b4,0.03,false,false,near_mint,en,USD
Hop to It,BLB,Bloomburrow,16,normal,uncommon,2,95851,ee7207f8-5daa-42af-aeea-7a489047110b,0.07,false,false,near_mint,en,USD
Psychic Whorl,BLB,Bloomburrow,105,normal,common,5,96127,df900308-8432-4a0a-be21-17482026012b,0.04,false,false,near_mint,en,USD
Barkform Harvester,BLB,Bloomburrow,243,normal,common,4,95984,f77049a6-0f22-415b-bc89-20bcb32accf6,0.06,false,false,near_mint,en,USD
Daring Waverider,BLB,Bloomburrow,44,normal,uncommon,2,95896,19422406-0c1a-497e-bed1-708bc556491a,0.06,false,false,near_mint,en,USD
Plumecreed Escort,BLB,Bloomburrow,65,normal,uncommon,2,95983,f71320ed-2f30-49ce-bcb0-19aebba3f0e8,0.05,false,false,near_mint,en,USD
Parting Gust,BLB,Bloomburrow,24,normal,uncommon,2,95744,1086e826-94b8-4398-8a38-d8eacca56a43,0.38,false,false,near_mint,en,USD
Veteran Guardmouse,BLB,Bloomburrow,237,normal,common,3,95771,3db43c46-b616-4ef8-80ed-0fab345ab3d0,0.01,false,false,near_mint,en,USD
Dire Downdraft,BLB,Bloomburrow,46,normal,common,6,96526,f1931f22-974c-43ad-911e-684bf3f9995d,0.02,false,false,near_mint,en,USD
Waterspout Warden,BLB,Bloomburrow,80,normal,common,4,95909,35898b39-98e2-405b-8f18-0e054bd2c29e,0.01,false,false,near_mint,en,USD
Lupinflower Village,BLB,Bloomburrow,256,normal,uncommon,2,95634,8ab9d56f-9178-4ec9-a5f6-b934f50d8d9d,0.1,false,false,near_mint,en,USD
Heartfire Hero,BLB,Bloomburrow,138,normal,uncommon,2,95870,48ace959-66b2-40c8-9bff-fd7ed9c99a82,2.1,false,false,near_mint,en,USD
Peerless Recycling,BLB,Bloomburrow,188,normal,uncommon,2,95925,5f72466c-505b-4371-9366-0fde525a37e6,0.1,false,false,near_mint,en,USD
Pond Prophet,BLB,Bloomburrow,229,normal,common,4,95861,fb959e74-61ea-453d-bb9f-ad0183c0e1b1,0.09,false,false,near_mint,en,USD
Crumb and Get It,BLB,Bloomburrow,8,normal,common,2,96259,3c7b3b25-d4b3-4451-9f5c-6eb369541175,0.03,false,false,near_mint,en,USD
Wildfire Howl,BLB,Bloomburrow,162,normal,uncommon,2,96059,7392d397-9836-4df2-944d-c930c9566811,0.05,false,false,near_mint,en,USD
Bark-Knuckle Boxer,BLB,Bloomburrow,164,normal,uncommon,2,95921,582637a9-6aa0-4824-bed7-d5fc91bda35e,0.03,false,false,near_mint,en,USD
Ruthless Negotiation,BLB,Bloomburrow,108,normal,uncommon,2,95828,c7f4360c-8d68-4058-b9ec-da9948cb060d,0.1,false,false,near_mint,en,USD
Three Tree Mascot,FDN,Foundations,682,normal,common,3,100412,40b8bf3a-1cb5-4ce2-ac25-9410f17130de,0.11,false,false,near_mint,en,USD
Tempest Angler,BLB,Bloomburrow,235,normal,common,2,95803,850daae4-f0b7-4604-95e7-ad044ec165c3,0.04,false,false,near_mint,en,USD
Starscape Cleric,BLB,Bloomburrow,116,normal,uncommon,2,96037,53a938a7-0154-4350-87cb-00da24ec3824,0.62,false,false,near_mint,en,USD
Wick's Patrol,BLB,Bloomburrow,121,normal,uncommon,3,95926,5fa0c53d-fe7b-4b8b-ad81-7967ca318ff7,0.07,false,false,near_mint,en,USD
Fireglass Mentor,BLB,Bloomburrow,213,normal,uncommon,2,95823,b78fbaa3-c580-4290-9c28-b74169aab2fc,0.08,false,false,near_mint,en,USD
Steampath Charger,BLB,Bloomburrow,153,normal,common,2,95890,03bf1296-e347-4070-8c6f-5c362c2f9364,0.03,false,false,near_mint,en,USD
Whiskerquill Scribe,BLB,Bloomburrow,161,normal,common,2,96124,da653996-9bd4-40bd-afb4-48c7e070a269,0.01,false,false,near_mint,en,USD
Lilysplash Mentor,BLB,Bloomburrow,222,normal,uncommon,3,95789,64de7b1f-a03e-4407-91f1-e108a2f26735,0.12,false,false,near_mint,en,USD
Roughshod Duo,BLB,Bloomburrow,150,normal,common,3,96343,78cdcfb9-a247-4c2d-a098-5b57570f8cd5,0.03,false,false,near_mint,en,USD
Bonebind Orator,BLB,Bloomburrow,84,normal,common,3,96535,faf226fa-ca09-4468-8804-87b2a7de2c66,0.02,false,false,near_mint,en,USD
Agate Assault,BLB,Bloomburrow,122,normal,common,2,96066,7dd9946b-515e-4e0d-9da2-711e126e9fa6,0.02,false,false,near_mint,en,USD
Nocturnal Hunger,BLB,Bloomburrow,102,normal,common,3,96060,742c0409-9abd-4559-b52e-932cc90c531a,0.02,false,false,near_mint,en,USD
Jolly Gerbils,BLB,Bloomburrow,19,normal,uncommon,2,96167,0eab51d6-ba17-4a8c-8834-25db363f2b6b,0.04,false,false,near_mint,en,USD
Downwind Ambusher,BLB,Bloomburrow,92,normal,uncommon,2,95920,55cfd628-933a-4d3d-b2e5-70bc86960d1c,0.02,false,false,near_mint,en,USD
Scales of Shale,BLB,Bloomburrow,110,normal,common,2,95955,9ae14276-dbbd-4257-80e9-accd6c19f5b2,0.02,false,false,near_mint,en,USD
Treetop Sentries,BLB,Bloomburrow,201,normal,common,4,95974,e16d4d6e-1fe5-4ff6-9877-8c849a24f5e0,0.03,false,false,near_mint,en,USD
Seedpod Squire,BLB,Bloomburrow,232,normal,common,4,95852,f3684577-51ce-490e-9b59-b19c733be466,0.01,false,false,near_mint,en,USD
Savor,BLB,Bloomburrow,109,normal,common,4,96178,1397f689-dca1-4d35-864b-92c5606afb9a,0.04,false,false,near_mint,en,USD
Polliwallop,BLB,Bloomburrow,189,normal,common,2,95935,6bc4963c-d90b-4588-bdb7-85956e42a623,0.03,false,false,near_mint,en,USD
Sonar Strike,BLB,Bloomburrow,32,normal,common,2,96093,a50da179-751f-47a8-a547-8c4a291ed381,0.02,false,false,near_mint,en,USD
Uncharted Haven,FDN,Foundations,564,normal,common,3,97170,172cd5b7-98fc-4add-b858-a0b3dfb75c19,0.14,false,false,near_mint,en,USD
Teapot Slinger,BLB,Bloomburrow,157,normal,uncommon,2,96015,30506844-349f-4b68-8cc1-d028c1611cc7,0.06,false,false,near_mint,en,USD
Harvestrite Host,BLB,Bloomburrow,15,normal,uncommon,2,95915,41762689-0c13-4d45-9d81-ba2afad980f8,0.07,false,false,near_mint,en,USD
Spellgyre,BLB,Bloomburrow,72,normal,uncommon,2,96139,f6f6620a-1d40-429d-9a0c-aaeb62adaa71,0.08,false,false,near_mint,en,USD
Oakhollow Village,BLB,Bloomburrow,258,normal,uncommon,2,95624,0d49b016-b02b-459f-85e9-c04f6bdcb94e,0.35,false,false,near_mint,en,USD
Bumbleflower's Sharepot,BLB,Bloomburrow,244,normal,common,2,95924,5f0affd5-5dcd-4dd1-a694-37a9aedf4084,0.02,false,false,near_mint,en,USD
Overprotect,BLB,Bloomburrow,185,normal,uncommon,2,95891,079e979f-b618-4625-989c-e0ea5b61ed8a,0.55,false,false,near_mint,en,USD
Heaped Harvest,BLB,Bloomburrow,175,normal,common,3,96255,3b5349db-0e0a-4b15-886e-0db403ef49cb,0.1,false,false,near_mint,en,USD
Flowerfoot Swordmaster,BLB,Bloomburrow,14,normal,uncommon,2,95812,97ff118f-9c3c-43a2-8085-980c7fe7d227,0.15,false,false,near_mint,en,USD
Banishing Light,BLB,Bloomburrow,1,normal,common,6,96011,25a06f82-ebdb-4dd6-bfe8-958018ce557c,0.04,false,false,near_mint,en,USD
Sazacap's Brew,BLB,Bloomburrow,151,normal,common,3,96330,6d963080-b3ec-467d-82f7-39db6ecd6bbc,0.05,false,false,near_mint,en,USD
Diresight,BLB,Bloomburrow,91,normal,common,3,95985,fada29c0-5293-40a4-b36d-d073ee99e650,0.1,false,false,near_mint,en,USD
Gossip's Talent,BLB,Bloomburrow,51,normal,uncommon,2,95961,b299889a-03d6-4659-b0e1-f0830842e40f,0.18,false,false,near_mint,en,USD
Fountainport Bell,BLB,Bloomburrow,245,normal,common,3,96094,a5c94bc0-a49d-451b-8e8d-64d46b8b8603,0.04,false,false,near_mint,en,USD
Reptilian Recruiter,BLB,Bloomburrow,149,normal,uncommon,2,96072,81dec453-c9d7-42cb-980a-c82f82bede76,0.02,false,false,near_mint,en,USD
Thistledown Players,BLB,Bloomburrow,35,normal,common,2,95960,afa8d83f-8586-4127-8b55-9715e9547488,0.01,false,false,near_mint,en,USD
Clifftop Lookout,BLB,Bloomburrow,168,normal,uncommon,2,95931,662d3bcc-65f3-4c69-8ea1-446870a1193d,0.16,false,false,near_mint,en,USD
Rust-Shield Rampager,BLB,Bloomburrow,190,normal,common,2,96117,c96b01f5-83de-4237-a68d-f946c53e31a6,0.02,false,false,near_mint,en,USD
Consumed by Greed,BLB,Bloomburrow,87,normal,uncommon,2,95884,e50acc41-3517-42db-b1d3-1bdfd7294d84,0.09,false,false,near_mint,en,USD
Rabbit Response,BLB,Bloomburrow,26,normal,common,2,96114,c4ded450-346d-4917-917a-b62bc0267509,0.02,false,false,near_mint,en,USD
Corpseberry Cultivator,BLB,Bloomburrow,210,normal,common,2,95829,c911a759-ed7b-452b-88a3-663478357610,0.02,false,false,near_mint,en,USD
Mind Drill Assailant,BLB,Bloomburrow,225,normal,common,2,95783,507ba708-ca9b-453e-b4c2-23b6650eb5a8,0.05,false,false,near_mint,en,USD
Hazardroot Herbalist,BLB,Bloomburrow,174,normal,uncommon,2,96130,e2882982-b3a3-4762-a550-6b82db1038e8,0.04,false,false,near_mint,en,USD
Dewdrop Cure,BLB,Bloomburrow,10,normal,uncommon,2,95932,666aefc2-44e0-4c27-88d5-7906f245a71f,0.13,false,false,near_mint,en,USD
Valley Rally,BLB,Bloomburrow,159,normal,uncommon,2,95878,b6178258-1ad6-4122-a56f-6eb7d0611e84,0.04,false,false,near_mint,en,USD
Blacksmith's Talent,BLB,Bloomburrow,125,normal,uncommon,2,96029,4bb318fa-481d-40a7-978e-f01b49101ae0,0.17,false,false,near_mint,en,USD
Pileated Provisioner,BLB,Bloomburrow,25,normal,common,2,96102,ae442cd6-c4df-4aad-9b1d-ccd936c5ec96,0.02,false,false,near_mint,en,USD
Short Bow,BLB,Bloomburrow,248,normal,uncommon,2,96281,51d8b72b-fa8f-48d3-bddc-d3ce9b8ba2ea,0.15,false,false,near_mint,en,USD
Warren Elder,BLB,Bloomburrow,37,normal,common,2,96030,4bf20069-5a20-4f95-976b-6af2b69f3ad0,0.03,false,false,near_mint,en,USD
1 Name Set code Set name Collector number Foil Rarity Quantity ManaBox ID Scryfall ID Purchase price Misprint Altered Condition Language Purchase price currency
2 Tinybones, Bauble Burglar FDN Foundations 72 normal rare 1 101414 ff3d85bc-ef2d-4251-baf4-a14bd0cee61e 0.66 false false near_mint en USD
3 Scrawling Crawler FDN Foundations 132 normal rare 1 100912 a1176dcf-40ee-4342-aa74-791b8352e99a 4.81 false false near_mint en USD
4 Giada, Font of Hope FDN Foundations 141 normal rare 1 100804 8ae6fc26-cfad-4da8-98d9-49c27c24d293 1.33 false false near_mint en USD
5 Blasphemous Edict FDN Foundations 57 normal rare 1 100168 11040ecd-3153-4029-b42b-1441bc51ec34 6.9 false false near_mint en USD
6 Drakuseth, Maw of Flames FDN Foundations 193 normal rare 1 100092 029b1edb-e1de-4f1c-81df-8d17f4920318 0.33 false false near_mint en USD
7 Koma, World-Eater FDN Foundations 347 normal rare 1 100792 8889e1ca-eec1-408b-b11e-98cc0a357a97 4.69 false false near_mint en USD
8 Ghalta, Primal Hunger FDN Foundations 222 normal rare 1 100635 6a9c39e4-a8cf-42dd-8d0e-45634b335546 0.54 false false near_mint en USD
9 Sire of Seven Deaths FDN Foundations 1 normal mythic 1 100812 8d8432a7-1c8a-4cfb-947c-ecf9791063eb 18.63 false false near_mint en USD
10 Hero's Downfall FDN Foundations 319 normal uncommon 1 101639 10cedc6d-075a-4f9b-a858-e2c29809ee33 0.39 false false near_mint en USD
11 Etali, Primal Storm FDN Foundations 194 normal rare 1 101037 b6af9894-95b5-4c8e-902f-a9ba70f02e4a 0.32 false false near_mint en USD
12 High Fae Trickster FDN Foundations 307 normal rare 1 100918 a21180a4-208f-4c13-a704-58403ddaf12f 3.39 false false near_mint en USD
13 Mocking Sprite FDN Foundations 159 foil common 1 101624 f6792f63-b651-497d-8aa5-cddf4cedeca8 0.09 false false near_mint en USD
14 Bake into a Pie FDN Foundations 169 foil common 1 101494 2ab0e660-86a3-4b92-82fa-77dcb5db947d 0.06 false false near_mint en USD
15 Boltwave FDN Foundations 79 foil uncommon 1 100810 8d1ec351-5e70-4eb2-b590-6bff94ef8178 4.27 false false near_mint en USD
16 Jungle Hollow FDN Foundations 263 foil common 1 101224 dc758e14-d370-45e4-bbc5-938fb4d21127 0.08 false false near_mint en USD
17 Ambush Wolf FDN Foundations 98 foil common 1 101492 2903832c-318e-42ab-bf58-c682ec2f7afd 0.03 false false near_mint en USD
18 An Offer You Can't Refuse FDN Foundations 160 foil uncommon 1 100948 a829747f-cf9b-4d81-ba66-9f0630ed4565 1.51 false false near_mint en USD
19 Sower of Chaos FDN Foundations 95 foil common 1 101556 7ff50606-491c-4946-8d03-719b01cfad77 0.02 false false near_mint en USD
20 Guarded Heir FDN Foundations 14 foil uncommon 1 100505 525ba5c7-3ce5-4e52-b8b5-96c9040a6738 0.06 false false near_mint en USD
21 Wind-Scarred Crag FDN Foundations 271 foil common 1 100684 759e99df-11a8-4aee-b6bc-344e84e10d94 0.08 false false near_mint en USD
22 Think Twice FDN Foundations 165 foil common 1 101202 d88faaa1-eb41-40f7-991c-5c06e1138f3d 0.03 false false near_mint en USD
23 Grow from the Ashes FDN Foundations 225 foil common 1 101502 42525f8a-aee7-4811-8f05-471b559c2c4a 0.07 false false near_mint en USD
24 Spitfire Lagac FDN Foundations 208 foil common 1 101496 30f600cd-b696-4f49-9cbc-5a33aa43d04c 0.05 false false near_mint en USD
25 Abyssal Harvester FDN Foundations 54 foil rare 1 101342 f2e0f538-5825-47e9-883c-3ec6fd5b25ea 3.18 false false near_mint en USD
26 Sanguine Syphoner FDN Foundations 68 foil common 1 101582 b1daf5bb-c8e9-4e79-a532-ca92a9a885cd 0.19 false false near_mint en USD
27 Goldvein Pick FDN Foundations 253 foil common 1 101572 a241317d-2277-467e-a8f9-aa71c944e244 0.06 false false near_mint en USD
28 Goblin Negotiation FDN Foundations 88 foil uncommon 1 101335 f2016585-e26c-4d13-b09f-af6383c192f7 0.14 false false near_mint en USD
29 Banishing Light FDN Foundations 138 foil common 1 101613 e38dc3b3-1629-491b-8afd-0e7a9a857713 0.05 false false near_mint en USD
30 Dauntless Veteran FDN Foundations 8 foil uncommon 1 100704 7a136f26-ac66-407f-b389-357222d2c4a2 0.06 false false near_mint en USD
31 Run Away Together FDN Foundations 162 foil common 1 101614 e598eb7b-10dc-49e6-ac60-2fefa987173e 0.02 false false near_mint en USD
32 Tatyova, Benthic Druid FDN Foundations 247 foil uncommon 1 101301 eabc978a-0666-472d-bdc6-d4b29d29eca4 0.14 false false near_mint en USD
33 Balmor, Battlemage Captain FDN Foundations 237 foil uncommon 1 100142 0b45ab13-9bb6-48af-8b37-d97b25801ac8 0.13 false false near_mint en USD
34 Involuntary Employment FDN Foundations 203 foil common 1 101622 f3ad3d62-2f24-4562-b3fa-809213dbc4a4 0.03 false false near_mint en USD
35 Dwynen, Gilt-Leaf Daen FDN Foundations 217 foil uncommon 1 100086 01c00d7b-7fac-4f8c-a1ea-de2cf4d06627 0.23 false false near_mint en USD
36 Swiftfoot Boots FDN Foundations 258 foil uncommon 1 100414 41040541-b129-4cf4-9411-09b1d9d32c19 2.03 false false near_mint en USD
37 Soul-Shackled Zombie FDN Foundations 70 foil common 1 101609 deea5690-6eb2-4353-b917-cbbf840e4e71 0.05 false false near_mint en USD
38 Fake Your Own Death FDN Foundations 174 foil common 1 101539 693635a6-df50-44c5-9598-0c79b45d4df4 0.09 false false near_mint en USD
39 Gnarlid Colony FDN Foundations 224 foil common 1 101508 47565d10-96bf-4fb0-820f-f20a44a76b6f 0.05 false false near_mint en USD
40 Apothecary Stomper FDN Foundations 99 foil common 1 101537 680b7b0c-0e1b-46ce-9917-9fc6e05aa148 0.02 false false near_mint en USD
41 Rugged Highlands FDN Foundations 265 foil common 1 101400 fd6eaf8e-8881-4d7b-bafc-75e4ca5cbef6 0.05 false false near_mint en USD
42 Firebrand Archer FDN Foundations 196 foil common 1 101630 fe0312f1-4c98-4b7f-8a34-0059ea80edef 0.13 false false near_mint en USD
43 Scoured Barrens FDN Foundations 266 foil common 1 100277 2632a4b2-9ca6-4b67-9a99-14f52ad3dc41 0.12 false false near_mint en USD
44 Courageous Goblin FDN Foundations 82 foil common 1 101566 8db6819c-666a-409d-85a5-b9ac34d8dd2f 0.02 false false near_mint en USD
45 Jungle Hollow FDN Foundations 263 normal common 1 101224 dc758e14-d370-45e4-bbc5-938fb4d21127 0.07 false false near_mint en USD
46 Wind-Scarred Crag FDN Foundations 271 normal common 1 100684 759e99df-11a8-4aee-b6bc-344e84e10d94 0.04 false false near_mint en USD
47 Dismal Backwater FDN Foundations 261 normal common 1 101220 dbb0df36-8467-4a41-8e1c-6c3584d4fd10 0.06 false false near_mint en USD
48 Bloodfell Caves FDN Foundations 259 normal common 1 100806 8b90dc92-cb66-41d9-89f9-2b6e3cfc8082 0.05 false false near_mint en USD
49 Rugged Highlands FDN Foundations 265 normal common 1 101400 fd6eaf8e-8881-4d7b-bafc-75e4ca5cbef6 0.05 false false near_mint en USD
50 Scavenging Ooze FDN Foundations 232 normal rare 1 100808 8c504c23-1e9a-411b-9cfe-4180d0c744f6 0.15 false false near_mint en USD
51 Kiora, the Rising Tide FDN Foundations 45 normal rare 1 100762 83f20a32-9f5d-4a68-8995-549e57554da2 1.57 false false near_mint en USD
52 Curator of Destinies FDN Foundations 34 normal rare 1 100908 9ff79da7-c3f7-4541-87a0-503544c699b5 0.12 false false near_mint en USD
53 Loot, Exuberant Explorer FDN Foundations 106 normal rare 1 100131 09980ce6-425b-4e03-94d0-0f02043cb361 4.8 false false near_mint en USD
54 Micromancer FDN Foundations 158 normal uncommon 1 101274 e6af54ea-b57a-4e50-8e46-1747cca14430 0.07 false false near_mint en USD
55 Ruby, Daring Tracker FDN Foundations 245 normal uncommon 1 101405 fe3e7dd2-b66d-4218-9fde-f84bec26b7bf 0.05 false false near_mint en USD
56 Mild-Mannered Librarian FDN Foundations 228 normal uncommon 1 100515 5389663a-fe25-41b9-8c92-1f4d7721ffc2 0.03 false false near_mint en USD
57 Guarded Heir FDN Foundations 14 normal uncommon 1 100505 525ba5c7-3ce5-4e52-b8b5-96c9040a6738 0.05 false false near_mint en USD
58 Garruk's Uprising FDN Foundations 220 normal uncommon 1 100447 4805c303-e73b-443b-a09f-49d2c2c88bb5 0.25 false false near_mint en USD
59 Vampire Nighthawk FDN Foundations 186 normal uncommon 1 101474 0a1934ab-3171-4fc6-8033-ad998899ba73 0.12 false false near_mint en USD
60 Soulstone Sanctuary FDN Foundations 133 normal rare 1 100596 642553a7-6d0f-483d-a873-3a703786db42 1.9 false false near_mint en USD
61 Balmor, Battlemage Captain FDN Foundations 237 normal uncommon 1 100142 0b45ab13-9bb6-48af-8b37-d97b25801ac8 0.07 false false near_mint en USD
62 Adventuring Gear FDN Foundations 249 normal uncommon 1 100358 361f9b99-5b5d-40da-b4b9-5ad90f6280ee 0.06 false false near_mint en USD
63 Grappling Kraken FDN Foundations 39 normal uncommon 1 101165 d1f5cab3-3fc0-448d-8252-cd55abf5b596 0.12 false false near_mint en USD
64 Quakestrider Ceratops FDN Foundations 110 normal uncommon 1 100120 067f72c2-ead6-4879-bc9d-696c9f87c0b2 0.11 false false near_mint en USD
65 Genesis Wave FDN Foundations 221 normal rare 1 101177 d46f7ddb-f986-4f1f-b096-ae1a02d0bdc8 0.29 false false near_mint en USD
66 Lathril, Blade of the Elves FDN Foundations 242 normal rare 1 100811 8d4e5480-a287-4a25-b855-a26dae555b1c 0.25 false false near_mint en USD
67 Elvish Archdruid FDN Foundations 219 normal rare 1 100341 341da856-7414-403b-b2e3-4bebd58a5aa4 0.4 false false near_mint en USD
68 Imprisoned in the Moon FDN Foundations 156 normal uncommon 1 101313 ee28e147-6622-4399-a314-c14a5c912dd0 0.18 false false near_mint en USD
69 Inspiring Call FDN Foundations 226 normal uncommon 1 100400 3e241642-5172-4437-b694-f6aa159d5cd9 0.15 false false near_mint en USD
70 Essence Scatter FDN Foundations 153 normal uncommon 1 101226 dd05c850-f91e-4ffb-b4cc-8418d49dad90 0.04 false false near_mint en USD
71 Exemplar of Light FDN Foundations 11 normal rare 1 100832 920c8fc5-fdd2-446a-a676-5c363f96928f 2.82 false false near_mint en USD
72 Meteor Golem FDN Foundations 256 normal uncommon 1 101167 d291ea1e-36bc-46b3-b3ae-084fa0ba69eb 0.05 false false near_mint en USD
73 Swiftfoot Boots FDN Foundations 258 normal uncommon 1 100414 41040541-b129-4cf4-9411-09b1d9d32c19 1.19 false false near_mint en USD
74 Brazen Scourge FDN Foundations 191 normal uncommon 1 101616 eb84b86c-3276-4fc1-a09d-47de388cb729 0.02 false false near_mint en USD
75 Sylvan Scavenging FDN Foundations 113 normal rare 1 101100 c35b683c-d3b2-46a1-876a-81b34e8ba2fc 0.25 false false near_mint en USD
76 Claws Out FDN Foundations 6 normal uncommon 1 100429 4396049c-b976-4b7f-8ecd-564e24ebd631 0.1 false false near_mint en USD
77 Snakeskin Veil FDN Foundations 233 normal uncommon 1 100645 6cc4c21d-9bdc-4490-9203-17f51db0ddd1 0.08 false false near_mint en USD
78 Skyship Buccaneer FDN Foundations 50 normal uncommon 1 100587 62958fc3-55dc-4b97-a070-490d6ed27820 0.02 false false near_mint en USD
79 Arcane Epiphany FDN Foundations 29 normal uncommon 1 100116 06431793-5dfe-4cbf-990b-4bcc960d1f31 0.03 false false near_mint en USD
80 Brass's Bounty FDN Foundations 190 normal rare 1 100610 65fe7127-b0ec-400f-97f1-6e17ab8e319d 0.14 false false near_mint en USD
81 Fiendish Panda FDN Foundations 120 normal uncommon 1 100483 4e434d74-cad0-45f5-bc8d-f34aa5e1d879 0.09 false false near_mint en USD
82 Frenzied Goblin FDN Foundations 199 normal uncommon 1 101602 d5592573-2889-40b1-b1d5-c2802482549a 0.03 false false near_mint en USD
83 Lunar Insight FDN Foundations 46 normal rare 1 100958 a9a159f6-fecf-4bdd-b2f8-a9665a5cc32d 0.25 false false near_mint en USD
84 Twinblade Blessing FDN Foundations 26 normal uncommon 1 101310 ecf01cbe-9fcb-4f35-bc6b-2280620b06ff 0.1 false false near_mint en USD
85 Tatyova, Benthic Druid FDN Foundations 247 normal uncommon 1 101301 eabc978a-0666-472d-bdc6-d4b29d29eca4 0.06 false false near_mint en USD
86 Dragon Trainer FDN Foundations 84 normal uncommon 1 100830 91bd75a1-cb54-4e38-9ce1-e8f32a73c6eb 0.04 false false near_mint en USD
87 Raise the Past FDN Foundations 22 normal rare 1 100641 6c6be129-56da-4fe7-a6bd-6a1d402c09e1 2.27 false false near_mint en USD
88 Divine Resilience FDN Foundations 10 normal uncommon 1 101347 f3a08245-a535-4d24-b8c0-78759bb9c4b0 0.11 false false near_mint en USD
89 Bulk Up FDN Foundations 80 normal uncommon 1 100857 977dcc50-da10-4281-b522-9240c1204f5d 0.2 false false near_mint en USD
90 Diregraf Ghoul FDN Foundations 171 normal uncommon 1 100439 4682012c-d7e0-4257-b538-3de497507464 0.03 false false near_mint en USD
91 Drake Hatcher FDN Foundations 35 normal rare 1 101071 bcaf4196-6bf3-47fa-b5c7-0e77f45cf820 0.12 false false near_mint en USD
92 Youthful Valkyrie FDN Foundations 149 normal uncommon 1 100894 9d795f79-c3a5-4ea1-a5cf-1ce73d6837b6 0.14 false false near_mint en USD
93 Seeker's Folly FDN Foundations 69 normal uncommon 1 101067 bc359da6-8b7f-45ec-b530-ce159fc35953 0.06 false false near_mint en USD
94 Heroic Reinforcements FDN Foundations 241 normal uncommon 1 100631 6a05e8d5-c2ad-489a-888d-22622886b620 0.04 false false near_mint en USD
95 Inspiration from Beyond FDN Foundations 43 normal uncommon 1 101033 b636fe95-664f-4fb1-aab9-28856edeccd6 0.04 false false near_mint en USD
96 Dwynen, Gilt-Leaf Daen FDN Foundations 217 normal uncommon 1 100086 01c00d7b-7fac-4f8c-a1ea-de2cf4d06627 0.14 false false near_mint en USD
97 Twinflame Tyrant FDN Foundations 97 normal mythic 1 100228 1eb34f51-0bd2-43c3-af95-2ce8dabcc7bb 17.77 false false near_mint en USD
98 Sun-Blessed Healer FDN Foundations 25 normal uncommon 1 100332 323d029e-9a88-4188-b3a4-38ef32cffc9f 0.09 false false near_mint en USD
99 Seismic Rupture FDN Foundations 205 normal uncommon 1 100268 2519a51a-26a0-4884-9ba8-9db135c9ee49 0.02 false false near_mint en USD
100 Slumbering Cerberus FDN Foundations 94 normal uncommon 1 100892 9d06faa8-201d-45db-b398-ad56f7b01848 0.03 false false near_mint en USD
101 Tragic Banshee FDN Foundations 73 normal uncommon 1 100324 30df3e33-2f17-4067-99f1-5db6b0f41fd4 0.03 false false near_mint en USD
102 Stromkirk Bloodthief FDN Foundations 185 normal uncommon 1 97176 485d6a5a-2054-47d5-91b8-71ce308ed4dc 0.04 false false near_mint en USD
103 Blanchwood Armor FDN Foundations 213 normal uncommon 1 100237 1fd7ec1a-dafa-42ca-bc25-f6848fb03f60 0.07 false false near_mint en USD
104 Spectral Sailor FDN Foundations 164 normal uncommon 1 100100 03a49535-c5f3-4a6f-b333-7ac7bffdc9ae 0.06 false false near_mint en USD
105 Extravagant Replication FDN Foundations 154 normal rare 1 100634 6a41dfae-bc7e-4105-8f7e-fd0109197ad8 0.43 false false near_mint en USD
106 Electroduplicate FDN Foundations 85 normal rare 1 100976 abb06b1c-5d4e-49b9-9c4a-e60ab656a257 0.3 false false near_mint en USD
107 Angel of Finality FDN Foundations 136 normal uncommon 1 101057 baaabd52-3aa9-4e2f-9369-d4db8b405ba8 0.07 false false near_mint en USD
108 Battlesong Berserker FDN Foundations 78 normal uncommon 1 100917 a1f8b199-5d62-485f-b1c3-b30aa550595b 0.03 false false near_mint en USD
109 Swiftblade Vindicator FDN Foundations 246 normal rare 1 101372 f94618ec-000c-4371-b925-05ff82bfe221 0.12 false false near_mint en USD
110 Dauntless Veteran FDN Foundations 8 normal uncommon 1 100704 7a136f26-ac66-407f-b389-357222d2c4a2 0.05 false false near_mint en USD
111 Hero's Downfall FDN Foundations 175 normal uncommon 1 97185 ad2c01d9-8f54-46c0-9dc9-d4d4764ce1c9 0.1 false false near_mint en USD
112 Resolute Reinforcements FDN Foundations 145 normal uncommon 1 100841 940f3989-77cc-49a9-92e0-095a75d80f0f 0.09 false false near_mint en USD
113 Zombify FDN Foundations 187 normal uncommon 1 101225 dc798e6f-13c4-457c-b052-b7b65bc83cfe 0.09 false false near_mint en USD
114 Fiery Annihilation FDN Foundations 86 normal uncommon 1 100523 54fe00aa-d284-48f9-b5a2-1bd4c5fa8e58 0.07 false false near_mint en USD
115 Clinquant Skymage FDN Foundations 33 normal uncommon 1 100357 36012810-0e83-4640-8ba7-7262229f1b84 0.05 false false near_mint en USD
116 Consuming Aberration FDN Foundations 238 normal rare 1 101066 bc2b28fd-66b0-457c-80ea-7caed2cc7926 0.16 false false near_mint en USD
117 Fishing Pole FDN Foundations 128 normal uncommon 1 101128 c95ab836-3277-4223-9aaa-ef2c77256b65 0.07 false false near_mint en USD
118 Felling Blow FDN Foundations 105 normal uncommon 1 100854 96948ae3-b15d-4d6d-aa73-9f52084cd903 0.05 false false near_mint en USD
119 Abrade FDN Foundations 188 normal uncommon 1 100522 548947dc-a5ca-43b5-9531-bcef20fa4ae5 0.09 false false near_mint en USD
120 Spinner of Souls FDN Foundations 112 normal rare 1 101358 f50a8dec-b079-4192-9098-6cdc1026c693 0.66 false false near_mint en USD
121 Vampire Gourmand FDN Foundations 74 normal uncommon 1 100827 917514c0-9cd5-4b97-85b9-c4f753560ad4 0.09 false false near_mint en USD
122 Needletooth Pack FDN Foundations 108 normal uncommon 1 100868 993c1679-e02b-44f2-b34e-12fd6b5142e9 0.05 false false near_mint en USD
123 Burnished Hart FDN Foundations 250 normal uncommon 1 100609 65ebbff0-fbe6-4310-a33f-e00bb2534979 0.06 false false near_mint en USD
124 Arbiter of Woe FDN Foundations 55 normal uncommon 1 101008 b2496c4a-df03-4583-bd76-f98ed5cb61ee 0.06 false false near_mint en USD
125 Good-Fortune Unicorn FDN Foundations 240 normal uncommon 1 101300 eabbe163-2b15-42e3-89ce-7363e6250d3a 0.1 false false near_mint en USD
126 Reassembling Skeleton FDN Foundations 182 normal uncommon 1 100291 28e84b1b-1c05-4e1b-93b8-9cc2ca73509d 0.08 false false near_mint en USD
127 Reclamation Sage FDN Foundations 231 normal uncommon 1 100197 1918ea65-ab7f-4d40-97fd-a656c892a2a1 0.14 false false near_mint en USD
128 Leyline Axe FDN Foundations 129 normal rare 1 101052 b9c03336-a321-4c06-94d1-809f328fabd8 3.17 false false near_mint en USD
129 An Offer You Can't Refuse FDN Foundations 160 normal uncommon 1 100948 a829747f-cf9b-4d81-ba66-9f0630ed4565 0.99 false false near_mint en USD
130 Goblin Negotiation FDN Foundations 88 normal uncommon 1 101335 f2016585-e26c-4d13-b09f-af6383c192f7 0.09 false false near_mint en USD
131 Empyrean Eagle FDN Foundations 239 normal uncommon 1 100533 577e99a7-4a55-4314-8f08-2ae0c33b85c7 0.08 false false near_mint en USD
132 Solemn Simulacrum FDN Foundations 257 normal rare 1 100514 5383f45e-3da2-40fb-beee-801448bbb60f 0.3 false false near_mint en USD
133 Crystal Barricade FDN Foundations 7 normal rare 1 100822 905d3e02-ea06-45e7-9adb-c8e7583323a2 1.24 false false near_mint en USD
134 Hidetsugu's Second Rite FDN Foundations 202 normal uncommon 1 100577 609421da-8d89-4365-b18b-778832d91482 0.04 false false near_mint en USD
135 Affectionate Indrik FDN Foundations 211 normal uncommon 1 100310 2da8347d-06a4-46e0-a55e-cc2da4660263 0.02 false false near_mint en USD
136 Infernal Vessel FDN Foundations 63 normal uncommon 1 101560 877b6330-2d0b-4f2f-a848-f10b06fb4ef5 0.06 false false near_mint en USD
137 Zimone, Paradox Sculptor FDN Foundations 126 normal mythic 1 100241 20ccbfdd-ddae-440c-9bc0-38b15a56fdd1 2.13 false false near_mint en USD
138 High-Society Hunter FDN Foundations 61 normal rare 1 100501 51da4a4b-ea12-4169-a7cf-eb4427f13e84 0.64 false false near_mint en USD
139 Heraldic Banner FDN Foundations 254 normal uncommon 1 100678 743ea709-dbb3-4db8-a2ce-544f47eb6339 0.24 false false near_mint en USD
140 Wardens of the Cycle FDN Foundations 125 normal uncommon 1 100761 83ea9b2c-5723-4eff-88ac-6669975939e3 0.07 false false near_mint en USD
141 Preposterous Proportions FDN Foundations 109 normal rare 1 100983 acb65189-60e4-42e0-9fb1-da6b716b91d7 0.94 false false near_mint en USD
142 Savannah Lions FDN Foundations 146 normal uncommon 1 97184 9c9ac1bc-cdf3-4fa6-8319-a7ea164e9e47 0.04 false false near_mint en USD
143 Secluded Courtyard FDN Foundations 267 normal uncommon 1 101161 d13373d2-139b-48c7-a8c9-828cefc4f150 0.12 false false near_mint en USD
144 Ajani's Pridemate FDN Foundations 135 normal uncommon 1 100255 222c1a68-e34c-4103-b1be-17d4ceaef6ce 0.06 false false near_mint en USD
145 Arahbo, the First Fang FDN Foundations 2 normal rare 1 100503 524a5d93-26ed-436d-a437-dc9460acce98 1.0 false false near_mint en USD
146 Authority of the Consuls FDN Foundations 137 normal rare 1 100425 42ce2d7f-5924-47c0-b5ed-dacf9f9617a0 5.3 false false near_mint en USD
147 Nine-Lives Familiar FDN Foundations 321 normal rare 1 100060 6cc1623f-370d-42b5-88a2-039f31e9be0b 2.67 false false near_mint en USD
148 Ajani's Pridemate FDN Foundations 293 foil uncommon 1 101180 d4cfb9bc-4273-4e5f-a7ac-2006a8345a4e 0.38 false false near_mint en USD
149 Helpful Hunter FDN Foundations 16 foil common 1 97172 1b9a0e91-80b5-428f-8f08-931d0631be14 1.61 false false near_mint en USD
150 Felidar Savior FDN Foundations 12 foil common 1 97191 cd092b14-d72f-4de0-8f19-1338661b9e3b 0.05 false false near_mint en USD
151 Thrill of Possibility FDN Foundations 210 normal common 3 101561 882b348c-076b-41d8-b505-063480636669 0.03 false false near_mint en USD
152 Lightshell Duo FDN Foundations 157 normal common 7 101063 bb75315c-ea8f-4eb0-899e-c73ef75fc396 0.04 false false near_mint en USD
153 Mischievous Pup FDN Foundations 144 normal uncommon 2 100670 7214d984-6400-44d7-bde6-57d96b606e78 0.04 false false near_mint en USD
154 Swiftwater Cliffs FDN Foundations 268 normal common 3 101389 fb88667d-7088-4889-960f-317486ebe856 0.03 false false near_mint en USD
155 Hare Apparent FDN Foundations 15 normal common 3 100907 9fc6f0e9-eb5f-4bc0-b3d7-756644b66d12 3.62 false false near_mint en USD
156 Dazzling Angel FDN Foundations 9 normal common 3 101468 027dc444-e544-4693-8653-3dcdda530162 0.1 false false near_mint en USD
157 Bigfin Bouncer FDN Foundations 31 normal common 3 100882 9b1d5b76-b07e-45c6-800d-4cfce085164f 0.02 false false near_mint en USD
158 Ambush Wolf FDN Foundations 98 normal common 4 101492 2903832c-318e-42ab-bf58-c682ec2f7afd 0.05 false false near_mint en USD
159 Healer's Hawk FDN Foundations 142 normal common 3 101595 cc8e4563-04bb-46b5-835e-64ba11c0e972 0.09 false false near_mint en USD
160 Rune-Sealed Wall FDN Foundations 49 normal uncommon 2 101212 da0f147b-95ed-4f32-9b46-6a633ae31976 0.15 false false near_mint en USD
161 Pilfer FDN Foundations 181 normal common 4 101564 8c7c88b5-6d09-453b-b9c1-7dcbba8f1080 0.03 false false near_mint en USD
162 Stab FDN Foundations 71 normal common 3 101538 6859a5ba-1c1c-4631-bba8-f9900b827178 0.04 false false near_mint en USD
163 Heartfire Immolator FDN Foundations 201 normal uncommon 2 100390 3ca38f4d-01f5-4a02-9000-01261a440dbf 0.03 false false near_mint en USD
164 Marauding Blight-Priest FDN Foundations 178 normal common 3 101528 5f70dafc-c638-4ec0-ab5b-62998f752720 0.12 false false near_mint en USD
165 Broken Wings FDN Foundations 214 normal common 3 100584 61f9cbeb-cc9c-4562-be65-8a77053faefe 0.02 false false near_mint en USD
166 Firespitter Whelp FDN Foundations 197 normal uncommon 2 100463 4b3a4c7d-3126-4bde-9dca-cb6a1e2f37c9 0.15 false false near_mint en USD
167 Make Your Move FDN Foundations 143 normal common 3 101546 7368f861-3288-4645-90a7-ca35d6da3721 0.03 false false near_mint en USD
168 Treetop Snarespinner FDN Foundations 114 normal common 4 101562 88e68fa3-159d-49a6-8ac6-afc9bd6f1718 0.06 false false near_mint en USD
169 Vengeful Bloodwitch FDN Foundations 76 normal uncommon 2 97189 bd0c12dd-f138-45c0-9614-d83a1d8e8399 0.17 false false near_mint en USD
170 Evolving Wilds FDN Foundations 262 normal common 4 100376 3a0b9356-5b91-4542-8802-f0f7275238e1 0.06 false false near_mint en USD
171 Bite Down FDN Foundations 212 normal common 3 101625 f8d70b3b-f6f9-4b3c-ad70-0ce369e812b5 0.04 false false near_mint en USD
172 Elfsworn Giant FDN Foundations 103 normal common 3 100497 5128a5be-ffa6-4998-8488-872d80b24cb2 0.06 false false near_mint en USD
173 Apothecary Stomper FDN Foundations 99 normal common 3 101537 680b7b0c-0e1b-46ce-9917-9fc6e05aa148 0.05 false false near_mint en USD
174 Axgard Cavalry FDN Foundations 189 normal common 3 101631 fe3cc41a-adae-4c9b-b4d3-03f3ca862fed 0.03 false false near_mint en USD
175 Wary Thespian FDN Foundations 235 normal common 3 101574 a3d62d04-0974-4cb5-9a35-5e996c6456e2 0.01 false false near_mint en USD
176 Fleeting Flight FDN Foundations 13 normal common 3 101513 55139100-9342-41fd-b10a-8e9932e605d4 0.04 false false near_mint en USD
177 Quick-Draw Katana FDN Foundations 130 normal common 3 101540 69beec98-c89c-4673-953c-8b3ef3d81560 0.07 false false near_mint en USD
178 Goblin Surprise FDN Foundations 200 normal common 3 101512 527dd5d4-5f72-40bb-8a9d-1f5ac3f81e2e 0.05 false false near_mint en USD
179 Sower of Chaos FDN Foundations 95 normal common 4 101556 7ff50606-491c-4946-8d03-719b01cfad77 0.01 false false near_mint en USD
180 Involuntary Employment FDN Foundations 203 normal common 4 101622 f3ad3d62-2f24-4562-b3fa-809213dbc4a4 0.06 false false near_mint en USD
181 Burst Lightning FDN Foundations 192 normal common 3 100994 aec5d380-d354-4750-931a-6c91853e2edc 0.08 false false near_mint en USD
182 Banishing Light FDN Foundations 138 normal common 4 101613 e38dc3b3-1629-491b-8afd-0e7a9a857713 0.03 false false near_mint en USD
183 Blossoming Sands FDN Foundations 260 normal common 2 100364 37676ed8-588c-4bca-8065-874b74d84807 0.05 false false near_mint en USD
184 Felidar Savior FDN Foundations 12 normal common 3 97191 cd092b14-d72f-4de0-8f19-1338661b9e3b 0.02 false false near_mint en USD
185 Revenge of the Rats FDN Foundations 67 normal uncommon 2 100232 1f463c55-39a0-4f2f-aae3-0c5540bde5b7 0.12 false false near_mint en USD
186 Armasaur Guide FDN Foundations 3 normal common 3 101591 c80fc380-0499-4499-8a60-c43844c02c9b 0.03 false false near_mint en USD
187 Campus Guide FDN Foundations 251 normal common 3 101504 43c59814-3167-4b05-bb85-6c736f3956a4 0.02 false false near_mint en USD
188 Dreadwing Scavenger FDN Foundations 118 normal uncommon 2 101252 e24d838b-ab48-410a-9a50-dbfea5da089b 0.04 false false near_mint en USD
189 Gleaming Barrier FDN Foundations 252 normal common 3 101479 1b49b009-e6f2-494a-9235-f5c25c2d70a9 0.06 false false near_mint en USD
190 Scoured Barrens FDN Foundations 266 normal common 2 100277 2632a4b2-9ca6-4b67-9a99-14f52ad3dc41 0.07 false false near_mint en USD
191 Erudite Wizard FDN Foundations 37 normal common 3 100835 9273c417-0fcd-4273-b24e-afff76336d0c 0.01 false false near_mint en USD
192 Gorehorn Raider FDN Foundations 89 normal common 3 101551 78ce6c40-3452-4aa0-a45b-dbfd70f8d220 0.02 false false near_mint en USD
193 Cackling Prowler FDN Foundations 101 normal common 3 101481 1bd8e971-c075-4203-8d83-c28f22d4f9b9 0.03 false false near_mint en USD
194 Burglar Rat FDN Foundations 170 normal common 4 101608 de1c8758-ce3d-49cf-8173-c0eb46f5e7bc 0.05 false false near_mint en USD
195 Mocking Sprite FDN Foundations 159 normal common 3 101624 f6792f63-b651-497d-8aa5-cddf4cedeca8 0.03 false false near_mint en USD
196 Cathar Commando FDN Foundations 139 normal common 3 100204 19cf024d-edb6-4a79-8676-73f8db0cdf1f 0.06 false false near_mint en USD
197 Hungry Ghoul FDN Foundations 62 normal common 3 100701 790f9433-7565-4f7f-88e8-8af762ea0296 0.04 false false near_mint en USD
198 Vampire Soulcaller FDN Foundations 75 normal common 3 101495 2d076293-3b45-4878-8f67-978927cc1f68 0.04 false false near_mint en USD
199 Exsanguinate FDN Foundations 173 normal uncommon 1 101330 f11d7311-4066-4a5d-ba28-9857fa707a0b 0.4 false false near_mint en USD
200 Fanatical Firebrand FDN Foundations 195 normal common 3 101598 d1296316-7781-4e98-95e6-7020648be6a5 0.03 false false near_mint en USD
201 Sanguine Syphoner FDN Foundations 68 normal common 4 101582 b1daf5bb-c8e9-4e79-a532-ca92a9a885cd 0.07 false false near_mint en USD
202 Boltwave FDN Foundations 79 normal uncommon 2 100810 8d1ec351-5e70-4eb2-b590-6bff94ef8178 4.08 false false near_mint en USD
203 Nessian Hornbeetle FDN Foundations 229 normal uncommon 2 100395 3d4d93de-85c6-4653-8ddd-d8bf21516d44 0.05 false false near_mint en USD
204 Goldvein Pick FDN Foundations 253 normal common 3 101572 a241317d-2277-467e-a8f9-aa71c944e244 0.06 false false near_mint en USD
205 Icewind Elemental FDN Foundations 42 normal common 3 101629 fd0eba76-3829-408b-828f-0b223c884728 0.05 false false near_mint en USD
206 Fleeting Distraction FDN Foundations 155 normal common 3 101587 c0b86a7b-4912-43a7-ab89-c3432385baa1 0.02 false false near_mint en USD
207 Faebloom Trick FDN Foundations 38 normal uncommon 2 100148 0c3bee8f-f5be-4404-a696-c902637799c3 0.17 false false near_mint en USD
208 Brineborn Cutthroat FDN Foundations 152 normal uncommon 2 100986 acf7aafb-931f-49e5-8691-eab8cb34b05e 0.02 false false near_mint en USD
209 Gutless Plunderer FDN Foundations 60 normal common 3 101567 909d7778-c7f8-4fa4-89f2-8b32e86e96e4 0.05 false false near_mint en USD
210 Thornwood Falls FDN Foundations 269 normal common 2 100424 42799f51-0f8c-444b-974e-dae281a5c697 0.05 false false near_mint en USD
211 Tranquil Cove FDN Foundations 270 normal common 2 100719 7c9cabca-5bcc-4b97-b2ac-a345ad3ee43c 0.06 false false near_mint en USD
212 Fake Your Own Death FDN Foundations 174 normal common 3 101539 693635a6-df50-44c5-9598-0c79b45d4df4 0.05 false false near_mint en USD
213 Crypt Feaster FDN Foundations 59 normal common 4 100382 3b072811-998a-4a71-b59c-6afecc0dc4b6 0.03 false false near_mint en USD
214 Incinerating Blast FDN Foundations 90 normal common 3 101603 d58e20ab-c5ca-4295-884d-78efdaa83243 0.03 false false near_mint en USD
215 Refute FDN Foundations 48 normal common 3 100368 38806934-dd9c-4ad4-a59c-a16dce03a14a 0.06 false false near_mint en USD
216 Tolarian Terror FDN Foundations 167 normal common 3 100270 2569d4f3-55ed-4f99-9592-34c7df0aab72 0.09 false false near_mint en USD
217 Joust Through FDN Foundations 19 normal uncommon 2 100767 846adb38-f9bb-4fed-b8ed-36ec7885f989 0.05 false false near_mint en USD
218 Bake into a Pie FDN Foundations 169 normal common 3 101494 2ab0e660-86a3-4b92-82fa-77dcb5db947d 0.03 false false near_mint en USD
219 Soul-Shackled Zombie FDN Foundations 70 normal common 4 101609 deea5690-6eb2-4353-b917-cbbf840e4e71 0.04 false false near_mint en USD
220 Perforating Artist FDN Foundations 124 normal uncommon 2 100674 72980409-53f0-43c1-965e-06f22e7bb608 0.1 false false near_mint en USD
221 Serra Angel FDN Foundations 147 normal uncommon 2 100391 3cee9303-9d65-45a2-93d4-ef4aba59141b 0.05 false false near_mint en USD
222 Squad Rallier FDN Foundations 24 normal common 3 101534 65e1ee86-6f08-4aa0-bf63-ae12028ef080 0.04 false false near_mint en USD
223 Elementalist Adept FDN Foundations 36 normal common 3 101605 d9768cc6-8f53-4922-ae32-376a2f32d719 0.02 false false near_mint en USD
224 Elvish Regrower FDN Foundations 104 normal uncommon 2 100278 2694e3cd-26ed-4a10-ae55-fb84d7800253 0.09 false false near_mint en USD
225 Infestation Sage FDN Foundations 64 normal common 3 101601 d40c73de-7a5f-46f2-a70b-449bc8ecfe24 0.07 false false near_mint en USD
226 Inspiring Paladin FDN Foundations 18 normal common 3 101472 0763be06-25b2-4d6b-ab33-a1af85aeb443 0.02 false false near_mint en USD
227 Luminous Rebuke FDN Foundations 20 normal common 3 101529 621839e1-2756-4cdc-a25c-5f76ea98dd87 0.07 false false near_mint en USD
228 Gnarlid Colony FDN Foundations 224 normal common 3 101508 47565d10-96bf-4fb0-820f-f20a44a76b6f 0.02 false false near_mint en USD
229 Sure Strike FDN Foundations 209 normal common 3 101525 5de6a1e4-5c66-43e6-9f2a-2635bdab03f6 0.03 false false near_mint en USD
230 Helpful Hunter FDN Foundations 16 normal common 3 97172 1b9a0e91-80b5-428f-8f08-931d0631be14 0.14 false false near_mint en USD
231 Goblin Boarders FDN Foundations 87 normal common 3 101506 4409a063-bf2a-4a49-803e-3ce6bd474353 0.04 false false near_mint en USD
232 Macabre Waltz FDN Foundations 177 normal common 3 101509 4d1f3c84-89ba-4426-a80b-d524f172c912 0.03 false false near_mint en USD
233 Grow from the Ashes FDN Foundations 225 normal common 3 101502 42525f8a-aee7-4811-8f05-471b559c2c4a 0.03 false false near_mint en USD
234 Stroke of Midnight FDN Foundations 148 normal uncommon 2 100970 ab135925-d924-456d-851a-6ccdaaf27271 0.17 false false near_mint en USD
235 Eaten Alive FDN Foundations 172 normal common 3 100216 1c4f7b20-b2a8-498c-8c36-dc296863b0b9 0.02 false false near_mint en USD
236 Aetherize FDN Foundations 151 normal uncommon 2 100225 1e5530fc-0291-4a17-b048-c5d24e6f51d8 0.17 false false near_mint en USD
237 Giant Growth FDN Foundations 223 normal common 4 101073 bd0bf74e-14c1-4428-88d8-2181a080b5d0 0.03 false false near_mint en USD
238 Billowing Shriekmass FDN Foundations 56 normal uncommon 2 100711 7b3587a9-0667-4d53-807b-c437bcb1d7b3 0.02 false false near_mint en USD
239 Think Twice FDN Foundations 165 normal common 4 101202 d88faaa1-eb41-40f7-991c-5c06e1138f3d 0.05 false false near_mint en USD
240 Beast-Kin Ranger FDN Foundations 100 normal common 3 100082 0102e0be-5783-4825-9489-713b1b1df0b2 0.05 false false near_mint en USD
241 Spitfire Lagac FDN Foundations 208 normal common 4 101496 30f600cd-b696-4f49-9cbc-5a33aa43d04c 0.02 false false near_mint en USD
242 Aegis Turtle FDN Foundations 150 normal common 3 101590 c7f2014a-fbc9-447c-a440-e06d01066bb9 0.08 false false near_mint en USD
243 Firebrand Archer FDN Foundations 196 normal common 3 101630 fe0312f1-4c98-4b7f-8a34-0059ea80edef 0.05 false false near_mint en USD
244 Shivan Dragon FDN Foundations 206 normal uncommon 2 100236 1fcff1e0-2745-448d-a27b-e31719e222e9 0.05 false false near_mint en USD
245 Cephalid Inkmage FDN Foundations 32 normal uncommon 2 101040 b7e47680-18c7-4ffb-aac4-c5db6e7095ba 0.05 false false near_mint en USD
246 Prideful Parent FDN Foundations 21 normal common 3 97188 b742117a-8a72-43b9-b05d-274829d138a2 0.04 false false near_mint en USD
247 Uncharted Voyage FDN Foundations 53 normal common 4 101611 e0846820-e595-4743-8a28-29c57d728677 0.01 false false near_mint en USD
248 Eager Trufflesnout FDN Foundations 102 normal uncommon 2 100940 a6e8433d-eb2a-43d1-b59b-7d70ff97c8e7 0.04 false false near_mint en USD
249 Juggernaut FDN Foundations 255 normal uncommon 2 101351 f4468fff-cd6f-428c-b7a0-ff89f5bbea2e 0.07 false false near_mint en USD
250 Llanowar Elves FDN Foundations 227 normal common 3 95583 6a0b230b-d391-4998-a3f7-7b158a0ec2cd 0.15 false false near_mint en USD
251 Overrun FDN Foundations 230 normal uncommon 2 100220 1d8e9cbb-8bf4-4a48-a58e-79deb3abdf7f 0.14 false false near_mint en USD
252 Crackling Cyclops FDN Foundations 83 normal common 3 101541 6e5b899a-52f7-471b-ad50-4fa6566758fd 0.01 false false near_mint en USD
253 Mischievous Mystic FDN Foundations 47 normal uncommon 2 100242 20d89cec-528b-4b2a-87db-e11ce0000622 0.14 false false near_mint en USD
254 Witness Protection FDN Foundations 168 normal common 3 101621 f231e981-0069-43ce-ac1c-c85ced613e93 0.08 false false near_mint en USD
255 Dwynen's Elite FDN Foundations 218 normal common 3 100800 89d94c28-ea2e-4a3d-935f-6b2d9f2efc7a 0.05 false false near_mint en USD
256 Bushwhack FDN Foundations 215 normal common 3 101469 03ebdb36-55e0-49dd-a514-785fbeb4ae19 0.1 false false near_mint en USD
257 Run Away Together FDN Foundations 162 normal common 3 101614 e598eb7b-10dc-49e6-ac60-2fefa987173e 0.05 false false near_mint en USD
258 Strongbox Raider FDN Foundations 96 normal uncommon 2 101006 b2223eb8-59f9-489b-a3f3-b6496218cb79 0.02 false false near_mint en USD
259 Vanguard Seraph FDN Foundations 28 normal common 4 101503 4329c861-fc16-4a96-9c03-25af6ac2adc8 0.06 false false near_mint en USD
260 Self-Reflection FDN Foundations 163 normal uncommon 2 101247 e1e6abc9-25b2-4d51-b519-2525079eab51 0.04 false false near_mint en USD
261 Strix Lookout FDN Foundations 52 normal common 3 101627 fbd2422e-8e84-4c39-af29-3b4d38baee63 0.03 false false near_mint en USD
262 Cat Collector FDN Foundations 4 normal uncommon 2 100507 526fe356-bff1-4211-9e88-bf913ac76b1d 0.1 false false near_mint en USD
263 Courageous Goblin FDN Foundations 82 normal common 3 101566 8db6819c-666a-409d-85a5-b9ac34d8dd2f 0.03 false false near_mint en USD
264 Ygra, Eater of All BLB Bloomburrow 241 normal mythic 1 95825 b9ac7673-eae8-4c4b-889e-5025213a6151 11.58 false false near_mint en USD
265 Lifecreed Duo BLB Bloomburrow 20 normal common 1 95968 ca543405-5e12-48a0-9a77-082ac9bcb2f2 0.06 false false near_mint en USD
266 Take Out the Trash BLB Bloomburrow 156 normal common 1 95940 7a1c6f00-af4c-4d35-b682-6c0e759df9a5 0.04 false false near_mint en USD
267 Ravine Raider BLB Bloomburrow 106 normal common 1 96370 874510be-7ecd-4eff-abad-b9594eb4821a 0.02 false false near_mint en USD
268 Longstalk Brawl BLB Bloomburrow 182 normal common 1 95966 c7ef748c-b5e5-4e7d-bf2e-d3e6c08edb42 0.04 false false near_mint en USD
269 Valley Floodcaller BLB Bloomburrow 79 normal rare 1 95876 90b12da0-f666-471d-95f5-15d8c9b31c92 2.65 false false near_mint en USD
270 Bandit's Talent BLB Bloomburrow 83 normal uncommon 1 95917 485dc8d8-9e44-4a0f-9ff6-fa448e232290 0.47 false false near_mint en USD
271 Brambleguard Veteran BLB Bloomburrow 165 normal uncommon 1 95880 bac9f6f8-6797-4580-9fc4-9a825872e017 0.09 false false near_mint en USD
272 Mouse Trapper BLB Bloomburrow 22 normal uncommon 1 95948 8ba1bc5a-03e7-44ec-893e-44042cbc02ef 0.04 false false near_mint en USD
273 Bushy Bodyguard BLB Bloomburrow 166 normal uncommon 1 95997 0de60cf7-fa82-4b6f-9f88-6590fba5c863 0.08 false false near_mint en USD
274 Valley Mightcaller BLB Bloomburrow 202 normal rare 1 96057 7256451f-0122-452a-88e8-0fb0f6bea3f3 1.01 false false near_mint en USD
275 Druid of the Spade BLB Bloomburrow 170 normal common 1 96054 6b485cf7-bad0-4824-9ba7-cb112ce4769f 0.02 false false near_mint en USD
276 Skyskipper Duo BLB Bloomburrow 71 normal common 1 96476 d6844bad-ffbe-4c6e-b438-08562eccea52 0.04 false false near_mint en USD
277 Osteomancer Adept BLB Bloomburrow 103 normal rare 1 95800 7d8238dd-858f-466c-96de-986bd66861d7 0.36 false false near_mint en USD
278 Tender Wildguide BLB Bloomburrow 196 normal rare 1 95792 6b8bfa91-adb0-4596-8c16-d8bb64fdb26d 0.49 false false near_mint en USD
279 Huskburster Swarm BLB Bloomburrow 98 normal uncommon 1 95978 ed2f61d7-4eb0-41c5-8a34-a0793c2abc51 0.13 false false near_mint en USD
280 Scrapshooter BLB Bloomburrow 191 normal rare 1 96113 c42ab407-e72d-4c48-9a9e-2055b5e71c69 0.38 false false near_mint en USD
281 Scavenger's Talent BLB Bloomburrow 111 normal rare 1 96084 9a52b7fe-87ae-425b-85fd-b24e6e0395f1 1.54 false false near_mint en USD
282 Valley Rotcaller BLB Bloomburrow 119 normal rare 1 95781 4da80a9a-b1d5-4fc5-92f7-36946195d0c7 1.45 false false near_mint en USD
283 Thornplate Intimidator BLB Bloomburrow 117 normal common 1 96019 42f66c4a-feaa-4ba6-aa56-955b43329a9e 0.02 false false near_mint en USD
284 Bakersbane Duo BLB Bloomburrow 163 normal common 1 96035 5309354f-1ff4-4fa9-9141-01ea2f7588ab 0.1 false false near_mint en USD
285 Shore Up BLB Bloomburrow 69 normal common 1 96277 4dc3b49e-3674-494c-bdea-4374cefd10f4 0.08 false false near_mint en USD
286 Emberheart Challenger BLB Bloomburrow 133 normal rare 1 95888 0035082e-bb86-4f95-be48-ffc87fe5286d 4.13 false false near_mint en USD
287 Gev, Scaled Scorch BLB Bloomburrow 214 normal rare 1 96001 131ea976-289e-4f32-896d-27bbfd423ba9 0.37 false false near_mint en USD
288 Starfall Invocation BLB Bloomburrow 34 normal rare 1 95904 2aea38e6-ec58-4091-b27c-2761bdd12b13 0.88 false false near_mint en USD
289 Tidecaller Mentor BLB Bloomburrow 236 normal uncommon 1 95859 fa10ffac-7cc2-41ef-b8a0-9431923c0542 0.04 false false near_mint en USD
290 Jackdaw Savior BLB Bloomburrow 18 normal rare 1 96000 121af600-6143-450a-9f87-12ce4833f1ec 0.27 false false near_mint en USD
291 Helga, Skittish Seer BLB Bloomburrow 217 normal mythic 1 95914 40339715-22d0-4f99-822b-a00d9824f27a 2.0 false false near_mint en USD
292 Long River Lurker BLB Bloomburrow 57 normal uncommon 1 95941 7c267719-cd03-4003-b281-e732d5e42a1e 0.1 false false near_mint en USD
293 Thornvault Forager BLB Bloomburrow 197 normal rare 1 95807 8c2d6b02-a453-40f9-992a-5c5542987cfb 0.65 false false near_mint en USD
294 Eddymurk Crab BLB Bloomburrow 48 normal uncommon 1 96132 e6d45abe-4962-47d9-a54e-7e623ea8647c 0.18 false false near_mint en USD
295 Moonstone Harbinger BLB Bloomburrow 101 normal uncommon 1 95922 59e4aa8d-1d06-48db-b205-aa2f1392bbcb 0.03 false false near_mint en USD
296 Brazen Collector BLB Bloomburrow 128 normal uncommon 1 95873 78b55a58-c669-4dc6-aa63-5d9dff52e613 0.09 false false near_mint en USD
297 Brightblade Stoat BLB Bloomburrow 4 normal uncommon 1 95882 df7fea2e-7414-4bc8-adb0-9342e174c009 0.07 false false near_mint en USD
298 Warren Warleader BLB Bloomburrow 38 normal mythic 1 95849 eb5237a0-5ac3-4ded-9f92-5f782a7bbbd7 3.14 false false near_mint en USD
299 Kitnap BLB Bloomburrow 53 normal rare 1 95739 085be5d1-fd85-46d1-ad39-a8aa75a06a96 0.14 false false near_mint en USD
300 Fountainport BLB Bloomburrow 253 normal rare 1 96052 658cfcb7-81b7-48c6-9dd2-1663d06108cf 5.77 false false near_mint en USD
301 Whiskervale Forerunner BLB Bloomburrow 40 normal rare 1 95927 60a78d59-af31-4af9-95aa-2573fe553925 0.17 false false near_mint en USD
302 Dreamdew Entrancer BLB Bloomburrow 211 normal rare 1 95755 26bd6b0d-8606-4a37-8be3-a852f1a8e99c 0.28 false false near_mint en USD
303 Playful Shove BLB Bloomburrow 145 normal uncommon 1 95993 07956edf-34c1-4218-9784-ddbca13e380c 0.1 false false near_mint en USD
304 Feed the Cycle BLB Bloomburrow 94 normal uncommon 1 96067 7e017ff8-2936-4a1b-bece-00004cfbad06 0.12 false false near_mint en USD
305 Hoarder's Overflow BLB Bloomburrow 141 normal uncommon 1 96112 c2ed5079-07b4-4575-a2c8-5f0cbff888c3 0.04 false false near_mint en USD
306 Sunspine Lynx BLB Bloomburrow 155 normal rare 1 95875 8995ceaf-b7e0-423c-8f3e-25212d522502 1.8 false false near_mint en USD
307 Stormcatch Mentor BLB Bloomburrow 234 normal uncommon 1 95813 99754055-6d67-4fde-aff3-41f6af6ea764 0.21 false false near_mint en USD
308 For the Common Good BLB Bloomburrow 172 normal rare 1 95912 3ec72a27-b622-47d7-bdf3-970ccaef0d2a 0.87 false false near_mint en USD
309 Dawn's Truce BLB Bloomburrow 295 normal rare 1 95893 0cce7aec-f9b0-461b-8245-5286b741409d 8.43 false false near_mint en USD
310 Clement, the Worrywort BLB Bloomburrow 329 normal rare 1 95835 d1a68d51-cd4e-4ee3-abc7-01435085aa26 0.55 false false near_mint en USD
311 Tender Wildguide BLB Bloomburrow 325 normal rare 1 95760 2dc164c8-62ca-4d59-ae1c-ef273fde9d10 0.63 false false near_mint en USD
312 Valley Questcaller BLB Bloomburrow 299 normal rare 1 95839 d9f25130-678d-4338-8eb4-b20d2da5bc74 1.0 false false near_mint en USD
313 Heirloom Epic BLB Bloomburrow 246 normal uncommon 1 96061 7839ce48-0175-494a-ab89-9bdfb7a50cb1 0.06 false false near_mint en USD
314 Shrike Force BLB Bloomburrow 31 normal uncommon 1 95763 306fec2c-d8b7-4f4b-8f58-10e3b9f3158f 0.14 false false near_mint en USD
315 Into the Flood Maw BLB Bloomburrow 52 normal uncommon 1 95919 50b9575a-53d9-4df7-b86c-cda021107d3f 1.48 false false near_mint en USD
316 Salvation Swan BLB Bloomburrow 28 normal rare 1 95635 b2656160-d319-4530-a6e5-c418596c3f12 0.27 false false near_mint en USD
317 Hired Claw BLB Bloomburrow 140 normal rare 1 95897 1ae41080-0d67-4719-adb2-49bf2a268b6c 2.43 false false near_mint en USD
318 Starseer Mentor BLB Bloomburrow 233 normal uncommon 1 95791 6b2f6dc5-9fe8-49c1-b24c-1d99ce1da619 0.05 false false near_mint en USD
319 Mistbreath Elder BLB Bloomburrow 184 normal rare 1 95975 e5246540-5a84-41d8-9e30-8e7a6c0e84e1 0.37 false false near_mint en USD
320 Hivespine Wolverine BLB Bloomburrow 177 normal uncommon 1 95943 821970a3-a291-4fe9-bb13-dfc54f9c3caf 0.06 false false near_mint en USD
321 Patchwork Banner BLB Bloomburrow 247 normal uncommon 1 96097 a8a982c8-bc08-44ba-b3ed-9e4b124615d6 4.68 false false near_mint en USD
322 Beza, the Bounding Spring BLB Bloomburrow 2 normal mythic 1 95862 fc310a26-b6a0-4e42-98ab-bdfd7b06cb63 9.56 false false near_mint en USD
323 Essence Channeler BLB Bloomburrow 12 normal rare 1 96042 5aaf7e4c-4d5d-4acc-a834-e6c4a7629408 1.27 false false near_mint en USD
324 Valley Questcaller BLB Bloomburrow 36 normal rare 1 95826 ba629ca8-a368-4282-8a61-9bf6a5c217f0 1.12 false false near_mint en USD
325 Conduct Electricity BLB Bloomburrow 130 normal common 1 95906 2f373dd6-2412-453c-85ba-10230dfe473a 0.02 false false near_mint en USD
326 Glidedive Duo BLB Bloomburrow 96 normal common 1 96026 4831e7ae-54e3-4bd9-b5af-52dc29f81715 0.02 false false near_mint en USD
327 Mind Spiral BLB Bloomburrow 59 normal common 1 96068 7e24fe6a-607b-49b8-9fca-cecb1e40de7f 0.01 false false near_mint en USD
328 Starforged Sword BLB Bloomburrow 249 normal uncommon 1 96110 c23d8e96-b972-4c6c-b0c4-b6627621f048 0.03 false false near_mint en USD
329 Vinereap Mentor BLB Bloomburrow 238 normal uncommon 1 95902 29b615ba-45c4-42a1-8525-1535f0b55300 0.16 false false near_mint en USD
330 Mindwhisker BLB Bloomburrow 60 normal uncommon 1 96099 aaa10f34-5bfd-4d87-8f07-58de3b0f5663 0.08 false false near_mint en USD
331 Persistent Marshstalker BLB Bloomburrow 104 normal uncommon 1 95947 8b900c71-713b-4b7e-b4be-ad9f4aa0c139 0.13 false false near_mint en USD
332 Portent of Calamity BLB Bloomburrow 66 normal rare 1 96073 8599e2dd-9164-4da3-814f-adccef3b9497 0.14 false false near_mint en USD
333 Fabled Passage BLB Bloomburrow 252 normal rare 1 96075 8809830f-d8e1-4603-9652-0ad8b00234e9 5.13 false false near_mint en USD
334 Stormsplitter BLB Bloomburrow 154 normal mythic 1 96040 56f214d3-6b93-40db-a693-55e491c8a283 3.12 false false near_mint en USD
335 Stargaze BLB Bloomburrow 114 normal uncommon 1 95939 777fc599-8de7-44d2-8fdd-9bddf5948a0c 0.14 false false near_mint en USD
336 Coruscation Mage BLB Bloomburrow 131 normal uncommon 1 95972 dc2c1de0-6233-469a-be72-a050b97d2c8f 0.32 false false near_mint en USD
337 Dour Port-Mage BLB Bloomburrow 47 normal rare 1 96049 6402133e-eed1-4a46-9667-8b7a310362c1 2.17 false false near_mint en USD
338 Muerra, Trash Tactician BLB Bloomburrow 227 normal rare 1 95821 b40e4658-fd68-46d0-9a89-25570a023d19 0.31 false false near_mint en USD
339 Stormchaser's Talent BLB Bloomburrow 75 normal rare 1 96092 a36e682d-b43d-4e08-bf5b-70d7e924dbe5 13.62 false false near_mint en USD
340 Sinister Monolith BLB Bloomburrow 113 normal uncommon 1 96012 2a15e06c-2608-4e7a-a16c-d35417669d86 0.08 false false near_mint en USD
341 Pawpatch Formation BLB Bloomburrow 186 normal uncommon 1 95963 b82c20ad-0f69-4822-ae76-770832cccdf7 1.83 false false near_mint en USD
342 Plumecreed Mentor BLB Bloomburrow 228 normal uncommon 1 95819 b1aa988f-547e-449a-9f1a-296c01d68d96 0.03 false false near_mint en USD
343 Baylen, the Haymaker BLB Bloomburrow 205 normal rare 1 95889 00e93be2-e06b-4774-8ba5-ccf82a6da1d8 1.04 false false near_mint en USD
344 Long River's Pull BLB Bloomburrow 58 normal uncommon 1 95900 1c81d0fa-81a1-4f9b-a5fd-5a648fd01dea 0.23 false false near_mint en USD
345 Bonecache Overseer BLB Bloomburrow 85 normal uncommon 1 95944 82defb87-237f-4b77-9673-5bf00607148f 0.08 false false near_mint en USD
346 Three Tree Scribe BLB Bloomburrow 199 normal uncommon 1 95977 ea2ca1b3-4c1a-4be5-b321-f57db5ff0528 0.15 false false near_mint en USD
347 Cruelclaw's Heist BLB Bloomburrow 88 normal rare 1 96121 cab4539a-0157-4cbe-b50f-6e2575df74e9 0.48 false false near_mint en USD
348 Manifold Mouse BLB Bloomburrow 143 normal rare 1 95881 db3832b5-e83f-4569-bd49-fb7b86fa2d47 3.37 false false near_mint en USD
349 Iridescent Vinelasher BLB Bloomburrow 99 normal rare 1 95877 b2bc854c-4e72-48e0-a098-e3451d6e511d 1.11 false false near_mint en USD
350 Daggerfang Duo BLB Bloomburrow 89 normal common 1 96468 cea2bb34-e328-44fb-918a-72208c9457e4 0.03 false false near_mint en USD
351 Stickytongue Sentinel BLB Bloomburrow 193 normal common 1 96105 b5fa9651-b217-4f93-9c46-9bdb11feedcb 0.03 false false near_mint en USD
352 Brave-Kin Duo BLB Bloomburrow 3 normal common 1 95824 b8dd4693-424d-4d6e-86cf-24401a23d6b1 0.03 false false near_mint en USD
353 Driftgloom Coyote BLB Bloomburrow 11 normal uncommon 1 95969 d7ab2de3-3aea-461a-a74f-fb742cf8a198 0.03 false false near_mint en USD
354 Rockface Village BLB Bloomburrow 259 normal uncommon 1 95629 62799d24-39a6-4e66-8ac3-7cafa99e6e6d 0.48 false false near_mint en USD
355 Flamecache Gecko BLB Bloomburrow 135 normal uncommon 1 96142 fb8e7c97-8393-41b8-bb0b-3983dcc5e7f4 0.08 false false near_mint en USD
356 Innkeeper's Talent BLB Bloomburrow 180 normal rare 1 95954 941b0afc-0e8f-45f2-ae7f-07595e164611 19.36 false false near_mint en USD
357 Repel Calamity BLB Bloomburrow 27 foil uncommon 1 95834 d068192a-6270-4981-819d-4945fa4a2b83 0.08 false false near_mint en USD
358 Galewind Moose BLB Bloomburrow 173 foil uncommon 1 95871 58706bd8-558a-43b9-9f1e-c1ff0044203b 0.14 false false near_mint en USD
359 Brave-Kin Duo BLB Bloomburrow 3 foil common 1 95824 b8dd4693-424d-4d6e-86cf-24401a23d6b1 0.06 false false near_mint en USD
360 Agate Assault BLB Bloomburrow 122 foil common 1 96066 7dd9946b-515e-4e0d-9da2-711e126e9fa6 0.03 false false near_mint en USD
361 Flamecache Gecko BLB Bloomburrow 135 foil uncommon 1 96142 fb8e7c97-8393-41b8-bb0b-3983dcc5e7f4 0.12 false false near_mint en USD
362 Rabid Gnaw BLB Bloomburrow 147 foil uncommon 1 96014 2f815bae-820a-49f6-8eed-46f658e7b6ff 0.1 false false near_mint en USD
363 Pond Prophet BLB Bloomburrow 229 foil common 1 95861 fb959e74-61ea-453d-bb9f-ad0183c0e1b1 0.16 false false near_mint en USD
364 Star Charter BLB Bloomburrow 33 foil uncommon 1 95894 0e209237-00f7-4bf0-8287-ccde02ce8e8d 0.12 false false near_mint en USD
365 Kindlespark Duo BLB Bloomburrow 142 foil common 1 96096 a839fba3-1b66-4dd1-bf43-9b015b44fc81 0.07 false false near_mint en USD
366 Crumb and Get It BLB Bloomburrow 8 foil common 1 96259 3c7b3b25-d4b3-4451-9f5c-6eb369541175 0.04 false false near_mint en USD
367 Peerless Recycling BLB Bloomburrow 188 foil uncommon 1 95925 5f72466c-505b-4371-9366-0fde525a37e6 0.23 false false near_mint en USD
368 Nocturnal Hunger BLB Bloomburrow 102 foil common 1 96060 742c0409-9abd-4559-b52e-932cc90c531a 0.02 false false near_mint en USD
369 Seedpod Squire BLB Bloomburrow 232 foil common 1 95852 f3684577-51ce-490e-9b59-b19c733be466 0.03 false false near_mint en USD
370 Nettle Guard BLB Bloomburrow 23 foil common 1 95949 8c9c3cc3-2aa2-453e-a17c-2baeeaabe0a9 0.05 false false near_mint en USD
371 Sazacap's Brew BLB Bloomburrow 151 foil common 1 96330 6d963080-b3ec-467d-82f7-39db6ecd6bbc 0.05 false false near_mint en USD
372 Waterspout Warden BLB Bloomburrow 80 foil common 1 95909 35898b39-98e2-405b-8f18-0e054bd2c29e 0.04 false false near_mint en USD
373 Mindwhisker BLB Bloomburrow 60 foil uncommon 1 96099 aaa10f34-5bfd-4d87-8f07-58de3b0f5663 0.12 false false near_mint en USD
374 Splash Portal BLB Bloomburrow 74 foil uncommon 1 95958 adbaa356-28ba-487f-930a-a957d9960ab0 0.28 false false near_mint en USD
375 Festival of Embers BLB Bloomburrow 134 foil rare 1 96023 4433ee12-2013-4fdc-979f-ae065f63a527 0.2 false false near_mint en USD
376 Brightblade Stoat BLB Bloomburrow 4 foil uncommon 1 95882 df7fea2e-7414-4bc8-adb0-9342e174c009 0.11 false false near_mint en USD
377 Mind Spiral BLB Bloomburrow 59 foil common 1 96068 7e24fe6a-607b-49b8-9fca-cecb1e40de7f 0.04 false false near_mint en USD
378 Rust-Shield Rampager BLB Bloomburrow 190 foil common 1 96117 c96b01f5-83de-4237-a68d-f946c53e31a6 0.04 false false near_mint en USD
379 Barkform Harvester BLB Bloomburrow 243 foil common 1 95984 f77049a6-0f22-415b-bc89-20bcb32accf6 0.11 false false near_mint en USD
380 Wax-Wane Witness BLB Bloomburrow 39 foil common 1 95971 d90ea719-5320-46c6-a347-161853a14776 0.05 false false near_mint en USD
381 Warren Elder BLB Bloomburrow 37 foil common 1 96030 4bf20069-5a20-4f95-976b-6af2b69f3ad0 0.04 false false near_mint en USD
382 Stickytongue Sentinel BLB Bloomburrow 193 foil common 1 96105 b5fa9651-b217-4f93-9c46-9bdb11feedcb 0.05 false false near_mint en USD
383 Vren, the Relentless BLB Bloomburrow 239 foil rare 1 95930 6506277d-f031-4db5-9d16-bf2389094785 0.71 false false near_mint en USD
384 Three Tree Scribe BLB Bloomburrow 199 foil uncommon 1 95977 ea2ca1b3-4c1a-4be5-b321-f57db5ff0528 0.2 false false near_mint en USD
385 Glidedive Duo BLB Bloomburrow 96 foil common 1 96026 4831e7ae-54e3-4bd9-b5af-52dc29f81715 0.03 false false near_mint en USD
386 Bushy Bodyguard BLB Bloomburrow 166 foil uncommon 1 95997 0de60cf7-fa82-4b6f-9f88-6590fba5c863 0.12 false false near_mint en USD
387 Conduct Electricity BLB Bloomburrow 130 foil common 1 95906 2f373dd6-2412-453c-85ba-10230dfe473a 0.03 false false near_mint en USD
388 Daggerfang Duo BLB Bloomburrow 89 foil common 1 96468 cea2bb34-e328-44fb-918a-72208c9457e4 0.07 false false near_mint en USD
389 Shore Up BLB Bloomburrow 69 foil common 1 96277 4dc3b49e-3674-494c-bdea-4374cefd10f4 0.13 false false near_mint en USD
390 Hidden Grotto BLB Bloomburrow 254 foil common 1 95918 4ba8f2e7-8357-4862-97dc-1942d066023a 0.17 false false near_mint en USD
391 Cindering Cutthroat BLB Bloomburrow 208 foil common 1 95820 b2ea10dd-21ea-4622-be27-79d03a802b85 0.01 false false near_mint en USD
392 Glarb, Calamity's Augur BLB Bloomburrow 215 foil mythic 1 95864 ffc70b2d-5a3a-49ea-97db-175a62248302 4.3 false false near_mint en USD
393 Kindlespark Duo BLB Bloomburrow 142 normal common 5 96096 a839fba3-1b66-4dd1-bf43-9b015b44fc81 0.04 false false near_mint en USD
394 Finch Formation BLB Bloomburrow 50 normal common 2 95899 1c671eab-d1ef-4d79-94eb-8b85f0d18699 0.02 false false near_mint en USD
395 Builder's Talent BLB Bloomburrow 5 normal uncommon 2 96002 15fa581a-724e-4196-a9a3-ff84c54bdb7d 0.08 false false near_mint en USD
396 Might of the Meek BLB Bloomburrow 144 normal common 9 95627 509bf254-8a2b-4dfa-9ae5-386321b35e8b 0.09 false false near_mint en USD
397 Nightwhorl Hermit BLB Bloomburrow 62 normal common 3 95994 0928e04f-2568-41e8-b603-7a25cf5f94d0 0.02 false false near_mint en USD
398 Fell BLB Bloomburrow 95 normal uncommon 2 95830 c96ac326-de44-470b-a592-a4c2a052c091 0.3 false false near_mint en USD
399 Sunshower Druid BLB Bloomburrow 195 normal common 6 95630 7740abc5-54e1-478d-966e-0fa64e727995 0.04 false false near_mint en USD
400 Wandertale Mentor BLB Bloomburrow 240 normal uncommon 2 95808 8c399a55-d02e-41ed-b827-8784b738c118 0.09 false false near_mint en USD
401 Thought-Stalker Warlock BLB Bloomburrow 118 normal uncommon 2 96018 42e80284-d489-493b-ae92-95b742d07cb3 0.12 false false near_mint en USD
402 Splash Portal BLB Bloomburrow 74 normal uncommon 2 95958 adbaa356-28ba-487f-930a-a957d9960ab0 0.23 false false near_mint en USD
403 Alania's Pathmaker BLB Bloomburrow 123 normal common 7 96123 d3871fe6-e26e-4ab4-bd81-7e3c7b8135c1 0.02 false false near_mint en USD
404 Head of the Homestead BLB Bloomburrow 216 normal common 3 95762 2fc20157-edd3-484d-8864-925c071c0551 0.04 false false near_mint en USD
405 Hidden Grotto BLB Bloomburrow 254 normal common 4 95918 4ba8f2e7-8357-4862-97dc-1942d066023a 0.08 false false near_mint en USD
406 Star Charter BLB Bloomburrow 33 normal uncommon 3 95894 0e209237-00f7-4bf0-8287-ccde02ce8e8d 0.04 false false near_mint en USD
407 War Squeak BLB Bloomburrow 160 normal common 4 95999 105964a7-88b7-4340-aa66-e908189a3638 0.02 false false near_mint en USD
408 Bellowing Crier BLB Bloomburrow 42 normal common 2 96119 ca2215dd-6300-49cf-b9b2-3a840b786c31 0.04 false false near_mint en USD
409 Cindering Cutthroat BLB Bloomburrow 208 normal common 4 95820 b2ea10dd-21ea-4622-be27-79d03a802b85 0.02 false false near_mint en USD
410 Intrepid Rabbit BLB Bloomburrow 17 normal common 7 96276 4d70b99d-c8bf-4a56-8957-cf587fe60b81 0.03 false false near_mint en USD
411 Carrot Cake BLB Bloomburrow 7 normal common 3 95636 eb03bb4f-8b4b-417e-bfc6-294cd2186b2e 0.06 false false near_mint en USD
412 Thought Shucker BLB Bloomburrow 77 normal common 7 95916 44b0d83b-cc41-4f82-892c-ef6d3293228a 0.02 false false near_mint en USD
413 Seasoned Warrenguard BLB Bloomburrow 30 normal uncommon 2 96081 90873995-876f-4e89-8bc7-41a74f4d931f 0.09 false false near_mint en USD
414 Junkblade Bruiser BLB Bloomburrow 220 normal common 3 95810 918fd89b-5ab7-4ae2-920c-faca5e9da7b9 0.04 false false near_mint en USD
415 Cache Grab BLB Bloomburrow 167 normal common 2 95842 dfd977dc-a7c3-4d0a-aca7-b25bd154e963 0.08 false false near_mint en USD
416 Lilypad Village BLB Bloomburrow 255 normal uncommon 2 95631 7e95a7cc-ed77-4ca4-80db-61c0fc68bf50 0.14 false false near_mint en USD
417 Agate-Blade Assassin BLB Bloomburrow 82 normal common 5 96017 39ebb84a-1c52-4b07-9bd0-b360523b3a5b 0.03 false false near_mint en USD
418 Repel Calamity BLB Bloomburrow 27 normal uncommon 2 95834 d068192a-6270-4981-819d-4945fa4a2b83 0.07 false false near_mint en USD
419 Hazel's Nocturne BLB Bloomburrow 97 normal uncommon 2 96009 239363df-4de8-4b64-80fc-a1f4b5c36027 0.07 false false near_mint en USD
420 Treeguard Duo BLB Bloomburrow 200 normal common 4 96077 89c8456e-c971-42b7-abf3-ff5ae1320abe 0.01 false false near_mint en USD
421 Calamitous Tide BLB Bloomburrow 43 normal uncommon 2 96003 178bc8b2-ffa0-4549-aead-aacb3db3cf19 0.03 false false near_mint en USD
422 Splash Lasher BLB Bloomburrow 73 normal uncommon 2 95910 362ee125-35a0-46cd-a201-e6797d12d33a 0.04 false false near_mint en USD
423 Blooming Blast BLB Bloomburrow 126 normal uncommon 2 95996 0cd92a83-cec3-4085-a929-3f204e3e0140 0.06 false false near_mint en USD
424 Sugar Coat BLB Bloomburrow 76 normal uncommon 2 95887 fcacbe71-efb0-49e1-b2d0-3ee65ec6cf8b 0.05 false false near_mint en USD
425 Dazzling Denial BLB Bloomburrow 45 normal common 6 96369 8739f1ac-2e57-4b52-a7ff-cc8df5936aad 0.04 false false near_mint en USD
426 Nettle Guard BLB Bloomburrow 23 normal common 4 95949 8c9c3cc3-2aa2-453e-a17c-2baeeaabe0a9 0.03 false false near_mint en USD
427 Raccoon Rallier BLB Bloomburrow 148 normal common 5 96104 b5b5180f-5a1c-4df8-9019-195e65a50ce3 0.04 false false near_mint en USD
428 High Stride BLB Bloomburrow 176 normal common 8 96153 09c8cf4b-8e65-4a1c-b458-28b5ab56b390 0.04 false false near_mint en USD
429 Otterball Antics BLB Bloomburrow 63 normal uncommon 2 95913 3ff83ff7-e428-4ccc-8341-f223dab76bd1 0.1 false false near_mint en USD
430 Frilled Sparkshooter BLB Bloomburrow 136 normal common 7 95934 674bbd6d-e329-42cf-963d-88d1ce8fe51e 0.02 false false near_mint en USD
431 Moonrise Cleric BLB Bloomburrow 226 normal common 3 95767 35f2a71f-31e8-4b51-9dd4-51a5336b3b86 0.04 false false near_mint en USD
432 Wax-Wane Witness BLB Bloomburrow 39 normal common 3 95971 d90ea719-5320-46c6-a347-161853a14776 0.02 false false near_mint en USD
433 Pearl of Wisdom BLB Bloomburrow 64 normal common 7 95625 13cb9575-1138-4f99-8e90-0eaf00bdf4a1 0.01 false false near_mint en USD
434 Run Away Together BLB Bloomburrow 67 normal common 3 95799 7cb7ec70-a5a4-4188-ba1a-e88b81bdbad0 0.04 false false near_mint en USD
435 Early Winter BLB Bloomburrow 93 normal common 2 95626 5030e6ac-211d-4145-8c87-998a8351a467 0.05 false false near_mint en USD
436 Three Tree Rootweaver BLB Bloomburrow 198 normal common 2 96469 d1ab6e14-26e0-4174-b5c6-bc0f5c26b177 0.04 false false near_mint en USD
437 Mudflat Village BLB Bloomburrow 257 normal uncommon 2 95628 53ec4ad3-9cf0-4f1b-a9db-d63feee594ab 0.24 false false near_mint en USD
438 Starlit Soothsayer BLB Bloomburrow 115 normal common 6 95895 184c1eca-2991-438f-b5d2-cd2529b9c9b4 0.03 false false near_mint en USD
439 Hop to It BLB Bloomburrow 16 normal uncommon 2 95851 ee7207f8-5daa-42af-aeea-7a489047110b 0.07 false false near_mint en USD
440 Psychic Whorl BLB Bloomburrow 105 normal common 5 96127 df900308-8432-4a0a-be21-17482026012b 0.04 false false near_mint en USD
441 Barkform Harvester BLB Bloomburrow 243 normal common 4 95984 f77049a6-0f22-415b-bc89-20bcb32accf6 0.06 false false near_mint en USD
442 Daring Waverider BLB Bloomburrow 44 normal uncommon 2 95896 19422406-0c1a-497e-bed1-708bc556491a 0.06 false false near_mint en USD
443 Plumecreed Escort BLB Bloomburrow 65 normal uncommon 2 95983 f71320ed-2f30-49ce-bcb0-19aebba3f0e8 0.05 false false near_mint en USD
444 Parting Gust BLB Bloomburrow 24 normal uncommon 2 95744 1086e826-94b8-4398-8a38-d8eacca56a43 0.38 false false near_mint en USD
445 Veteran Guardmouse BLB Bloomburrow 237 normal common 3 95771 3db43c46-b616-4ef8-80ed-0fab345ab3d0 0.01 false false near_mint en USD
446 Dire Downdraft BLB Bloomburrow 46 normal common 6 96526 f1931f22-974c-43ad-911e-684bf3f9995d 0.02 false false near_mint en USD
447 Waterspout Warden BLB Bloomburrow 80 normal common 4 95909 35898b39-98e2-405b-8f18-0e054bd2c29e 0.01 false false near_mint en USD
448 Lupinflower Village BLB Bloomburrow 256 normal uncommon 2 95634 8ab9d56f-9178-4ec9-a5f6-b934f50d8d9d 0.1 false false near_mint en USD
449 Heartfire Hero BLB Bloomburrow 138 normal uncommon 2 95870 48ace959-66b2-40c8-9bff-fd7ed9c99a82 2.1 false false near_mint en USD
450 Peerless Recycling BLB Bloomburrow 188 normal uncommon 2 95925 5f72466c-505b-4371-9366-0fde525a37e6 0.1 false false near_mint en USD
451 Pond Prophet BLB Bloomburrow 229 normal common 4 95861 fb959e74-61ea-453d-bb9f-ad0183c0e1b1 0.09 false false near_mint en USD
452 Crumb and Get It BLB Bloomburrow 8 normal common 2 96259 3c7b3b25-d4b3-4451-9f5c-6eb369541175 0.03 false false near_mint en USD
453 Wildfire Howl BLB Bloomburrow 162 normal uncommon 2 96059 7392d397-9836-4df2-944d-c930c9566811 0.05 false false near_mint en USD
454 Bark-Knuckle Boxer BLB Bloomburrow 164 normal uncommon 2 95921 582637a9-6aa0-4824-bed7-d5fc91bda35e 0.03 false false near_mint en USD
455 Ruthless Negotiation BLB Bloomburrow 108 normal uncommon 2 95828 c7f4360c-8d68-4058-b9ec-da9948cb060d 0.1 false false near_mint en USD
456 Three Tree Mascot FDN Foundations 682 normal common 3 100412 40b8bf3a-1cb5-4ce2-ac25-9410f17130de 0.11 false false near_mint en USD
457 Tempest Angler BLB Bloomburrow 235 normal common 2 95803 850daae4-f0b7-4604-95e7-ad044ec165c3 0.04 false false near_mint en USD
458 Starscape Cleric BLB Bloomburrow 116 normal uncommon 2 96037 53a938a7-0154-4350-87cb-00da24ec3824 0.62 false false near_mint en USD
459 Wick's Patrol BLB Bloomburrow 121 normal uncommon 3 95926 5fa0c53d-fe7b-4b8b-ad81-7967ca318ff7 0.07 false false near_mint en USD
460 Fireglass Mentor BLB Bloomburrow 213 normal uncommon 2 95823 b78fbaa3-c580-4290-9c28-b74169aab2fc 0.08 false false near_mint en USD
461 Steampath Charger BLB Bloomburrow 153 normal common 2 95890 03bf1296-e347-4070-8c6f-5c362c2f9364 0.03 false false near_mint en USD
462 Whiskerquill Scribe BLB Bloomburrow 161 normal common 2 96124 da653996-9bd4-40bd-afb4-48c7e070a269 0.01 false false near_mint en USD
463 Lilysplash Mentor BLB Bloomburrow 222 normal uncommon 3 95789 64de7b1f-a03e-4407-91f1-e108a2f26735 0.12 false false near_mint en USD
464 Roughshod Duo BLB Bloomburrow 150 normal common 3 96343 78cdcfb9-a247-4c2d-a098-5b57570f8cd5 0.03 false false near_mint en USD
465 Bonebind Orator BLB Bloomburrow 84 normal common 3 96535 faf226fa-ca09-4468-8804-87b2a7de2c66 0.02 false false near_mint en USD
466 Agate Assault BLB Bloomburrow 122 normal common 2 96066 7dd9946b-515e-4e0d-9da2-711e126e9fa6 0.02 false false near_mint en USD
467 Nocturnal Hunger BLB Bloomburrow 102 normal common 3 96060 742c0409-9abd-4559-b52e-932cc90c531a 0.02 false false near_mint en USD
468 Jolly Gerbils BLB Bloomburrow 19 normal uncommon 2 96167 0eab51d6-ba17-4a8c-8834-25db363f2b6b 0.04 false false near_mint en USD
469 Downwind Ambusher BLB Bloomburrow 92 normal uncommon 2 95920 55cfd628-933a-4d3d-b2e5-70bc86960d1c 0.02 false false near_mint en USD
470 Scales of Shale BLB Bloomburrow 110 normal common 2 95955 9ae14276-dbbd-4257-80e9-accd6c19f5b2 0.02 false false near_mint en USD
471 Treetop Sentries BLB Bloomburrow 201 normal common 4 95974 e16d4d6e-1fe5-4ff6-9877-8c849a24f5e0 0.03 false false near_mint en USD
472 Seedpod Squire BLB Bloomburrow 232 normal common 4 95852 f3684577-51ce-490e-9b59-b19c733be466 0.01 false false near_mint en USD
473 Savor BLB Bloomburrow 109 normal common 4 96178 1397f689-dca1-4d35-864b-92c5606afb9a 0.04 false false near_mint en USD
474 Polliwallop BLB Bloomburrow 189 normal common 2 95935 6bc4963c-d90b-4588-bdb7-85956e42a623 0.03 false false near_mint en USD
475 Sonar Strike BLB Bloomburrow 32 normal common 2 96093 a50da179-751f-47a8-a547-8c4a291ed381 0.02 false false near_mint en USD
476 Uncharted Haven FDN Foundations 564 normal common 3 97170 172cd5b7-98fc-4add-b858-a0b3dfb75c19 0.14 false false near_mint en USD
477 Teapot Slinger BLB Bloomburrow 157 normal uncommon 2 96015 30506844-349f-4b68-8cc1-d028c1611cc7 0.06 false false near_mint en USD
478 Harvestrite Host BLB Bloomburrow 15 normal uncommon 2 95915 41762689-0c13-4d45-9d81-ba2afad980f8 0.07 false false near_mint en USD
479 Spellgyre BLB Bloomburrow 72 normal uncommon 2 96139 f6f6620a-1d40-429d-9a0c-aaeb62adaa71 0.08 false false near_mint en USD
480 Oakhollow Village BLB Bloomburrow 258 normal uncommon 2 95624 0d49b016-b02b-459f-85e9-c04f6bdcb94e 0.35 false false near_mint en USD
481 Bumbleflower's Sharepot BLB Bloomburrow 244 normal common 2 95924 5f0affd5-5dcd-4dd1-a694-37a9aedf4084 0.02 false false near_mint en USD
482 Overprotect BLB Bloomburrow 185 normal uncommon 2 95891 079e979f-b618-4625-989c-e0ea5b61ed8a 0.55 false false near_mint en USD
483 Heaped Harvest BLB Bloomburrow 175 normal common 3 96255 3b5349db-0e0a-4b15-886e-0db403ef49cb 0.1 false false near_mint en USD
484 Flowerfoot Swordmaster BLB Bloomburrow 14 normal uncommon 2 95812 97ff118f-9c3c-43a2-8085-980c7fe7d227 0.15 false false near_mint en USD
485 Banishing Light BLB Bloomburrow 1 normal common 6 96011 25a06f82-ebdb-4dd6-bfe8-958018ce557c 0.04 false false near_mint en USD
486 Sazacap's Brew BLB Bloomburrow 151 normal common 3 96330 6d963080-b3ec-467d-82f7-39db6ecd6bbc 0.05 false false near_mint en USD
487 Diresight BLB Bloomburrow 91 normal common 3 95985 fada29c0-5293-40a4-b36d-d073ee99e650 0.1 false false near_mint en USD
488 Gossip's Talent BLB Bloomburrow 51 normal uncommon 2 95961 b299889a-03d6-4659-b0e1-f0830842e40f 0.18 false false near_mint en USD
489 Fountainport Bell BLB Bloomburrow 245 normal common 3 96094 a5c94bc0-a49d-451b-8e8d-64d46b8b8603 0.04 false false near_mint en USD
490 Reptilian Recruiter BLB Bloomburrow 149 normal uncommon 2 96072 81dec453-c9d7-42cb-980a-c82f82bede76 0.02 false false near_mint en USD
491 Thistledown Players BLB Bloomburrow 35 normal common 2 95960 afa8d83f-8586-4127-8b55-9715e9547488 0.01 false false near_mint en USD
492 Clifftop Lookout BLB Bloomburrow 168 normal uncommon 2 95931 662d3bcc-65f3-4c69-8ea1-446870a1193d 0.16 false false near_mint en USD
493 Rust-Shield Rampager BLB Bloomburrow 190 normal common 2 96117 c96b01f5-83de-4237-a68d-f946c53e31a6 0.02 false false near_mint en USD
494 Consumed by Greed BLB Bloomburrow 87 normal uncommon 2 95884 e50acc41-3517-42db-b1d3-1bdfd7294d84 0.09 false false near_mint en USD
495 Rabbit Response BLB Bloomburrow 26 normal common 2 96114 c4ded450-346d-4917-917a-b62bc0267509 0.02 false false near_mint en USD
496 Corpseberry Cultivator BLB Bloomburrow 210 normal common 2 95829 c911a759-ed7b-452b-88a3-663478357610 0.02 false false near_mint en USD
497 Mind Drill Assailant BLB Bloomburrow 225 normal common 2 95783 507ba708-ca9b-453e-b4c2-23b6650eb5a8 0.05 false false near_mint en USD
498 Hazardroot Herbalist BLB Bloomburrow 174 normal uncommon 2 96130 e2882982-b3a3-4762-a550-6b82db1038e8 0.04 false false near_mint en USD
499 Dewdrop Cure BLB Bloomburrow 10 normal uncommon 2 95932 666aefc2-44e0-4c27-88d5-7906f245a71f 0.13 false false near_mint en USD
500 Valley Rally BLB Bloomburrow 159 normal uncommon 2 95878 b6178258-1ad6-4122-a56f-6eb7d0611e84 0.04 false false near_mint en USD
501 Blacksmith's Talent BLB Bloomburrow 125 normal uncommon 2 96029 4bb318fa-481d-40a7-978e-f01b49101ae0 0.17 false false near_mint en USD
502 Pileated Provisioner BLB Bloomburrow 25 normal common 2 96102 ae442cd6-c4df-4aad-9b1d-ccd936c5ec96 0.02 false false near_mint en USD
503 Short Bow BLB Bloomburrow 248 normal uncommon 2 96281 51d8b72b-fa8f-48d3-bddc-d3ce9b8ba2ea 0.15 false false near_mint en USD
504 Warren Elder BLB Bloomburrow 37 normal common 2 96030 4bf20069-5a20-4f95-976b-6af2b69f3ad0 0.03 false false near_mint en USD

View File

@ -0,0 +1,88 @@
Name,Set code,Set name,Collector number,Foil,Rarity,Quantity,ManaBox ID,Scryfall ID,Purchase price,Misprint,Altered,Condition,Language,Purchase price currency
Sunpearl Kirin,TDM,Tarkir: Dragonstorm,29,foil,uncommon,1,104470,18292b9c-0f42-4ce2-8b85-35d06cf45a63,1.24,false,false,near_mint,en,USD
Humbling Elder,TDM,Tarkir: Dragonstorm,48,foil,common,1,104562,3a84c3f8-0030-4653-880e-b2d19272f5fa,0.06,false,false,near_mint,en,USD
Fortress Kin-Guard,TDM,Tarkir: Dragonstorm,12,foil,common,1,104900,b647a018-1d70-43a1-a265-928bcd863689,0.04,false,false,near_mint,en,USD
Wild Ride,TDM,Tarkir: Dragonstorm,132,foil,common,1,105154,abc8c6f5-6135-428e-8476-1751f82623f9,0.14,false,false,near_mint,en,USD
Jade-Cast Sentinel,TDM,Tarkir: Dragonstorm,243,foil,common,1,104293,516ce5fa-bd00-429b-ba22-b38c7dd9306c,0.07,false,false,near_mint,en,USD
Sibsig Appraiser,TDM,Tarkir: Dragonstorm,56,foil,common,1,105135,670c5b96-bac6-449b-a2bd-cb43750d3911,0.04,false,false,near_mint,en,USD
Channeled Dragonfire,TDM,Tarkir: Dragonstorm,102,foil,uncommon,1,104499,24204881-690c-4043-8771-20cb93385072,0.07,false,false,near_mint,en,USD
Meticulous Artisan,TDM,Tarkir: Dragonstorm,112,foil,common,1,104912,baf4c9dd-0546-41ac-a7ba-0bc312fef31e,0.03,false,false,near_mint,en,USD
Kishla Trawlers,TDM,Tarkir: Dragonstorm,50,foil,uncommon,1,104472,190fbc55-e8e9-4077-9532-1de7406baabf,0.08,false,false,near_mint,en,USD
Rite of Renewal,TDM,Tarkir: Dragonstorm,153,foil,uncommon,1,104390,f737698a-d934-4851-b238-828959ef4835,0.07,false,false,near_mint,en,USD
Twin Bolt,TDM,Tarkir: Dragonstorm,128,foil,common,1,105137,688d8e93-d071-4089-9ef9-565ac4ae9ae0,0.04,false,false,near_mint,en,USD
Heritage Reclamation,TDM,Tarkir: Dragonstorm,145,foil,common,1,104636,4f8fee37-a050-4329-8b10-46d150e7a95e,0.33,false,false,near_mint,en,USD
Sarkhan's Resolve,TDM,Tarkir: Dragonstorm,158,foil,common,1,104952,cae56fef-b661-4bc5-b9a1-3871ae06e491,0.04,false,false,near_mint,en,USD
Delta Bloodflies,TDM,Tarkir: Dragonstorm,77,foil,common,1,104457,119bb72d-aed9-47dc-9285-7bc836cc3776,0.05,false,false,near_mint,en,USD
Dirgur Island Dragon // Skimming Strike,TDM,Tarkir: Dragonstorm,40,foil,common,1,104342,b1d21a9a-6b0c-4fbc-a427-81be885d326b,0.16,false,false,near_mint,en,USD
Kheru Goldkeeper,TDM,Tarkir: Dragonstorm,199,foil,uncommon,1,104798,8d11183a-57f5-4ddb-8a6e-15fff704b114,0.37,false,false,near_mint,en,USD
Rainveil Rejuvenator,TDM,Tarkir: Dragonstorm,152,foil,uncommon,1,105148,9bc5c316-6a41-48ba-864b-da3030dd3e0e,0.12,false,false,near_mint,en,USD
Rebellious Strike,TDM,Tarkir: Dragonstorm,20,foil,common,1,104949,c9bafe19-3bd6-4da0-b3e5-e0b89262504c,0.06,false,false,near_mint,en,USD
Jeskai Brushmaster,TDM,Tarkir: Dragonstorm,195,foil,uncommon,1,104526,2eb06c36-cf7e-47a9-819e-adfc54284153,0.09,false,false,near_mint,en,USD
Highspire Bell-Ringer,TDM,Tarkir: Dragonstorm,47,foil,common,1,105020,e75dccf7-2894-4c4a-b516-3eee73acddd3,0.06,false,false,near_mint,en,USD
Sagu Pummeler,TDM,Tarkir: Dragonstorm,156,foil,common,1,105169,def9cb5b-4062-481e-b682-3a30443c2e56,0.03,false,false,near_mint,en,USD
Roamer's Routine,TDM,Tarkir: Dragonstorm,154,foil,common,1,104396,fb8c2d5c-ba0c-4d50-8898-5c6574b1e974,0.13,false,false,near_mint,en,USD
Monastery Messenger,TDM,Tarkir: Dragonstorm,208,foil,common,1,104443,0c9eeced-6464-41f0-bbea-05b3af4cc005,0.04,false,false,near_mint,en,USD
Equilibrium Adept,TDM,Tarkir: Dragonstorm,106,foil,uncommon,1,104335,a4ba6d74-c6be-4a5e-8859-b791bb6b8f51,0.07,false,false,near_mint,en,USD
Dispelling Exhale,TDM,Tarkir: Dragonstorm,41,foil,common,1,104477,1c9af3f1-711e-42ae-803a-1100eba3fb13,0.21,false,false,near_mint,en,USD
Nightblade Brigade,TDM,Tarkir: Dragonstorm,85,foil,common,1,105134,648debd9-d4cf-4788-8882-f1601a3d87f5,0.08,false,false,near_mint,en,USD
Tranquil Cove,TDM,Tarkir: Dragonstorm,270,foil,common,1,104249,1c4efa6c-4f29-41cd-a728-bf0e479ace05,0.06,false,false,near_mint,en,USD
Rugged Highlands,TDM,Tarkir: Dragonstorm,265,foil,common,1,104267,31261eca-28ad-407c-84ef-0c124d0d7451,0.07,false,false,near_mint,en,USD
Swiftwater Cliffs,TDM,Tarkir: Dragonstorm,268,foil,common,1,104361,ca53fb19-b8ca-485b-af1a-5117ae54bfe3,0.11,false,false,near_mint,en,USD
Dismal Backwater,TDM,Tarkir: Dragonstorm,254,foil,common,1,104238,082b52c9-c46e-44d3-b723-546ba528e07b,0.06,false,false,near_mint,en,USD
Wind-Scarred Crag,TDM,Tarkir: Dragonstorm,271,foil,common,1,104286,4912e4d0-b16a-4aa6-a583-3430d26bd591,0.05,false,false,near_mint,en,USD
Jungle Hollow,TDM,Tarkir: Dragonstorm,258,foil,common,1,104375,ea13440b-3f7b-4182-9541-27c1fa3121e5,0.07,false,false,near_mint,en,USD
Plains,TDM,Tarkir: Dragonstorm,272,foil,common,1,104240,0d0f1dd6-9564-4adc-af7d-f83252e8581a,0.39,false,false,near_mint,en,USD
Purging Stormbrood // Absorb Essence,TDM,Tarkir: Dragonstorm,315,foil,uncommon,1,104395,fb293f4f-9ba2-48f5-a4fb-d902aa531bfc,0.18,false,false,near_mint,en,USD
Barrensteppe Siege,TDM,Tarkir: Dragonstorm,384,foil,rare,1,104002,c09d4015-f101-4529-a603-c66192dcfd92,1.68,false,false,near_mint,en,USD
United Battlefront,TDM,Tarkir: Dragonstorm,32,foil,rare,1,104370,dff398be-4ba4-4976-9acc-be99d2e07a61,0.8,false,false,near_mint,en,USD
Cori-Steel Cutter,TDM,Tarkir: Dragonstorm,103,foil,rare,1,104608,490eb213-9ae2-4b45-abec-6f1dfc83792a,15.2,false,false,near_mint,en,USD
Plains,TDM,Tarkir: Dragonstorm,272,normal,common,1,104240,0d0f1dd6-9564-4adc-af7d-f83252e8581a,0.52,false,false,near_mint,en,USD
Skirmish Rhino,TDM,Tarkir: Dragonstorm,224,normal,uncommon,1,103992,4a2e9ba1-c254-41e3-9845-4e81f9fec38d,0.18,false,false,near_mint,en,USD
Kheru Goldkeeper,TDM,Tarkir: Dragonstorm,199,normal,uncommon,1,104798,8d11183a-57f5-4ddb-8a6e-15fff704b114,0.17,false,false,near_mint,en,USD
Hardened Tactician,TDM,Tarkir: Dragonstorm,191,normal,uncommon,1,104780,86b225cb-5c45-4da1-a64e-b04091e483e8,0.19,false,false,near_mint,en,USD
Auroral Procession,TDM,Tarkir: Dragonstorm,169,normal,uncommon,1,104701,672f94ad-65d6-4c7d-925d-165ef264626f,0.2,false,false,near_mint,en,USD
Barrensteppe Siege,TDM,Tarkir: Dragonstorm,171,normal,rare,1,103989,2556a35b-2229-42c7-8cb3-c8c668403dd2,0.46,false,false,near_mint,en,USD
Heritage Reclamation,TDM,Tarkir: Dragonstorm,145,normal,common,1,104636,4f8fee37-a050-4329-8b10-46d150e7a95e,0.19,false,false,near_mint,en,USD
Windcrag Siege,TDM,Tarkir: Dragonstorm,235,normal,rare,1,104534,31a8329b-23a1-4c49-a579-a5da8d01435a,1.97,false,false,near_mint,en,USD
Ambling Stormshell,TDM,Tarkir: Dragonstorm,37,normal,rare,1,104942,c74d4a57-0f66-4965-9ed7-f88a08aa1d15,0.18,false,false,near_mint,en,USD
"Narset, Jeskai Waymaster",TDM,Tarkir: Dragonstorm,209,normal,rare,1,103995,6b77cbc1-dbc8-44d9-aa29-15cbb19afecd,0.21,false,false,near_mint,en,USD
Revival of the Ancestors,TDM,Tarkir: Dragonstorm,218,normal,rare,1,105074,fd742ff5-f0ea-4f4b-911e-4c09e2154dba,0.14,false,false,near_mint,en,USD
Dragonback Assault,TDM,Tarkir: Dragonstorm,179,normal,mythic,1,104985,d54cc838-d79d-433a-99fb-d6e4d1c1431d,3.49,false,false,near_mint,en,USD
Call the Spirit Dragons,TDM,Tarkir: Dragonstorm,174,normal,mythic,1,104888,b1ad91db-5f16-4392-baf1-f8400ec11e0a,3.99,false,false,near_mint,en,USD
Flamehold Grappler,TDM,Tarkir: Dragonstorm,185,normal,rare,1,104958,cc8443a6-282f-4218-9dc8-144b5570d891,0.26,false,false,near_mint,en,USD
New Way Forward,TDM,Tarkir: Dragonstorm,211,normal,rare,1,104996,d9d48f9e-79f0-478c-9db0-ff7ac4a8f401,0.2,false,false,near_mint,en,USD
Eshki Dragonclaw,TDM,Tarkir: Dragonstorm,182,normal,rare,1,104445,0d369c44-78ee-4f3c-bf2b-cddba7fe26d4,0.22,false,false,near_mint,en,USD
Warden of the Grove,TDM,Tarkir: Dragonstorm,166,normal,rare,1,104498,2414db96-0e2b-4f7c-9b97-41f8e310b752,0.94,false,false,near_mint,en,USD
United Battlefront,TDM,Tarkir: Dragonstorm,32,normal,rare,1,104370,dff398be-4ba4-4976-9acc-be99d2e07a61,0.58,false,false,near_mint,en,USD
Dalkovan Encampment,TDM,Tarkir: Dragonstorm,253,normal,rare,1,104822,98ad5f0c-8775-4e89-8e92-84a6ade93e35,0.3,false,false,near_mint,en,USD
Marang River Regent // Coil and Catch,TDM,Tarkir: Dragonstorm,51,normal,rare,1,104393,f890bdc7-32e6-4492-bac7-7cabf54a8bfd,2.87,false,false,near_mint,en,USD
Sage of the Skies,TDM,Tarkir: Dragonstorm,22,normal,rare,1,104710,6ade6918-6d1d-448d-ab56-93996051e9a9,0.28,false,false,near_mint,en,USD
Tersa Lightshatter,TDM,Tarkir: Dragonstorm,127,normal,rare,1,104825,99e96b34-b1c4-4647-a38e-2cf1aedaaace,1.92,false,false,near_mint,en,USD
Voice of Victory,TDM,Tarkir: Dragonstorm,33,normal,rare,1,104377,ec3de5f4-bb55-4ab9-995f-f3e0dc22c1bb,10.45,false,false,near_mint,en,USD
Yathan Roadwatcher,TDM,Tarkir: Dragonstorm,236,normal,rare,1,104800,8e77339b-dd82-481c-9ee2-4156ca69ad35,0.15,false,false,near_mint,en,USD
Great Arashin City,TDM,Tarkir: Dragonstorm,257,normal,rare,1,105033,ecba23b6-9f3a-431e-bc22-f1fb04d27b68,0.31,false,false,near_mint,en,USD
"Taigam, Master Opportunist",TDM,Tarkir: Dragonstorm,60,normal,mythic,1,104320,8693d631-05f6-414d-9e49-6385746e8960,1.69,false,false,near_mint,en,USD
Temur Battlecrier,TDM,Tarkir: Dragonstorm,228,normal,rare,1,104309,72184791-0767-4108-920c-763e92dae2d4,0.74,false,false,near_mint,en,USD
Fangkeeper's Familiar,TDM,Tarkir: Dragonstorm,183,normal,rare,1,104696,655fa2e1-3e1c-424c-b17a-daa7b8fface4,0.27,false,false,near_mint,en,USD
Nature's Rhythm,TDM,Tarkir: Dragonstorm,150,normal,rare,1,104460,1397d904-c51d-451e-8505-7f3118acc1f6,3.21,false,false,near_mint,en,USD
Stillness in Motion,TDM,Tarkir: Dragonstorm,59,normal,rare,1,104864,a6289251-17e4-4987-96b9-2fb1a8f90e2a,0.18,false,false,near_mint,en,USD
Stadium Headliner,TDM,Tarkir: Dragonstorm,122,normal,rare,1,104552,37d4ab2a-a06a-4768-b5e1-e1def957d7f4,0.46,false,false,near_mint,en,USD
Sinkhole Surveyor,TDM,Tarkir: Dragonstorm,93,normal,rare,1,104551,37cb5599-7d2c-48e9-978b-902a01a74bde,0.23,false,false,near_mint,en,USD
Naga Fleshcrafter,TDM,Tarkir: Dragonstorm,52,normal,rare,1,104675,5df17423-9fdd-4432-8660-1d267c685595,0.31,false,false,near_mint,en,USD
Herd Heirloom,TDM,Tarkir: Dragonstorm,144,normal,rare,1,104873,a88c7713-b3a9-4685-b1d3-623d35b62365,4.48,false,false,near_mint,en,USD
Severance Priest,TDM,Tarkir: Dragonstorm,222,normal,rare,1,104917,bc779a1b-128c-4c74-bebd-bdb687867f68,0.21,false,false,near_mint,en,USD
Dracogenesis,TDM,Tarkir: Dragonstorm,105,normal,mythic,1,104241,0d5674f9-22b2-45f9-902d-4fd245485c60,15.05,false,false,near_mint,en,USD
Kishla Village,TDM,Tarkir: Dragonstorm,259,normal,rare,1,104840,9f0ff90d-7312-44df-afc5-29c768fa7758,0.3,false,false,near_mint,en,USD
"Sarkhan, Dragon Ascendant",TDM,Tarkir: Dragonstorm,302,normal,rare,1,103994,57c03255-e3dc-44c2-982b-7efa188280df,0.49,false,false,near_mint,en,USD
Bloomvine Regent // Claim Territory,TDM,Tarkir: Dragonstorm,381,normal,rare,1,104237,081f2de5-251a-41c9-a62f-11487f54d355,1.97,false,false,near_mint,en,USD
Kheru Goldkeeper,TDM,Tarkir: Dragonstorm,313,normal,uncommon,1,105150,9d85ba44-8f29-4c49-b77f-8a6692d23c8c,0.45,false,false,near_mint,en,USD
Boulderborn Dragon,TDM,Tarkir: Dragonstorm,323,normal,common,1,104326,970e11f0-337a-46b5-9bff-4bcb7843ed3a,0.1,false,false,near_mint,en,USD
Sagu Wildling // Roost Seek,TDM,Tarkir: Dragonstorm,306,normal,common,1,104903,b72ee8f9-5e79-4f77-ae7e-e4c274f78187,0.11,false,false,near_mint,en,USD
Tempest Hawk,TDM,Tarkir: Dragonstorm,31,normal,common,1,104587,422f9453-ab12-4e3c-8c51-be87391395a1,0.66,false,false,near_mint,en,USD
Eshki Dragonclaw,TDM,Tarkir: Dragonstorm,356,normal,rare,1,104877,aafaa59e-87e1-4953-8c04-8e7a3a509827,0.44,false,false,near_mint,en,USD
Lotuslight Dancers,TDM,Tarkir: Dragonstorm,363,normal,rare,1,104751,79dc69dc-6245-43fc-95a2-85b2c2957182,0.32,false,false,near_mint,en,USD
Rakshasa's Bargain,TDM,Tarkir: Dragonstorm,214,normal,uncommon,1,104299,5c409f4f-3b2c-4c33-b850-55b2a46f51ca,0.32,false,false,near_mint,en,USD
Glacial Dragonhunt,TDM,Tarkir: Dragonstorm,188,normal,uncommon,1,104814,95994c88-e404-4a4f-8be6-b99d703d4609,0.1,false,false,near_mint,en,USD
"Elspeth, Storm Slayer",TDM,Tarkir: Dragonstorm,11,normal,mythic,1,104311,73a065e3-b530-4e62-ab3c-4f6f908184ec,39.69,false,false,near_mint,en,USD
Host of the Hereafter,TDM,Tarkir: Dragonstorm,193,normal,uncommon,2,104448,0f182957-8133-45a7-80a3-1944bead4d43,0.16,false,false,near_mint,en,USD
Sunset Strikemaster,TDM,Tarkir: Dragonstorm,126,normal,uncommon,2,104394,f8f1a2f2-526d-4b2c-985b-0acfdc21a2ee,0.17,false,false,near_mint,en,USD
1 Name Set code Set name Collector number Foil Rarity Quantity ManaBox ID Scryfall ID Purchase price Misprint Altered Condition Language Purchase price currency
2 Sunpearl Kirin TDM Tarkir: Dragonstorm 29 foil uncommon 1 104470 18292b9c-0f42-4ce2-8b85-35d06cf45a63 1.24 false false near_mint en USD
3 Humbling Elder TDM Tarkir: Dragonstorm 48 foil common 1 104562 3a84c3f8-0030-4653-880e-b2d19272f5fa 0.06 false false near_mint en USD
4 Fortress Kin-Guard TDM Tarkir: Dragonstorm 12 foil common 1 104900 b647a018-1d70-43a1-a265-928bcd863689 0.04 false false near_mint en USD
5 Wild Ride TDM Tarkir: Dragonstorm 132 foil common 1 105154 abc8c6f5-6135-428e-8476-1751f82623f9 0.14 false false near_mint en USD
6 Jade-Cast Sentinel TDM Tarkir: Dragonstorm 243 foil common 1 104293 516ce5fa-bd00-429b-ba22-b38c7dd9306c 0.07 false false near_mint en USD
7 Sibsig Appraiser TDM Tarkir: Dragonstorm 56 foil common 1 105135 670c5b96-bac6-449b-a2bd-cb43750d3911 0.04 false false near_mint en USD
8 Channeled Dragonfire TDM Tarkir: Dragonstorm 102 foil uncommon 1 104499 24204881-690c-4043-8771-20cb93385072 0.07 false false near_mint en USD
9 Meticulous Artisan TDM Tarkir: Dragonstorm 112 foil common 1 104912 baf4c9dd-0546-41ac-a7ba-0bc312fef31e 0.03 false false near_mint en USD
10 Kishla Trawlers TDM Tarkir: Dragonstorm 50 foil uncommon 1 104472 190fbc55-e8e9-4077-9532-1de7406baabf 0.08 false false near_mint en USD
11 Rite of Renewal TDM Tarkir: Dragonstorm 153 foil uncommon 1 104390 f737698a-d934-4851-b238-828959ef4835 0.07 false false near_mint en USD
12 Twin Bolt TDM Tarkir: Dragonstorm 128 foil common 1 105137 688d8e93-d071-4089-9ef9-565ac4ae9ae0 0.04 false false near_mint en USD
13 Heritage Reclamation TDM Tarkir: Dragonstorm 145 foil common 1 104636 4f8fee37-a050-4329-8b10-46d150e7a95e 0.33 false false near_mint en USD
14 Sarkhan's Resolve TDM Tarkir: Dragonstorm 158 foil common 1 104952 cae56fef-b661-4bc5-b9a1-3871ae06e491 0.04 false false near_mint en USD
15 Delta Bloodflies TDM Tarkir: Dragonstorm 77 foil common 1 104457 119bb72d-aed9-47dc-9285-7bc836cc3776 0.05 false false near_mint en USD
16 Dirgur Island Dragon // Skimming Strike TDM Tarkir: Dragonstorm 40 foil common 1 104342 b1d21a9a-6b0c-4fbc-a427-81be885d326b 0.16 false false near_mint en USD
17 Kheru Goldkeeper TDM Tarkir: Dragonstorm 199 foil uncommon 1 104798 8d11183a-57f5-4ddb-8a6e-15fff704b114 0.37 false false near_mint en USD
18 Rainveil Rejuvenator TDM Tarkir: Dragonstorm 152 foil uncommon 1 105148 9bc5c316-6a41-48ba-864b-da3030dd3e0e 0.12 false false near_mint en USD
19 Rebellious Strike TDM Tarkir: Dragonstorm 20 foil common 1 104949 c9bafe19-3bd6-4da0-b3e5-e0b89262504c 0.06 false false near_mint en USD
20 Jeskai Brushmaster TDM Tarkir: Dragonstorm 195 foil uncommon 1 104526 2eb06c36-cf7e-47a9-819e-adfc54284153 0.09 false false near_mint en USD
21 Highspire Bell-Ringer TDM Tarkir: Dragonstorm 47 foil common 1 105020 e75dccf7-2894-4c4a-b516-3eee73acddd3 0.06 false false near_mint en USD
22 Sagu Pummeler TDM Tarkir: Dragonstorm 156 foil common 1 105169 def9cb5b-4062-481e-b682-3a30443c2e56 0.03 false false near_mint en USD
23 Roamer's Routine TDM Tarkir: Dragonstorm 154 foil common 1 104396 fb8c2d5c-ba0c-4d50-8898-5c6574b1e974 0.13 false false near_mint en USD
24 Monastery Messenger TDM Tarkir: Dragonstorm 208 foil common 1 104443 0c9eeced-6464-41f0-bbea-05b3af4cc005 0.04 false false near_mint en USD
25 Equilibrium Adept TDM Tarkir: Dragonstorm 106 foil uncommon 1 104335 a4ba6d74-c6be-4a5e-8859-b791bb6b8f51 0.07 false false near_mint en USD
26 Dispelling Exhale TDM Tarkir: Dragonstorm 41 foil common 1 104477 1c9af3f1-711e-42ae-803a-1100eba3fb13 0.21 false false near_mint en USD
27 Nightblade Brigade TDM Tarkir: Dragonstorm 85 foil common 1 105134 648debd9-d4cf-4788-8882-f1601a3d87f5 0.08 false false near_mint en USD
28 Tranquil Cove TDM Tarkir: Dragonstorm 270 foil common 1 104249 1c4efa6c-4f29-41cd-a728-bf0e479ace05 0.06 false false near_mint en USD
29 Rugged Highlands TDM Tarkir: Dragonstorm 265 foil common 1 104267 31261eca-28ad-407c-84ef-0c124d0d7451 0.07 false false near_mint en USD
30 Swiftwater Cliffs TDM Tarkir: Dragonstorm 268 foil common 1 104361 ca53fb19-b8ca-485b-af1a-5117ae54bfe3 0.11 false false near_mint en USD
31 Dismal Backwater TDM Tarkir: Dragonstorm 254 foil common 1 104238 082b52c9-c46e-44d3-b723-546ba528e07b 0.06 false false near_mint en USD
32 Wind-Scarred Crag TDM Tarkir: Dragonstorm 271 foil common 1 104286 4912e4d0-b16a-4aa6-a583-3430d26bd591 0.05 false false near_mint en USD
33 Jungle Hollow TDM Tarkir: Dragonstorm 258 foil common 1 104375 ea13440b-3f7b-4182-9541-27c1fa3121e5 0.07 false false near_mint en USD
34 Plains TDM Tarkir: Dragonstorm 272 foil common 1 104240 0d0f1dd6-9564-4adc-af7d-f83252e8581a 0.39 false false near_mint en USD
35 Purging Stormbrood // Absorb Essence TDM Tarkir: Dragonstorm 315 foil uncommon 1 104395 fb293f4f-9ba2-48f5-a4fb-d902aa531bfc 0.18 false false near_mint en USD
36 Barrensteppe Siege TDM Tarkir: Dragonstorm 384 foil rare 1 104002 c09d4015-f101-4529-a603-c66192dcfd92 1.68 false false near_mint en USD
37 United Battlefront TDM Tarkir: Dragonstorm 32 foil rare 1 104370 dff398be-4ba4-4976-9acc-be99d2e07a61 0.8 false false near_mint en USD
38 Cori-Steel Cutter TDM Tarkir: Dragonstorm 103 foil rare 1 104608 490eb213-9ae2-4b45-abec-6f1dfc83792a 15.2 false false near_mint en USD
39 Plains TDM Tarkir: Dragonstorm 272 normal common 1 104240 0d0f1dd6-9564-4adc-af7d-f83252e8581a 0.52 false false near_mint en USD
40 Skirmish Rhino TDM Tarkir: Dragonstorm 224 normal uncommon 1 103992 4a2e9ba1-c254-41e3-9845-4e81f9fec38d 0.18 false false near_mint en USD
41 Kheru Goldkeeper TDM Tarkir: Dragonstorm 199 normal uncommon 1 104798 8d11183a-57f5-4ddb-8a6e-15fff704b114 0.17 false false near_mint en USD
42 Hardened Tactician TDM Tarkir: Dragonstorm 191 normal uncommon 1 104780 86b225cb-5c45-4da1-a64e-b04091e483e8 0.19 false false near_mint en USD
43 Auroral Procession TDM Tarkir: Dragonstorm 169 normal uncommon 1 104701 672f94ad-65d6-4c7d-925d-165ef264626f 0.2 false false near_mint en USD
44 Barrensteppe Siege TDM Tarkir: Dragonstorm 171 normal rare 1 103989 2556a35b-2229-42c7-8cb3-c8c668403dd2 0.46 false false near_mint en USD
45 Heritage Reclamation TDM Tarkir: Dragonstorm 145 normal common 1 104636 4f8fee37-a050-4329-8b10-46d150e7a95e 0.19 false false near_mint en USD
46 Windcrag Siege TDM Tarkir: Dragonstorm 235 normal rare 1 104534 31a8329b-23a1-4c49-a579-a5da8d01435a 1.97 false false near_mint en USD
47 Ambling Stormshell TDM Tarkir: Dragonstorm 37 normal rare 1 104942 c74d4a57-0f66-4965-9ed7-f88a08aa1d15 0.18 false false near_mint en USD
48 Narset, Jeskai Waymaster TDM Tarkir: Dragonstorm 209 normal rare 1 103995 6b77cbc1-dbc8-44d9-aa29-15cbb19afecd 0.21 false false near_mint en USD
49 Revival of the Ancestors TDM Tarkir: Dragonstorm 218 normal rare 1 105074 fd742ff5-f0ea-4f4b-911e-4c09e2154dba 0.14 false false near_mint en USD
50 Dragonback Assault TDM Tarkir: Dragonstorm 179 normal mythic 1 104985 d54cc838-d79d-433a-99fb-d6e4d1c1431d 3.49 false false near_mint en USD
51 Call the Spirit Dragons TDM Tarkir: Dragonstorm 174 normal mythic 1 104888 b1ad91db-5f16-4392-baf1-f8400ec11e0a 3.99 false false near_mint en USD
52 Flamehold Grappler TDM Tarkir: Dragonstorm 185 normal rare 1 104958 cc8443a6-282f-4218-9dc8-144b5570d891 0.26 false false near_mint en USD
53 New Way Forward TDM Tarkir: Dragonstorm 211 normal rare 1 104996 d9d48f9e-79f0-478c-9db0-ff7ac4a8f401 0.2 false false near_mint en USD
54 Eshki Dragonclaw TDM Tarkir: Dragonstorm 182 normal rare 1 104445 0d369c44-78ee-4f3c-bf2b-cddba7fe26d4 0.22 false false near_mint en USD
55 Warden of the Grove TDM Tarkir: Dragonstorm 166 normal rare 1 104498 2414db96-0e2b-4f7c-9b97-41f8e310b752 0.94 false false near_mint en USD
56 United Battlefront TDM Tarkir: Dragonstorm 32 normal rare 1 104370 dff398be-4ba4-4976-9acc-be99d2e07a61 0.58 false false near_mint en USD
57 Dalkovan Encampment TDM Tarkir: Dragonstorm 253 normal rare 1 104822 98ad5f0c-8775-4e89-8e92-84a6ade93e35 0.3 false false near_mint en USD
58 Marang River Regent // Coil and Catch TDM Tarkir: Dragonstorm 51 normal rare 1 104393 f890bdc7-32e6-4492-bac7-7cabf54a8bfd 2.87 false false near_mint en USD
59 Sage of the Skies TDM Tarkir: Dragonstorm 22 normal rare 1 104710 6ade6918-6d1d-448d-ab56-93996051e9a9 0.28 false false near_mint en USD
60 Tersa Lightshatter TDM Tarkir: Dragonstorm 127 normal rare 1 104825 99e96b34-b1c4-4647-a38e-2cf1aedaaace 1.92 false false near_mint en USD
61 Voice of Victory TDM Tarkir: Dragonstorm 33 normal rare 1 104377 ec3de5f4-bb55-4ab9-995f-f3e0dc22c1bb 10.45 false false near_mint en USD
62 Yathan Roadwatcher TDM Tarkir: Dragonstorm 236 normal rare 1 104800 8e77339b-dd82-481c-9ee2-4156ca69ad35 0.15 false false near_mint en USD
63 Great Arashin City TDM Tarkir: Dragonstorm 257 normal rare 1 105033 ecba23b6-9f3a-431e-bc22-f1fb04d27b68 0.31 false false near_mint en USD
64 Taigam, Master Opportunist TDM Tarkir: Dragonstorm 60 normal mythic 1 104320 8693d631-05f6-414d-9e49-6385746e8960 1.69 false false near_mint en USD
65 Temur Battlecrier TDM Tarkir: Dragonstorm 228 normal rare 1 104309 72184791-0767-4108-920c-763e92dae2d4 0.74 false false near_mint en USD
66 Fangkeeper's Familiar TDM Tarkir: Dragonstorm 183 normal rare 1 104696 655fa2e1-3e1c-424c-b17a-daa7b8fface4 0.27 false false near_mint en USD
67 Nature's Rhythm TDM Tarkir: Dragonstorm 150 normal rare 1 104460 1397d904-c51d-451e-8505-7f3118acc1f6 3.21 false false near_mint en USD
68 Stillness in Motion TDM Tarkir: Dragonstorm 59 normal rare 1 104864 a6289251-17e4-4987-96b9-2fb1a8f90e2a 0.18 false false near_mint en USD
69 Stadium Headliner TDM Tarkir: Dragonstorm 122 normal rare 1 104552 37d4ab2a-a06a-4768-b5e1-e1def957d7f4 0.46 false false near_mint en USD
70 Sinkhole Surveyor TDM Tarkir: Dragonstorm 93 normal rare 1 104551 37cb5599-7d2c-48e9-978b-902a01a74bde 0.23 false false near_mint en USD
71 Naga Fleshcrafter TDM Tarkir: Dragonstorm 52 normal rare 1 104675 5df17423-9fdd-4432-8660-1d267c685595 0.31 false false near_mint en USD
72 Herd Heirloom TDM Tarkir: Dragonstorm 144 normal rare 1 104873 a88c7713-b3a9-4685-b1d3-623d35b62365 4.48 false false near_mint en USD
73 Severance Priest TDM Tarkir: Dragonstorm 222 normal rare 1 104917 bc779a1b-128c-4c74-bebd-bdb687867f68 0.21 false false near_mint en USD
74 Dracogenesis TDM Tarkir: Dragonstorm 105 normal mythic 1 104241 0d5674f9-22b2-45f9-902d-4fd245485c60 15.05 false false near_mint en USD
75 Kishla Village TDM Tarkir: Dragonstorm 259 normal rare 1 104840 9f0ff90d-7312-44df-afc5-29c768fa7758 0.3 false false near_mint en USD
76 Sarkhan, Dragon Ascendant TDM Tarkir: Dragonstorm 302 normal rare 1 103994 57c03255-e3dc-44c2-982b-7efa188280df 0.49 false false near_mint en USD
77 Bloomvine Regent // Claim Territory TDM Tarkir: Dragonstorm 381 normal rare 1 104237 081f2de5-251a-41c9-a62f-11487f54d355 1.97 false false near_mint en USD
78 Kheru Goldkeeper TDM Tarkir: Dragonstorm 313 normal uncommon 1 105150 9d85ba44-8f29-4c49-b77f-8a6692d23c8c 0.45 false false near_mint en USD
79 Boulderborn Dragon TDM Tarkir: Dragonstorm 323 normal common 1 104326 970e11f0-337a-46b5-9bff-4bcb7843ed3a 0.1 false false near_mint en USD
80 Sagu Wildling // Roost Seek TDM Tarkir: Dragonstorm 306 normal common 1 104903 b72ee8f9-5e79-4f77-ae7e-e4c274f78187 0.11 false false near_mint en USD
81 Tempest Hawk TDM Tarkir: Dragonstorm 31 normal common 1 104587 422f9453-ab12-4e3c-8c51-be87391395a1 0.66 false false near_mint en USD
82 Eshki Dragonclaw TDM Tarkir: Dragonstorm 356 normal rare 1 104877 aafaa59e-87e1-4953-8c04-8e7a3a509827 0.44 false false near_mint en USD
83 Lotuslight Dancers TDM Tarkir: Dragonstorm 363 normal rare 1 104751 79dc69dc-6245-43fc-95a2-85b2c2957182 0.32 false false near_mint en USD
84 Rakshasa's Bargain TDM Tarkir: Dragonstorm 214 normal uncommon 1 104299 5c409f4f-3b2c-4c33-b850-55b2a46f51ca 0.32 false false near_mint en USD
85 Glacial Dragonhunt TDM Tarkir: Dragonstorm 188 normal uncommon 1 104814 95994c88-e404-4a4f-8be6-b99d703d4609 0.1 false false near_mint en USD
86 Elspeth, Storm Slayer TDM Tarkir: Dragonstorm 11 normal mythic 1 104311 73a065e3-b530-4e62-ab3c-4f6f908184ec 39.69 false false near_mint en USD
87 Host of the Hereafter TDM Tarkir: Dragonstorm 193 normal uncommon 2 104448 0f182957-8133-45a7-80a3-1944bead4d43 0.16 false false near_mint en USD
88 Sunset Strikemaster TDM Tarkir: Dragonstorm 126 normal uncommon 2 104394 f8f1a2f2-526d-4b2c-985b-0acfdc21a2ee 0.17 false false near_mint en USD

View File

@ -10,6 +10,7 @@ from pathlib import Path
from app.routes import routes
from app.db.database import init_db, SessionLocal
from app.services.service_manager import ServiceManager
from app.models.tcgplayer_products import MostRecentTCGPlayerPrice
# Configure logging
log_file = Path("app.log")
@ -30,7 +31,7 @@ file_handler.setFormatter(formatter)
# Configure root logger
root_logger = logging.getLogger()
root_logger.setLevel(logging.INFO)
root_logger.setLevel(logging.DEBUG)
root_logger.addHandler(console_handler)
root_logger.addHandler(file_handler)
@ -57,12 +58,19 @@ async def lifespan(app: FastAPI):
# Get a database session
db = SessionLocal()
try:
data_init_service = service_manager.get_service('data_initialization')
data_init = await data_init_service.initialize_data(db, game_ids=[1, 3], use_cache=False, init_categories=False, init_products=False, init_groups=False, init_archived_prices=False, init_mtgjson=False, archived_prices_start_date="2024-03-05", archived_prices_end_date="2025-04-17")
logger.info(f"Data initialization results: {data_init}")
#data_init_service = service_manager.get_service('data_initialization')
#data_init = await data_init_service.initialize_data(db, game_ids=[1], use_cache=False, init_categories=True, init_products=True, init_groups=True, init_archived_prices=True, init_mtgjson=False, archived_prices_start_date="2025-05-22", archived_prices_end_date="2025-05-23")
#logger.info(f"Data initialization results: {data_init}")
# Update most recent prices
#MostRecentTCGPlayerPrice.update_most_recent_prices(db)
logger.info("Most recent prices updated successfully")
# Create default customer, vendor, and marketplace
#inv_data_init = await data_init_service.initialize_inventory_data(db)
#logger.info(f"Inventory data initialization results: {inv_data_init}")
# Start the scheduler
scheduler = service_manager.get_service('scheduler')
#await scheduler.refresh_tcgplayer_inventory_table(db)
await scheduler.start_scheduled_tasks(db)
logger.info("Scheduler started successfully")
@ -107,10 +115,50 @@ async def read_app_js():
raise HTTPException(status_code=404, detail="App.js file not found")
return FileResponse(js_path)
# Serve manabox.html
@app.get("/manabox.html")
async def read_manabox_html():
html_path = Path('app/static/manabox.html')
if not html_path.exists():
raise HTTPException(status_code=404, detail="Manabox.html file not found")
return FileResponse(html_path)
# Serve manabox.js
@app.get("/manabox.js")
async def read_manabox_js():
js_path = Path('app/static/manabox.js')
if not js_path.exists():
raise HTTPException(status_code=404, detail="Manabox.js file not found")
return FileResponse(js_path)
# serve transactions.html
@app.get("/transactions.html")
async def read_transactions_html():
html_path = Path('app/static/transactions.html')
if not html_path.exists():
raise HTTPException(status_code=404, detail="Transaction.html file not found")
return FileResponse(html_path)
# serve transactions.js
@app.get("/transactions.js")
async def read_transactions_js():
js_path = Path('app/static/transactions.js')
if not js_path.exists():
raise HTTPException(status_code=404, detail="Transaction.js file not found")
return FileResponse(js_path)
# serve styles.css
@app.get("/styles.css")
async def read_styles_css():
css_path = Path('app/static/styles.css')
if not css_path.exists():
raise HTTPException(status_code=404, detail="Styles.css file not found")
return FileResponse(css_path)
# Configure CORS with specific origins in production
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"], # Update with your frontend URL
allow_origins=["http://localhost:3000", "http://192.168.1.124:3000"], # Update with your frontend URL
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],

View File

@ -1,3 +1,4 @@
from app.models.critical_error_log import CriticalErrorLog
from app.models.file import File
from app.models.inventory_management import (
PhysicalItem,
@ -7,25 +8,32 @@ from app.models.inventory_management import (
Vendor,
Customer,
Transaction,
CostBasis
SealedExpectedValue,
Marketplace,
MarketplaceListing
)
from app.models.tcgplayer_products import (
MTGJSONCard,
MTGJSONSKU,
TCGPlayerProduct,
TCGPlayerCategory,
TCGPlayerGroup,
TCGPlayerPriceHistory,
MostRecentTCGPlayerPrice
)
from app.models.mtgjson_card import MTGJSONCard
from app.models.mtgjson_sku import MTGJSONSKU
from app.models.product import Product
from app.models.tcgplayer_category import TCGPlayerCategory
from app.models.tcgplayer_group import TCGPlayerGroup
from app.models.tcgplayer_inventory import TCGPlayerInventory
from app.models.tcgplayer_order import (
TCGPlayerOrder,
TCGPlayerOrderTransaction,
TCGPlayerOrderProduct,
TCGPlayerOrderRefund
)
from app.models.tcgplayer_price_history import TCGPlayerPriceHistory
from app.models.tcgplayer_product import TCGPlayerProduct
from app.models.tcgplayer_inventory import TCGPlayerInventory
from app.models.manabox_import_staging import ManaboxImportStaging
from app.models.pricing import PricingEvent
# This makes all models available for Alembic to discover
__all__ = [
'CriticalErrorLog',
'File',
'PhysicalItem',
'InventoryItem',
@ -34,17 +42,21 @@ __all__ = [
'Vendor',
'Customer',
'Transaction',
'CostBasis',
'SealedExpectedValue',
'Marketplace',
'MarketplaceListing',
'MTGJSONCard',
'MTGJSONSKU',
'Product',
'TCGPlayerProduct',
'TCGPlayerCategory',
'TCGPlayerGroup',
'TCGPlayerInventory',
'ManaboxImportStaging',
'TCGPlayerOrder',
'TCGPlayerOrderTransaction',
'TCGPlayerOrderProduct',
'TCGPlayerOrderRefund',
'TCGPlayerPriceHistory',
'TCGPlayerProduct'
'MostRecentTCGPlayerPrice',
'PricingEvent'
]

View File

@ -0,0 +1,11 @@
from sqlalchemy import Column, Integer, String, DateTime
from sqlalchemy.sql import func
from app.db.database import Base
class CriticalErrorLog(Base):
__tablename__ = "critical_error_logs"
id = Column(Integer, primary_key=True, index=True)
error_message = Column(String, nullable=False)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())

View File

@ -1,9 +1,6 @@
from pydantic import BaseModel, ConfigDict
from typing import List, Optional
from datetime import datetime
from sqlalchemy import Column, Integer, String, DateTime, JSON
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
from app.db.database import Base
@ -15,7 +12,11 @@ class File(Base):
file_type = Column(String)
content_type = Column(String)
path = Column(String)
size = Column(Integer) # File size in bytes
size = Column(Integer)
file_metadata = Column(JSON)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
deleted_at = Column(DateTime(timezone=True), nullable=True)
# Relationships
manabox_import_staging = relationship("ManaboxImportStaging", back_populates="file")

View File

@ -1,149 +1,271 @@
from sqlalchemy import Column, Integer, String, Float, DateTime, ForeignKey, Table
from sqlalchemy import Column, Integer, String, Float, DateTime, ForeignKey, CheckConstraint, Index, Boolean, Table, UniqueConstraint
from sqlalchemy.orm import relationship
from app.db.database import Base
from sqlalchemy import event
from sqlalchemy.orm import Session
from sqlalchemy import func
from sqlalchemy.ext.hybrid import hybrid_property
from datetime import datetime
from app.models.critical_error_log import CriticalErrorLog
import logging
logger = logging.getLogger(__name__)
open_event_resulting_items = Table(
"open_event_resulting_items",
Base.metadata,
Column("event_id", Integer, ForeignKey("open_events.id"), primary_key=True),
Column("item_id", Integer, ForeignKey("physical_items.id"), primary_key=True)
)
class PhysicalItem(Base):
__tablename__ = "physical_items"
id = Column(Integer, primary_key=True)
item_type = Column(String)
product_id = Column(Integer, ForeignKey("products.id"), nullable=False)
created_at = Column(DateTime(timezone=True))
updated_at = Column(DateTime(timezone=True))
# at least one of these must be set to pass the constraint
tcgplayer_product_id = Column(Integer, nullable=True)
tcgplayer_sku_id = Column(Integer, ForeignKey("mtgjson_skus.tcgplayer_sku_id"), nullable=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
deleted_at = Column(DateTime(timezone=True), nullable=True)
__table_args__ = (
CheckConstraint(
"(tcgplayer_sku_id IS NOT NULL OR tcgplayer_product_id IS NOT NULL)",
name="ck_physical_items_sku_or_product_not_null"
),
)
__mapper_args__ = {
'polymorphic_on': item_type,
'polymorphic_identity': 'physical_item'
}
# Relationships
product = relationship("Product")
sku = relationship("MTGJSONSKU", back_populates="physical_items", primaryjoin="PhysicalItem.tcgplayer_sku_id == MTGJSONSKU.tcgplayer_sku_id")
product_direct = relationship("TCGPlayerProduct",
back_populates="physical_items_direct",
primaryjoin="PhysicalItem.tcgplayer_product_id == foreign(TCGPlayerProduct.tcgplayer_product_id)", uselist=False)
inventory_item = relationship("InventoryItem", uselist=False, back_populates="physical_item")
transaction_items = relationship("TransactionItem", back_populates="physical_item")
#transaction_items = relationship("TransactionItem", back_populates="physical_item")
source_open_events = relationship(
"OpenEvent",
back_populates="source_item",
foreign_keys="[OpenEvent.source_item_id]"
)
resulting_open_events = relationship(
"OpenEvent",
secondary=open_event_resulting_items,
back_populates="resulting_items"
)
class SealedCase(PhysicalItem):
__tablename__ = "sealed_cases"
@hybrid_property
def is_sealed(self):
return not self.source_open_events
id = Column(Integer, ForeignKey('physical_items.id'), primary_key=True)
__mapper_args__ = {
'polymorphic_identity': 'sealed_case'
}
# Relationships
boxes = relationship("SealedBox", back_populates="case")
open_event = relationship("OpenEvent", uselist=False, back_populates="sealed_case")
class SealedBox(PhysicalItem):
__tablename__ = "sealed_boxes"
id = Column(Integer, ForeignKey('physical_items.id'), primary_key=True)
case_id = Column(Integer, ForeignKey("sealed_cases.id"), nullable=True)
__mapper_args__ = {
'polymorphic_identity': 'sealed_box'
}
# Relationships
case = relationship("SealedCase", back_populates="boxes")
open_event = relationship("OpenEvent", uselist=False, back_populates="sealed_box")
class OpenBox(PhysicalItem):
__tablename__ = "open_boxes"
id = Column(Integer, ForeignKey('physical_items.id'), primary_key=True)
open_event_id = Column(Integer, ForeignKey("open_events.id"))
sealed_box_id = Column(Integer, ForeignKey("sealed_boxes.id"))
__mapper_args__ = {
'polymorphic_identity': 'open_box'
}
# Relationships
open_event = relationship("OpenEvent", back_populates="resulting_boxes")
sealed_box = relationship("SealedBox")
cards = relationship("OpenCard", back_populates="box")
class OpenCard(PhysicalItem):
__tablename__ = "open_cards"
id = Column(Integer, ForeignKey('physical_items.id'), primary_key=True)
open_event_id = Column(Integer, ForeignKey("open_events.id"))
box_id = Column(Integer, ForeignKey("open_boxes.id"), nullable=True)
__mapper_args__ = {
'polymorphic_identity': 'open_card'
}
# Relationships
open_event = relationship("OpenEvent", back_populates="resulting_cards")
box = relationship("OpenBox", back_populates="cards")
@hybrid_property
def products(self):
if self.sku and self.sku.product:
return self.sku.product
elif self.product_direct:
return self.product_direct
else:
return None
class InventoryItem(Base):
__tablename__ = "inventory_items"
id = Column(Integer, primary_key=True, index=True)
physical_item_id = Column(Integer, ForeignKey("physical_items.id"), unique=True)
cost_basis = Column(Float) # Current cost basis for this item
parent_id = Column(Integer, ForeignKey("inventory_items.id"), nullable=True) # For tracking hierarchy
created_at = Column(DateTime(timezone=True))
updated_at = Column(DateTime(timezone=True))
cost_basis = Column(Float)
parent_id = Column(Integer, ForeignKey("inventory_items.id"), nullable=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
deleted_at = Column(DateTime(timezone=True), nullable=True)
# Relationships
physical_item = relationship("PhysicalItem", back_populates="inventory_item")
parent = relationship("InventoryItem", remote_side=[id])
children = relationship("InventoryItem")
parent = relationship("InventoryItem", remote_side=[id], back_populates="children")
children = relationship("InventoryItem", back_populates="parent", overlaps="parent")
marketplace_listing = relationship("MarketplaceListing", back_populates="inventory_item")
transaction_items = relationship("TransactionItem", back_populates="inventory_item")
@property
def products(self):
"""
Proxy access to the associated TCGPlayerProduct(s) via the linked PhysicalItem.
Returns:
list[TCGPlayerProduct] or [] if no physical item or no linked products.
"""
return self.physical_item.products if self.physical_item else None
def soft_delete(self, timestamp=None):
if not timestamp:
timestamp = datetime.now()
self.deleted_at = timestamp
for child in self.children:
child.soft_delete(timestamp)
class Box(PhysicalItem):
__tablename__ = "boxes"
id = Column(Integer, ForeignKey('physical_items.id'), primary_key=True)
expected_value = Column(Float)
__mapper_args__ = {
'polymorphic_identity': 'box'
}
class Case(PhysicalItem):
__tablename__ = "cases"
id = Column(Integer, ForeignKey('physical_items.id'), primary_key=True)
expected_value = Column(Float)
num_boxes = Column(Integer)
__mapper_args__ = {
'polymorphic_identity': 'case'
}
class Card(PhysicalItem):
__tablename__ = "cards"
id = Column(Integer, ForeignKey('physical_items.id'), primary_key=True)
__mapper_args__ = {
'polymorphic_identity': 'card'
}
class OpenEvent(Base):
__tablename__ = "open_events"
id = Column(Integer, primary_key=True, index=True)
source_item_id = Column(Integer, ForeignKey("physical_items.id"))
open_date = Column(DateTime(timezone=True))
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
deleted_at = Column(DateTime(timezone=True), nullable=True)
__table_args__ = (
UniqueConstraint("source_item_id", name="uq_openevent_one_per_source"),
)
# Relationships
source_item = relationship(
"PhysicalItem",
back_populates="source_open_events",
foreign_keys=[source_item_id]
)
resulting_items = relationship(
"PhysicalItem",
secondary=open_event_resulting_items,
back_populates="resulting_open_events"
)
class SealedExpectedValue(Base):
__tablename__ = "sealed_expected_values"
__table_args__ = (
Index('idx_sealed_expected_value_product_id_deleted_at', 'tcgplayer_product_id', 'deleted_at', unique=True),
)
id = Column(Integer, primary_key=True, index=True)
tcgplayer_product_id = Column(Integer, nullable=False)
expected_value = Column(Float, nullable=False)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
deleted_at = Column(DateTime(timezone=True), nullable=True)
# Relationships
product = relationship(
"TCGPlayerProduct",
primaryjoin="SealedExpectedValue.tcgplayer_product_id == foreign(TCGPlayerProduct.tcgplayer_product_id)",
viewonly=True)
# helper for ev
#def assign_expected_value(target, session):
# products = target.products
# if not products:
# raise ValueError(f"No product found for item ID {target.id}")
# if len(products) > 1:
# product_names = [p.name for p in products]
# critical_error = CriticalErrorLog(
# error_type="multiple_products_found",
# error_message=f"Multiple products found when assigning expected value for item ID {target.id} product names {product_names}"
# )
# session.add(critical_error)
# session.commit()
# raise ValueError(f"Multiple products found when assigning expected value for item ID {target.id} product names {product_names}")
# product_id = products[0].tcgplayer_product_id # reliable lookup key
# expected_value_entry = session.query(SealedExpectedValue).filter(
# SealedExpectedValue.tcgplayer_product_id == product_id,
# SealedExpectedValue.deleted_at == None
# ).order_by(SealedExpectedValue.created_at.desc()).first()
# if expected_value_entry:
# target.expected_value = expected_value_entry.expected_value
# else:
# critical_error = CriticalErrorLog(
# error_type="no_expected_value_found",
# error_message=f"No expected value found for product {products[0].name}"
# )
# session.add(critical_error)
# session.commit()
# raise ValueError(f"No expected value found for product {products[0].name}")
# event listeners
#@event.listens_for(InventoryItem, 'before_insert')
#def ev_before_insert(mapper, connection, target):
# session = Session.object_session(target)
# if session:
# assign_expected_value(target, session)
class TransactionItem(Base):
__tablename__ = "transaction_items"
id = Column(Integer, primary_key=True, index=True)
transaction_id = Column(Integer, ForeignKey("transactions.id"))
physical_item_id = Column(Integer, ForeignKey("physical_items.id"))
inventory_item_id = Column(Integer, ForeignKey("inventory_items.id"))
unit_price = Column(Float, nullable=False)
created_at = Column(DateTime(timezone=True))
updated_at = Column(DateTime(timezone=True))
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
deleted_at = Column(DateTime(timezone=True), nullable=True)
# Relationships
transaction = relationship("Transaction", back_populates="transaction_items")
physical_item = relationship("PhysicalItem", back_populates="transaction_items")
class OpenEvent(Base):
__tablename__ = "open_events"
id = Column(Integer, primary_key=True, index=True)
sealed_case_id = Column(Integer, ForeignKey("sealed_cases.id"), nullable=True)
sealed_box_id = Column(Integer, ForeignKey("sealed_boxes.id"), nullable=True)
open_date = Column(DateTime(timezone=True))
created_at = Column(DateTime(timezone=True))
updated_at = Column(DateTime(timezone=True))
deleted_at = Column(DateTime(timezone=True), nullable=True)
# Relationships
sealed_case = relationship("SealedCase", back_populates="open_event")
sealed_box = relationship("SealedBox", back_populates="open_event")
resulting_boxes = relationship("OpenBox", back_populates="open_event")
resulting_cards = relationship("OpenCard", back_populates="open_event")
inventory_item = relationship("InventoryItem", back_populates="transaction_items")
class Vendor(Base):
__tablename__ = "vendors"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, unique=True, index=True)
created_at = Column(DateTime(timezone=True))
updated_at = Column(DateTime(timezone=True))
name = Column(String, index=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
deleted_at = Column(DateTime(timezone=True), nullable=True)
# Relationships
transactions = relationship("Transaction", back_populates="vendors")
class Customer(Base):
__tablename__ = "customers"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, unique=True, index=True)
created_at = Column(DateTime(timezone=True))
updated_at = Column(DateTime(timezone=True))
name = Column(String, index=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
deleted_at = Column(DateTime(timezone=True), nullable=True)
# Relationships
transactions = relationship("Transaction", back_populates="customers")
class Transaction(Base):
__tablename__ = "transactions"
@ -151,35 +273,49 @@ class Transaction(Base):
id = Column(Integer, primary_key=True, index=True)
vendor_id = Column(Integer, ForeignKey("vendors.id"), nullable=True)
customer_id = Column(Integer, ForeignKey("customers.id"), nullable=True)
marketplace_id = Column(Integer, ForeignKey("marketplaces.id"), nullable=True)
transaction_type = Column(String) # 'purchase' or 'sale'
transaction_date = Column(DateTime(timezone=True))
transaction_total_amount = Column(Float)
transaction_notes = Column(String)
created_at = Column(DateTime(timezone=True))
updated_at = Column(DateTime(timezone=True))
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
deleted_at = Column(DateTime(timezone=True), nullable=True)
# Relationships
transaction_items = relationship("TransactionItem", back_populates="transaction")
vendors = relationship("Vendor", back_populates="transactions")
customers = relationship("Customer", back_populates="transactions")
marketplaces = relationship("Marketplace", back_populates="transactions")
class CostBasis(Base):
__tablename__ = "cost_basis"
class Marketplace(Base):
__tablename__ = "marketplaces"
id = Column(Integer, primary_key=True, index=True)
transaction_item_id = Column(Integer, ForeignKey("transaction_items.id"))
sealed_case_id = Column(Integer, ForeignKey("sealed_cases.id"), nullable=True)
sealed_box_id = Column(Integer, ForeignKey("sealed_boxes.id"), nullable=True)
open_box_id = Column(Integer, ForeignKey("open_boxes.id"), nullable=True)
open_card_id = Column(Integer, ForeignKey("open_cards.id"), nullable=True)
quantity = Column(Integer, nullable=False, default=1)
unit_cost = Column(Float, nullable=False)
created_at = Column(DateTime(timezone=True))
updated_at = Column(DateTime(timezone=True))
name = Column(String, index=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
deleted_at = Column(DateTime(timezone=True), nullable=True)
# Relationships
transaction_item = relationship("TransactionItem")
sealed_case = relationship("SealedCase")
sealed_box = relationship("SealedBox")
open_box = relationship("OpenBox")
open_card = relationship("OpenCard")
listings = relationship("MarketplaceListing", back_populates="marketplace")
transactions = relationship("Transaction", back_populates="marketplaces")
class MarketplaceListing(Base):
__tablename__ = "marketplace_listings"
id = Column(Integer, primary_key=True, index=True)
inventory_item_id = Column(Integer, ForeignKey("inventory_items.id"), nullable=False)
marketplace_id = Column(Integer, ForeignKey("marketplaces.id"), nullable=False)
recommended_price_id = Column(Integer, ForeignKey("pricing_events.id"), nullable=True)
listed_price_id = Column(Integer, ForeignKey("pricing_events.id"), nullable=True)
listing_date = Column(DateTime(timezone=True), nullable=True)
delisting_date = Column(DateTime(timezone=True), nullable=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
deleted_at = Column(DateTime(timezone=True), nullable=True)
# Relationships
inventory_item = relationship("InventoryItem", back_populates="marketplace_listing")
marketplace = relationship("Marketplace", back_populates="listings")
recommended_price = relationship("PricingEvent", foreign_keys=[recommended_price_id])
listed_price = relationship("PricingEvent", foreign_keys=[listed_price_id])

View File

@ -0,0 +1,19 @@
from sqlalchemy import Column, Integer, String, Float, DateTime, ForeignKey
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
from app.db.database import Base
class ManaboxImportStaging(Base):
__tablename__ = "manabox_import_staging"
id = Column(Integer, primary_key=True)
file_id = Column(Integer, ForeignKey("files.id"))
tcgplayer_product_id = Column(Integer)
tcgplayer_sku_id = Column(Integer)
quantity = Column(Integer)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
deleted_at = Column(DateTime(timezone=True), nullable=True)
# Relationships
file = relationship("File", back_populates="manabox_import_staging")

View File

@ -1,43 +0,0 @@
from sqlalchemy import Column, Integer, String, DateTime
from sqlalchemy.sql import func
from app.db.database import Base
class MTGJSONCard(Base):
__tablename__ = "mtgjson_cards"
id = Column(Integer, primary_key=True, index=True)
card_id = Column(String, unique=True, index=True)
name = Column(String)
set_code = Column(String)
uuid = Column(String)
# Identifiers
abu_id = Column(String, nullable=True)
card_kingdom_etched_id = Column(String, nullable=True)
card_kingdom_foil_id = Column(String, nullable=True)
card_kingdom_id = Column(String, nullable=True)
cardsphere_id = Column(String, nullable=True)
cardsphere_foil_id = Column(String, nullable=True)
cardtrader_id = Column(String, nullable=True)
csi_id = Column(String, nullable=True)
mcm_id = Column(String, nullable=True)
mcm_meta_id = Column(String, nullable=True)
miniaturemarket_id = Column(String, nullable=True)
mtg_arena_id = Column(String, nullable=True)
mtgjson_foil_version_id = Column(String, nullable=True)
mtgjson_non_foil_version_id = Column(String, nullable=True)
mtgjson_v4_id = Column(String, nullable=True)
mtgo_foil_id = Column(String, nullable=True)
mtgo_id = Column(String, nullable=True)
multiverse_id = Column(String, nullable=True)
scg_id = Column(String, nullable=True)
scryfall_id = Column(String, nullable=True)
scryfall_card_back_id = Column(String, nullable=True)
scryfall_oracle_id = Column(String, nullable=True)
scryfall_illustration_id = Column(String, nullable=True)
tcgplayer_product_id = Column(String, nullable=True)
tcgplayer_etched_product_id = Column(String, nullable=True)
tnt_id = Column(String, nullable=True)
created_at = Column(DateTime(timezone=True), server_default=func.current_timestamp())
updated_at = Column(DateTime(timezone=True), onupdate=func.current_timestamp())

View File

@ -1,17 +0,0 @@
from sqlalchemy import Column, Integer, String, DateTime
from sqlalchemy.sql import func
from app.db.database import Base
class MTGJSONSKU(Base):
__tablename__ = "mtgjson_skus"
id = Column(Integer, primary_key=True, index=True)
sku_id = Column(String, index=True)
product_id = Column(String, index=True)
condition = Column(String)
finish = Column(String)
language = Column(String)
printing = Column(String)
card_id = Column(String, nullable=True)
created_at = Column(DateTime(timezone=True), server_default=func.current_timestamp())
updated_at = Column(DateTime(timezone=True), onupdate=func.current_timestamp())

29
app/models/pricing.py Normal file
View File

@ -0,0 +1,29 @@
from sqlalchemy import Column, Integer, String, Float, DateTime, ForeignKey, CheckConstraint, Index, Boolean, Table, UniqueConstraint
from sqlalchemy.orm import relationship
from app.db.database import Base
from sqlalchemy import event
from sqlalchemy.orm import Session
from sqlalchemy import func
import logging
logger = logging.getLogger(__name__)
class PricingEvent(Base):
__tablename__ = "pricing_events"
id = Column(Integer, primary_key=True)
inventory_item_id = Column(Integer, ForeignKey("inventory_items.id"))
price = Column(Float)
price_used = Column(String)
price_reason = Column(String)
free_shipping_adjustment = Column(Boolean, default=False)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
deleted_at = Column(DateTime(timezone=True), nullable=True)
# Relationships
inventory_item = relationship(
"InventoryItem",
primaryjoin="PricingEvent.inventory_item_id == foreign(InventoryItem.id)",
viewonly=True
)

View File

@ -1,12 +0,0 @@
from sqlalchemy import Column, Integer, String, DateTime
from app.db.database import Base
class Product(Base):
__tablename__ = "products"
id = Column(Integer, primary_key=True)
name = Column(String)
tcgplayer_id = Column(String)
created_at = Column(DateTime(timezone=True))
updated_at = Column(DateTime(timezone=True))
deleted_at = Column(DateTime(timezone=True), nullable=True)

View File

@ -1,23 +0,0 @@
from sqlalchemy import Column, Integer, String, Boolean, DateTime
from sqlalchemy.sql import func
from app.db.database import Base
class TCGPlayerCategory(Base):
__tablename__ = "tcgplayer_categories"
id = Column(Integer, primary_key=True, index=True)
category_id = Column(Integer, unique=True, index=True)
name = Column(String, nullable=False)
display_name = Column(String)
seo_category_name = Column(String)
category_description = Column(String)
category_page_title = Column(String)
sealed_label = Column(String)
non_sealed_label = Column(String)
condition_guide_url = Column(String)
is_scannable = Column(Boolean, default=False)
popularity = Column(Integer, default=0)
is_direct = Column(Boolean, default=False)
modified_on = Column(DateTime)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())

View File

@ -1,17 +0,0 @@
from sqlalchemy import Column, Integer, String, Boolean, DateTime
from sqlalchemy.sql import func
from app.db.database import Base
class TCGPlayerGroup(Base):
__tablename__ = "tcgplayer_groups"
id = Column(Integer, primary_key=True, index=True)
group_id = Column(Integer, unique=True, index=True)
name = Column(String, nullable=False)
abbreviation = Column(String)
is_supplemental = Column(Boolean, default=False)
published_on = Column(DateTime)
modified_on = Column(DateTime)
category_id = Column(Integer)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())

View File

@ -1,12 +1,13 @@
from sqlalchemy import Column, Integer, String, Float, DateTime
from sqlalchemy import Column, Integer, String, Float, DateTime, ForeignKey
from sqlalchemy.sql import func
from app.db.database import Base
from sqlalchemy.orm import relationship
class TCGPlayerInventory(Base):
__tablename__ = "tcgplayer_inventory"
id = Column(Integer, primary_key=True, index=True)
tcgplayer_id = Column(String, unique=True, index=True)
tcgplayer_sku_id = Column(Integer, unique=True, index=True)
product_line = Column(String)
set_name = Column(String)
product_name = Column(String)
@ -22,6 +23,39 @@ class TCGPlayerInventory(Base):
add_to_quantity = Column(Integer)
tcg_marketplace_price = Column(Float)
photo_url = Column(String)
created_at = Column(DateTime(timezone=True), server_default=func.current_timestamp())
updated_at = Column(DateTime(timezone=True), onupdate=func.current_timestamp())
deleted_at = Column(DateTime(timezone=True), nullable=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
# relationships
unmanaged_inventory = relationship("UnmanagedTCGPlayerInventory", back_populates="tcgplayer_inventory")
sku = relationship("MTGJSONSKU", primaryjoin="foreign(MTGJSONSKU.tcgplayer_sku_id) == TCGPlayerInventory.tcgplayer_sku_id", viewonly=True)
class UnmanagedTCGPlayerInventory(Base):
__tablename__ = "unmanaged_tcgplayer_inventory"
id = Column(Integer, primary_key=True, index=True)
tcgplayer_inventory_id = Column(Integer, ForeignKey("tcgplayer_inventory.id"), unique=True, index=True)
tcgplayer_sku_id = Column(Integer, unique=True, index=True)
product_line = Column(String)
set_name = Column(String)
product_name = Column(String)
title = Column(String)
number = Column(String)
rarity = Column(String)
condition = Column(String)
tcg_market_price = Column(Float)
tcg_direct_low = Column(Float)
tcg_low_price_with_shipping = Column(Float)
tcg_low_price = Column(Float)
total_quantity = Column(Integer)
add_to_quantity = Column(Integer)
tcg_marketplace_price = Column(Float)
photo_url = Column(String)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
# relationships
tcgplayer_inventory = relationship("TCGPlayerInventory", back_populates="unmanaged_inventory")
sku = relationship("MTGJSONSKU", primaryjoin="foreign(MTGJSONSKU.tcgplayer_sku_id) == UnmanagedTCGPlayerInventory.tcgplayer_sku_id", viewonly=True)

View File

@ -1,13 +1,14 @@
from sqlalchemy import Column, Integer, String, Float, DateTime, JSON
from datetime import datetime, UTC
from sqlalchemy import Column, Integer, String, Float, DateTime, JSON, ForeignKey
from sqlalchemy.sql import func
from app.db.database import Base
from sqlalchemy.orm import relationship
class TCGPlayerOrder(Base):
__tablename__ = "tcgplayer_orders"
id = Column(Integer, primary_key=True, index=True)
order_number = Column(String, index=True)
order_created_at = Column(DateTime)
order_created_at = Column(DateTime(timezone=True))
status = Column(String)
channel = Column(String)
fulfillment = Column(String)
@ -16,7 +17,7 @@ class TCGPlayerOrder(Base):
payment_type = Column(String)
pickup_status = Column(String)
shipping_type = Column(String)
estimated_delivery_date = Column(DateTime)
estimated_delivery_date = Column(DateTime(timezone=True))
recipient_name = Column(String)
address_line_1 = Column(String)
address_line_2 = Column(String)
@ -25,8 +26,8 @@ class TCGPlayerOrder(Base):
zip_code = Column(String)
country = Column(String)
tracking_numbers = Column(JSON)
created_at = Column(DateTime, default=lambda: datetime.now(UTC))
updated_at = Column(DateTime, default=lambda: datetime.now(UTC), onupdate=lambda: datetime.now(UTC))
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
class TCGPlayerOrderTransaction(Base):
@ -41,8 +42,8 @@ class TCGPlayerOrderTransaction(Base):
net_amount = Column(Float)
direct_fee_amount = Column(Float)
taxes = Column(JSON)
created_at = Column(DateTime, default=lambda: datetime.now(UTC))
updated_at = Column(DateTime, default=lambda: datetime.now(UTC), onupdate=lambda: datetime.now(UTC))
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
class TCGPlayerOrderProduct(Base):
@ -55,17 +56,18 @@ class TCGPlayerOrderProduct(Base):
extended_price = Column(Float)
quantity = Column(Integer)
url = Column(String)
product_id = Column(String)
sku_id = Column(String)
created_at = Column(DateTime, default=lambda: datetime.now(UTC))
updated_at = Column(DateTime, default=lambda: datetime.now(UTC), onupdate=lambda: datetime.now(UTC))
product_id = Column(Integer)
sku_id = Column(Integer)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
class TCGPlayerOrderRefund(Base):
__tablename__ = "tcgplayer_order_refunds"
id = Column(Integer, primary_key=True, index=True)
order_number = Column(String, index=True)
refund_created_at = Column(DateTime)
refund_created_at = Column(DateTime(timezone=True))
type = Column(String)
amount = Column(Float)
type = Column(String)
@ -73,5 +75,5 @@ class TCGPlayerOrderRefund(Base):
origin = Column(String)
shipping_amount = Column(Float)
products = Column(JSON)
created_at = Column(DateTime, default=lambda: datetime.now(UTC))
updated_at = Column(DateTime, default=lambda: datetime.now(UTC), onupdate=lambda: datetime.now(UTC))
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())

View File

@ -1,19 +0,0 @@
from sqlalchemy import Column, Integer, Float, DateTime, String
from sqlalchemy.sql import func
from app.db.database import Base
class TCGPlayerPriceHistory(Base):
__tablename__ = "tcgplayer_price_history"
id = Column(Integer, primary_key=True, index=True)
product_id = Column(Integer, index=True)
date = Column(DateTime, index=True)
low_price = Column(Float)
mid_price = Column(Float)
high_price = Column(Float)
market_price = Column(Float)
direct_low_price = Column(Float)
sub_type_name = Column(String)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())

View File

@ -1,33 +0,0 @@
from sqlalchemy import Column, Integer, String, Float, DateTime
from sqlalchemy.sql import func
from app.db.database import Base
class TCGPlayerProduct(Base):
__tablename__ = "tcgplayer_products"
id = Column(Integer, primary_key=True, index=True)
product_id = Column(Integer, index=True)
name = Column(String, nullable=False)
clean_name = Column(String)
image_url = Column(String)
category_id = Column(Integer)
group_id = Column(Integer)
url = Column(String)
modified_on = Column(DateTime)
image_count = Column(Integer)
ext_rarity = Column(String)
ext_subtype = Column(String)
ext_oracle_text = Column(String)
ext_number = Column(String)
low_price = Column(Float)
mid_price = Column(Float)
high_price = Column(Float)
market_price = Column(Float)
direct_low_price = Column(Float)
sub_type_name = Column(String)
ext_power = Column(String)
ext_toughness = Column(String)
ext_flavor_text = Column(String)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())

View File

@ -0,0 +1,295 @@
from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Table, Float, Boolean, Index, text, DDL, event, UniqueConstraint, ForeignKeyConstraint
from sqlalchemy.sql import func
from app.db.database import Base
from sqlalchemy.orm import relationship
from sqlalchemy.orm import Session
from sqlalchemy import and_
# =============================================================================
# Core Models
# =============================================================================
class MTGJSONSKU(Base):
__tablename__ = "mtgjson_skus"
id = Column(Integer, primary_key=True, index=True)
mtgjson_uuid = Column(String, ForeignKey("mtgjson_cards.mtgjson_uuid"), index=True)
tcgplayer_sku_id = Column(Integer, index=True, unique=True)
tcgplayer_product_id = Column(Integer, nullable=False)
normalized_printing = Column(String, nullable=False)
condition = Column(String)
finish = Column(String, nullable=True)
language = Column(String)
printing = Column(String)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
__table_args__ = (
ForeignKeyConstraint(
["tcgplayer_product_id", "normalized_printing"],
["tcgplayer_products.tcgplayer_product_id", "tcgplayer_products.normalized_sub_type_name"],
name="fk_sku_to_product_composite"
),
Index('idx_sku_product_printing', 'tcgplayer_product_id', 'normalized_printing'),
)
product = relationship("TCGPlayerProduct", back_populates="skus")
physical_items = relationship(
"PhysicalItem",
back_populates="sku",
primaryjoin="PhysicalItem.tcgplayer_sku_id == MTGJSONSKU.tcgplayer_sku_id"
)
card = relationship("MTGJSONCard", back_populates="skus")
class MTGJSONCard(Base):
"""Represents a Magic: The Gathering card."""
__tablename__ = "mtgjson_cards"
id = Column(Integer, primary_key=True, index=True)
mtgjson_uuid = Column(String, ForeignKey("mtgjson_cards.mtgjson_uuid"), unique=True, index=True)
name = Column(String)
set_code = Column(String)
abu_id = Column(String, nullable=True)
card_kingdom_etched_id = Column(String, nullable=True)
card_kingdom_foil_id = Column(String, nullable=True)
card_kingdom_id = Column(String, nullable=True)
cardsphere_id = Column(String, nullable=True)
cardsphere_foil_id = Column(String, nullable=True)
cardtrader_id = Column(String, nullable=True)
csi_id = Column(String, nullable=True)
mcm_id = Column(String, nullable=True)
mcm_meta_id = Column(String, nullable=True)
miniaturemarket_id = Column(String, nullable=True)
mtg_arena_id = Column(String, nullable=True)
mtgjson_foil_version_id = Column(String, nullable=True)
mtgjson_non_foil_version_id = Column(String, nullable=True)
mtgjson_v4_id = Column(String, nullable=True)
mtgo_foil_id = Column(String, nullable=True)
mtgo_id = Column(String, nullable=True)
multiverse_id = Column(String, nullable=True)
scg_id = Column(String, nullable=True)
scryfall_id = Column(String, nullable=True)
scryfall_card_back_id = Column(String, nullable=True)
scryfall_oracle_id = Column(String, nullable=True)
scryfall_illustration_id = Column(String, nullable=True)
tcgplayer_product_id = Column(Integer, nullable=True)
tcgplayer_etched_product_id = Column(Integer, nullable=True)
tnt_id = Column(String, nullable=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
__table_args__ = (
UniqueConstraint("mtgjson_uuid", name="uq_card_mtgjson_uuid"),
)
skus = relationship("MTGJSONSKU", back_populates="card")
class TCGPlayerProduct(Base):
"""Represents a higher-level TCGPlayer product concept."""
__tablename__ = "tcgplayer_products"
id = Column(Integer, primary_key=True, index=True)
tcgplayer_product_id = Column(Integer, nullable=False)
normalized_sub_type_name = Column(String, nullable=False) # normalized for FK
sub_type_name = Column(String) # original unnormalized field
name = Column(String, nullable=False)
clean_name = Column(String)
image_url = Column(String)
category_id = Column(Integer, ForeignKey("tcgplayer_categories.category_id"))
group_id = Column(Integer, ForeignKey("tcgplayer_groups.group_id"))
url = Column(String)
modified_on = Column(DateTime)
image_count = Column(Integer)
ext_rarity = Column(String)
ext_subtype = Column(String)
ext_oracle_text = Column(String)
ext_number = Column(String)
low_price = Column(Float)
mid_price = Column(Float)
high_price = Column(Float)
market_price = Column(Float)
direct_low_price = Column(Float)
ext_power = Column(String)
ext_toughness = Column(String)
ext_flavor_text = Column(String)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
# Enforce uniqueness for composite key
__table_args__ = (
UniqueConstraint("tcgplayer_product_id", "normalized_sub_type_name", name="uq_product_subtype"),
Index('idx_product_subtype', 'tcgplayer_product_id', 'normalized_sub_type_name'),
)
# Backref to SKUs that link via composite FK
skus = relationship("MTGJSONSKU", back_populates="product")
physical_items_direct = relationship("PhysicalItem",
back_populates="product_direct",
primaryjoin="foreign(TCGPlayerProduct.tcgplayer_product_id) == PhysicalItem.tcgplayer_product_id",
viewonly=True,
uselist=False)
category = relationship("TCGPlayerCategory", back_populates="products")
group = relationship("TCGPlayerGroup", back_populates="products")
price_history = relationship("TCGPlayerPriceHistory",
back_populates="product",
primaryjoin="and_(foreign(TCGPlayerProduct.tcgplayer_product_id) == TCGPlayerPriceHistory.product_id, "
"foreign(TCGPlayerProduct.sub_type_name) == TCGPlayerPriceHistory.sub_type_name)")
most_recent_tcgplayer_price = relationship("MostRecentTCGPlayerPrice",
back_populates="product",
primaryjoin="and_(foreign(TCGPlayerProduct.tcgplayer_product_id) == MostRecentTCGPlayerPrice.product_id, "
"foreign(TCGPlayerProduct.sub_type_name) == MostRecentTCGPlayerPrice.sub_type_name)")
sealed_expected_value = relationship("SealedExpectedValue",
primaryjoin="and_(TCGPlayerProduct.tcgplayer_product_id == foreign(SealedExpectedValue.tcgplayer_product_id), "
"foreign(SealedExpectedValue.deleted_at) == None)",
viewonly=True,
uselist=False)
# =============================================================================
# Supporting Models
# =============================================================================
class TCGPlayerCategory(Base):
"""Represents a TCGPlayer product category."""
__tablename__ = "tcgplayer_categories"
id = Column(Integer, primary_key=True, index=True)
category_id = Column(Integer, unique=True, index=True)
name = Column(String, nullable=False)
display_name = Column(String)
seo_category_name = Column(String)
category_description = Column(String)
category_page_title = Column(String)
sealed_label = Column(String)
non_sealed_label = Column(String)
condition_guide_url = Column(String)
is_scannable = Column(Boolean, default=False)
popularity = Column(Integer, default=0)
is_direct = Column(Boolean, default=False)
modified_on = Column(DateTime)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
products = relationship("TCGPlayerProduct", back_populates="category")
class TCGPlayerGroup(Base):
"""Represents a TCGPlayer product group."""
__tablename__ = "tcgplayer_groups"
id = Column(Integer, primary_key=True, index=True)
group_id = Column(Integer, unique=True, index=True)
name = Column(String, nullable=False)
abbreviation = Column(String)
is_supplemental = Column(Boolean, default=False)
published_on = Column(DateTime)
modified_on = Column(DateTime)
category_id = Column(Integer, ForeignKey("tcgplayer_categories.category_id"))
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
products = relationship("TCGPlayerProduct", back_populates="group")
class TCGPlayerPriceHistory(Base):
"""Represents the price history for a product variant (foil/non-foil).
Each product has exactly two price history records - one for foil and one for non-foil.
The relationship to TCGPlayerProduct is effectively 1:1 when considering both product_id
and sub_type_name.
"""
__tablename__ = "tcgplayer_price_history"
id = Column(Integer, primary_key=True, index=True)
product_id = Column(Integer, nullable=False, index=True)
sub_type_name = Column(String, index=True) # This indicates foil/non-foil
date = Column(DateTime, index=True)
low_price = Column(Float)
mid_price = Column(Float)
high_price = Column(Float)
market_price = Column(Float)
direct_low_price = Column(Float)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
# Add a unique constraint on (product_id, sub_type_name, date) to prevent duplicate entries
__table_args__ = (
Index('idx_price_history_product_subtype_date', 'product_id', 'sub_type_name', 'date'),
)
product = relationship("TCGPlayerProduct",
back_populates="price_history",
primaryjoin="and_(TCGPlayerPriceHistory.product_id == foreign(TCGPlayerProduct.tcgplayer_product_id), "
"TCGPlayerPriceHistory.sub_type_name == foreign(TCGPlayerProduct.sub_type_name))")
class MostRecentTCGPlayerPrice(Base):
"""Represents the most recent price for a product.
THIS ISNT A MATERIALIZED VIEW ANYMORE FUCK IT
"""
__tablename__ = "most_recent_tcgplayer_price"
id = Column(Integer, primary_key=True)
product_id = Column(Integer, nullable=False, index=True)
sub_type_name = Column(String, index=True) # This indicates foil/non-foil
date = Column(DateTime, nullable=False)
low_price = Column(Float)
mid_price = Column(Float)
high_price = Column(Float)
market_price = Column(Float)
direct_low_price = Column(Float)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
__table_args__ = (
Index('idx_most_recent_price_product_subtype', 'product_id', 'sub_type_name', unique=True),
)
product = relationship("TCGPlayerProduct",
back_populates="most_recent_tcgplayer_price",
primaryjoin="and_(MostRecentTCGPlayerPrice.product_id == foreign(TCGPlayerProduct.tcgplayer_product_id), "
"MostRecentTCGPlayerPrice.sub_type_name == foreign(TCGPlayerProduct.sub_type_name))")
@classmethod
def update_most_recent_prices(cls, db: Session) -> None:
"""Update the most recent prices from the price history table."""
# Delete all existing records
db.query(cls).delete()
# Get the most recent price for each product and sub_type_name
subquery = db.query(
TCGPlayerPriceHistory.product_id,
TCGPlayerPriceHistory.sub_type_name,
func.max(TCGPlayerPriceHistory.date).label('max_date')
).group_by(
TCGPlayerPriceHistory.product_id,
TCGPlayerPriceHistory.sub_type_name
).subquery()
# Join with price history to get the full records
latest_prices = db.query(TCGPlayerPriceHistory).join(
subquery,
and_(
TCGPlayerPriceHistory.product_id == subquery.c.product_id,
TCGPlayerPriceHistory.sub_type_name == subquery.c.sub_type_name,
TCGPlayerPriceHistory.date == subquery.c.max_date
)
).all()
# Create new MostRecentTCGPlayerPrice records
for price in latest_prices:
most_recent = cls(
product_id=price.product_id,
sub_type_name=price.sub_type_name,
date=price.date,
low_price=price.low_price,
mid_price=price.mid_price,
high_price=price.high_price,
market_price=price.market_price,
direct_low_price=price.direct_low_price
)
db.add(most_recent)
db.commit()

View File

@ -0,0 +1,488 @@
from fastapi import APIRouter, Depends, HTTPException
from datetime import datetime
from sqlalchemy.orm import Session
from sqlalchemy import and_, func
from app.db.database import get_db
from app.services.service_manager import ServiceManager
from app.contexts.inventory_item import InventoryItemContextFactory
from app.schemas.transaction import PurchaseTransactionCreate, SaleTransactionCreate, SealedExpectedValueCreate, GetAllTransactionsResponse, TransactionResponse, TransactionItemResponse, InventoryItemResponse, TCGPlayerProductResponse, OpenEventResponse, OpenEventCreate, OpenEventResultingItemsResponse, OpenEventsForInventoryItemResponse
from app.models.inventory_management import Transaction
from app.models.tcgplayer_products import TCGPlayerProduct
from typing import List
from fastapi.responses import StreamingResponse
router = APIRouter(prefix="/inventory")
service_manager = ServiceManager()
# Sealed Routes
@router.get("/sealed/boxes")
async def get_sealed_boxes(
skip: int = 0,
limit: int = 100,
db: Session = Depends(get_db)
):
sealed_box_service = service_manager.get_service("sealed_box")
raw_boxes = sealed_box_service.get_all(db, skip=skip, limit=limit)
boxes = []
for box in raw_boxes:
inventory_item = InventoryItemContextFactory(db).get_context(box.inventory_item)
boxes.append(inventory_item)
return boxes
@router.get("/sealed/boxes/{sealed_box_id}")
async def get_sealed_box(
sealed_box_id: int,
db: Session = Depends(get_db)
):
sealed_box_service = service_manager.get_service("sealed_box")
sealed_box = sealed_box_service.get(db, sealed_box_id)
return InventoryItemContextFactory(db).get_context(sealed_box.inventory_item)
@router.post("/sealed/boxes")
async def create_sealed_box(
product_id: int,
cost_basis: float,
case_id: int = None,
db: Session = Depends(get_db)
):
try:
sealed_box_service = service_manager.get_service("sealed_box")
sealed_box = await sealed_box_service.create_sealed_box(db, product_id, cost_basis, case_id)
return InventoryItemContextFactory(db).get_context(sealed_box.inventory_item)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/sealed/boxes/{sealed_box_id}/open")
async def open_sealed_box(
sealed_box_id: int,
manabox_file_upload_ids: List[int],
db: Session = Depends(get_db)
):
try:
inventory_service = service_manager.get_service("inventory")
sealed_box_service = service_manager.get_service("sealed_box")
file_service = service_manager.get_service("file")
sealed_box = sealed_box_service.get(db, sealed_box_id)
manabox_file_uploads = []
for manabox_file_upload_id in manabox_file_upload_ids:
manabox_file_upload = file_service.get_file(db, manabox_file_upload_id)
manabox_file_uploads.append(manabox_file_upload)
success = await inventory_service.process_manabox_import_staging(db, manabox_file_uploads, sealed_box)
if not success:
raise HTTPException(status_code=400, detail="Failed to process Manabox import staging")
return {"message": "Manabox import staging processed successfully"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/sealed/cases")
async def get_sealed_cases(
skip: int = 0,
limit: int = 100,
db: Session = Depends(get_db)
):
sealed_case_service = service_manager.get_service("sealed_case")
raw_cases = sealed_case_service.get_all(db, skip=skip, limit=limit)
cases = []
for case in raw_cases:
inventory_item = InventoryItemContextFactory(db).get_context(case.inventory_item)
cases.append(inventory_item)
return cases
@router.get("/sealed/cases/{sealed_case_id}")
async def get_sealed_case(
sealed_case_id: int,
db: Session = Depends(get_db)
):
sealed_case_service = service_manager.get_service("sealed_case")
sealed_case = sealed_case_service.get(db, sealed_case_id)
return InventoryItemContextFactory(db).get_context(sealed_case.inventory_item)
@router.post("/sealed/cases")
async def create_sealed_case(
product_id: int,
cost_basis: float,
num_boxes: int,
db: Session = Depends(get_db)
):
try:
sealed_case_service = service_manager.get_service("sealed_case")
sealed_case = await sealed_case_service.create_sealed_case(db, product_id, cost_basis, num_boxes)
return InventoryItemContextFactory(db).get_context(sealed_case.inventory_item)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.post("/sealed/cases/{sealed_case_id}/open")
async def open_sealed_case(
sealed_case_id: int,
db: Session = Depends(get_db)
):
try:
sealed_case_service = service_manager.get_service("sealed_case")
sealed_case = sealed_case_service.get(db, sealed_case_id)
await sealed_case_service.open_sealed_case(db, sealed_case)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
# Transaction Routes
@router.post("/transactions/purchase")
async def create_purchase_transaction(
transaction_data: PurchaseTransactionCreate,
db: Session = Depends(get_db)
):
inventory_service = service_manager.get_service("inventory")
transaction = await inventory_service.create_purchase_transaction(db, transaction_data)
return transaction
@router.post("/transactions/sale")
async def create_sale_transaction(
transaction_data: SaleTransactionCreate,
db: Session = Depends(get_db)
):
inventory_service = service_manager.get_service("inventory")
transaction = await inventory_service.create_sale_transaction(db, transaction_data)
return transaction
@router.post("/customers")
async def create_customer(
customer_name: str,
db: Session = Depends(get_db)
):
inventory_service = service_manager.get_service("inventory")
customer = await inventory_service.create_customer(db, customer_name)
return customer
@router.post("/vendors")
async def create_vendor(
vendor_name: str,
db: Session = Depends(get_db)
):
inventory_service = service_manager.get_service("inventory")
vendor = await inventory_service.create_vendor(db, vendor_name)
return vendor
@router.get("/vendors")
async def get_vendors(
db: Session = Depends(get_db)
):
inventory_service = service_manager.get_service("inventory")
vendors = await inventory_service.get_vendors(db)
return vendors
@router.post("/marketplaces")
async def create_marketplace(
marketplace_name: str,
db: Session = Depends(get_db)
):
inventory_service = service_manager.get_service("inventory")
marketplace = await inventory_service.create_marketplace(db, marketplace_name)
return marketplace
@router.get("/marketplaces")
async def get_marketplaces(
db: Session = Depends(get_db)
):
inventory_service = service_manager.get_service("inventory")
marketplaces = await inventory_service.get_marketplaces(db)
return marketplaces
@router.get("/products/search")
async def get_products(q: str, db: Session = Depends(get_db)):
query = ' & '.join(q.lower().split()) # This ensures all terms must match
products = db.query(TCGPlayerProduct).filter(
func.to_tsvector('english', TCGPlayerProduct.name)
.op('@@')(func.to_tsquery('english', query))
).all()
return products
@router.get("/products/{product_id}/expected-value")
async def get_expected_value(
product_id: int,
db: Session = Depends(get_db)
):
inventory_service = service_manager.get_service("inventory")
expected_value = await inventory_service.get_expected_value(db, product_id)
return expected_value
@router.post("/products/expected-value")
async def create_expected_value(
expected_value_data: SealedExpectedValueCreate,
db: Session = Depends(get_db)
):
inventory_service = service_manager.get_service("inventory")
expected_value = await inventory_service.create_expected_value(db, expected_value_data)
return expected_value
@router.post("/transactions/purchase")
async def create_purchase_transaction(
transaction_data: PurchaseTransactionCreate,
db: Session = Depends(get_db)
):
inventory_service = service_manager.get_service("inventory")
transaction = await inventory_service.create_purchase_transaction(db, transaction_data)
return transaction
@router.get("/transactions")
async def get_transactions(
db: Session = Depends(get_db),
skip: int = 0,
limit: int = 100
):
inventory_service = service_manager.get_service("inventory")
total = db.query(func.count(Transaction.id)).filter(Transaction.deleted_at == None).scalar()
transactions = await inventory_service.get_transactions(db, skip, limit)
return GetAllTransactionsResponse(
total=total,
transactions=[TransactionResponse(
id=transaction.id,
vendor_id=transaction.vendor_id,
customer_id=transaction.customer_id,
marketplace_id=transaction.marketplace_id,
transaction_type=transaction.transaction_type,
transaction_date=transaction.transaction_date,
transaction_total_amount=transaction.transaction_total_amount,
transaction_notes=transaction.transaction_notes,
created_at=transaction.created_at,
updated_at=transaction.updated_at,
transaction_items=[TransactionItemResponse(
id=transaction_item.id,
transaction_id=transaction_item.transaction_id,
inventory_item_id=transaction_item.inventory_item_id,
unit_price=transaction_item.unit_price,
created_at=transaction_item.created_at,
updated_at=transaction_item.updated_at,
deleted_at=transaction_item.deleted_at
) for transaction_item in transaction.transaction_items]
) for transaction in transactions]
)
@router.get("/transactions/{transaction_id}")
async def get_transaction(
transaction_id: int,
db: Session = Depends(get_db)
):
inventory_service = service_manager.get_service("inventory")
transaction = await inventory_service.get_transaction(db, transaction_id)
if not transaction:
raise HTTPException(status_code=404, detail="Transaction not found")
return TransactionResponse(
id=transaction.id,
vendor_id=transaction.vendor_id,
customer_id=transaction.customer_id,
marketplace_id=transaction.marketplace_id,
transaction_type=transaction.transaction_type,
transaction_date=transaction.transaction_date,
transaction_total_amount=transaction.transaction_total_amount,
transaction_notes=transaction.transaction_notes,
created_at=transaction.created_at,
updated_at=transaction.updated_at,
transaction_items=[TransactionItemResponse(
id=transaction_item.id,
transaction_id=transaction_item.transaction_id,
inventory_item_id=transaction_item.inventory_item_id,
unit_price=transaction_item.unit_price,
created_at=transaction_item.created_at,
updated_at=transaction_item.updated_at,
deleted_at=transaction_item.deleted_at
) for transaction_item in transaction.transaction_items]
)
@router.get("/items/{inventory_item_id}")
async def get_inventory_item(
inventory_item_id: int,
db: Session = Depends(get_db)
):
inventory_service = service_manager.get_service("inventory")
inventory_item = await inventory_service.get_inventory_item(db, inventory_item_id)
marketplace_listing_service = service_manager.get_service("marketplace_listing")
marketplace = await inventory_service.create_marketplace(db, "Tcgplayer")
marketplace_listing = await marketplace_listing_service.get_marketplace_listing(db, inventory_item, marketplace)
if marketplace_listing is None:
listed_price = None
recommended_price = None
marketplace_listing_id = None
else:
if marketplace_listing.listed_price is not None:
listed_price = marketplace_listing.listed_price.price if marketplace_listing.listed_price.price is not None else None
else:
listed_price = None
if marketplace_listing.recommended_price is not None:
recommended_price = marketplace_listing.recommended_price.price if marketplace_listing.recommended_price.price is not None else None
else:
recommended_price = None
marketplace_listing_id = marketplace_listing.id
return InventoryItemResponse(
id=inventory_item.id,
physical_item_id=inventory_item.physical_item_id,
cost_basis=inventory_item.cost_basis,
parent_id=inventory_item.parent_id,
created_at=inventory_item.created_at,
updated_at=inventory_item.updated_at,
item_type=inventory_item.physical_item.item_type,
listed_price=listed_price,
recommended_price=recommended_price,
marketplace_listing_id=marketplace_listing_id,
product=TCGPlayerProductResponse(
id=inventory_item.physical_item.product_direct.id,
tcgplayer_product_id=inventory_item.physical_item.product_direct.tcgplayer_product_id,
name=inventory_item.physical_item.product_direct.name,
image_url=inventory_item.physical_item.product_direct.image_url,
category_id=inventory_item.physical_item.product_direct.category_id,
group_id=inventory_item.physical_item.product_direct.group_id,
url=inventory_item.physical_item.product_direct.url,
market_price=inventory_item.physical_item.product_direct.most_recent_tcgplayer_price.market_price,
category_name=inventory_item.physical_item.product_direct.category.name,
group_name=inventory_item.physical_item.product_direct.group.name
)
)
@router.post("/items/{inventory_item_id}/open")
async def open_box_or_case(
open_event_data: OpenEventCreate,
db: Session = Depends(get_db)
):
inventory_service = service_manager.get_service("inventory")
inventory_item = await inventory_service.get_inventory_item(db, open_event_data.inventory_item_id)
file_service = service_manager.get_service("file")
files = [await file_service.get_file(db, file_id) for file_id in open_event_data.manabox_file_upload_ids]
if inventory_item.physical_item.item_type == "box":
box_service = service_manager.get_service("box")
open_event = await box_service.open_box(db, inventory_item.physical_item, files)
return OpenEventResponse(
id=open_event.id,
source_item_id=open_event.source_item_id,
created_at=open_event.created_at,
updated_at=open_event.updated_at
)
elif inventory_item.physical_item.item_type == "case":
case_service = service_manager.get_service("case")
open_event = await case_service.open_case(db, inventory_item.physical_item, files)
return OpenEventResponse(
id=open_event.id,
source_item_id=open_event.source_item_id,
created_at=open_event.created_at,
updated_at=open_event.updated_at
)
else:
raise HTTPException(status_code=400, detail="Invalid item type")
@router.get("/items/{inventory_item_id}/open-events")
async def get_open_events(
inventory_item_id: int,
db: Session = Depends(get_db)
):
inventory_service = service_manager.get_service("inventory")
inventory_item = await inventory_service.get_inventory_item(db, inventory_item_id)
# Don't return open events for cards
if inventory_item.physical_item.item_type == 'card':
return OpenEventsForInventoryItemResponse(open_events=[])
open_events = await inventory_service.get_open_events_for_inventory_item(db, inventory_item)
return OpenEventsForInventoryItemResponse(
open_events=[OpenEventResponse(
id=open_event.id,
source_item_id=open_event.source_item_id,
created_at=open_event.created_at,
updated_at=open_event.updated_at
) for open_event in open_events]
)
@router.get("/items/{inventory_item_id}/open-events/{open_event_id}/resulting-items", response_model=List[InventoryItemResponse])
async def get_resulting_items(
inventory_item_id: int,
open_event_id: int,
db: Session = Depends(get_db)
):
inventory_service = service_manager.get_service("inventory")
inventory_item = await inventory_service.get_inventory_item(db, inventory_item_id)
open_event = await inventory_service.get_open_event(db, inventory_item, open_event_id)
resulting_items = await inventory_service.get_resulting_items_for_open_event(db, open_event)
marketplace_listing_service = service_manager.get_service("marketplace_listing")
marketplace = await inventory_service.create_marketplace(db, "Tcgplayer")
marketplace_listing = await marketplace_listing_service.get_marketplace_listing(db, inventory_item, marketplace)
if marketplace_listing is None:
listed_price = None
recommended_price = None
marketplace_listing_id = None
else:
if marketplace_listing.listed_price is not None:
listed_price = marketplace_listing.listed_price.price if marketplace_listing.listed_price.price is not None else None
else:
listed_price = None
if marketplace_listing.recommended_price is not None:
recommended_price = marketplace_listing.recommended_price.price if marketplace_listing.recommended_price.price is not None else None
else:
recommended_price = None
marketplace_listing_id = marketplace_listing.id
return [InventoryItemResponse(
id=resulting_item.id,
physical_item_id=resulting_item.physical_item_id,
cost_basis=resulting_item.cost_basis,
parent_id=resulting_item.parent_id,
product=TCGPlayerProductResponse(
id=resulting_item.physical_item.product_direct.id,
tcgplayer_product_id=resulting_item.physical_item.product_direct.tcgplayer_product_id,
name=resulting_item.physical_item.product_direct.name,
image_url=resulting_item.physical_item.product_direct.image_url,
category_id=resulting_item.physical_item.product_direct.category_id,
group_id=resulting_item.physical_item.product_direct.group_id,
url=resulting_item.physical_item.product_direct.url,
market_price=resulting_item.physical_item.product_direct.most_recent_tcgplayer_price.market_price,
category_name=resulting_item.physical_item.product_direct.category.name,
group_name=resulting_item.physical_item.product_direct.group.name
),
item_type=resulting_item.physical_item.item_type,
marketplace_listing_id=marketplace_listing_id,
listed_price=listed_price,
recommended_price=recommended_price,
created_at=resulting_item.created_at,
updated_at=resulting_item.updated_at) for resulting_item in resulting_items]
@router.post("/items/{inventory_item_id}/open-events/{open_event_id}/create-listings")
async def create_marketplace_listings(
inventory_item_id: int,
open_event_id: int,
db: Session = Depends(get_db)
):
inventory_service = service_manager.get_service("inventory")
inventory_item = await inventory_service.get_inventory_item(db, inventory_item_id)
open_event = await inventory_service.get_open_event(db, inventory_item, open_event_id)
resulting_items = await inventory_service.get_resulting_items_for_open_event(db, open_event)
marketplace = await inventory_service.create_marketplace(db, "Tcgplayer")
marketplace_listing_service = service_manager.get_service("marketplace_listing")
for resulting_item in resulting_items:
await marketplace_listing_service.create_marketplace_listing(db, resulting_item, marketplace)
return {"message": f"{len(resulting_items)} marketplace listings created successfully"}
@router.post("/items/{inventory_item_id}/open-events/{open_event_id}/confirm-listings")
async def confirm_listings(
inventory_item_id: int,
open_event_id: int,
db: Session = Depends(get_db)
):
inventory_service = service_manager.get_service("inventory")
inventory_item = await inventory_service.get_inventory_item(db, inventory_item_id)
open_event = await inventory_service.get_open_event(db, inventory_item, open_event_id)
marketplace_listing_service = service_manager.get_service("marketplace_listing")
marketplace = await inventory_service.create_marketplace(db, "Tcgplayer")
try:
csv_string = await marketplace_listing_service.confirm_listings(db, open_event, marketplace)
if not csv_string:
raise ValueError("No CSV data generated")
# Create a streaming response with the CSV data
return StreamingResponse(
iter([csv_string]),
media_type="text/csv",
headers={
"Content-Disposition": f"attachment; filename=tcgplayer_add_file_{open_event.id}_{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.csv"
}
)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

View File

@ -0,0 +1,94 @@
from fastapi import APIRouter, Depends, UploadFile, File, HTTPException, Form, BackgroundTasks
from sqlalchemy.orm import Session
from app.db.database import get_db
from app.services.service_manager import ServiceManager
import csv
import io
router = APIRouter(prefix="/manabox")
service_manager = ServiceManager()
REQUIRED_HEADERS = {
'Name', 'Set code', 'Set name', 'Collector number', 'Foil', 'Rarity',
'Quantity', 'ManaBox ID', 'Scryfall ID', 'Purchase price', 'Misprint',
'Altered', 'Condition', 'Language', 'Purchase price currency'
}
def is_valid_csv(file: UploadFile) -> tuple[bool, str]:
# Check if filename ends with .csv
if not file.filename.lower().endswith('.csv'):
return False, "File must have a .csv extension"
try:
# Try to read the content as CSV
content = file.file.read()
# Reset file pointer for later use
file.file.seek(0)
# Try to parse the content as CSV
csv_reader = csv.reader(io.StringIO(content.decode('utf-8')))
# Get headers from first row
headers = set(next(csv_reader))
# Check for missing headers
missing_headers = REQUIRED_HEADERS - headers
if missing_headers:
return False, f"Missing required columns: {', '.join(missing_headers)}"
# Check for extra headers
extra_headers = headers - REQUIRED_HEADERS
if extra_headers:
return False, f"Unexpected columns found: {', '.join(extra_headers)}"
return True, "Valid CSV format"
except (csv.Error, UnicodeDecodeError) as e:
return False, f"Invalid CSV format: {str(e)}"
except StopIteration:
return False, "Empty file"
@router.post("/process-csv")
async def process_manabox_csv(
background_tasks: BackgroundTasks,
file: UploadFile = File(...),
source: str = Form(...),
description: str = Form(...),
db: Session = Depends(get_db)
):
try:
is_valid, error_message = is_valid_csv(file)
if not is_valid:
raise HTTPException(
status_code=400,
detail=error_message
)
content = await file.read()
metadata = {
"source": source,
"description": description
}
manabox_service = service_manager.get_service("manabox")
success = await manabox_service.process_manabox_csv(db, content, metadata, background_tasks)
if not success:
raise HTTPException(status_code=400, detail="Failed to process CSV file")
return {"message": "CSV processed successfully"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/manabox-file-uploads")
async def get_manabox_file_uploads(
skip: int = 0,
limit: int = 100,
db: Session = Depends(get_db)
):
file_service = service_manager.get_service("file")
files = await file_service.list_files(db, skip=skip, limit=limit, file_type="manabox")
return files

View File

@ -1,17 +1,13 @@
from fastapi import APIRouter, HTTPException, Depends, Query, UploadFile, File
from fastapi import APIRouter, HTTPException, Depends, UploadFile, File
from typing import List
from datetime import datetime
from enum import Enum
from app.schemas.tcgplayer import TCGPlayerAPIOrderSummary, TCGPlayerAPIOrder
from app.schemas.generate import GenerateAddressLabelsRequest, GeneratePackingSlipsRequest, GeneratePullSheetsRequest, GenerateResponse, GenerateReturnLabelsRequest
from app.schemas.file import FileUpload
from app.services.service_manager import ServiceManager
from app.services.file_service import FileService
from sqlalchemy.orm import Session
from app.db.database import get_db
import os
import tempfile
import logging
from datetime import datetime
logger = logging.getLogger(__name__)
@ -223,3 +219,25 @@ async def print_pirate_ship_label(
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to print Pirate Ship label: {str(e)}")
# what even is this TODO delete
@router.post("/process-manabox-csv")
async def process_manabox_csv(
file: UploadFile = File(...),
db: Session = Depends(get_db)
) -> GenerateResponse:
try:
# ensure csv
if file.content_type != "text/csv":
raise HTTPException(status_code=400, detail="File must be a CSV")
# read file
content = await file.read()
# save file
file_service = service_manager.get_service('file')
stored_file = await file_service.save_file(db, content, f'manabox_upload_{datetime.now().strftime("%Y%m%d_%H%M%S")}.csv', "manabox_csvs", "csv")
# process csv
manabox_service = service_manager.get_service('manabox')
success = await manabox_service.process_manabox_csv(db, stored_file)
return {"success": success, "message": "Manabox CSV processed successfully"}
except Exception as e:
raise HTTPException(status_code=500, detail=f"Failed to process Manabox CSV: {str(e)}")

View File

@ -5,7 +5,8 @@ from app.models.file import File as FileModel
from app.schemas.file import FileCreate, FileUpdate, FileDelete, FileList, FileInDB
from app.routes.set_label_routes import router as set_label_router
from app.routes.order_routes import router as order_router
from app.routes.manabox_routes import router as manabox_router
from app.routes.inventory_management_routes import router as inventory_management_router
router = APIRouter(prefix="/api")
# Include set label routes
@ -14,6 +15,12 @@ router.include_router(set_label_router)
# Include order routes
router.include_router(order_router)
# Include manabox routes
router.include_router(manabox_router)
# Include inventory management routes
router.include_router(inventory_management_router)
# ============================================================================
# Health Check & Root Endpoints
# ============================================================================

0
app/schemas/inventory.py Normal file
View File

109
app/schemas/transaction.py Normal file
View File

@ -0,0 +1,109 @@
from typing import List, Optional
from pydantic import BaseModel
from datetime import datetime
from app.models.tcgplayer_products import TCGPlayerProduct
class PurchaseItem(BaseModel):
product_id: int
unit_price: float
quantity: int
item_type: str
num_boxes: Optional[int] = None
# TODO: remove is_case and num_boxes, should derive from product_id
class SaleItem(BaseModel):
inventory_item_id: int
unit_price: float
class PurchaseTransactionCreate(BaseModel):
vendor_id: int
transaction_date: datetime
items: List[PurchaseItem]
transaction_notes: Optional[str] = None
class SaleTransactionCreate(BaseModel):
customer_id: int
marketplace_id: Optional[int] = None
transaction_date: datetime
items: List[SaleItem]
transaction_notes: Optional[str] = None
class TransactionItemResponse(BaseModel):
id: int
transaction_id: int
inventory_item_id: int
unit_price: float
created_at: datetime
updated_at: datetime
deleted_at: Optional[datetime] = None
class TransactionResponse(BaseModel):
id: int
vendor_id: Optional[int] = None
customer_id: Optional[int] = None
marketplace_id: Optional[int] = None
transaction_type: str
transaction_date: datetime
transaction_total_amount: float
transaction_notes: Optional[str] = None
created_at: datetime
updated_at: datetime
deleted_at: Optional[datetime] = None
transaction_items: List[TransactionItemResponse]
class GetAllTransactionsResponse(BaseModel):
total: int
transactions: List[TransactionResponse]
class SealedExpectedValueResponse(BaseModel):
id: int
tcgplayer_product_id: int
expected_value: float
class SealedExpectedValueCreate(BaseModel):
tcgplayer_product_id: int
expected_value: float
class TCGPlayerProductResponse(BaseModel):
id: int
tcgplayer_product_id: int
name: str
image_url: str
category_id: int
group_id: int
url: str
market_price: float
category_name: str
group_name: str
class InventoryItemResponse(BaseModel):
id: int
physical_item_id: int
cost_basis: float
item_type: str
listed_price: Optional[float] = None
marketplace_listing_id: Optional[int] = None
recommended_price: Optional[float] = None
parent_id: Optional[int] = None
created_at: datetime
updated_at: datetime
product: Optional[TCGPlayerProductResponse] = None
class OpenEventResponse(BaseModel):
id: int
source_item_id: int
created_at: datetime
updated_at: datetime
class OpenEventCreate(BaseModel):
inventory_item_id: int
manabox_file_upload_ids: List[int]
class OpenEventResultingItemsResponse(BaseModel):
id: int
source_item_id: int
created_at: datetime
updated_at: datetime
resulting_items: List[InventoryItemResponse]
class OpenEventsForInventoryItemResponse(BaseModel):
open_events: List[OpenEventResponse]

View File

@ -14,6 +14,8 @@ from app.services.set_label_service import SetLabelService
from app.services.scheduler.scheduler_service import SchedulerService
from app.services.external_api.tcgplayer.order_management_service import OrderManagementService
from app.services.external_api.tcgplayer.tcgplayer_inventory_service import TCGPlayerInventoryService
from app.services.pricing_service import PricingService
from app.services.inventory_service import MarketplaceListingService
__all__ = [
'BaseService',
@ -31,5 +33,7 @@ __all__ = [
'SetLabelService',
'SchedulerService',
'OrderManagementService',
'TCGPlayerInventoryService'
'TCGPlayerInventoryService',
'PricingService',
'MarketplaceListingService'
]

View File

@ -1,23 +1,27 @@
import os
import json
from datetime import datetime, timedelta
from typing import Optional, List, Dict, Any, Union, Generator, Callable
from datetime import datetime, timezone
from typing import Optional, List, Dict, Any, Union
from sqlalchemy.orm import Session
from app.models.tcgplayer_group import TCGPlayerGroup
from app.models.tcgplayer_product import TCGPlayerProduct
from app.models.tcgplayer_category import TCGPlayerCategory
from app.models.tcgplayer_products import TCGPlayerProduct, TCGPlayerCategory, TCGPlayerGroup
from app.models.inventory_management import SealedExpectedValue
from app.services.base_service import BaseService
from app.schemas.file import FileInDB
from app.db.database import transaction
from app.db.database import transaction as db_transaction
from app.schemas.transaction import PurchaseTransactionCreate, PurchaseItem
from app.contexts.inventory_item import InventoryItemContextFactory
from app.models.tcgplayer_products import MTGJSONSKU, MTGJSONCard
from app.models.tcgplayer_products import TCGPlayerPriceHistory
from app.models.critical_error_log import CriticalErrorLog
import csv
import io
import logging
from app.models.tcgplayer_price_history import TCGPlayerPriceHistory
from sqlalchemy import and_, bindparam, update, insert
import py7zr
import shutil
import py7zr
logger = logging.getLogger(__name__)
class DataInitializationService(BaseService):
def __init__(self):
super().__init__(None)
@ -54,7 +58,8 @@ class DataInitializationService(BaseService):
file_record = await self.file_service.get_file_by_filename(db, filename)
if file_record:
# Check if cache is expired (7 days)
cache_age = datetime.now() - file_record.created_at
# Ensure both datetimes are timezone-aware
cache_age = datetime.now(timezone.utc) - file_record.created_at
if cache_age.days < 7:
with open(file_record.path, 'r') as f:
return json.load(f)
@ -70,7 +75,7 @@ class DataInitializationService(BaseService):
batch_size = 1000 # Process in batches of 1000
total_categories = len(categories)
with transaction(db):
with db_transaction(db):
for i in range(0, total_categories, batch_size):
batch = categories[i:i + batch_size]
for category_data in batch:
@ -150,7 +155,7 @@ class DataInitializationService(BaseService):
batch_size = 1000 # Process in batches of 1000
total_groups = len(groups)
with transaction(db):
with db_transaction(db):
for i in range(0, total_groups, batch_size):
batch = groups[i:i + batch_size]
for group_data in batch:
@ -214,8 +219,6 @@ class DataInitializationService(BaseService):
async def sync_products(self, db: Session, products_data: str):
"""Sync products data to the database using streaming for large datasets"""
import csv
import io
# Parse CSV data
csv_reader = csv.DictReader(io.StringIO(products_data))
@ -223,36 +226,46 @@ class DataInitializationService(BaseService):
batch_size = 1000 # Process in batches of 1000
total_products = len(products_list)
with transaction(db):
with db_transaction(db):
for i in range(0, total_products, batch_size):
batch = products_list[i:i + batch_size]
for product_data in batch:
existing_product = db.query(TCGPlayerProduct).filter(TCGPlayerProduct.product_id == product_data["productId"]).first()
sub_type_name = product_data.get("subTypeName") if product_data.get("subTypeName") else "other"
existing_product = db.query(TCGPlayerProduct).filter(TCGPlayerProduct.tcgplayer_product_id == product_data["productId"]).filter(TCGPlayerProduct.sub_type_name == sub_type_name).first()
if existing_product:
# Update existing product
for key, value in {
"name": product_data["name"],
"clean_name": product_data.get("cleanName"),
"image_url": product_data.get("imageUrl"),
"sub_type_name": product_data.get("subTypeName") if product_data.get("subTypeName") else "other",
"normalized_sub_type_name": product_data.get("subTypeName").lower().replace(" ", "_") if product_data.get("subTypeName") else "other",
"category_id": product_data.get("categoryId"),
"group_id": product_data.get("groupId"),
"url": product_data.get("url"),
"modified_on": datetime.fromisoformat(product_data["modifiedOn"].replace("Z", "+00:00")) if product_data.get("modifiedOn") else None,
"image_count": product_data.get("imageCount", 0),
"ext_rarity": product_data.get("extRarity"),
"ext_subtype": product_data.get("extSubtype"),
"ext_oracle_text": product_data.get("extOracleText"),
"ext_number": product_data.get("extNumber"),
"low_price": float(product_data.get("lowPrice")) if product_data.get("lowPrice") else None,
"mid_price": float(product_data.get("midPrice")) if product_data.get("midPrice") else None,
"high_price": float(product_data.get("highPrice")) if product_data.get("highPrice") else None,
"market_price": float(product_data.get("marketPrice")) if product_data.get("marketPrice") else None,
"direct_low_price": float(product_data.get("directLowPrice")) if product_data.get("directLowPrice") else None,
"sub_type_name": product_data.get("subTypeName")
"ext_flavor_text": product_data.get("extFlavorText"),
"ext_power": product_data.get("extPower"),
"ext_toughness": product_data.get("extToughness"),
"ext_flavor_text": product_data.get("extFlavorText")
}.items():
setattr(existing_product, key, value)
else:
logger.debug(f"Creating new product: {product_data['productId']} product name: {product_data['name']}")
new_product = TCGPlayerProduct(
product_id=product_data["productId"],
tcgplayer_product_id=product_data["productId"],
name=product_data["name"],
normalized_sub_type_name=product_data.get("subTypeName").lower().replace(" ", "_") if product_data.get("subTypeName") else "other",
clean_name=product_data.get("cleanName"),
image_url=product_data.get("imageUrl"),
category_id=product_data.get("categoryId"),
@ -269,7 +282,7 @@ class DataInitializationService(BaseService):
high_price=float(product_data.get("highPrice")) if product_data.get("highPrice") else None,
market_price=float(product_data.get("marketPrice")) if product_data.get("marketPrice") else None,
direct_low_price=float(product_data.get("directLowPrice")) if product_data.get("directLowPrice") else None,
sub_type_name=product_data.get("subTypeName"),
sub_type_name=product_data.get("subTypeName") if product_data.get("subTypeName") else "other",
ext_power=product_data.get("extPower"),
ext_toughness=product_data.get("extToughness"),
ext_flavor_text=product_data.get("extFlavorText")
@ -319,50 +332,81 @@ class DataInitializationService(BaseService):
async def sync_archived_prices(self, db: Session, archived_prices_data: dict, date: datetime):
"""Sync archived prices data to the database using bulk operations.
Note: Historical prices are never updated, only new records are inserted."""
from sqlalchemy import insert
from app.models.tcgplayer_price_history import TCGPlayerPriceHistory
# Prepare data for bulk operations
price_records = []
for price_data in archived_prices_data.get("results", []):
record = {
"product_id": price_data["productId"],
"date": date,
"sub_type_name": price_data["subTypeName"],
"low_price": price_data.get("lowPrice"),
"mid_price": price_data.get("midPrice"),
"high_price": price_data.get("highPrice"),
"market_price": price_data.get("marketPrice"),
"direct_low_price": price_data.get("directLowPrice")
}
price_records.append(record)
if not price_records:
if not archived_prices_data.get("success"):
logger.error("Price data sync failed - success flag is false")
return
# Get existing records in bulk to avoid duplicates
product_ids = [r["product_id"] for r in price_records]
sub_type_names = [r["sub_type_name"] for r in price_records]
# Get existing records in bulk to avoid duplicates using a composite key
existing_records = db.query(TCGPlayerPriceHistory).filter(
TCGPlayerPriceHistory.product_id.in_(product_ids),
TCGPlayerPriceHistory.date == date,
TCGPlayerPriceHistory.sub_type_name.in_(sub_type_names)
TCGPlayerPriceHistory.date == date
).all()
# Filter out existing records
existing_keys = {(r.product_id, r.date, r.sub_type_name) for r in existing_records}
to_insert = [
record for record in price_records
if (record["product_id"], record["date"], record["sub_type_name"]) not in existing_keys
]
# Perform bulk insert for new records only
if to_insert:
stmt = insert(TCGPlayerPriceHistory)
db.execute(stmt, to_insert)
db.commit()
# Prepare batch insert data
price_history_batch = []
# Process price data in batches
for price_data in archived_prices_data.get("results", []):
try:
# Get the subtype name from the price data
sub_type_name = price_data.get("subTypeName", "None")
# First try to find product with the requested subtype
product = db.query(TCGPlayerProduct).filter(
TCGPlayerProduct.tcgplayer_product_id == price_data["productId"],
TCGPlayerProduct.sub_type_name == sub_type_name
).first()
# If not found and subtype isn't "other", try with "other" subtype
if not product and sub_type_name != "other":
product = db.query(TCGPlayerProduct).filter(
TCGPlayerProduct.tcgplayer_product_id == price_data["productId"],
TCGPlayerProduct.sub_type_name == "other"
).first()
if product:
sub_type_name = "other"
#logger.info(f"Found product {price_data['productId']} with 'other' subtype as fallback for {sub_type_name}")
if not product:
logger.warning(f"No product found for {price_data['productId']} with subtype {sub_type_name} or 'other'")
continue
# Skip if record already exists
if (product.tcgplayer_product_id, date, sub_type_name) in existing_keys:
continue
# Validate and convert price data
try:
price_history = TCGPlayerPriceHistory(
product_id=product.tcgplayer_product_id,
sub_type_name=sub_type_name,
date=date,
low_price=float(price_data.get("lowPrice")) if price_data.get("lowPrice") else None,
mid_price=float(price_data.get("midPrice")) if price_data.get("midPrice") else None,
high_price=float(price_data.get("highPrice")) if price_data.get("highPrice") else None,
market_price=float(price_data.get("marketPrice")) if price_data.get("marketPrice") else None,
direct_low_price=float(price_data.get("directLowPrice")) if price_data.get("directLowPrice") else None
)
price_history_batch.append(price_history)
except (ValueError, TypeError) as e:
logger.error(f"Invalid price data for product {price_data['productId']}: {str(e)}")
continue
# Process in batches of 1000
if len(price_history_batch) >= 1000:
with db_transaction(db):
db.bulk_save_objects(price_history_batch)
price_history_batch = []
except Exception as e:
logger.error(f"Error processing price data for product {price_data['productId']}: {str(e)}")
continue
# Process any remaining records
if price_history_batch:
with db_transaction(db):
db.bulk_save_objects(price_history_batch)
async def init_archived_prices(self, db: Session, start_date: datetime, end_date: datetime, use_cache: bool = True, game_ids: List[int] = None) -> bool:
"""Initialize archived prices data"""
@ -462,227 +506,194 @@ class DataInitializationService(BaseService):
identifiers_count = 0
skus_count = 0
# Process identifiers
if use_cache:
cached_file = await self.file_service.get_file_by_filename(db, "mtgjson_identifiers.json")
if cached_file and os.path.exists(cached_file.path):
logger.info("MTGJSON identifiers initialized from cache")
identifiers_count = await self._process_streamed_data(
db,
self._stream_json_file(cached_file.path),
"mtgjson_identifiers.json",
"mtgjson",
self.sync_mtgjson_identifiers
)
else:
logger.info("Downloading MTGJSON identifiers from API")
identifiers_count = await self._process_streamed_data(
db,
await mtgjson_service.get_identifiers(db),
"mtgjson_identifiers.json",
"mtgjson",
self.sync_mtgjson_identifiers
)
else:
logger.info("Downloading MTGJSON identifiers from API")
identifiers_count = await self._process_streamed_data(
db,
await mtgjson_service.get_identifiers(db),
"mtgjson_identifiers.json",
"mtgjson",
self.sync_mtgjson_identifiers
)
# Get identifiers data
identifiers_data = await mtgjson_service.get_identifiers(db, use_cache)
if identifiers_data and "data" in identifiers_data:
identifiers_count = await self.sync_mtgjson_identifiers(db, list(identifiers_data["data"].values()))
# Process SKUs
if use_cache:
cached_file = await self.file_service.get_file_by_filename(db, "mtgjson_skus.json")
if cached_file and os.path.exists(cached_file.path):
logger.info("MTGJSON SKUs initialized from cache")
skus_count = await self._process_streamed_data(
db,
self._stream_json_file(cached_file.path),
"mtgjson_skus.json",
"mtgjson",
self.sync_mtgjson_skus
)
else:
logger.info("Downloading MTGJSON SKUs from API")
skus_count = await self._process_streamed_data(
db,
await mtgjson_service.get_skus(db),
"mtgjson_skus.json",
"mtgjson",
self.sync_mtgjson_skus
)
else:
logger.info("Downloading MTGJSON SKUs from API")
skus_count = await self._process_streamed_data(
db,
await mtgjson_service.get_skus(db),
"mtgjson_skus.json",
"mtgjson",
self.sync_mtgjson_skus
)
# Get SKUs data
skus_data = await mtgjson_service.get_skus(db, use_cache)
if skus_data and "data" in skus_data:
skus_count = await self.sync_mtgjson_skus(db, skus_data)
return {
"identifiers_processed": identifiers_count,
"skus_processed": skus_count
}
async def _process_streamed_data(
self,
db: Session,
data_stream: Generator[Dict[str, Any], None, None],
filename: str,
subdir: str,
sync_func: Callable
) -> int:
"""Process streamed data and sync to database"""
async def sync_mtgjson_identifiers(self, db: Session, identifiers_data: List[dict]) -> int:
count = 0
items = []
batch_size = 1000
with db_transaction(db):
# Load all existing UUIDs once
existing_cards = {
card.mtgjson_uuid: card
for card in db.query(MTGJSONCard).all()
}
for item in data_stream:
if item["type"] == "meta":
# Handle meta data separately
continue
new_cards = []
count += 1
items.append(item["data"])
for card_data in identifiers_data:
if not isinstance(card_data, dict):
logger.debug(f"Skipping non-dict item: {card_data}")
continue
# Process in batches
if len(items) >= batch_size:
await sync_func(db, items)
items = []
uuid = card_data.get("uuid")
identifiers = card_data.get("identifiers", {})
# Process any remaining items
if items:
await sync_func(db, items)
if uuid in existing_cards:
card = existing_cards[uuid]
updates = {
"name": card_data.get("name"),
"set_code": card_data.get("setCode"),
"abu_id": identifiers.get("abuId"),
"card_kingdom_etched_id": identifiers.get("cardKingdomEtchedId"),
"card_kingdom_foil_id": identifiers.get("cardKingdomFoilId"),
"card_kingdom_id": identifiers.get("cardKingdomId"),
"cardsphere_id": identifiers.get("cardsphereId"),
"cardsphere_foil_id": identifiers.get("cardsphereFoilId"),
"cardtrader_id": identifiers.get("cardtraderId"),
"csi_id": identifiers.get("csiId"),
"mcm_id": identifiers.get("mcmId"),
"mcm_meta_id": identifiers.get("mcmMetaId"),
"miniaturemarket_id": identifiers.get("miniaturemarketId"),
"mtg_arena_id": identifiers.get("mtgArenaId"),
"mtgjson_foil_version_id": identifiers.get("mtgjsonFoilVersionId"),
"mtgjson_non_foil_version_id": identifiers.get("mtgjsonNonFoilVersionId"),
"mtgjson_v4_id": identifiers.get("mtgjsonV4Id"),
"mtgo_foil_id": identifiers.get("mtgoFoilId"),
"mtgo_id": identifiers.get("mtgoId"),
"multiverse_id": identifiers.get("multiverseId"),
"scg_id": identifiers.get("scgId"),
"scryfall_id": identifiers.get("scryfallId"),
"scryfall_card_back_id": identifiers.get("scryfallCardBackId"),
"scryfall_oracle_id": identifiers.get("scryfallOracleId"),
"scryfall_illustration_id": identifiers.get("scryfallIllustrationId"),
"tcgplayer_product_id": identifiers.get("tcgplayerProductId"),
"tcgplayer_etched_product_id": identifiers.get("tcgplayerEtchedProductId"),
"tnt_id": identifiers.get("tntId")
}
for k, v in updates.items():
if getattr(card, k) != v:
setattr(card, k, v)
else:
new_cards.append(MTGJSONCard(
mtgjson_uuid=uuid,
name=card_data.get("name"),
set_code=card_data.get("setCode"),
abu_id=identifiers.get("abuId"),
card_kingdom_etched_id=identifiers.get("cardKingdomEtchedId"),
card_kingdom_foil_id=identifiers.get("cardKingdomFoilId"),
card_kingdom_id=identifiers.get("cardKingdomId"),
cardsphere_id=identifiers.get("cardsphereId"),
cardsphere_foil_id=identifiers.get("cardsphereFoilId"),
cardtrader_id=identifiers.get("cardtraderId"),
csi_id=identifiers.get("csiId"),
mcm_id=identifiers.get("mcmId"),
mcm_meta_id=identifiers.get("mcmMetaId"),
miniaturemarket_id=identifiers.get("miniaturemarketId"),
mtg_arena_id=identifiers.get("mtgArenaId"),
mtgjson_foil_version_id=identifiers.get("mtgjsonFoilVersionId"),
mtgjson_non_foil_version_id=identifiers.get("mtgjsonNonFoilVersionId"),
mtgjson_v4_id=identifiers.get("mtgjsonV4Id"),
mtgo_foil_id=identifiers.get("mtgoFoilId"),
mtgo_id=identifiers.get("mtgoId"),
multiverse_id=identifiers.get("multiverseId"),
scg_id=identifiers.get("scgId"),
scryfall_id=identifiers.get("scryfallId"),
scryfall_card_back_id=identifiers.get("scryfallCardBackId"),
scryfall_oracle_id=identifiers.get("scryfallOracleId"),
scryfall_illustration_id=identifiers.get("scryfallIllustrationId"),
tcgplayer_product_id=identifiers.get("tcgplayerProductId"),
tcgplayer_etched_product_id=identifiers.get("tcgplayerEtchedProductId"),
tnt_id=identifiers.get("tntId")
))
count += 1
if new_cards:
db.bulk_save_objects(new_cards)
return count
async def sync_mtgjson_identifiers(self, db: Session, identifiers_data: dict):
"""Sync MTGJSON identifiers data to the database"""
from app.models.mtgjson_card import MTGJSONCard
async def sync_mtgjson_skus(self, db: Session, skus_data: dict) -> int:
count = 0
sku_details_by_key = {}
with transaction(db):
for card_id, card_data in identifiers_data.items():
existing_card = db.query(MTGJSONCard).filter(MTGJSONCard.card_id == card_id).first()
if existing_card:
# Update existing card
for key, value in {
"name": card_data.get("name"),
"set_code": card_data.get("setCode"),
"uuid": card_data.get("uuid"),
"abu_id": card_data.get("identifiers", {}).get("abuId"),
"card_kingdom_etched_id": card_data.get("identifiers", {}).get("cardKingdomEtchedId"),
"card_kingdom_foil_id": card_data.get("identifiers", {}).get("cardKingdomFoilId"),
"card_kingdom_id": card_data.get("identifiers", {}).get("cardKingdomId"),
"cardsphere_id": card_data.get("identifiers", {}).get("cardsphereId"),
"cardsphere_foil_id": card_data.get("identifiers", {}).get("cardsphereFoilId"),
"cardtrader_id": card_data.get("identifiers", {}).get("cardtraderId"),
"csi_id": card_data.get("identifiers", {}).get("csiId"),
"mcm_id": card_data.get("identifiers", {}).get("mcmId"),
"mcm_meta_id": card_data.get("identifiers", {}).get("mcmMetaId"),
"miniaturemarket_id": card_data.get("identifiers", {}).get("miniaturemarketId"),
"mtg_arena_id": card_data.get("identifiers", {}).get("mtgArenaId"),
"mtgjson_foil_version_id": card_data.get("identifiers", {}).get("mtgjsonFoilVersionId"),
"mtgjson_non_foil_version_id": card_data.get("identifiers", {}).get("mtgjsonNonFoilVersionId"),
"mtgjson_v4_id": card_data.get("identifiers", {}).get("mtgjsonV4Id"),
"mtgo_foil_id": card_data.get("identifiers", {}).get("mtgoFoilId"),
"mtgo_id": card_data.get("identifiers", {}).get("mtgoId"),
"multiverse_id": card_data.get("identifiers", {}).get("multiverseId"),
"scg_id": card_data.get("identifiers", {}).get("scgId"),
"scryfall_id": card_data.get("identifiers", {}).get("scryfallId"),
"scryfall_card_back_id": card_data.get("identifiers", {}).get("scryfallCardBackId"),
"scryfall_oracle_id": card_data.get("identifiers", {}).get("scryfallOracleId"),
"scryfall_illustration_id": card_data.get("identifiers", {}).get("scryfallIllustrationId"),
"tcgplayer_product_id": card_data.get("identifiers", {}).get("tcgplayerProductId"),
"tcgplayer_etched_product_id": card_data.get("identifiers", {}).get("tcgplayerEtchedProductId"),
"tnt_id": card_data.get("identifiers", {}).get("tntId")
}.items():
setattr(existing_card, key, value)
for mtgjson_uuid, product_data in skus_data["data"].items():
for sku_data in product_data:
sku_id = sku_data.get("skuId")
if sku_id is None or sku_id in sku_details_by_key:
continue # Skip if missing or already added
sku_details_by_key[sku_id] = {
"mtgjson_uuid": mtgjson_uuid,
"tcgplayer_sku_id": sku_id,
"tcgplayer_product_id": sku_data.get("productId"),
"printing": sku_data.get("printing"),
"normalized_printing": sku_data.get("printing", "").lower().replace(" ", "_").replace("non_foil", "normal") if sku_data.get("printing") else None,
"condition": sku_data.get("condition"),
"finish": sku_data.get("finish"),
"language": sku_data.get("language"),
}
with db_transaction(db):
db.flush()
valid_uuids = {uuid for (uuid,) in db.query(MTGJSONCard.mtgjson_uuid).all()}
valid_product_keys = {
(product.tcgplayer_product_id, product.normalized_sub_type_name)
for product in db.query(TCGPlayerProduct.tcgplayer_product_id, TCGPlayerProduct.normalized_sub_type_name)
}
existing_sku_ids = {
sku.tcgplayer_sku_id
for sku in db.query(MTGJSONSKU.tcgplayer_sku_id).all()
}
existing = {
(sku.mtgjson_uuid, sku.tcgplayer_sku_id): sku
for sku in db.query(MTGJSONSKU).all()
}
new_skus = []
for data in sku_details_by_key.values():
sku_id = data["tcgplayer_sku_id"]
if sku_id in existing_sku_ids:
continue
mtgjson_uuid = data["mtgjson_uuid"]
product_id = data["tcgplayer_product_id"]
normalized_printing = data["normalized_printing"]
if mtgjson_uuid not in valid_uuids:
continue
if (product_id, normalized_printing) not in valid_product_keys:
continue
key = (mtgjson_uuid, sku_id)
if key in existing:
record = existing[key]
for field, value in data.items():
if field not in ("mtgjson_uuid", "tcgplayer_sku_id") and getattr(record, field) != value:
setattr(record, field, value)
else:
new_card = MTGJSONCard(
card_id=card_id,
name=card_data.get("name"),
set_code=card_data.get("setCode"),
uuid=card_data.get("uuid"),
abu_id=card_data.get("identifiers", {}).get("abuId"),
card_kingdom_etched_id=card_data.get("identifiers", {}).get("cardKingdomEtchedId"),
card_kingdom_foil_id=card_data.get("identifiers", {}).get("cardKingdomFoilId"),
card_kingdom_id=card_data.get("identifiers", {}).get("cardKingdomId"),
cardsphere_id=card_data.get("identifiers", {}).get("cardsphereId"),
cardsphere_foil_id=card_data.get("identifiers", {}).get("cardsphereFoilId"),
cardtrader_id=card_data.get("identifiers", {}).get("cardtraderId"),
csi_id=card_data.get("identifiers", {}).get("csiId"),
mcm_id=card_data.get("identifiers", {}).get("mcmId"),
mcm_meta_id=card_data.get("identifiers", {}).get("mcmMetaId"),
miniaturemarket_id=card_data.get("identifiers", {}).get("miniaturemarketId"),
mtg_arena_id=card_data.get("identifiers", {}).get("mtgArenaId"),
mtgjson_foil_version_id=card_data.get("identifiers", {}).get("mtgjsonFoilVersionId"),
mtgjson_non_foil_version_id=card_data.get("identifiers", {}).get("mtgjsonNonFoilVersionId"),
mtgjson_v4_id=card_data.get("identifiers", {}).get("mtgjsonV4Id"),
mtgo_foil_id=card_data.get("identifiers", {}).get("mtgoFoilId"),
mtgo_id=card_data.get("identifiers", {}).get("mtgoId"),
multiverse_id=card_data.get("identifiers", {}).get("multiverseId"),
scg_id=card_data.get("identifiers", {}).get("scgId"),
scryfall_id=card_data.get("identifiers", {}).get("scryfallId"),
scryfall_card_back_id=card_data.get("identifiers", {}).get("scryfallCardBackId"),
scryfall_oracle_id=card_data.get("identifiers", {}).get("scryfallOracleId"),
scryfall_illustration_id=card_data.get("identifiers", {}).get("scryfallIllustrationId"),
tcgplayer_product_id=card_data.get("identifiers", {}).get("tcgplayerProductId"),
tcgplayer_etched_product_id=card_data.get("identifiers", {}).get("tcgplayerEtchedProductId"),
tnt_id=card_data.get("identifiers", {}).get("tntId")
)
db.add(new_card)
new_skus.append(MTGJSONSKU(**data))
count += 1
if new_skus:
db.bulk_save_objects(new_skus)
return count
async def sync_mtgjson_skus(self, db: Session, skus_data: dict):
"""Sync MTGJSON SKUs data to the database"""
from app.models.mtgjson_sku import MTGJSONSKU
with transaction(db):
for card_uuid, sku_list in skus_data.items():
for sku in sku_list:
# Handle case where sku is a string (skuId)
if isinstance(sku, str):
sku_id = sku
existing_sku = db.query(MTGJSONSKU).filter(MTGJSONSKU.sku_id == sku_id).first()
if existing_sku:
# Update existing SKU
existing_sku.card_id = card_uuid
else:
new_sku = MTGJSONSKU(
sku_id=sku_id,
card_id=card_uuid
)
db.add(new_sku)
# Handle case where sku is a dictionary
else:
sku_id = str(sku.get("skuId"))
existing_sku = db.query(MTGJSONSKU).filter(MTGJSONSKU.sku_id == sku_id).first()
if existing_sku:
# Update existing SKU
for key, value in {
"product_id": str(sku.get("productId")),
"condition": sku.get("condition"),
"finish": sku.get("finish"),
"language": sku.get("language"),
"printing": sku.get("printing"),
"card_id": card_uuid
}.items():
setattr(existing_sku, key, value)
else:
new_sku = MTGJSONSKU(
sku_id=sku_id,
product_id=str(sku.get("productId")),
condition=sku.get("condition"),
finish=sku.get("finish"),
language=sku.get("language"),
printing=sku.get("printing"),
card_id=card_uuid
)
db.add(new_sku)
async def initialize_data(
self,
@ -735,3 +746,65 @@ class DataInitializationService(BaseService):
await self.file_service.delete_file(db, file.id)
await self.mtgjson_service.clear_cache()
print("Cache cleared")
async def initialize_inventory_data(self, db: Session) -> None:
"""Initialize inventory data"""
with db_transaction(db):
logger.info("Initializing inventory data...")
# set expected value
expected_value_box = SealedExpectedValue(
tcgplayer_product_id=619645,
expected_value=136.42
)
db.add(expected_value_box)
#db.flush()
#expected_value_case = SealedExpectedValue(
# tcgplayer_product_id=562119,
# expected_value=820.69
#)
#db.add(expected_value_case)
db.flush()
inventory_service = self.get_service("inventory")
customer = await inventory_service.create_customer(db, "Bob Smith")
vendor = await inventory_service.create_vendor(db, "Joe Blow")
marketplace = await inventory_service.create_marketplace(db, "Tcgplayer")
transaction = await inventory_service.create_purchase_transaction(db, PurchaseTransactionCreate(
vendor_id=vendor.id,
transaction_date=datetime.now(),
items=[PurchaseItem(product_id=619645, unit_price=100, quantity=1, item_type="box")],
transaction_notes="tdm real box test"
#PurchaseItem(product_id=562119, unit_price=800.01, quantity=2, is_case=True, num_boxes=6)],
#transaction_notes="Test Transaction: 1 case and 2 boxes of foundations"
))
logger.info(f"Transaction created: {transaction}")
case_num = 0
for item in transaction.transaction_items:
logger.info(f"Item: {item}")
if item.inventory_item.physical_item.item_type == "box":
manabox_service = self.get_service("manabox")
#file_path = 'app/data/test_data/manabox_test_file.csv'
file_path = 'app/data/test_data/dragon.csv'
file_bytes = open(file_path, 'rb').read()
manabox_file = await manabox_service.process_manabox_csv(db, file_bytes, {"source": "test", "description": "test"}, wait=True)
# Ensure manabox_file is a list before passing it
if not isinstance(manabox_file, list):
manabox_file = [manabox_file]
box_service = self.get_service("box")
open_event = await box_service.open_box(db, item.inventory_item.physical_item, manabox_file)
# get all cards from box
cards = open_event.resulting_items if open_event.resulting_items else []
marketplace_listing_service = self.get_service("marketplace_listing")
for card in cards:
logger.info(f"card: {card}")
# create marketplace listing
await marketplace_listing_service.create_marketplace_listing(db, card.inventory_item, marketplace)
elif item.inventory_item.physical_item.item_type == "case":
if case_num == 0:
logger.info(f"sealed case {case_num} opening...")
case_service = self.get_service("case")
success = await case_service.open_case(db, item.inventory_item.physical_item, 562119)
logger.info(f"sealed case {case_num} opening success: {success}")
case_num += 1
logger.info("Inventory data initialized")

View File

@ -2,7 +2,8 @@ import os
import json
import zipfile
import time
from typing import Dict, Any, Optional, Generator
import shutil
from typing import Dict, Any, Optional
from sqlalchemy.orm import Session
from app.services.external_api.base_external_service import BaseExternalService
from app.schemas.file import FileInDB
@ -11,32 +12,10 @@ import logging
logger = logging.getLogger(__name__)
class MTGJSONService(BaseExternalService):
def __init__(self, cache_dir: str = "app/data/cache/mtgjson"):
def __init__(self):
super().__init__(base_url="https://mtgjson.com/api/v5/")
# Ensure the cache directory exists
os.makedirs(cache_dir, exist_ok=True)
self.cache_dir = cache_dir
self.identifiers_dir = os.path.join(cache_dir, "identifiers")
self.skus_dir = os.path.join(cache_dir, "skus")
# Ensure subdirectories exist
os.makedirs(self.identifiers_dir, exist_ok=True)
os.makedirs(self.skus_dir, exist_ok=True)
def _format_progress(self, current: int, total: int, start_time: float) -> str:
"""Format a progress message with percentage and timing information"""
elapsed = time.time() - start_time
if total > 0:
percent = (current / total) * 100
items_per_second = current / elapsed if elapsed > 0 else 0
eta = (total - current) / items_per_second if items_per_second > 0 else 0
return f"[{current}/{total} ({percent:.1f}%)] {items_per_second:.1f} items/sec, ETA: {eta:.1f}s"
return f"[{current} items] {current/elapsed:.1f} items/sec"
def _print_progress(self, message: str, end: str = "\n") -> None:
"""Print progress message with flush"""
print(message, end=end, flush=True)
async def _download_file(self, db: Session, url: str, filename: str, subdir: str) -> FileInDB:
async def _download_and_unzip_file(self, db: Session, url: str, filename: str, subdir: str) -> FileInDB:
"""Download a file from the given URL and save it using FileService"""
print(f"Downloading {url}...")
start_time = time.time()
@ -49,7 +28,7 @@ class MTGJSONService(BaseExternalService):
)
# Save the file using the file service
return await self.file_service.save_file(
file_record = await self.file_service.save_file(
db=db,
file_data=file_data,
filename=filename,
@ -58,17 +37,23 @@ class MTGJSONService(BaseExternalService):
content_type="application/zip"
)
async def _unzip_file(self, file_record: FileInDB, subdir: str, db: Session) -> str:
# Unzip the file
await self._unzip_file(file_record, subdir, db)
return file_record
async def _unzip_file(self, file_record: FileInDB, subdir: str, db: Session) -> FileInDB:
"""Unzip a file to the specified subdirectory and return the path to the extracted JSON file"""
try:
# Use the appropriate subdirectory based on the type
extract_path = self.identifiers_dir if subdir == "identifiers" else self.skus_dir
os.makedirs(extract_path, exist_ok=True)
file_service = self.get_service('file')
cache_dir = file_service.base_cache_dir
temp_dir = os.path.join(cache_dir,'mtgjson', subdir, 'temp')
os.makedirs(temp_dir, exist_ok=True)
with zipfile.ZipFile(file_record.path, 'r') as zip_ref:
json_filename = zip_ref.namelist()[0]
zip_ref.extractall(extract_path)
json_path = os.path.join(extract_path, json_filename)
zip_ref.extractall(temp_dir)
json_path = os.path.join(temp_dir, json_filename)
# Create a file record for the extracted JSON file
with open(json_path, 'r') as f:
@ -82,127 +67,61 @@ class MTGJSONService(BaseExternalService):
content_type="application/json"
)
return str(json_file_record.path)
# remove the temp directory
shutil.rmtree(temp_dir)
return json_file_record
except Exception as e:
logger.error(f"Error unzipping file: {e}")
raise
def _stream_json_file(self, file_path: str) -> Generator[Dict[str, Any], None, None]:
"""Stream a JSON file and yield items one at a time using a streaming parser"""
logger.info(f"Starting to stream JSON file: {file_path}")
try:
with open(file_path, 'r') as f:
# First, we need to find the start of the data section
data_started = False
current_key = None
current_value = []
brace_count = 0
for line in f:
line = line.strip()
if not line:
continue
if not data_started:
if '"data":' in line:
data_started = True
# Skip the opening brace of the data object
line = line[line.find('"data":') + 7:].strip()
if line.startswith('{'):
line = line[1:].strip()
else:
# Yield meta data if found
if '"meta":' in line:
meta_start = line.find('"meta":') + 7
meta_end = line.rfind('}')
if meta_end > meta_start:
meta_json = line[meta_start:meta_end + 1]
try:
meta_data = json.loads(meta_json)
yield {"type": "meta", "data": meta_data}
except json.JSONDecodeError as e:
logger.warning(f"Failed to parse meta data: {e}")
continue
# Process the data section
if data_started:
if not current_key:
# Look for a new key
if '"' in line:
key_start = line.find('"') + 1
key_end = line.find('"', key_start)
if key_end > key_start:
current_key = line[key_start:key_end]
# Get the rest of the line after the key
line = line[key_end + 1:].strip()
if ':' in line:
line = line[line.find(':') + 1:].strip()
if current_key:
# Accumulate the value
current_value.append(line)
brace_count += line.count('{') - line.count('}')
if brace_count == 0 and line.endswith(','):
# We have a complete value
value_str = ''.join(current_value).rstrip(',')
try:
value = json.loads(value_str)
yield {"type": "item", "data": {current_key: value}}
except json.JSONDecodeError as e:
logger.warning(f"Failed to parse value for key {current_key}: {e}")
current_key = None
current_value = []
except Exception as e:
logger.error(f"Error streaming JSON file: {e}")
raise
async def get_identifiers(self, db: Session) -> Generator[Dict[str, Any], None, None]:
async def get_identifiers(self, db: Session, use_cache: bool = True) -> Dict[str, Any]:
"""Download and get MTGJSON identifiers data"""
# Check if we have a cached version
cached_file = await self.file_service.get_file_by_filename(db, "AllIdentifiers.json")
if cached_file:
# Ensure the file exists at the path
if os.path.exists(cached_file.path):
return self._stream_json_file(cached_file.path)
if cached_file and os.path.exists(cached_file.path) and use_cache:
with open(cached_file.path, 'r') as f:
logger.debug(f"Loaded identifiers from cache: {cached_file.path}")
return json.load(f)
else:
# Download and process the file
logger.debug(f"Downloading identifiers from MTGJSON")
file_record = await self._download_and_unzip_file(
db=db,
url="https://mtgjson.com/api/v5/AllIdentifiers.json.zip",
filename="AllIdentifiers.json.zip",
subdir="identifiers"
)
# Download and process the file
file_record = await self._download_file(
db=db,
url="https://mtgjson.com/api/v5/AllIdentifiers.json.zip",
filename="AllIdentifiers.json.zip",
subdir="identifiers"
)
json_file = await self._unzip_file(file_record, "identifiers", db)
# Unzip and process the file
json_path = await self._unzip_file(file_record, "identifiers", db)
with open(json_file.path, 'r') as f:
logger.debug(f"Loaded identifiers from MTGJSON: {json_file.path}")
return json.load(f)
# Return a generator that streams the JSON file
return self._stream_json_file(json_path)
async def get_skus(self, db: Session) -> Generator[Dict[str, Any], None, None]:
async def get_skus(self, db: Session, use_cache: bool = True) -> Dict[str, Any]:
"""Download and get MTGJSON SKUs data"""
# Check if we have a cached version
cached_file = await self.file_service.get_file_by_filename(db, "TcgplayerSkus.json")
if cached_file:
# Ensure the file exists at the path
if os.path.exists(cached_file.path):
return self._stream_json_file(cached_file.path)
if cached_file and os.path.exists(cached_file.path) and use_cache:
with open(cached_file.path, 'r') as f:
logger.debug(f"Loaded SKUs from cache: {cached_file.path}")
return json.load(f)
else:
# Download and process the file
logger.debug(f"Downloading SKUs from MTGJSON")
file_record = await self._download_and_unzip_file(
db=db,
url="https://mtgjson.com/api/v5/TcgplayerSkus.json.zip",
filename="TcgplayerSkus.json.zip",
subdir="skus"
)
# Download and process the file
file_record = await self._download_file(
db=db,
url="https://mtgjson.com/api/v5/TcgplayerSkus.json.zip",
filename="TcgplayerSkus.json.zip",
subdir="skus"
)
json_file = await self._unzip_file(file_record, "skus", db)
# Unzip and process the file
json_path = await self._unzip_file(file_record, "skus", db)
# Return a generator that streams the JSON file
return self._stream_json_file(json_path)
with open(json_file.path, 'r') as f:
logger.debug(f"Loaded SKUs from MTGJSON: {json_file.path}")
return json.load(f)
async def clear_cache(self, db: Session) -> None:
"""Clear all cached data"""

View File

@ -16,6 +16,7 @@ from app.models.tcgplayer_order import (
TCGPlayerOrderProduct,
TCGPlayerOrderRefund
)
from app.models.tcgplayer_products import TCGPlayerProduct
from sqlalchemy.orm import Session
from app.db.database import transaction
import os
@ -190,8 +191,7 @@ class OrderManagementService(BaseTCGPlayerService):
direct_fee_amount=api_order.transaction.directFeeAmount,
taxes=[{"code": t.code, "amount": t.amount} for t in api_order.transaction.taxes]
)
# Create products
# Create products
db_products = [
TCGPlayerOrderProductCreate(
order_number=api_order.orderNumber,
@ -376,8 +376,8 @@ class OrderManagementService(BaseTCGPlayerService):
('extended_price', 'extendedPrice'),
('quantity', 'quantity'),
('url', 'url'),
('product_id', 'productId'),
('sku_id', 'skuId')
('tcgplayer_product_id', 'productId'),
('tcgplayer_sku_id', 'skuId')
]
for db_field, api_field in product_fields_to_compare:

View File

@ -2,6 +2,11 @@ from typing import Dict, List, Optional
from app.services.external_api.tcgplayer.base_tcgplayer_service import BaseTCGPlayerService
from sqlalchemy.orm import Session
from app.schemas.file import FileInDB
from app.models.tcgplayer_inventory import TCGPlayerInventory, UnmanagedTCGPlayerInventory
import csv
from app.db.database import transaction
from app.models.inventory_management import MarketplaceListing, InventoryItem, Marketplace
from sqlalchemy import func
class TCGPlayerInventoryService(BaseTCGPlayerService):
def __init__(self):
@ -24,10 +29,101 @@ class TCGPlayerInventoryService(BaseTCGPlayerService):
raise ValueError(f"Invalid export type: {export_type}, must be 'staged', 'live', or 'pricing'")
file_bytes = await self._make_request("GET", endpoint, download_file=True)
return await self.save_file(
return await self.file_service.save_file(
db=db,
file_data=file_bytes,
file_name=f"tcgplayer_{export_type}_export.csv",
filename=f"tcgplayer_{export_type}_export.csv",
subdir="tcgplayer/inventory",
file_type=file_type
)
async def refresh_tcgplayer_inventory_table(self, db: Session):
"""
Refresh the TCGPlayer inventory table
"""
export = await self.get_tcgplayer_export(db, "live")
csv_string = await self.file_service.file_in_db_to_csv(db, export)
reader = csv.DictReader(csv_string.splitlines())
# Convert CSV rows to list of dictionaries for bulk insert
inventory_data = []
for row in reader:
if row.get("TCGplayer Id") is None:
continue
inventory_data.append({
"tcgplayer_sku_id": int(row.get("TCGplayer Id")),
"product_line": row.get("Product Line") if row.get("Product Line") else None,
"set_name": row.get("Set Name") if row.get("Set Name") else None,
"product_name": row.get("Product Name") if row.get("Product Name") else None,
"title": row.get("Title") if row.get("Title") else None,
"number": row.get("Number") if row.get("Number") else None,
"rarity": row.get("Rarity") if row.get("Rarity") else None,
"condition": row.get("Condition") if row.get("Condition") else None,
"tcg_market_price": float(row.get("TCG Market Price")) if row.get("TCG Market Price") else None,
"tcg_direct_low": float(row.get("TCG Direct Low")) if row.get("TCG Direct Low") else None,
"tcg_low_price_with_shipping": float(row.get("TCG Low Price With Shipping")) if row.get("TCG Low Price With Shipping") else None,
"tcg_low_price": float(row.get("TCG Low Price")) if row.get("TCG Low Price") else None,
"total_quantity": int(row.get("Total Quantity")) if row.get("Total Quantity") else None,
"add_to_quantity": int(row.get("Add to Quantity")) if row.get("Add to Quantity") else None,
"tcg_marketplace_price": float(row.get("TCG Marketplace Price")) if row.get("TCG Marketplace Price") else None,
"photo_url": row.get("Photo URL") if row.get("Photo URL") else None
})
with transaction(db):
# Bulk insert new data
db.bulk_insert_mappings(TCGPlayerInventory, inventory_data)
async def refresh_unmanaged_tcgplayer_inventory_table(self, db: Session):
"""
Refresh the TCGPlayer unmanaged inventory table
unmanaged inventory is any inventory that cannot be mapped to a card with a marketplace listing
"""
with transaction(db):
# Get active marketplace listings with their physical items in a single query
listed_cards = (
db.query(MarketplaceListing)
.join(MarketplaceListing.inventory_item)
.join(InventoryItem.physical_item)
.filter(
func.lower(Marketplace.name) == func.lower("tcgplayer"),
MarketplaceListing.delisting_date == None,
MarketplaceListing.deleted_at == None,
MarketplaceListing.listing_date != None
)
.all()
)
# Get current inventory and create lookup dict
current_inventory = db.query(TCGPlayerInventory).all()
# Create a set of SKUs that have active listings
listed_skus = {
card.inventory_item.physical_item.tcgplayer_sku_id
for card in listed_cards
}
unmanaged_inventory = []
for inventory in current_inventory:
# Only include SKUs that have no active listings
if inventory.tcgplayer_sku_id not in listed_skus:
unmanaged_inventory.append({
"tcgplayer_inventory_id": inventory.id,
"tcgplayer_sku_id": inventory.tcgplayer_sku_id,
"product_line": inventory.product_line,
"set_name": inventory.set_name,
"product_name": inventory.product_name,
"title": inventory.title,
"number": inventory.number,
"rarity": inventory.rarity,
"condition": inventory.condition,
"tcg_market_price": inventory.tcg_market_price,
"tcg_direct_low": inventory.tcg_direct_low,
"tcg_low_price_with_shipping": inventory.tcg_low_price_with_shipping,
"tcg_low_price": inventory.tcg_low_price,
"total_quantity": inventory.total_quantity,
"add_to_quantity": inventory.add_to_quantity,
"tcg_marketplace_price": inventory.tcg_marketplace_price,
"photo_url": inventory.photo_url
})
db.bulk_insert_mappings(UnmanagedTCGPlayerInventory, unmanaged_inventory)

View File

@ -6,8 +6,6 @@ import json
from datetime import datetime
from sqlalchemy.orm import Session
from app.db.database import transaction
from app.models.tcgplayer_inventory import TCGPlayerInventory
from app.models.tcgplayer_product import TCGPlayerProduct
from app.services.inventory_service import InventoryService
class FileProcessingService:

View File

@ -120,7 +120,7 @@ class FileService:
"""List files with optional filtering"""
query = db.query(File)
if file_type:
query = query.filter(File.type == file_type)
query = query.filter(File.file_type == file_type).filter(File.deleted_at == None).order_by(File.created_at.desc())
files = query.offset(skip).limit(limit).all()
return [FileInDB.model_validate(file) for file in files]
@ -153,7 +153,13 @@ class FileService:
async def get_file_by_filename(self, db: Session, filename: str) -> Optional[FileInDB]:
"""Get a file record from the database by filename"""
file_record = db.query(File).filter(File.name == filename).first()
# get most recent file by filename
file_record = db.query(File).filter(File.name == filename).order_by(File.created_at.desc()).first()
if file_record:
return FileInDB.model_validate(file_record)
return None
async def file_in_db_to_csv(self, db: Session, file: FileInDB) -> str:
"""Convert a file in the database to a CSV string"""
with open(file.path, "r") as f:
return f.read()

View File

@ -1,63 +1,505 @@
from typing import List, Optional, Dict
from sqlalchemy.orm import Session
from app.models.tcgplayer_inventory import TCGPlayerInventory
from typing import List, Optional, Dict, TypedDict
from sqlalchemy.orm import Session, joinedload
from decimal import Decimal
from app.services.base_service import BaseService
from app.models.manabox_import_staging import ManaboxImportStaging
from app.contexts.inventory_item import InventoryItemContextFactory
from app.models.inventory_management import (
OpenEvent, Card, InventoryItem, Case, SealedExpectedValue,
Transaction, TransactionItem, Customer, Vendor, Marketplace, Box, MarketplaceListing
)
from app.schemas.file import FileInDB
from app.models.inventory_management import PhysicalItem
from app.schemas.transaction import PurchaseTransactionCreate, SaleTransactionCreate, TransactionResponse, SealedExpectedValueCreate
from app.db.database import transaction as db_transaction
from datetime import datetime
from typing import Any
import logging
class InventoryService(BaseService[TCGPlayerInventory]):
logger = logging.getLogger(__name__)
class InventoryService(BaseService):
def __init__(self):
super().__init__(TCGPlayerInventory)
super().__init__(None)
def create(self, db: Session, obj_in: Dict) -> TCGPlayerInventory:
async def get_resulting_items_for_open_event(self, db: Session, open_event: OpenEvent) -> List[InventoryItem]:
# Get the IDs of resulting items
resulting_item_ids = [item.id for item in open_event.resulting_items]
# Query using the IDs
return db.query(InventoryItem).filter(InventoryItem.physical_item_id.in_(resulting_item_ids)).filter(InventoryItem.deleted_at == None).all()
async def get_open_event(self, db: Session, inventory_item: InventoryItem, open_event_id: int) -> OpenEvent:
return db.query(OpenEvent).filter(OpenEvent.source_item == inventory_item.physical_item).filter(OpenEvent.id == open_event_id).filter(OpenEvent.deleted_at == None).first()
async def get_open_events_for_inventory_item(self, db: Session, inventory_item: InventoryItem) -> List[OpenEvent]:
return db.query(OpenEvent).filter(OpenEvent.source_item == inventory_item.physical_item).filter(OpenEvent.deleted_at == None).all()
async def get_inventory_item(self, db: Session, inventory_item_id: int) -> InventoryItem:
return db.query(InventoryItem)\
.options(
joinedload(InventoryItem.physical_item).joinedload(PhysicalItem.product_direct)
)\
.filter(InventoryItem.id == inventory_item_id)\
.first()
async def get_expected_value(self, db: Session, product_id: int) -> float:
expected_value = db.query(SealedExpectedValue).filter(SealedExpectedValue.tcgplayer_product_id == product_id).filter(SealedExpectedValue.deleted_at == None).first()
return expected_value.expected_value if expected_value else None
async def get_transactions(self, db: Session, skip: int, limit: int) -> List[Transaction]:
return db.query(Transaction)\
.filter(Transaction.deleted_at == None)\
.order_by(Transaction.transaction_date.desc())\
.offset(skip)\
.limit(limit)\
.all()
async def get_transaction(self, db: Session, transaction_id: int) -> Transaction:
return db.query(Transaction)\
.options(
joinedload(Transaction.transaction_items).joinedload(TransactionItem.inventory_item).joinedload(InventoryItem.physical_item).joinedload(PhysicalItem.product_direct),
joinedload(Transaction.vendors),
joinedload(Transaction.customers),
joinedload(Transaction.marketplaces)
)\
.filter(Transaction.id == transaction_id)\
.filter(Transaction.deleted_at == None)\
.first()
async def create_expected_value(self, db: Session, expected_value_data: SealedExpectedValueCreate) -> SealedExpectedValue:
with db_transaction(db):
expected_value = SealedExpectedValue(
tcgplayer_product_id=expected_value_data.tcgplayer_product_id,
expected_value=expected_value_data.expected_value
)
db.add(expected_value)
db.flush()
return expected_value
async def create_purchase_transaction(
self,
db: Session,
transaction_data: PurchaseTransactionCreate
) -> Transaction:
"""
Create a new inventory item in the database.
Args:
db: Database session
obj_in: Dictionary containing inventory data
Returns:
Inventory: The created inventory object
Creates a purchase transaction from a vendor.
For each item:
1. Creates a PhysicalItem (SealedCase/SealedBox)
2. Creates an InventoryItem with the purchase price as cost basis
3. Creates TransactionItems linking the purchase to the items
"""
return super().create(db, obj_in)
try:
with db_transaction(db):
# Create the transaction
transaction = Transaction(
vendor_id=transaction_data.vendor_id,
transaction_type='purchase',
transaction_date=transaction_data.transaction_date,
transaction_notes=transaction_data.transaction_notes
)
db.add(transaction)
db.flush()
def update(self, db: Session, db_obj: TCGPlayerInventory, obj_in: Dict) -> TCGPlayerInventory:
"""
Update an existing inventory item in the database.
total_amount = 0
physical_items = []
case_service = self.get_service("case")
box_service = self.get_service("box")
for item in transaction_data.items:
# Create the physical item based on type
# TODO: remove is_case and num_boxes, should derive from product_id
# TODO: add support for purchasing single cards
if item.item_type == "case":
for i in range(item.quantity):
physical_item = await case_service.create_case(
db=db,
product_id=item.product_id,
cost_basis=item.unit_price,
num_boxes=item.num_boxes
)
physical_items.append(physical_item)
elif item.item_type == "box":
for i in range(item.quantity):
physical_item = await box_service.create_box(
db=db,
product_id=item.product_id,
cost_basis=item.unit_price
)
physical_items.append(physical_item)
else:
raise ValueError(f"Invalid item type: {item.item_type}")
# TODO: add support for purchasing single cards
Args:
db: Database session
db_obj: The inventory object to update
obj_in: Dictionary containing updated inventory data
for physical_item in physical_items:
# Create transaction item
transaction.transaction_items.append(TransactionItem(
inventory_item_id=physical_item.inventory_item.id,
unit_price=item.unit_price
))
Returns:
Inventory: The updated inventory object
"""
return super().update(db, db_obj, obj_in)
# Update transaction total
transaction.transaction_total_amount = total_amount
return transaction
def get_by_tcgplayer_id(self, db: Session, tcgplayer_id: str) -> Optional[TCGPlayerInventory]:
"""
Get an inventory item by its TCGPlayer ID.
except Exception as e:
raise e
Args:
db: Database session
tcgplayer_id: The TCGPlayer ID to find
async def create_customer(
self,
db: Session,
customer_name: str
) -> Customer:
try:
# check if customer already exists
existing_customer = db.query(Customer).filter(Customer.name == customer_name).first()
if existing_customer:
return existing_customer
Returns:
Optional[TCGPlayerInventory]: The inventory item if found, None otherwise
"""
return db.query(self.model).filter(self.model.tcgplayer_id == tcgplayer_id).first()
with db_transaction(db):
customer = Customer(
name=customer_name
)
db.add(customer)
db.flush()
return customer
def get_by_set(self, db: Session, set_name: str, skip: int = 0, limit: int = 100) -> List[TCGPlayerInventory]:
"""
Get all inventory items from a specific set.
except Exception as e:
raise e
Args:
db: Database session
set_name: The name of the set to filter by
skip: Number of records to skip (for pagination)
limit: Maximum number of records to return
async def create_vendor(
self,
db: Session,
vendor_name: str
) -> Vendor:
try:
# check if vendor already exists
existing_vendor = db.query(Vendor).filter(Vendor.name == vendor_name).first()
if existing_vendor:
return existing_vendor
Returns:
List[TCGPlayerInventory]: List of inventory items from the specified set
"""
return db.query(self.model).filter(self.model.set_name == set_name).offset(skip).limit(limit).all()
with db_transaction(db):
vendor = Vendor(
name=vendor_name
)
db.add(vendor)
db.flush()
return vendor
except Exception as e:
raise e
async def get_vendors(
self,
db: Session
) -> List[Vendor]:
return db.query(Vendor).all()
async def create_marketplace(
self,
db: Session,
marketplace_name: str
) -> Marketplace:
try:
# check if marketplace already exists
existing_marketplace = db.query(Marketplace).filter(Marketplace.name == marketplace_name).first()
if existing_marketplace:
return existing_marketplace
with db_transaction(db):
marketplace = Marketplace(
name=marketplace_name
)
db.add(marketplace)
db.flush()
return marketplace
except Exception as e:
raise e
async def get_marketplaces(
self,
db: Session
) -> List[Marketplace]:
return db.query(Marketplace).all()
class BoxService(BaseService[Box]):
def __init__(self):
super().__init__(Box)
async def create_box(
self,
db: Session,
product_id: int,
cost_basis: float
) -> Box:
try:
with db_transaction(db):
# Create the SealedBox
box = Box(
tcgplayer_product_id=product_id
)
db.add(box)
db.flush() # Get the ID for relationships
expected_value = box.products.sealed_expected_value.expected_value
box.expected_value = expected_value
db.flush()
# Create the InventoryItem for the sealed box
inventory_item = InventoryItem(
physical_item=box,
cost_basis=cost_basis
)
db.add(inventory_item)
return box
except Exception as e:
raise e
async def calculate_cost_basis_for_opened_cards(self, db: Session, open_event: OpenEvent) -> float:
box_cost_basis = open_event.source_item.inventory_item.cost_basis
box_expected_value = open_event.source_item.products.sealed_expected_value.expected_value
for resulting_card in open_event.resulting_items:
# ensure card
if resulting_card.item_type != "card":
raise ValueError(f"Expected card, got {resulting_card.item_type}")
resulting_card_market_value = resulting_card.products.most_recent_tcgplayer_price.market_price
resulting_card_cost_basis = (resulting_card_market_value / box_expected_value) * box_cost_basis
resulting_card.inventory_item.cost_basis = resulting_card_cost_basis
db.flush()
async def open_box(self, db: Session, box: Box, manabox_file_uploads: List[FileInDB]) -> bool:
with db_transaction(db):
# create open event
open_event = OpenEvent(
source_item=box,
open_date=datetime.now()
)
db.add(open_event)
db.flush()
manabox_upload_ids = [manabox_file_upload.id for manabox_file_upload in manabox_file_uploads]
staging_data = db.query(ManaboxImportStaging).filter(ManaboxImportStaging.file_id.in_(manabox_upload_ids)).all()
for record in staging_data:
for i in range(record.quantity):
open_card = Card(
tcgplayer_product_id=record.tcgplayer_product_id,
tcgplayer_sku_id=record.tcgplayer_sku_id
)
open_event.resulting_items.append(open_card)
inventory_item = InventoryItem(
physical_item=open_card,
cost_basis=0
)
db.add(inventory_item)
db.flush()
# calculate cost basis for opened cards
await self.calculate_cost_basis_for_opened_cards(db, open_event)
return open_event
class CaseService(BaseService[Case]):
def __init__(self):
super().__init__(Case)
async def create_case(self, db: Session, product_id: int, cost_basis: float, num_boxes: int) -> Case:
try:
with db_transaction(db):
# Create the SealedCase
case = Case(
tcgplayer_product_id=product_id,
num_boxes=num_boxes
)
db.add(case)
db.flush() # Get the ID for relationships
case.expected_value = case.products.sealed_expected_value.expected_value
# Create the InventoryItem for the sealed case
inventory_item = InventoryItem(
physical_item=case,
cost_basis=cost_basis
)
db.add(inventory_item)
return case
except Exception as e:
raise e
async def open_case(self, db: Session, case: Case, child_product_id: int) -> bool:
try:
## TODO should be able to import a manabox file with a case
## cost basis will be able to flow down to the card accurately
with db_transaction(db):
# Create the OpenEvent
open_event = OpenEvent(
source_item=case,
open_date=datetime.now()
)
db.add(open_event)
db.flush() # Get the ID for relationships
# Create num_boxes SealedBoxes
for i in range(case.num_boxes):
new_box = Box(
tcgplayer_product_id=child_product_id
)
open_event.resulting_items.append(new_box)
db.flush()
per_box_cost_basis = case.inventory_item.cost_basis / case.num_boxes
# Create the InventoryItem for the sealed box
inventory_item = InventoryItem(
physical_item=new_box,
cost_basis=per_box_cost_basis
)
db.add(inventory_item)
return True
except Exception as e:
raise e
class MarketplaceListingService(BaseService[MarketplaceListing]):
def __init__(self):
super().__init__(MarketplaceListing)
self.pricing_service = self.service_manager.get_service("pricing")
async def create_marketplace_listing(self, db: Session, inventory_item: InventoryItem, marketplace: Marketplace) -> MarketplaceListing:
try:
with db_transaction(db):
recommended_price = await self.pricing_service.set_price(db, inventory_item)
logger.info(f"recommended_price: {recommended_price.price}")
marketplace_listing = MarketplaceListing(
inventory_item=inventory_item,
marketplace=marketplace,
recommended_price=recommended_price,
listing_date=None,
delisting_date=None
)
db.add(marketplace_listing)
db.flush()
return marketplace_listing
except Exception as e:
raise e
async def update_marketplace_listing_price(self, db: Session, marketplace_listing: MarketplaceListing) -> MarketplaceListing:
try:
with db_transaction(db):
marketplace_listing.listed_price = self.pricing_service.set_price(marketplace_listing.inventory_item)
db.flush()
return marketplace_listing
except Exception as e:
raise e
async def get_marketplace_listing(self, db: Session, inventory_item: InventoryItem, marketplace: Marketplace) -> MarketplaceListing:
return db.query(MarketplaceListing).filter(MarketplaceListing.inventory_item == inventory_item).filter(MarketplaceListing.delisting_date == None).filter(MarketplaceListing.deleted_at == None).filter(MarketplaceListing.marketplace == marketplace).order_by(MarketplaceListing.created_at.desc()).first()
async def get_active_marketplace_listing(self, db: Session, inventory_item: InventoryItem, marketplace: Marketplace) -> MarketplaceListing:
return db.query(MarketplaceListing).filter(MarketplaceListing.inventory_item == inventory_item).filter(MarketplaceListing.delisting_date == None).filter(MarketplaceListing.deleted_at == None).filter(MarketplaceListing.marketplace == marketplace).filter(MarketplaceListing.listing_date != None).order_by(MarketplaceListing.created_at.desc()).first()
async def confirm_listings(self, db: Session, open_event: OpenEvent, marketplace: Marketplace) -> str:
tcgplayer_add_file = await self.create_tcgplayer_add_file(db, open_event, marketplace)
if not tcgplayer_add_file:
raise ValueError("No TCGplayer add file created")
with db_transaction(db):
for resulting_item in open_event.resulting_items:
marketplace_listing = await self.get_marketplace_listing(db, resulting_item.inventory_item, marketplace)
if marketplace_listing is None:
raise ValueError(f"No active marketplace listing found for inventory item id {resulting_item.inventory_item.id} in {marketplace.name}")
marketplace_listing.listing_date = datetime.now()
db.flush()
return tcgplayer_add_file
async def create_tcgplayer_add_file(self, db: Session, open_event: OpenEvent, marketplace: Marketplace) -> str:
# TCGplayer Id,Product Line,Set Name,Product Name,Title,Number,Rarity,Condition,TCG Market Price,TCG Direct Low,TCG Low Price With Shipping,TCG Low Price,Total Quantity,Add to Quantity,TCG Marketplace Price,Photo URL
headers = [
"TCGplayer Id",
"Product Line",
"Set Name",
"Product Name",
"Title",
"Number",
"Rarity",
"Condition",
"TCG Market Price",
"TCG Direct Low",
"TCG Low Price With Shipping",
"TCG Low Price",
"Total Quantity",
"Add to Quantity",
"TCG Marketplace Price",
"Photo URL"
]
data = {}
for resulting_item in open_event.resulting_items:
marketplace_listing = await self.get_marketplace_listing(db, resulting_item.inventory_item, marketplace)
if marketplace_listing is None:
raise ValueError(f"No active marketplace listing found for inventory item id {resulting_item.inventory_item.id} in {marketplace.name}")
tcgplayer_sku_id = resulting_item.tcgplayer_sku_id
if tcgplayer_sku_id in data:
data[tcgplayer_sku_id]["Add to Quantity"] += 1
continue
product_line = resulting_item.products.category.name
set_name = resulting_item.products.group.name
product_name = resulting_item.products.name
title = ""
number = resulting_item.products.ext_number
rarity = resulting_item.products.ext_rarity
condition = " ".join([condition.capitalize() for condition in resulting_item.sku.condition.split(" ")]) + (" " + resulting_item.products.sub_type_name if resulting_item.products.sub_type_name == "Foil" else "")
tcg_market_price = resulting_item.products.most_recent_tcgplayer_price.market_price
tcg_direct_low = resulting_item.products.most_recent_tcgplayer_price.direct_low_price
tcg_low_price_with_shipping = resulting_item.products.most_recent_tcgplayer_price.low_price
tcg_low_price = resulting_item.products.most_recent_tcgplayer_price.low_price
total_quantity = ""
add_to_quantity = 1
# get average recommended price of product
# get inventory items with same tcgplayer_product_id
inventory_items = db.query(InventoryItem).filter(InventoryItem.physical_item.has(tcgplayer_sku_id=tcgplayer_sku_id)).all()
inventory_item_ids = [inventory_item.id for inventory_item in inventory_items]
logger.debug(f"inventory_item_ids: {inventory_item_ids}")
valid_listings = db.query(MarketplaceListing).filter(MarketplaceListing.inventory_item_id.in_(inventory_item_ids)).filter(MarketplaceListing.delisting_date == None).filter(MarketplaceListing.deleted_at == None).filter(MarketplaceListing.listing_date == None).all()
logger.debug(f"valid_listings: {valid_listings}")
avg_recommended_price = sum([listing.recommended_price.price for listing in valid_listings]) / len(valid_listings)
data[tcgplayer_sku_id] = {
"TCGplayer Id": tcgplayer_sku_id,
"Product Line": product_line,
"Set Name": set_name,
"Product Name": product_name,
"Title": title,
"Number": number,
"Rarity": rarity,
"Condition": condition,
"TCG Market Price": tcg_market_price,
"TCG Direct Low": tcg_direct_low,
"TCG Low Price With Shipping": tcg_low_price_with_shipping,
"TCG Low Price": tcg_low_price,
"Total Quantity": total_quantity,
"Add to Quantity": add_to_quantity,
"TCG Marketplace Price": f"{Decimal(avg_recommended_price):.2f}",
"Photo URL": ""
}
# format data into csv
# header
header_row = ",".join(headers)
# data
def escape_csv_value(value):
if value is None:
return ""
value = str(value)
if any(c in value for c in [',', '"', '\n']):
return f'"{value.replace('"', '""')}"'
return value
data_rows = [",".join([escape_csv_value(data[tcgplayer_id][header]) for header in headers]) for tcgplayer_id in data]
csv_data = "\n".join([header_row] + data_rows)
return csv_data

View File

@ -0,0 +1,139 @@
from app.services.base_service import BaseService
from sqlalchemy.orm import Session
from app.db.database import transaction
from app.schemas.file import FileInDB
from app.models.tcgplayer_products import TCGPlayerProduct, MTGJSONCard, MTGJSONSKU
from app.models.critical_error_log import CriticalErrorLog
from app.models.manabox_import_staging import ManaboxImportStaging
from typing import Dict, Any, Union, List
import csv
import logging
from datetime import datetime
from fastapi import BackgroundTasks
logger = logging.getLogger(__name__)
class ManaboxService(BaseService):
def __init__(self):
super().__init__(None)
async def process_manabox_csv(self, db: Session, bytes: bytes, metadata: Dict[str, Any], background_tasks: BackgroundTasks, wait: bool = False) -> Union[bool, List[FileInDB]]:
# save file
file = await self.file_service.save_file(
db=db,
file_data=bytes,
filename=f"manabox_{datetime.now().strftime('%Y%m%d%H%M%S')}.csv",
subdir="manabox",
file_type="manabox",
content_type="text/csv",
metadata=metadata
)
if wait:
await self._process_file_background(db, file)
return_value = await self.file_service.get_file(db, file.id)
return [return_value] if return_value else []
else:
background_tasks.add_task(self._process_file_background, db, file)
return True
async def _process_file_background(self, db: Session, file: FileInDB):
try:
# Read the CSV file
with open(file.path, 'r') as csv_file:
reader = csv.DictReader(csv_file)
logger.debug(f"Processing file: {file.path}")
# Pre-fetch all MTGJSONCards for Scryfall IDs in the file
scryfall_ids = {row['Scryfall ID'] for row in reader}
mtg_json_map = {card.scryfall_id: card for card in db.query(MTGJSONCard).filter(MTGJSONCard.scryfall_id.in_(scryfall_ids)).all()}
logger.debug(f"len ids: {len(scryfall_ids)}")
# Re-read the file to process the rows
csv_file.seek(0)
logger.debug(f"header: {reader.fieldnames}")
next(reader) # Skip the header row
staging_entries = [] # To collect all staging entries for batch insert
critical_errors = [] # To collect errors for logging
for row in reader:
logger.debug(f"Processing row: {row}")
mtg_json = mtg_json_map.get(row['Scryfall ID'])
if not mtg_json:
error_message = f"Error: No MTGJSONCard found for scryfall id: {row['Scryfall ID']}"
critical_errors.append(error_message)
continue # Skip this row
language = 'ENGLISH' if row['Language'] == 'en' else 'JAPANESE' if row['Language'] == 'ja' else None # manabox only needs en and jp for now
printing = 'foil' if 'foil' in row['Foil'].lower() or 'etched' in row['Foil'].lower() else 'normal'
condition = row['Condition'].replace('_', ' ').upper()
# Query the correct TCGPlayer SKU
sku_query = db.query(MTGJSONSKU).filter(
MTGJSONSKU.tcgplayer_product_id == (mtg_json.tcgplayer_etched_product_id if row['Foil'].lower() == 'etched' else mtg_json.tcgplayer_product_id)
).filter(
MTGJSONSKU.condition == condition,
MTGJSONSKU.normalized_printing == printing,
MTGJSONSKU.language == language
).distinct()
if sku_query.count() != 1:
error_message = f"Error: Multiple TCGplayer SKUs found for mtgjson name: {mtg_json.name} condition: {row['Condition']} language: {language} printing: {printing}"
critical_errors.append(error_message)
continue # Skip this row
tcgplayer_sku = sku_query.first()
if not tcgplayer_sku:
error_message = f"Error: No TCGplayer SKU found for mtgjson name: {mtg_json.name} condition: {row['Condition']} language: {language} printing: {printing}"
critical_errors.append(error_message)
continue # Skip this row
# Query TCGPlayer product data
tcgplayer_product = db.query(TCGPlayerProduct).filter(
TCGPlayerProduct.tcgplayer_product_id == tcgplayer_sku.tcgplayer_product_id,
TCGPlayerProduct.normalized_sub_type_name == tcgplayer_sku.normalized_printing
).distinct()
if tcgplayer_product.count() != 1:
error_message = f"Error: Multiple TCGPlayer products found for SKU {tcgplayer_sku.tcgplayer_sku_id}"
critical_errors.append(error_message)
continue # Skip this row
tcgplayer_product = tcgplayer_product.first()
if not tcgplayer_product:
error_message = f"Error: No TCGPlayer product found for SKU {tcgplayer_sku.tcgplayer_sku_id}"
critical_errors.append(error_message)
continue # Skip this row
# Prepare the staging entry
quantity = int(row['Quantity'])
logger.debug(f"inserting row file id: {file.id} tcgplayer_product_id: {tcgplayer_product.tcgplayer_product_id} tcgplayer_sku_id: {tcgplayer_sku.tcgplayer_sku_id} quantity: {quantity}")
staging_entries.append(ManaboxImportStaging(
file_id=file.id,
tcgplayer_product_id=tcgplayer_product.tcgplayer_product_id,
tcgplayer_sku_id=tcgplayer_sku.tcgplayer_sku_id,
quantity=quantity
))
# Bulk insert all valid ManaboxImportStaging entries
if staging_entries:
logger.debug(f"inserting {len(staging_entries)} rows")
with transaction(db):
db.bulk_save_objects(staging_entries)
# Log any critical errors that occurred
for error_message in critical_errors:
logger.debug(f"logging critical error: {error_message}")
with transaction(db):
critical_error_log = CriticalErrorLog(error_message=error_message)
db.add(critical_error_log)
except Exception as e:
logger.error(f"Error processing file: {str(e)}")
with transaction(db):
critical_error_log = CriticalErrorLog(error_message=f"Error processing file: {str(e)}")
db.add(critical_error_log)

View File

@ -0,0 +1,182 @@
import logging
from sqlalchemy.orm import Session
from app.services.base_service import BaseService
from app.models.inventory_management import InventoryItem, MarketplaceListing
from app.models.tcgplayer_inventory import TCGPlayerInventory
from app.models.pricing import PricingEvent
from app.db.database import transaction
from decimal import Decimal
from datetime import datetime
logger = logging.getLogger(__name__)
class PricingService(BaseService):
def __init__(self):
super().__init__(None)
async def set_price(self, db: Session, inventory_item: InventoryItem) -> float:
"""
TODO This sets listed_price per inventory_item but listed_price can only be applied to a product
however, this may be desired on other marketplaces
when generating pricing file for tcgplayer, give the option to set min, max, avg price for product?
"""
# Fetch base pricing data
cost_basis = Decimal(str(inventory_item.cost_basis))
market_price = Decimal(str(inventory_item.physical_item.sku.product.most_recent_tcgplayer_price.market_price))
tcg_low = Decimal(str(inventory_item.physical_item.sku.product.most_recent_tcgplayer_price.low_price))
tcg_mid = Decimal(str(inventory_item.physical_item.sku.product.most_recent_tcgplayer_price.mid_price))
listed_price = Decimal(str(inventory_item.marketplace_listing.listed_price)) if inventory_item.marketplace_listing else None
logger.info(f"listed_price: {listed_price}")
logger.info(f"market_price: {market_price}")
logger.info(f"tcg_low: {tcg_low}")
logger.info(f"tcg_mid: {tcg_mid}")
logger.info(f"cost_basis: {cost_basis}")
# TODO: Add logic to fetch lowest price for seller with same quantity in stock
# NOT IMPLEMENTED YET
lowest_price_for_quantity = Decimal('0.0')
# Hardcoded configuration values (should be parameterized later)
shipping_cost = Decimal('1.0')
tcgplayer_shipping_fee = Decimal('1.31')
average_cards_per_order = Decimal('3.0')
marketplace_fee_percentage = Decimal('0.20')
target_margin = Decimal('0.20')
velocity_multiplier = Decimal('0.0')
global_margin_multiplier = Decimal('0.00')
min_floor_price = Decimal('0.25')
price_drop_threshold = Decimal('0.20')
# TODO add age of inventory price decrease multiplier
age_of_inventory_multiplier = Decimal('0.0')
# card cost margin multiplier
if market_price > 0 and market_price < 2:
card_cost_margin_multiplier = Decimal('-0.033')
elif market_price >= 2 and market_price < 10:
card_cost_margin_multiplier = Decimal('0.0')
elif market_price >= 10 and market_price < 30:
card_cost_margin_multiplier = Decimal('0.0125')
elif market_price >= 30 and market_price < 50:
card_cost_margin_multiplier = Decimal('0.025')
elif market_price >= 50 and market_price < 100:
card_cost_margin_multiplier = Decimal('0.033')
elif market_price >= 100 and market_price < 200:
card_cost_margin_multiplier = Decimal('0.05')
# Fetch current total quantity in stock for SKU
quantity_record = db.query(TCGPlayerInventory).filter(
TCGPlayerInventory.tcgplayer_sku_id == inventory_item.physical_item.tcgplayer_sku_id
).first()
quantity_in_stock = quantity_record.total_quantity if quantity_record else 0
# Determine quantity multiplier based on stock levels
if quantity_in_stock < 4:
quantity_multiplier = Decimal('0.0')
elif quantity_in_stock == 4:
quantity_multiplier = Decimal('0.2')
elif 5 <= quantity_in_stock < 10:
quantity_multiplier = Decimal('0.3')
elif quantity_in_stock >= 10:
quantity_multiplier = Decimal('0.4')
else:
quantity_multiplier = Decimal('0.0')
# Calculate adjusted target margin from base and global multipliers
adjusted_target_margin = target_margin + global_margin_multiplier + card_cost_margin_multiplier
# limit shipping cost offset to 10% of market price
shipping_cost_offset = min(shipping_cost / average_cards_per_order, market_price * Decimal('0.1'))
# Calculate base price considering cost, shipping, fees, and margin targets
base_price = (cost_basis + shipping_cost_offset) / (
(Decimal('1.0') - marketplace_fee_percentage) - adjusted_target_margin
)
# Adjust base price by quantity and velocity multipliers, limit markup to amount of shipping fee
adjusted_price = min(
base_price * (Decimal('1.0') + quantity_multiplier + velocity_multiplier - age_of_inventory_multiplier),
base_price + tcgplayer_shipping_fee
)
# Enforce minimum floor price to ensure profitability
if adjusted_price < min_floor_price:
adjusted_price = min_floor_price
# Adjust price based on market prices (TCG low and TCG mid)
if adjusted_price < tcg_low:
adjusted_price = tcg_mid
price_used = "tcg mid"
price_reason = "adjusted price below tcg low"
elif adjusted_price > tcg_low and adjusted_price < (market_price * Decimal('0.8')) and adjusted_price < (tcg_mid * Decimal('0.8')):
adjusted_price = tcg_mid
price_used = "tcg mid"
price_reason = f"adjusted price below 80% of market price and tcg mid"
else:
price_used = "adjusted price"
price_reason = "valid price assigned based on margin targets"
# TODO: Add logic to adjust price to beat competitor price with same quantity
# NOT IMPLEMENTED YET
if adjusted_price < lowest_price_for_quantity:
adjusted_price = lowest_price_for_quantity - Decimal('0.01')
price_used = "lowest price for quantity"
price_reason = "adjusted price below lowest price for quantity"
# Fine-tune price to optimize for free shipping promotions
free_shipping_adjustment = False
for x in range(1, 5):
quantity = Decimal(str(x))
if Decimal('5.00') <= adjusted_price * quantity <= Decimal('5.15'):
adjusted_price = Decimal('4.99') / quantity
free_shipping_adjustment = True
break
# prevent price drop over price drop threshold
if listed_price and adjusted_price < (listed_price * (1 - price_drop_threshold)):
adjusted_price = listed_price
price_used = "listed price"
price_reason = "adjusted price below price drop threshold"
# Record pricing event in database transaction
with transaction(db):
pricing_event = PricingEvent(
inventory_item_id=inventory_item.id,
price=float(adjusted_price),
price_used=price_used,
price_reason=price_reason,
free_shipping_adjustment=free_shipping_adjustment
)
db.add(pricing_event)
# delete previous pricing events for inventory item
if inventory_item.marketplace_listing and inventory_item.marketplace_listing.listed_price:
inventory_item.marketplace_listing.listed_price.deleted_at = datetime.now()
return pricing_event
def set_price_for_unmanaged_inventory(self, db: Session, tcgplayer_sku_id: int, quantity: int) -> float:
pass
def update_price_for_product(self, db: Session, tcgplayer_sku_id: int) -> list[PricingEvent]:
# get inventory items for sku
updated_prices = []
inventory_items = db.query(InventoryItem).filter(
InventoryItem.physical_item.tcgplayer_sku_id == tcgplayer_sku_id
).all()
for inventory_item in inventory_items:
pricing_event = self.set_price(db, inventory_item)
updated_prices.append(pricing_event)
return updated_prices
def set_price_for_product(self, db: Session, tcgplayer_sku_id: int) -> float:
# update price for all inventory items for sku
prices = self.update_price_for_product(db, tcgplayer_sku_id)
sum_prices = sum(price.price for price in prices)
average_price = sum_prices / len(prices)
return average_price

View File

@ -113,7 +113,7 @@ class PullSheetService(BaseService):
'quantity': str(int(row['Quantity'])), # Convert to string for template
'set': row['Set'],
'rarity': row['Rarity'],
'card_number': str(int(row['Number'])) if 'Number' in row else ''
'card_number': str(int(row['Number'])) if 'Number' in row and pd.notna(row['Number']) and '/' not in str(row['Number']) else str(row['Number']) if 'Number' in row and pd.notna(row['Number']) and '/' in str(row['Number']) else ''
})
return items

View File

@ -1,6 +1,7 @@
from typing import Callable, Dict, Any
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.interval import IntervalTrigger
from apscheduler.triggers.cron import CronTrigger
import logging
from apscheduler.schedulers.base import SchedulerNotRunningError
@ -16,18 +17,28 @@ class BaseScheduler:
self,
task_name: str,
func: Callable,
interval_seconds: int,
interval_seconds: int = None,
cron_expression: str = None,
*args,
**kwargs
) -> None:
"""Schedule a task to run at regular intervals using APScheduler"""
if task_name in self.jobs:
logger.warning(f"Task {task_name} already exists. Removing existing job.")
self.jobs[task_name].remove()
if interval_seconds and cron_expression:
raise ValueError("Cannot specify both interval_seconds and cron_expression")
elif not interval_seconds and not cron_expression:
raise ValueError("Must specify either interval_seconds or cron_expression")
if interval_seconds:
trigger = IntervalTrigger(seconds=interval_seconds)
else:
trigger = CronTrigger.from_crontab(cron_expression)
job = self.scheduler.add_job(
func,
trigger=IntervalTrigger(seconds=interval_seconds),
func=func,
trigger=trigger,
args=args,
kwargs=kwargs,
id=task_name,
@ -35,7 +46,12 @@ class BaseScheduler:
)
self.jobs[task_name] = job
logger.info(f"Scheduled task {task_name} to run every {interval_seconds} seconds")
if interval_seconds:
logger.info(f"Scheduled task {task_name} to run every {interval_seconds} seconds")
else:
logger.info(f"Scheduled task {task_name} with cron expression: {cron_expression}")
def remove_task(self, task_name: str) -> None:
"""Remove a scheduled task"""

View File

@ -1,21 +1,16 @@
from app.db.database import transaction
from app.services.scheduler.base_scheduler import BaseScheduler
from app.services.base_service import BaseService
from sqlalchemy import text
import logging
from app.models.tcgplayer_inventory import UnmanagedTCGPlayerInventory, TCGPlayerInventory
logger = logging.getLogger(__name__)
class SchedulerService:
class SchedulerService(BaseService):
def __init__(self):
# Initialize BaseService with None as model since this service doesn't have a specific model
super().__init__(None)
self.scheduler = BaseScheduler()
# Service manager will be set during initialization
self._service_manager = None
@property
def service_manager(self):
if self._service_manager is None:
from app.services.service_manager import ServiceManager
self._service_manager = ServiceManager()
return self._service_manager
async def update_open_orders_hourly(self, db):
"""
@ -61,19 +56,41 @@ class SchedulerService:
logger.error(f"Error updating all orders: {str(e)}")
raise
async def refresh_tcgplayer_inventory_table(self, db):
"""
Refresh the TCGPlayer inventory table
"""
tcgplayer_inventory_service = self.service_manager.get_service('tcgplayer_inventory')
with transaction(db):
db.query(UnmanagedTCGPlayerInventory).delete()
db.query(TCGPlayerInventory).delete()
db.flush()
await tcgplayer_inventory_service.refresh_tcgplayer_inventory_table(db)
db.flush()
await tcgplayer_inventory_service.refresh_unmanaged_tcgplayer_inventory_table(db)
async def start_scheduled_tasks(self, db):
"""Start all scheduled tasks"""
# Schedule open orders update to run hourly at 00 minutes
await self.scheduler.schedule_task(
task_name="update_open_orders_hourly",
func=lambda: self.update_open_orders_hourly(db),
interval_seconds=60 * 60, # 1 hour
func=self.update_open_orders_hourly,
cron_expression="10 * * * *", # Run at minute 10 of every hour
db=db
)
# Schedule all orders update to run daily at 1 AM
# Schedule all orders update to run daily at 3 AM
await self.scheduler.schedule_task(
task_name="update_all_orders_daily",
func=lambda: self.update_all_orders_daily(db),
interval_seconds=24 * 60 * 60, # 24 hours
func=self.update_all_orders_daily,
cron_expression="0 3 * * *", # Run at 3:00 AM every day
db=db
)
# Schedule TCGPlayer inventory refresh to run every 3 hours
await self.scheduler.schedule_task(
task_name="refresh_tcgplayer_inventory_table",
func=self.refresh_tcgplayer_inventory_table,
cron_expression="28 */3 * * *", # Run at minute 28 of every 3rd hour
db=db
)
self.scheduler.start()

View File

@ -28,7 +28,14 @@ class ServiceManager:
'scheduler': 'app.services.scheduler.scheduler_service.SchedulerService',
'file': 'app.services.file_service.FileService',
'tcgcsv': 'app.services.external_api.tcgcsv.tcgcsv_service.TCGCSVService',
'mtgjson': 'app.services.external_api.mtgjson.mtgjson_service.MTGJSONService'
'mtgjson': 'app.services.external_api.mtgjson.mtgjson_service.MTGJSONService',
'manabox': 'app.services.manabox_service.ManaboxService',
'pricing': 'app.services.pricing_service.PricingService',
'inventory': 'app.services.inventory_service.InventoryService',
'box': 'app.services.inventory_service.BoxService',
'case': 'app.services.inventory_service.CaseService',
'marketplace_listing': 'app.services.inventory_service.MarketplaceListingService'
}
self._service_configs = {
'label_printer': {'printer_api_url': "http://192.168.1.110:8000"},

View File

@ -10,7 +10,7 @@ import aiohttp
import jinja2
from weasyprint import HTML
from app.services.base_service import BaseService
from app.models.tcgplayer_group import TCGPlayerGroup
from app.models.tcgplayer_products import TCGPlayerProduct, TCGPlayerGroup
log = logging.getLogger(__name__)

View File

@ -20,6 +20,22 @@
</script>
</head>
<body class="bg-gray-900 min-h-screen text-gray-100">
<!-- Navigation Menu -->
<nav class="bg-gray-800 shadow-sm">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex items-center justify-between h-16">
<div class="flex items-center">
<a href="/" class="text-xl font-bold text-gray-100">TCGPlayer Manager</a>
</div>
<div class="flex items-center space-x-4">
<a href="/" class="px-3 py-2 rounded-md text-sm font-medium text-gray-300 hover:text-white hover:bg-gray-700">Orders</a>
<a href="/manabox.html" class="px-3 py-2 rounded-md text-sm font-medium text-gray-300 hover:text-white hover:bg-gray-700">Manabox</a>
<a href="/transactions.html" class="px-3 py-2 rounded-md text-sm font-medium text-gray-300 hover:text-white hover:bg-gray-700">Transactions</a>
</div>
</div>
</div>
</nav>
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div class="bg-gray-800 rounded-xl shadow-sm p-6 mb-8">
<h1 class="text-3xl font-bold text-gray-100 mb-2">TCGPlayer Order Management</h1>

86
app/static/manabox.html Normal file
View File

@ -0,0 +1,86 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Manabox Inventory Management</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<script>
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
fontFamily: {
sans: ['Inter', 'sans-serif'],
},
}
}
}
</script>
</head>
<body class="bg-gray-900 min-h-screen text-gray-100">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div class="bg-gray-800 rounded-xl shadow-sm p-6 mb-8">
<h1 class="text-3xl font-bold text-gray-100 mb-2">Manabox Inventory Management</h1>
<p class="text-gray-400">Upload and manage your Manabox inventory</p>
</div>
<!-- File Upload Section -->
<div class="bg-gray-800 rounded-xl shadow-sm p-6 mb-8">
<h2 class="text-xl font-semibold text-gray-100 mb-6">Upload Manabox CSV</h2>
<form id="uploadForm" class="space-y-4">
<div>
<label for="source" class="block text-sm font-medium text-gray-300 mb-2">Source</label>
<input type="text" id="source" name="source" required
class="w-full rounded-lg border-gray-600 bg-gray-700 text-gray-100 focus:ring-blue-500 focus:border-blue-500">
</div>
<div>
<label for="description" class="block text-sm font-medium text-gray-300 mb-2">Description</label>
<textarea id="description" name="description" rows="3" required
class="w-full rounded-lg border-gray-600 bg-gray-700 text-gray-100 focus:ring-blue-500 focus:border-blue-500"></textarea>
</div>
<div>
<label for="csvFile" class="block text-sm font-medium text-gray-300 mb-2">CSV File</label>
<input type="file" id="csvFile" name="file" accept=".csv" required
class="w-full rounded-lg border-gray-600 bg-gray-700 text-gray-100 focus:ring-blue-500 focus:border-blue-500">
</div>
<button type="submit" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors">
Upload CSV
</button>
</form>
</div>
<!-- File Uploads List Section -->
<div class="bg-gray-800 rounded-xl shadow-sm p-6">
<div class="flex items-center justify-between mb-6">
<h2 class="text-xl font-semibold text-gray-100">Recent Uploads</h2>
<div class="flex items-center space-x-4">
<button onclick="selectAllUploads()" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors">
Select All
</button>
</div>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-700">
<thead>
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">
<input type="checkbox" id="selectAll" class="rounded border-gray-600 bg-gray-800 text-blue-600 focus:ring-blue-500">
</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Source</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Description</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Upload Date</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Status</th>
</tr>
</thead>
<tbody id="uploadsList" class="divide-y divide-gray-700">
<!-- Uploads will be populated here -->
</tbody>
</table>
</div>
</div>
</div>
<script src="/manabox.js"></script>
</body>
</html>

170
app/static/manabox.js Normal file
View File

@ -0,0 +1,170 @@
// API base URL
const API_BASE_URL = '/api';
// Selected uploads for actions
let selectedUploads = new Set();
// Show toast notification
function showToast(message, type = 'success') {
const toast = document.createElement('div');
toast.className = `fixed bottom-4 right-4 px-6 py-3 rounded-lg shadow-lg text-white ${
type === 'success' ? 'bg-green-600' : 'bg-red-600'
} transform translate-y-0 opacity-100 transition-all duration-300`;
toast.textContent = message;
document.body.appendChild(toast);
setTimeout(() => {
toast.style.transform = 'translateY(100%)';
toast.style.opacity = '0';
setTimeout(() => toast.remove(), 300);
}, 3000);
}
// Show loading state
function setLoading(isLoading) {
const buttons = document.querySelectorAll('button');
buttons.forEach(button => {
if (isLoading) {
button.disabled = true;
button.classList.add('opacity-50', 'cursor-not-allowed');
} else {
button.disabled = false;
button.classList.remove('opacity-50', 'cursor-not-allowed');
}
});
}
// Handle form submission
document.getElementById('uploadForm').addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData();
formData.append('file', document.getElementById('csvFile').files[0]);
formData.append('source', document.getElementById('source').value);
formData.append('description', document.getElementById('description').value);
try {
setLoading(true);
const response = await fetch(`${API_BASE_URL}/manabox/process-csv`, {
method: 'POST',
body: formData
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.detail || 'Failed to upload CSV');
}
showToast('CSV uploaded successfully');
document.getElementById('uploadForm').reset();
fetchUploads(); // Refresh the uploads list
} catch (error) {
showToast('Error uploading CSV: ' + error.message, 'error');
} finally {
setLoading(false);
}
});
// Fetch uploads from the API
async function fetchUploads() {
try {
setLoading(true);
const response = await fetch(`${API_BASE_URL}/manabox/manabox-file-uploads`);
if (!response.ok) {
throw new Error('Failed to fetch uploads');
}
const uploads = await response.json();
displayUploads(uploads);
} catch (error) {
showToast('Error fetching uploads: ' + error.message, 'error');
} finally {
setLoading(false);
}
}
// Display uploads in the UI
function displayUploads(uploads) {
const uploadsList = document.getElementById('uploadsList');
uploadsList.innerHTML = '';
if (!uploads || uploads.length === 0) {
uploadsList.innerHTML = '<tr><td colspan="5" class="px-6 py-4 text-center text-gray-400">No uploads found</td></tr>';
return;
}
uploads.forEach(upload => {
const row = document.createElement('tr');
row.className = 'hover:bg-gray-700';
row.dataset.uploadId = upload.id;
row.innerHTML = `
<td class="px-6 py-4 whitespace-nowrap">
<input type="checkbox" class="upload-checkbox rounded border-gray-600 bg-gray-800 text-blue-600 focus:ring-blue-500">
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-300">${upload.name || 'N/A'}</td>
<td class="px-6 py-4 text-sm text-gray-300">${upload.file_metadata?.description || 'N/A'}</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-300">${formatDate(upload.created_at)}</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="px-2 py-1 text-xs rounded-full bg-green-900/50 text-green-300">Processed</span>
</td>
`;
uploadsList.appendChild(row);
// Add click event listener to the checkbox
const checkbox = row.querySelector('.upload-checkbox');
checkbox.addEventListener('change', () => {
const uploadId = row.dataset.uploadId;
if (checkbox.checked) {
selectedUploads.add(uploadId);
} else {
selectedUploads.delete(uploadId);
}
});
});
}
// Helper function to format date
function formatDate(dateString) {
if (!dateString) return 'N/A';
const date = new Date(dateString);
return date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
}
// Select all uploads
function selectAllUploads() {
const checkboxes = document.querySelectorAll('.upload-checkbox');
const allSelected = checkboxes.length > 0 && Array.from(checkboxes).every(checkbox => checkbox.checked);
checkboxes.forEach(checkbox => {
checkbox.checked = !allSelected;
const row = checkbox.closest('tr');
const uploadId = row.dataset.uploadId;
if (!allSelected) {
selectedUploads.add(uploadId);
} else {
selectedUploads.delete(uploadId);
}
});
showToast(allSelected ? 'All uploads deselected' : 'All uploads selected');
}
// Initialize the page
document.addEventListener('DOMContentLoaded', () => {
fetchUploads();
// Add event listener for the select all checkbox
document.getElementById('selectAll').addEventListener('change', (e) => {
const checkboxes = document.querySelectorAll('.upload-checkbox');
checkboxes.forEach(checkbox => {
checkbox.checked = e.target.checked;
const row = checkbox.closest('tr');
const uploadId = row.dataset.uploadId;
if (e.target.checked) {
selectedUploads.add(uploadId);
} else {
selectedUploads.delete(uploadId);
}
});
});
});

View File

@ -106,3 +106,125 @@ button:hover {
width: 100%;
}
}
/* Transaction Page Styles */
.transaction-form {
max-width: 800px;
margin: 0 auto;
}
.transaction-form .form-group {
margin-bottom: 1.5rem;
}
.transaction-form label {
display: block;
margin-bottom: 0.5rem;
font-weight: 500;
}
.transaction-form .form-control {
width: 100%;
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
}
.transaction-form .btn-add {
margin-left: 0.5rem;
padding: 0.25rem 0.5rem;
font-size: 0.875rem;
}
.transaction-form .items-section {
margin-top: 2rem;
padding: 1rem;
border: 1px solid #ddd;
border-radius: 4px;
}
.transaction-list {
margin-top: 2rem;
}
.transaction-card {
padding: 1rem;
margin-bottom: 1rem;
border: 1px solid #ddd;
border-radius: 4px;
background-color: white;
}
.transaction-card h3 {
margin-bottom: 0.5rem;
color: #333;
}
.transaction-card p {
margin-bottom: 0.25rem;
color: #666;
}
/* Modal Styles */
.modal-content {
border-radius: 8px;
}
.modal-header {
border-bottom: 1px solid #dee2e6;
padding: 1rem;
}
.modal-body {
padding: 1.5rem;
}
.modal-footer {
border-top: 1px solid #dee2e6;
padding: 1rem;
}
/* Dark Mode Support */
body.dark-mode {
background-color: #1a1a1a;
color: #ffffff;
}
body.dark-mode .container {
background-color: #2d2d2d;
}
body.dark-mode .transaction-card {
background-color: #2d2d2d;
border-color: #404040;
}
body.dark-mode .transaction-card h3 {
color: #ffffff;
}
body.dark-mode .transaction-card p {
color: #b3b3b3;
}
body.dark-mode .modal-content {
background-color: #2d2d2d;
color: #ffffff;
}
body.dark-mode .modal-header,
body.dark-mode .modal-footer {
border-color: #404040;
}
body.dark-mode .form-control {
background-color: #404040;
border-color: #505050;
color: #ffffff;
}
body.dark-mode .form-control:focus {
background-color: #404040;
border-color: #007bff;
color: #ffffff;
}

View File

@ -0,0 +1,179 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Transactions - AI Giga TCG</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<script>
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
fontFamily: {
sans: ['Inter', 'sans-serif'],
},
}
}
}
</script>
</head>
<body class="bg-gray-900 min-h-screen text-gray-100">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<div class="bg-gray-800 rounded-xl shadow-sm p-6 mb-8">
<h1 class="text-3xl font-bold text-gray-100 mb-2">Transactions</h1>
<p class="text-gray-400">Manage your transactions</p>
</div>
<!-- Create Transaction Button -->
<div class="bg-gray-800 rounded-xl shadow-sm p-6 mb-8">
<button id="createTransactionBtn" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors">
Create New Transaction
</button>
</div>
<!-- Transaction List -->
<div id="transactionList" class="bg-gray-800 rounded-xl shadow-sm p-6">
<div class="flex justify-between items-center mb-4">
<h2 class="text-xl font-semibold text-gray-100">Recent Transactions</h2>
<div class="flex items-center space-x-4">
<div class="flex items-center space-x-2">
<label for="limitSelect" class="text-sm text-gray-300">Show:</label>
<select id="limitSelect" class="rounded-lg border-gray-600 bg-gray-700 text-gray-100 focus:ring-blue-500 focus:border-blue-500">
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
</div>
</div>
</div>
<div id="transactionsTable" class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-700">
<thead>
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Date</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Type</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Vendor/Customer</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Total</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-300 uppercase tracking-wider">Notes</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-700" id="transactionsBody">
<!-- Transactions will be loaded here -->
</tbody>
</table>
</div>
<!-- Pagination Controls -->
<div class="flex justify-between items-center mt-4">
<button id="prevPageBtn" class="px-4 py-2 bg-gray-700 text-white rounded-lg hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 transition-colors disabled:opacity-50 disabled:cursor-not-allowed">
Previous
</button>
<span id="pageInfo" class="text-gray-300">Page 1</span>
<button id="nextPageBtn" class="px-4 py-2 bg-gray-700 text-white rounded-lg hover:bg-gray-600 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 transition-colors disabled:opacity-50 disabled:cursor-not-allowed">
Next
</button>
</div>
</div>
<!-- Create Transaction Modal -->
<div id="createTransactionModal" class="fixed inset-0 bg-black bg-opacity-50 hidden flex items-center justify-center">
<div class="bg-gray-800 rounded-lg p-6 max-w-2xl w-full mx-4">
<div class="flex items-center justify-between mb-4">
<h3 class="text-xl font-semibold text-gray-100">Create Transaction</h3>
<button onclick="closeTransactionModal()" class="text-gray-400 hover:text-white">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
<form id="transactionForm" class="space-y-4">
<!-- Transaction Type -->
<div>
<label for="transactionType" class="block text-sm font-medium text-gray-300 mb-2">Transaction Type</label>
<select id="transactionType" class="w-full rounded-lg border-gray-600 bg-gray-700 text-gray-100 focus:ring-blue-500 focus:border-blue-500">
<option value="purchase" selected>Purchase</option>
<option value="sale">Sale</option>
</select>
</div>
<!-- Vendor/Customer Selection -->
<div>
<div class="flex items-center justify-between mb-2">
<label for="vendorSelect" class="block text-sm font-medium text-gray-300">Vendor</label>
<button type="button" id="addVendorBtn" class="px-2 py-1 text-sm bg-blue-600 text-white rounded hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors">
Add New
</button>
</div>
<select id="vendorSelect" class="w-full rounded-lg border-gray-600 bg-gray-700 text-gray-100 focus:ring-blue-500 focus:border-blue-500">
<option value="">Select a vendor</option>
</select>
</div>
<!-- Marketplace Selection (for sales) -->
<div id="marketplaceSection" class="hidden">
<div class="flex items-center justify-between mb-2">
<label for="marketplaceSelect" class="block text-sm font-medium text-gray-300">Marketplace</label>
<button type="button" id="addMarketplaceBtn" class="px-2 py-1 text-sm bg-blue-600 text-white rounded hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors">
Add New
</button>
</div>
<select id="marketplaceSelect" class="w-full rounded-lg border-gray-600 bg-gray-700 text-gray-100 focus:ring-blue-500 focus:border-blue-500">
<option value="">Select a marketplace</option>
</select>
</div>
<!-- Transaction Date -->
<div>
<label for="transactionDate" class="block text-sm font-medium text-gray-300 mb-2">Transaction Date</label>
<input type="datetime-local" id="transactionDate" class="w-full rounded-lg border-gray-600 bg-gray-700 text-gray-100 focus:ring-blue-500 focus:border-blue-500" required>
</div>
<!-- Transaction Notes -->
<div>
<label for="transactionNotes" class="block text-sm font-medium text-gray-300 mb-2">Notes</label>
<textarea id="transactionNotes" class="w-full rounded-lg border-gray-600 bg-gray-700 text-gray-100 focus:ring-blue-500 focus:border-blue-500" rows="3"></textarea>
</div>
<!-- Items Section -->
<div id="itemsSection" class="border border-gray-700 rounded-lg p-4">
<h5 class="text-lg font-medium text-gray-100 mb-4">Items</h5>
<div id="itemsContainer" class="space-y-4">
<!-- Items will be added here -->
</div>
<button type="button" id="addItemBtn" class="mt-4 px-4 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 transition-colors">
Add Item
</button>
</div>
</form>
<div class="flex justify-end space-x-3 mt-6">
<button onclick="closeTransactionModal()" class="px-4 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2 transition-colors">
Cancel
</button>
<button id="saveTransactionBtn" class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition-colors">
Save Transaction
</button>
</div>
</div>
</div>
<!-- Transaction Details Modal -->
<div id="transactionDetailsModal" class="fixed inset-0 bg-black bg-opacity-50 hidden flex items-center justify-center">
<div class="bg-gray-800 rounded-lg p-6 max-w-4xl w-full mx-4 max-h-[90vh] overflow-y-auto">
<div class="flex items-center justify-between mb-4">
<h3 class="text-xl font-semibold text-gray-100">Transaction Details</h3>
<button onclick="closeTransactionDetailsModal()" class="text-gray-400 hover:text-white">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
</svg>
</button>
</div>
<div id="transactionDetails" class="space-y-4">
<!-- Transaction details will be loaded here -->
</div>
</div>
</div>
</div>
<script src="transactions.js"></script>
</body>
</html>

1077
app/static/transactions.js Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
{"success": true, "errors": [], "results": [{"productId": 38444, "lowPrice": 154.95, "midPrice": 223.55, "highPrice": 275.98, "marketPrice": 218.59, "directLowPrice": null, "subTypeName": "Foil"}, {"productId": 38445, "lowPrice": 349.49, "midPrice": 374.75, "highPrice": 400.0, "marketPrice": 385.0, "directLowPrice": null, "subTypeName": "Foil"}, {"productId": 38446, "lowPrice": 97.71, "midPrice": 120.99, "highPrice": 255.61, "marketPrice": 258.98, "directLowPrice": null, "subTypeName": "Foil"}, {"productId": 38447, "lowPrice": 80.0, "midPrice": 109.99, "highPrice": 214.45, "marketPrice": 99.92, "directLowPrice": 94.98, "subTypeName": "Foil"}, {"productId": 57653, "lowPrice": 71.99, "midPrice": 75.99, "highPrice": 100.0, "marketPrice": 69.44, "directLowPrice": null, "subTypeName": "Foil"}, {"productId": 67401, "lowPrice": 28.01, "midPrice": 78.72, "highPrice": 119.99, "marketPrice": 33.73, "directLowPrice": 114.99, "subTypeName": "Foil"}, {"productId": 71898, "lowPrice": 167.25, "midPrice": 183.34, "highPrice": 223.35, "marketPrice": 167.25, "directLowPrice": 168.28, "subTypeName": "Foil"}, {"productId": 78237, "lowPrice": 36.0, "midPrice": 89.24, "highPrice": 159.91, "marketPrice": 47.42, "directLowPrice": 59.99, "subTypeName": "Foil"}, {"productId": 95046, "lowPrice": 43.0, "midPrice": 62.92, "highPrice": 97.57, "marketPrice": 64.98, "directLowPrice": 44.0, "subTypeName": "Foil"}, {"productId": 110267, "lowPrice": 23.75, "midPrice": 31.21, "highPrice": 48.4, "marketPrice": 27.89, "directLowPrice": null, "subTypeName": "Foil"}, {"productId": 125324, "lowPrice": 18.0, "midPrice": 25.99, "highPrice": 53.45, "marketPrice": 25.86, "directLowPrice": 28.58, "subTypeName": "Foil"}, {"productId": 154792, "lowPrice": 16.73, "midPrice": 25.43, "highPrice": 49.0, "marketPrice": 17.47, "directLowPrice": null, "subTypeName": "Foil"}, {"productId": 181788, "lowPrice": 23.57, "midPrice": 27.83, "highPrice": 51.0, "marketPrice": 23.83, "directLowPrice": null, "subTypeName": "Foil"}, {"productId": 205180, "lowPrice": 16.99, "midPrice": 29.99, "highPrice": 70.2, "marketPrice": 18.3, "directLowPrice": 70.58, "subTypeName": "Foil"}, {"productId": 228752, "lowPrice": 24.99, "midPrice": 29.98, "highPrice": 224.99, "marketPrice": 29.43, "directLowPrice": null, "subTypeName": "Foil"}, {"productId": 257182, "lowPrice": 30.6, "midPrice": 75.0, "highPrice": 141.0, "marketPrice": 30.6, "directLowPrice": null, "subTypeName": "Foil"}, {"productId": 455848, "lowPrice": 15.0, "midPrice": 27.98, "highPrice": 134.0, "marketPrice": 20.73, "directLowPrice": null, "subTypeName": "Foil"}, {"productId": 527738, "lowPrice": 14.69, "midPrice": 19.31, "highPrice": 80.0, "marketPrice": 16.28, "directLowPrice": null, "subTypeName": "Foil"}, {"productId": 600517, "lowPrice": 47.97, "midPrice": 61.22, "highPrice": 75.0, "marketPrice": 61.22, "directLowPrice": null, "subTypeName": "Foil"}]}

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
{"success": true, "errors": [], "results": [{"productId": 21705, "lowPrice": 0.18, "midPrice": 0.57, "highPrice": 29.99, "marketPrice": 0.56, "directLowPrice": 0.43, "subTypeName": "Foil"}, {"productId": 37765, "lowPrice": 0.1, "midPrice": 0.25, "highPrice": 1.6, "marketPrice": 0.14, "directLowPrice": 0.18, "subTypeName": "Normal"}, {"productId": 37767, "lowPrice": 0.18, "midPrice": 0.41, "highPrice": 4.99, "marketPrice": 0.25, "directLowPrice": 0.6, "subTypeName": "Normal"}, {"productId": 37772, "lowPrice": 0.14, "midPrice": 0.39, "highPrice": 4.99, "marketPrice": 0.15, "directLowPrice": 0.17, "subTypeName": "Normal"}, {"productId": 37773, "lowPrice": 0.05, "midPrice": 0.24, "highPrice": 3.99, "marketPrice": 0.12, "directLowPrice": 0.07, "subTypeName": "Normal"}, {"productId": 37778, "lowPrice": 0.09, "midPrice": 0.25, "highPrice": 3.13, "marketPrice": 0.22, "directLowPrice": 0.18, "subTypeName": "Normal"}, {"productId": 37780, "lowPrice": 0.04, "midPrice": 0.37, "highPrice": 4.99, "marketPrice": 0.13, "directLowPrice": 0.19, "subTypeName": "Normal"}, {"productId": 37784, "lowPrice": 0.15, "midPrice": 0.3, "highPrice": 4.99, "marketPrice": 0.22, "directLowPrice": 0.2, "subTypeName": "Normal"}, {"productId": 37785, "lowPrice": 0.15, "midPrice": 0.37, "highPrice": 3.99, "marketPrice": 0.19, "directLowPrice": 0.09, "subTypeName": "Normal"}, {"productId": 37788, "lowPrice": 0.14, "midPrice": 0.3, "highPrice": 3.0, "marketPrice": 0.31, "directLowPrice": 0.2, "subTypeName": "Normal"}, {"productId": 37789, "lowPrice": 0.15, "midPrice": 0.35, "highPrice": 4.99, "marketPrice": 0.31, "directLowPrice": 0.15, "subTypeName": "Normal"}, {"productId": 37790, "lowPrice": 0.14, "midPrice": 0.39, "highPrice": 3.99, "marketPrice": 0.22, "directLowPrice": 0.14, "subTypeName": "Normal"}, {"productId": 37793, "lowPrice": 0.05, "midPrice": 0.25, "highPrice": 3.99, "marketPrice": 0.08, "directLowPrice": 0.08, "subTypeName": "Normal"}, {"productId": 37799, "lowPrice": 0.1, "midPrice": 0.25, "highPrice": 3.0, "marketPrice": 0.2, "directLowPrice": 0.19, "subTypeName": "Normal"}, {"productId": 37802, "lowPrice": 0.1, "midPrice": 0.25, "highPrice": 5.0, "marketPrice": 0.17, "directLowPrice": 0.1, "subTypeName": "Normal"}, {"productId": 37809, "lowPrice": 0.1, "midPrice": 0.25, "highPrice": 1.67, "marketPrice": 0.19, "directLowPrice": 0.07, "subTypeName": "Normal"}, {"productId": 37810, "lowPrice": 0.09, "midPrice": 0.35, "highPrice": 5.25, "marketPrice": 0.28, "directLowPrice": null, "subTypeName": "Normal"}, {"productId": 37812, "lowPrice": 0.18, "midPrice": 0.51, "highPrice": 4.99, "marketPrice": 0.83, "directLowPrice": 0.18, "subTypeName": "Normal"}, {"productId": 37813, "lowPrice": 0.15, "midPrice": 0.25, "highPrice": 2.99, "marketPrice": 0.22, "directLowPrice": 0.14, "subTypeName": "Normal"}, {"productId": 37814, "lowPrice": 0.14, "midPrice": 0.28, "highPrice": 1.76, "marketPrice": 0.18, "directLowPrice": null, "subTypeName": "Normal"}]}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
{"success": true, "errors": [], "results": [{"productId": 70757, "lowPrice": 0.8, "midPrice": 2.48, "highPrice": 54.0, "marketPrice": 2.61, "directLowPrice": null, "subTypeName": "Foil"}, {"productId": 70758, "lowPrice": 1.14, "midPrice": 1.74, "highPrice": 12.88, "marketPrice": 1.37, "directLowPrice": null, "subTypeName": "Foil"}, {"productId": 70759, "lowPrice": 0.75, "midPrice": 2.05, "highPrice": 8.99, "marketPrice": 2.51, "directLowPrice": null, "subTypeName": "Foil"}, {"productId": 70760, "lowPrice": 0.79, "midPrice": 1.61, "highPrice": 6.0, "marketPrice": 1.17, "directLowPrice": 1.6, "subTypeName": "Foil"}, {"productId": 70761, "lowPrice": 8.0, "midPrice": 11.12, "highPrice": 49.95, "marketPrice": 11.01, "directLowPrice": 11.96, "subTypeName": "Foil"}, {"productId": 70762, "lowPrice": 0.66, "midPrice": 1.34, "highPrice": 3.99, "marketPrice": 1.22, "directLowPrice": null, "subTypeName": "Foil"}, {"productId": 70763, "lowPrice": 4.1, "midPrice": 6.0, "highPrice": 19.99, "marketPrice": 5.09, "directLowPrice": null, "subTypeName": "Foil"}, {"productId": 70764, "lowPrice": 15.53, "midPrice": 19.96, "highPrice": 159.99, "marketPrice": 19.05, "directLowPrice": 21.69, "subTypeName": "Foil"}, {"productId": 70765, "lowPrice": 100.0, "midPrice": 119.99, "highPrice": 400.0, "marketPrice": 85.88, "directLowPrice": null, "subTypeName": "Normal"}, {"productId": 70782, "lowPrice": 7.26, "midPrice": 13.24, "highPrice": 20.57, "marketPrice": 13.16, "directLowPrice": 17.05, "subTypeName": "Foil"}, {"productId": 70783, "lowPrice": 4.0, "midPrice": 6.15, "highPrice": 11.84, "marketPrice": 5.69, "directLowPrice": null, "subTypeName": "Foil"}, {"productId": 70784, "lowPrice": 3.86, "midPrice": 5.41, "highPrice": 19.55, "marketPrice": 4.66, "directLowPrice": null, "subTypeName": "Foil"}, {"productId": 70785, "lowPrice": 1.25, "midPrice": 1.9, "highPrice": 7.4, "marketPrice": 1.6, "directLowPrice": null, "subTypeName": "Foil"}, {"productId": 70786, "lowPrice": 3.0, "midPrice": 4.4, "highPrice": 10.0, "marketPrice": 3.42, "directLowPrice": 4.39, "subTypeName": "Foil"}, {"productId": 70787, "lowPrice": 9.25, "midPrice": 11.47, "highPrice": 49.88, "marketPrice": 11.19, "directLowPrice": 19.37, "subTypeName": "Foil"}, {"productId": 70789, "lowPrice": 0.5, "midPrice": 1.04, "highPrice": 16.99, "marketPrice": 0.8, "directLowPrice": 1.07, "subTypeName": "Foil"}, {"productId": 70790, "lowPrice": 0.39, "midPrice": 0.99, "highPrice": 16.45, "marketPrice": 0.68, "directLowPrice": 0.6, "subTypeName": "Foil"}, {"productId": 70791, "lowPrice": 0.23, "midPrice": 0.59, "highPrice": 3.99, "marketPrice": 0.45, "directLowPrice": 0.49, "subTypeName": "Foil"}, {"productId": 70792, "lowPrice": 1.5, "midPrice": 2.49, "highPrice": 9.99, "marketPrice": 2.26, "directLowPrice": null, "subTypeName": "Foil"}, {"productId": 70793, "lowPrice": 49.0, "midPrice": 59.02, "highPrice": 200.0, "marketPrice": 59.02, "directLowPrice": 51.49, "subTypeName": "Foil"}, {"productId": 70794, "lowPrice": 1.8, "midPrice": 3.91, "highPrice": 88.0, "marketPrice": 3.69, "directLowPrice": 2.94, "subTypeName": "Foil"}]}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
{"success": true, "errors": [], "results": [{"productId": 21603, "lowPrice": 11.0, "midPrice": 16.55, "highPrice": 97.99, "marketPrice": 17.1, "directLowPrice": null, "subTypeName": "Normal"}, {"productId": 21604, "lowPrice": 4.95, "midPrice": 10.97, "highPrice": 14.97, "marketPrice": 6.99, "directLowPrice": null, "subTypeName": "Normal"}, {"productId": 21605, "lowPrice": 10.0, "midPrice": 13.95, "highPrice": 79.45, "marketPrice": 14.97, "directLowPrice": 5.01, "subTypeName": "Normal"}, {"productId": 21606, "lowPrice": 18.95, "midPrice": 21.88, "highPrice": 86.37, "marketPrice": 17.0, "directLowPrice": 43.94, "subTypeName": "Normal"}, {"productId": 21607, "lowPrice": 0.38, "midPrice": 2.46, "highPrice": 5.7, "marketPrice": 1.5, "directLowPrice": null, "subTypeName": "Normal"}, {"productId": 21608, "lowPrice": 1.55, "midPrice": 2.47, "highPrice": 4.95, "marketPrice": 2.0, "directLowPrice": null, "subTypeName": "Normal"}, {"productId": 21609, "lowPrice": 98.95, "midPrice": 103.94, "highPrice": 108.93, "marketPrice": 57.85, "directLowPrice": null, "subTypeName": "Normal"}, {"productId": 21610, "lowPrice": 4.73, "midPrice": 7.87, "highPrice": 11.0, "marketPrice": 7.61, "directLowPrice": null, "subTypeName": "Normal"}, {"productId": 21611, "lowPrice": 9.0, "midPrice": 16.58, "highPrice": 22.17, "marketPrice": 14.16, "directLowPrice": null, "subTypeName": "Normal"}, {"productId": 21612, "lowPrice": 5.99, "midPrice": 6.91, "highPrice": 15.74, "marketPrice": 6.29, "directLowPrice": null, "subTypeName": "Normal"}, {"productId": 21613, "lowPrice": 9.55, "midPrice": 14.61, "highPrice": 18.95, "marketPrice": 13.98, "directLowPrice": null, "subTypeName": "Normal"}, {"productId": 21614, "lowPrice": 1.7, "midPrice": 2.5, "highPrice": 4.95, "marketPrice": 1.4, "directLowPrice": null, "subTypeName": "Normal"}, {"productId": 21615, "lowPrice": 3.86, "midPrice": 5.5, "highPrice": 8.0, "marketPrice": 6.07, "directLowPrice": null, "subTypeName": "Normal"}, {"productId": 21616, "lowPrice": 8.84, "midPrice": 19.97, "highPrice": 24.95, "marketPrice": 8.5, "directLowPrice": null, "subTypeName": "Normal"}, {"productId": 21617, "lowPrice": 6.92, "midPrice": 8.0, "highPrice": 13.99, "marketPrice": 7.23, "directLowPrice": 5.23, "subTypeName": "Normal"}, {"productId": 21618, "lowPrice": 25.0, "midPrice": 29.91, "highPrice": 50.0, "marketPrice": 27.0, "directLowPrice": null, "subTypeName": "Normal"}, {"productId": 21619, "lowPrice": 8.0, "midPrice": 11.41, "highPrice": 18.61, "marketPrice": 9.89, "directLowPrice": null, "subTypeName": "Normal"}, {"productId": 21620, "lowPrice": 15.0, "midPrice": 18.95, "highPrice": 20.59, "marketPrice": 8.76, "directLowPrice": null, "subTypeName": "Normal"}, {"productId": 21621, "lowPrice": 18.5, "midPrice": 22.49, "highPrice": 49.95, "marketPrice": 25.0, "directLowPrice": null, "subTypeName": "Normal"}, {"productId": 21622, "lowPrice": 7.99, "midPrice": 16.72, "highPrice": 19.95, "marketPrice": 11.91, "directLowPrice": null, "subTypeName": "Normal"}, {"productId": 21623, "lowPrice": 12.0, "midPrice": 15.0, "highPrice": 16.99, "marketPrice": 15.95, "directLowPrice": null, "subTypeName": "Normal"}, {"productId": 21624, "lowPrice": 0.59, "midPrice": 1.87, "highPrice": 9.95, "marketPrice": 2.5, "directLowPrice": null, "subTypeName": "Normal"}, {"productId": 21625, "lowPrice": 49.95, "midPrice": 71.67, "highPrice": 99.95, "marketPrice": 66.45, "directLowPrice": null, "subTypeName": "Normal"}, {"productId": 21626, "lowPrice": 3.99, "midPrice": 4.87, "highPrice": 8.12, "marketPrice": 5.5, "directLowPrice": 3.0, "subTypeName": "Normal"}, {"productId": 21627, "lowPrice": 6.77, "midPrice": 9.03, "highPrice": 24.99, "marketPrice": 5.16, "directLowPrice": null, "subTypeName": "Normal"}, {"productId": 21628, "lowPrice": 1.1, "midPrice": 2.25, "highPrice": 4.23, "marketPrice": 1.5, "directLowPrice": null, "subTypeName": "Normal"}, {"productId": 21629, "lowPrice": 5.0, "midPrice": 5.22, "highPrice": 13.97, "marketPrice": 5.0, "directLowPrice": null, "subTypeName": "Normal"}, {"productId": 21630, "lowPrice": 16.8, "midPrice": 27.54, "highPrice": 45.0, "marketPrice": 15.95, "directLowPrice": null, "subTypeName": "Normal"}, {"productId": 21631, "lowPrice": 114.95, "midPrice": 150.0, "highPrice": 154.97, "marketPrice": 42.5, "directLowPrice": null, "subTypeName": "Normal"}, {"productId": 21632, "lowPrice": 16.99, "midPrice": 55.98, "highPrice": 150.0, "marketPrice": 14.79, "directLowPrice": null, "subTypeName": "Normal"}, {"productId": 21633, "lowPrice": 10.99, "midPrice": 13.84, "highPrice": 23.95, "marketPrice": 15.49, "directLowPrice": 19.88, "subTypeName": "Normal"}, {"productId": 21634, "lowPrice": 49.97, "midPrice": 74.36, "highPrice": 98.75, "marketPrice": 49.97, "directLowPrice": null, "subTypeName": "Normal"}]}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

Some files were not shown because too many files have changed in this diff Show More