color identity in pull sheet

This commit is contained in:
zman 2025-05-31 12:51:25 -04:00
parent fa089adb53
commit 7bc64115f2
5 changed files with 81 additions and 6 deletions

View File

@ -90,6 +90,12 @@ tr:hover {
text-align: center; text-align: center;
} }
.color-identity {
width: 40px;
text-align: center;
font-weight: bold;
}
.product-name { .product-name {
width: 200px; width: 200px;
} }
@ -119,6 +125,7 @@ tbody tr:hover {
<th class="set">Set</th> <th class="set">Set</th>
<th class="rarity">Rarity</th> <th class="rarity">Rarity</th>
<th class="card-number">Card #</th> <th class="card-number">Card #</th>
<th class="color-identity">Colors</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@ -129,6 +136,7 @@ tbody tr:hover {
<td class="set">{{ item.set }}</td> <td class="set">{{ item.set }}</td>
<td class="rarity">{{ item.rarity }}</td> <td class="rarity">{{ item.rarity }}</td>
<td class="card-number">{{ item.card_number }}</td> <td class="card-number">{{ item.card_number }}</td>
<td class="color-identity">{{ item.color_identity }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>

View File

@ -35,5 +35,6 @@ __all__ = [
'OrderManagementService', 'OrderManagementService',
'TCGPlayerInventoryService', 'TCGPlayerInventoryService',
'PricingService', 'PricingService',
'MarketplaceListingService' 'MarketplaceListingService',
'ScryfallService'
] ]

View File

@ -0,0 +1,11 @@
from app.services.external_api.base_external_service import BaseExternalService
class ScryfallService(BaseExternalService):
def __init__(self):
super().__init__(base_url="https://api.scryfall.com/")
async def get_color_identity(self, scryfall_id: str) -> str:
"""Get the color identity of a card from Scryfall API"""
endpoint = f"cards/{scryfall_id}"
results = await self._make_request("GET", endpoint)
return results['color_identity']

View File

@ -1,4 +1,5 @@
from typing import List, Dict from typing import List, Dict
import json
import pandas as pd import pandas as pd
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
@ -9,7 +10,7 @@ import asyncio
from app.schemas.file import FileInDB from app.schemas.file import FileInDB
from app.services.base_service import BaseService from app.services.base_service import BaseService
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.models.tcgplayer_products import TCGPlayerProduct, TCGPlayerGroup, MTGJSONSKU, MTGJSONCard
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -48,7 +49,7 @@ class PullSheetService(BaseService):
""" """
try: try:
# Read and process CSV data # Read and process CSV data
items = await self._read_and_process_csv(file.path) items = await self._read_and_process_csv(db, file.path)
# Prepare template data # Prepare template data
template_data = { template_data = {
@ -79,8 +80,51 @@ class PullSheetService(BaseService):
except Exception as e: except Exception as e:
logger.error(f"Error generating pull sheet PDF: {str(e)}") logger.error(f"Error generating pull sheet PDF: {str(e)}")
raise raise
async def _get_color_identity(self, db: Session, row: pd.Series) -> str:
"""Get color identity from a row.
Args:
row: pandas Series
"""
# get category id from set name
group_id = db.query(TCGPlayerGroup).filter(TCGPlayerGroup.name == row['Set']).first().group_id
# format number
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 ''
# get product info from category id
product_id = db.query(TCGPlayerProduct).filter(TCGPlayerProduct.group_id == group_id).filter(TCGPlayerProduct.name == row['Product Name']).filter(TCGPlayerProduct.ext_number == number).filter(TCGPlayerProduct.ext_rarity == row['Rarity']).first().tcgplayer_product_id
# get scryfall id from product id
mtgjson_id = db.query(MTGJSONSKU).filter(MTGJSONSKU.tcgplayer_product_id == product_id).first().mtgjson_uuid
scryfall_id = db.query(MTGJSONCard).filter(MTGJSONCard.mtgjson_uuid == mtgjson_id).first().scryfall_id
# get color identity from scryfall
scryfall_service = self.get_service('scryfall')
color_identity = await scryfall_service.get_color_identity(scryfall_id)
if color_identity is None:
return '?'
# color identity is str of json array, convert to human readable string of list
color_identity = [str(color) for color in color_identity]
# if color identity is empty, return C for colorless
if not color_identity:
return 'C'
# ensure order, W > U > B > R > G
color_identity = sorted(color_identity, key=lambda x: ['W', 'U', 'B', 'R', 'G'].index(x))
color_identity = ''.join(color_identity)
return color_identity
async def _update_row_color_identity(self, db: Session, row: pd.Series) -> pd.Series:
"""Update color identity from a row.
Args:
row: pandas Series
"""
# get color identity from row
color_identity = await self._get_color_identity(db, row)
# update row with color identity
row['Color Identity'] = color_identity
return row
async def _read_and_process_csv(self, csv_path: str) -> List[Dict]: async def _read_and_process_csv(self, db: Session, csv_path: str) -> List[Dict]:
"""Read and process CSV data using pandas. """Read and process CSV data using pandas.
Args: Args:
@ -103,6 +147,15 @@ class PullSheetService(BaseService):
# Sort by Set Release Date (descending) and then Product Name (ascending) # Sort by Set Release Date (descending) and then Product Name (ascending)
df = df.sort_values(['Set Release Date', 'Set', 'Product Name'], ascending=[False, True, True]) df = df.sort_values(['Set Release Date', 'Set', 'Product Name'], ascending=[False, True, True])
# Process color identities for all rows
color_identities = []
for _, row in df.iterrows():
color_identity = await self._get_color_identity(db, row)
color_identities.append(color_identity)
# Add color identity column to dataframe
df['Color Identity'] = color_identities
# Convert to list of dictionaries # Convert to list of dictionaries
items = [] items = []
@ -113,7 +166,8 @@ class PullSheetService(BaseService):
'quantity': str(int(row['Quantity'])), # Convert to string for template 'quantity': str(int(row['Quantity'])), # Convert to string for template
'set': row['Set'], 'set': row['Set'],
'rarity': row['Rarity'], 'rarity': row['Rarity'],
'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 '' '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 '',
'color_identity': row['Color Identity']
}) })
return items return items

View File

@ -34,7 +34,8 @@ class ServiceManager:
'inventory': 'app.services.inventory_service.InventoryService', 'inventory': 'app.services.inventory_service.InventoryService',
'box': 'app.services.inventory_service.BoxService', 'box': 'app.services.inventory_service.BoxService',
'case': 'app.services.inventory_service.CaseService', 'case': 'app.services.inventory_service.CaseService',
'marketplace_listing': 'app.services.inventory_service.MarketplaceListingService' 'marketplace_listing': 'app.services.inventory_service.MarketplaceListingService',
'scryfall': 'app.services.external_api.scryfall.scryfall_service.ScryfallService'
} }
self._service_configs = { self._service_configs = {