Add some more structuring

This commit is contained in:
polyfloyd 2024-01-06 23:39:19 +01:00
parent 5f6f9c52a7
commit ee9a00eb47
3 changed files with 66 additions and 31 deletions

View file

@ -1,8 +1,11 @@
import revbank import revbank
import sys import sys
import logging
def main(product_file): def main(product_file):
logging.basicConfig(level=logging.DEBUG)
with open(product_file, 'r') as fd: with open(product_file, 'r') as fd:
src = fd.read() src = fd.read()

View file

@ -1,49 +1,75 @@
from decimal import Decimal, ROUND_UP
import logging
import re import re
import scrapers import scrapers
from decimal import Decimal, ROUND_UP
profit_margin = Decimal('1.3')
our_margin = Decimal('1.3') class AutoUpdate:
_meta_re = re.compile(r'#\s*(?P<vendor>ah):(?P<sku>\S+)\s+(?P<units>\d+)x$')
def __init__(self, vendor, sku, units):
self.vendor = vendor
self.sku = sku
self.units = units
def __str__(self):
return f'{self.vendor}:{self.sku} {self.units}x'
@staticmethod
def from_product_line(line):
m = AutoUpdate._meta_re.search(line)
if not m:
raise Exception('no auto update directive found')
return AutoUpdate(m['vendor'], m['sku'], int(m['units']))
assert AutoUpdate.from_product_line('# ah:wi162664 8x')
assert AutoUpdate.from_product_line('8711327538481,liuk 0.80 Ola Liuk # ah:wi162664 8x')
def find_product_details(vendor_and_sku): def find_product_details(auto_update):
[vendor, sku] = vendor_and_sku.split(':', 2) if auto_update.vendor == 'ah':
return scrapers.ah_get_by_sku(auto_update.sku, auto_update.units)
if vendor == 'ah': raise Exception(f'unknown vendor: {auto_update.vendor}')
return scrapers.ah_get_by_sku(sku)
raise Exception(f'unknown vendor: {vendor}')
def update_product_pricings(src): def update_product_pricings(src):
find_updatable = re.compile(r'#\s*(?P<sku>\S+)\s+(?P<units>\d+)x$')
find_aliases = re.compile(r'^(?P<aliases>\S+)') find_aliases = re.compile(r'^(?P<aliases>\S+)')
lines = src.split('\n') lines = src.split('\n')
lines_out = [] lines_out = []
for line in lines: for line in lines:
m = find_updatable.search(line) try:
if not m: auto_update = AutoUpdate.from_product_line(line)
logging.debug('Found updatable product: %s', auto_update)
except Exception as err:
lines_out.append(line) lines_out.append(line)
continue continue
d = find_product_details(m['sku']) try:
prod_info = find_product_details(auto_update)
except Exception as err:
logging.error('could not update %s %s: %s', auto_update, err)
lines_out.append(line)
continue
product_aliases = set() product_aliases = set()
if not line.startswith('#'): if not line.startswith('#'):
product_aliases = set(find_aliases.search(line)['aliases'].split(',')) product_aliases = set(find_aliases.search(line)['aliases'].split(','))
product_aliases.add(d['ean']) product_aliases.add(prod_info.ean)
aliases = ','.join(sorted(product_aliases)) aliases = ','.join(sorted(product_aliases))
units = int(m["units"])
price = d['price']
# Apply a 30% margin and divide by the number of units per sold packaging. # Apply profit margin and divide by the number of units per sold packaging.
unit_price = price * our_margin / units unit_price = prod_info.price * profit_margin / prod_info.units
# Round up to 5ct. # Round up to 5ct.
unit_price = (unit_price * 20).quantize(Decimal('1'), rounding=ROUND_UP) / 20 unit_price = (unit_price * 20).quantize(Decimal('1'), rounding=ROUND_UP) / 20
lines_out.append(f'{aliases:<15} {unit_price:.2f} {d["name"]:<32} # {m["sku"]} {units}x') fmt_price = f'{unit_price:.2f}'
lines_out.append(f'{aliases:<15} {fmt_price:<6} {prod_info.name:<32} # {auto_update}')
logging.debug(f'Found "{prod_info.name}", buy €{prod_info.price/prod_info.units:.2f}, sell €{fmt_price}')
return '\n'.join(lines_out) return '\n'.join(lines_out)

View file

@ -5,12 +5,23 @@ import re
import subprocess import subprocess
class Product:
def __init__(self, name, price, ean, units):
self.name = name
self.price = price
self.ean = ean
self.units = units
def __str__(self):
return self.name
def get(url): def get(url):
compl = subprocess.run(['links', '-source', url], capture_output=True) compl = subprocess.run(['links', '-source', url], capture_output=True)
return compl.stdout return compl.stdout
def ah_get_by_sku(ah_sku): def ah_get_by_sku(ah_sku, units):
assert re.match('^wi\d+$', ah_sku) assert re.match('^wi\d+$', ah_sku)
html_src = get(f'https://www.ah.nl/producten/product/{ah_sku}') html_src = get(f'https://www.ah.nl/producten/product/{ah_sku}')
@ -24,14 +35,9 @@ def ah_get_by_sku(ah_sku):
else: else:
raise Exception(f'ah.nl returned no JSON metadata for SKU {ah_sku}') raise Exception(f'ah.nl returned no JSON metadata for SKU {ah_sku}')
name = schema['name'] return Product(
ean = schema['gtin13'] name=schema['name'],
sku = schema['sku'] price=Decimal(schema['offers']['price']),
price = Decimal(schema['offers']['price']) ean=schema['gtin13'],
units=units,
return { )
'name': name,
'price': price,
'ean': ean,
'sku': sku,
}