Compare commits
18 Commits
cc829c3b54
...
main
Author | SHA1 | Date | |
---|---|---|---|
8f1789101c | |||
976f2683a4 | |||
b151ef9920 | |||
fb3134723a | |||
8155ba2f8d | |||
a97ba6e858 | |||
3939c21a72 | |||
9ff87dc107 | |||
de3821aa80 | |||
e67c1aa9f3 | |||
94a3a517c7 | |||
64897392f1 | |||
3199a3259f | |||
0e5ba991db | |||
3601bcc81b | |||
c234285788 | |||
3ec9fef3cf | |||
9e656ef329 |
@ -27,6 +27,8 @@ class PricingService:
|
|||||||
# function for taking a tcgplayer pricing export with all set ids and loading it into the price table
|
# function for taking a tcgplayer pricing export with all set ids and loading it into the price table
|
||||||
# can be run as needed or scheduled
|
# can be run as needed or scheduled
|
||||||
def get_pricing_export_content(self, file: File = None) -> bytes:
|
def get_pricing_export_content(self, file: File = None) -> bytes:
|
||||||
|
if ACTIVE_PRICING_ALGORITHIM == 'default_pricing_algo' and FREE_SHIPPING == True:
|
||||||
|
print("asdf")
|
||||||
if file:
|
if file:
|
||||||
file_content = self.file_service.get_file_content(file.id)
|
file_content = self.file_service.get_file_content(file.id)
|
||||||
else:
|
else:
|
||||||
@ -101,16 +103,19 @@ class PricingService:
|
|||||||
self.tcgplayer_service.load_tcgplayer_cards(file_content)
|
self.tcgplayer_service.load_tcgplayer_cards(file_content)
|
||||||
self.load_pricing_csv_content_to_db(file_content)
|
self.load_pricing_csv_content_to_db(file_content)
|
||||||
|
|
||||||
def get_all_prices_for_products(self, product_ids: List[str]) -> Dict[str, Dict[str, float]]:
|
def get_latest_prices_for_products(self, product_ids: List[str]) -> Dict[str, Dict[str, float]]:
|
||||||
all_prices = self.db.query(Price).filter(
|
latest_prices = self.db.query(Price).filter(
|
||||||
Price.product_id.in_(product_ids)
|
Price.product_id.in_(product_ids)
|
||||||
|
).distinct(Price.product_id, Price.type).order_by(
|
||||||
|
Price.product_id,
|
||||||
|
Price.type,
|
||||||
|
Price.date_created.desc()
|
||||||
).all()
|
).all()
|
||||||
|
|
||||||
price_lookup = {}
|
price_lookup = {}
|
||||||
for price in all_prices:
|
for price in latest_prices:
|
||||||
if price.product_id not in price_lookup:
|
price_lookup.setdefault(price.product_id, {})[price.type] = price.price
|
||||||
price_lookup[price.product_id] = {}
|
|
||||||
price_lookup[price.product_id][price.type] = price.price
|
|
||||||
return price_lookup
|
return price_lookup
|
||||||
|
|
||||||
def apply_price_to_df_columns(self, row: pd.Series, price_lookup: Dict[str, Dict[str, float]]) -> pd.Series:
|
def apply_price_to_df_columns(self, row: pd.Series, price_lookup: Dict[str, Dict[str, float]]) -> pd.Series:
|
||||||
@ -119,7 +124,7 @@ class PricingService:
|
|||||||
row[price_type] = price
|
row[price_type] = price
|
||||||
return row
|
return row
|
||||||
|
|
||||||
def smooth_markup(price, markup_bands):
|
def smooth_markup(self, price, markup_bands):
|
||||||
"""
|
"""
|
||||||
Applies a smoothed markup based on the given price and markup bands.
|
Applies a smoothed markup based on the given price and markup bands.
|
||||||
Uses numpy for smooth transitions.
|
Uses numpy for smooth transitions.
|
||||||
@ -154,7 +159,7 @@ class PricingService:
|
|||||||
tcg_low = Decimal(str(row.get('tcg_low_price'))) if not pd.isna(row.get('tcg_low_price')) else None
|
tcg_low = Decimal(str(row.get('tcg_low_price'))) if not pd.isna(row.get('tcg_low_price')) else None
|
||||||
tcg_low_shipping = Decimal(str(row.get('tcg_low_price_with_shipping'))) if not pd.isna(row.get('tcg_low_price_with_shipping')) else None
|
tcg_low_shipping = Decimal(str(row.get('tcg_low_price_with_shipping'))) if not pd.isna(row.get('tcg_low_price_with_shipping')) else None
|
||||||
tcg_market_price = Decimal(str(row.get('tcg_market_price'))) if not pd.isna(row.get('tcg_market_price')) else None
|
tcg_market_price = Decimal(str(row.get('tcg_market_price'))) if not pd.isna(row.get('tcg_market_price')) else None
|
||||||
current_price = Decimal(str(row.get('tcg_marketplace_price'))) if not pd.isna(row.get('tcgplayer_marketplace_price')) else None
|
#current_price = Decimal(str(row.get('tcg_marketplace_price'))) if not pd.isna(row.get('tcg_marketplace_price')) else None
|
||||||
total_quantity = str(row.get('total_quantity')) if not pd.isna(row.get('total_quantity')) else "0"
|
total_quantity = str(row.get('total_quantity')) if not pd.isna(row.get('total_quantity')) else "0"
|
||||||
added_quantity = str(row.get('add_to_quantity')) if not pd.isna(row.get('add_to_quantity')) else "0"
|
added_quantity = str(row.get('add_to_quantity')) if not pd.isna(row.get('add_to_quantity')) else "0"
|
||||||
quantity = int(total_quantity) + int(added_quantity)
|
quantity = int(total_quantity) + int(added_quantity)
|
||||||
@ -163,54 +168,74 @@ class PricingService:
|
|||||||
logger.warning(f"Missing pricing data for row: {row}")
|
logger.warning(f"Missing pricing data for row: {row}")
|
||||||
row['new_price'] = None
|
row['new_price'] = None
|
||||||
return row
|
return row
|
||||||
# Define precision for rounding
|
|
||||||
TWO_PLACES = Decimal('0.01')
|
TWO_PLACES = Decimal('0.01')
|
||||||
|
|
||||||
# Apply pricing rules
|
# Original markup bands
|
||||||
markup_bands = {
|
markup_bands = {
|
||||||
2.53: (Decimal('0.01'), Decimal('0.50')),
|
Decimal('2.34'): (Decimal('0.01'), Decimal('0.50')),
|
||||||
1.42: (Decimal('0.51'), Decimal('1.00')),
|
Decimal('1.36'): (Decimal('0.51'), Decimal('1.00')),
|
||||||
1.29: (Decimal('1.01'), Decimal('3.00')),
|
Decimal('1.24'): (Decimal('1.01'), Decimal('3.00')),
|
||||||
1.17: (Decimal('3.01'), Decimal('20.00')),
|
Decimal('1.15'): (Decimal('3.01'), Decimal('20.00')),
|
||||||
1.07: (Decimal('20.01'), Decimal('35.00')),
|
Decimal('1.06'): (Decimal('20.01'), Decimal('35.00')),
|
||||||
1.05: (Decimal('35.01'), Decimal('50.00')),
|
Decimal('1.05'): (Decimal('35.01'), Decimal('50.00')),
|
||||||
1.03: (Decimal('50.01'), Decimal('100.00')),
|
Decimal('1.03'): (Decimal('50.01'), Decimal('100.00')),
|
||||||
1.02: (Decimal('100.01'), Decimal('200.00')),
|
Decimal('1.02'): (Decimal('100.01'), Decimal('200.00')),
|
||||||
1.01: (Decimal('200.01'), Decimal('1000.00'))
|
Decimal('1.01'): (Decimal('200.01'), Decimal('1000.00'))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Adjust markups if quantity is high
|
||||||
if quantity > 3:
|
if quantity > 3:
|
||||||
quantity_markup = Decimal('0.1')
|
adjusted_bands = {}
|
||||||
for markup in markup_bands:
|
increment = Decimal('0.20')
|
||||||
markup = markup + quantity_markup
|
for markup, price_range in zip(markup_bands.keys(), markup_bands.values()):
|
||||||
quantity_markup = quantity_markup - Decimal('0.01')
|
new_markup = Decimal(str(markup)) + increment
|
||||||
|
adjusted_bands[new_markup] = price_range
|
||||||
|
increment -= Decimal('0.02')
|
||||||
|
markup_bands = adjusted_bands
|
||||||
|
|
||||||
if FREE_SHIPPING:
|
#if FREE_SHIPPING:
|
||||||
free_shipping_markup = Decimal('0.05')
|
#if tcg_low_shipping and (tcg_low >= Decimal('5.00')):
|
||||||
for markup in markup_bands:
|
#tcg_compare_price = tcg_low_shipping
|
||||||
markup = markup + free_shipping_markup
|
#elif tcg_low_shipping and (tcg_low < Decimal('5.00')):
|
||||||
free_shipping_markup = free_shipping_markup - Decimal('0.005')
|
#tcg_compare_price = max(tcg_low_shipping - Decimal('1.31'), tcg_low)
|
||||||
|
#elif tcg_low:
|
||||||
|
#tcg_compare_price = tcg_low
|
||||||
|
#else:
|
||||||
|
#logger.warning(f"No TCG low or shipping price available for row: {row}")
|
||||||
|
#row['new_price'] = None
|
||||||
|
#return row
|
||||||
|
#else:
|
||||||
|
#tcg_compare_price = tcg_low
|
||||||
|
#if tcg_compare_price is None:
|
||||||
|
#logger.warning(f"No TCG low price available for row: {row}")
|
||||||
|
#row['new_price'] = None
|
||||||
|
#return row
|
||||||
|
|
||||||
|
tcg_compare_price = tcg_low
|
||||||
|
|
||||||
# Apply the smoothed markup
|
# Apply the smoothed markup
|
||||||
new_price = self.smooth_markup(tcg_market_price, markup_bands)
|
new_price = self.smooth_markup(tcg_compare_price, markup_bands)
|
||||||
|
|
||||||
if tcg_low_shipping is not None and tcg_low_shipping < new_price:
|
# Enforce minimum price
|
||||||
new_price = tcg_low_shipping
|
if new_price < Decimal('0.35'):
|
||||||
|
|
||||||
if new_price < Decimal('0.25'):
|
|
||||||
new_price = Decimal('0.25')
|
new_price = Decimal('0.25')
|
||||||
|
|
||||||
if current_price / new_price > Decimal('0.25'):
|
# Avoid huge price drops
|
||||||
logger.warning(f"Price drop too large for row: {row}")
|
#if current_price is not None and Decimal(str(((current_price - new_price) / current_price))) > Decimal('0.5'):
|
||||||
new_price = current_price
|
#logger.warning(f"Price drop too large for row: {row}")
|
||||||
|
#new_price = current_price
|
||||||
|
|
||||||
# Ensure exactly 2 decimal places
|
# Round to 2 decimal places
|
||||||
new_price = new_price.quantize(TWO_PLACES, rounding=ROUND_HALF_UP)
|
new_price = new_price.quantize(TWO_PLACES, rounding=ROUND_HALF_UP)
|
||||||
# Convert back to float or string as needed for your dataframe
|
|
||||||
|
# Convert back to float for dataframe
|
||||||
row['new_price'] = float(new_price)
|
row['new_price'] = float(new_price)
|
||||||
|
|
||||||
|
|
||||||
return row
|
return row
|
||||||
|
|
||||||
|
|
||||||
def default_pricing_algo(self, row: pd.Series) -> pd.Series:
|
def default_pricing_algo(self, row: pd.Series) -> pd.Series:
|
||||||
"""Default pricing algorithm with complex pricing rules"""
|
"""Default pricing algorithm with complex pricing rules"""
|
||||||
|
|
||||||
@ -263,11 +288,17 @@ class PricingService:
|
|||||||
|
|
||||||
def apply_pricing_algo(self, row: pd.Series, pricing_algo: callable = None) -> pd.Series:
|
def apply_pricing_algo(self, row: pd.Series, pricing_algo: callable = None) -> pd.Series:
|
||||||
"""Modified to handle the pricing algorithm as an instance method"""
|
"""Modified to handle the pricing algorithm as an instance method"""
|
||||||
if pricing_algo is None or ACTIVE_PRICING_ALGORITHIM == 'default_pricing_algo':
|
if pricing_algo:
|
||||||
|
logger.debug(f"Using custom pricing algorithm: {pricing_algo.__name__}")
|
||||||
|
return pricing_algo(row)
|
||||||
|
elif ACTIVE_PRICING_ALGORITHIM == 'default_pricing_algo':
|
||||||
|
logger.debug(f"Using default pricing algorithm: {self.default_pricing_algo.__name__}")
|
||||||
pricing_algo = self.default_pricing_algo
|
pricing_algo = self.default_pricing_algo
|
||||||
elif ACTIVE_PRICING_ALGORITHIM == 'tcgplayer_recommended_algo':
|
elif ACTIVE_PRICING_ALGORITHIM == 'tcgplayer_recommended_algo':
|
||||||
|
logger.debug(f"Using TCGPlayer recommended algorithm: {self.tcgplayer_recommended_algo.__name__}")
|
||||||
pricing_algo = self.tcgplayer_recommended_algo
|
pricing_algo = self.tcgplayer_recommended_algo
|
||||||
else:
|
else:
|
||||||
|
logger.debug(f"Using default pricing algorithm: {self.default_pricing_algo.__name__}")
|
||||||
pricing_algo = self.default_pricing_algo
|
pricing_algo = self.default_pricing_algo
|
||||||
|
|
||||||
return pricing_algo(row)
|
return pricing_algo(row)
|
||||||
@ -298,15 +329,24 @@ class PricingService:
|
|||||||
.filter(CardTCGPlayer.tcgplayer_id.in_(tcgplayer_ids))
|
.filter(CardTCGPlayer.tcgplayer_id.in_(tcgplayer_ids))
|
||||||
.all()
|
.all()
|
||||||
}
|
}
|
||||||
|
# set listed price
|
||||||
|
df['listed_price'] = df['tcg_marketplace_price'].copy()
|
||||||
|
|
||||||
# Map the ids using the dictionary
|
# Map tcgplayer_id to product_id, ensure strings, keep None if not found
|
||||||
df['product_id'] = df['tcgplayer_id'].map(product_id_mapping)
|
df['product_id'] = df['tcgplayer_id'].map(product_id_mapping).apply(lambda x: str(x) if pd.notnull(x) else None)
|
||||||
|
|
||||||
price_lookup = self.get_all_prices_for_products(df['product_id'].unique())
|
# Log any tcgplayer_ids that didn't map to a product_id
|
||||||
|
null_product_ids = df[df['product_id'].isnull()]['tcgplayer_id'].tolist()
|
||||||
|
if null_product_ids:
|
||||||
|
logger.warning(f"The following tcgplayer_ids could not be mapped to a product_id: {null_product_ids}")
|
||||||
|
|
||||||
|
price_lookup = self.get_latest_prices_for_products(df['product_id'].unique())
|
||||||
|
|
||||||
# Apply price columns
|
# Apply price columns
|
||||||
df = df.apply(lambda row: self.apply_price_to_df_columns(row, price_lookup), axis=1)
|
df = df.apply(lambda row: self.apply_price_to_df_columns(row, price_lookup), axis=1)
|
||||||
|
|
||||||
|
logger.debug(f"Applying pricing algorithm: {ACTIVE_PRICING_ALGORITHIM}")
|
||||||
|
|
||||||
# Apply pricing algorithm
|
# Apply pricing algorithm
|
||||||
df = df.apply(self.apply_pricing_algo, axis=1)
|
df = df.apply(self.apply_pricing_algo, axis=1)
|
||||||
|
|
||||||
@ -314,6 +354,7 @@ class PricingService:
|
|||||||
if update_type == 'update':
|
if update_type == 'update':
|
||||||
df = df[df['new_price'] != df['listed_price']]
|
df = df[df['new_price'] != df['listed_price']]
|
||||||
|
|
||||||
|
|
||||||
# Set marketplace price
|
# Set marketplace price
|
||||||
df['TCG Marketplace Price'] = df['new_price']
|
df['TCG Marketplace Price'] = df['new_price']
|
||||||
|
|
||||||
|
14
requests.md
14
requests.md
@ -2,19 +2,19 @@ curl -J http://192.168.1.41:8000/api/tcgplayer/inventory/update --remote-name
|
|||||||
|
|
||||||
curl -J -X POST http://192.168.1.41:8000/api/tcgplayer/inventory/add \
|
curl -J -X POST http://192.168.1.41:8000/api/tcgplayer/inventory/add \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{"open_box_ids": ["fcc28f9e-bc8b-4e89-a46e-7da746a96a5b","4a09930f-1830-4b8b-8006-a391e1adca66"]}' \
|
-d '{"open_box_ids": ["f4a8e94c-5592-4b27-97b7-5cdb3eb45a71","81918f03-cbd2-4129-9f5c-eada6e1a811f","c2083c29-6fac-4621-b4b6-30c2dac75fab", "4ac89a6b-b8b7-44cf-8a4e-4a95c8e9006d", "0e809522-cef6-4c3c-b8a3-742c2e3c83fd","9e68466f-5abb-4725-9da8-91e5aaa4e805"]}' \
|
||||||
--remote-name
|
--remote-name
|
||||||
|
|
||||||
curl -X POST http://192.168.1.41:8000/api/boxes \
|
curl -X POST http://192.168.1.41:8000/api/boxes \
|
||||||
-F "type=collector" \
|
-F "type=play" \
|
||||||
-F "set_code=TDM" \
|
-F "set_code=TDM" \
|
||||||
-F "sku=1234" \
|
-F "sku=1234" \
|
||||||
-F "num_cards_expected=123"
|
-F "num_cards_expected=420"
|
||||||
|
|
||||||
curl -X POST "http://192.168.1.41:8000/api/boxes/d95d26a8-1f82-47f2-89fa-3f88a4636823/open" \
|
curl -X POST "http://192.168.1.41:8000/api/boxes/a77194be-8bd6-41cc-89a0-820e92ef9c04/open" \
|
||||||
-F "product_id=d95d26a8-1f82-47f2-89fa-3f88a4636823" \
|
-F "product_id=a77194be-8bd6-41cc-89a0-820e92ef9c04" \
|
||||||
-F "file_ids=0f29efd2-448c-4a05-ba49-1420fd3d524b" \
|
-F "file_ids=b11a0292-bfdc-43de-90a8-6eb383332201" \
|
||||||
-F "date_opened=2025-04-04"
|
-F "date_opened=2025-04-14"
|
||||||
|
|
||||||
curl -X POST "http://192.168.1.41:8000/api/processOrders" \
|
curl -X POST "http://192.168.1.41:8000/api/processOrders" \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
|
Reference in New Issue
Block a user