from __future__ import annotations import collections import typing import cache import roi BUY = frozenset([ # definitely buy 'C', 'FLX', 'H', 'H2O', 'HAL', 'HCP', 'HE', 'LST', 'MG', 'N', 'NA', 'NCS', 'NS', 'O', 'PE', 'PG', 'S', 'TCL', # maybe buy 'AU', 'BRM', 'CU', 'FE', 'LI', 'RG', 'ROM', 'SI', 'TI', ]) def main() -> None: blueprint = { 'FFC': 1, 'FSE': 1, 'LFE': 2, 'MFE': 2, 'QCR': 1, 'SFE': 1, 'LCB': 1, 'MFL': 1, 'MSL': 1, 'LHP': 94, 'SSC': 128, 'BR1': 1, 'CQM': 1, } prices: typing.Mapping[str, RawPrice] = {p['MaterialTicker']: p for p in cache.get('https://refined-prun.github.io/refined-prices/all.json') if p['ExchangeCode'] == 'IC1'} recipes = recipe_for_mats() production: dict[str, dict[str, float]] = collections.defaultdict(lambda: collections.defaultdict(float)) cost = 0.0 for mat, amount in blueprint.items(): cost += analyze_mat(0, mat, amount, production, prices, recipes) print() print(f'total cost: {cost:,}') buildings: typing.Sequence[roi.Building] = cache.get('https://api.prunplanner.org/data/buildings/', expiry=cache.ONE_DAY) expertise = collections.defaultdict(list) for building in buildings: if building['building_ticker'] in production: expertise[building['expertise']].append(building['building_ticker']) for expertise, buildings in expertise.items(): print(expertise) for building in buildings: print(f'\t{building:3}:', end='') for mat, amount in production[building].items(): traded = prices[mat]['AverageTraded30D'] or 0 if traded > amount * 2: print(f' \033[32m{mat}\033[0m', end='') else: print(f' \033[31m{mat}\033[0m', end='') print() def recipe_for_mats() -> dict[str, roi.Recipe]: all_recipes: list[roi.Recipe] = cache.get('https://api.prunplanner.org/data/recipes/', expiry=cache.ONE_DAY) mat_recipes = collections.defaultdict(list) # all ways to make a mat for recipe in all_recipes: for output in recipe['outputs']: mat_recipes[output['material_ticker']].append(recipe) mat_recipe: dict[str, roi.Recipe] = {} # mats for which there's only one recipe to make for mat, recipes in mat_recipes.items(): if len(recipes) == 1: mat_recipe[mat] = recipes[0] return mat_recipe def analyze_mat(level: int, mat: str, amount: float, production: dict[str, dict[str, float]], prices: typing.Mapping[str, RawPrice], recipes: dict[str, roi.Recipe]) -> float: price = prices[mat] traded = price['AverageTraded30D'] or 0 if mat in BUY: print('\t' * level + f'{amount:g}×{mat} buy: {price["Ask"]}, daily traded {traded:5.1f}') assert price['Ask'] is not None return price['Ask'] * amount else: if (recipe := recipes.get(mat)) is None: print('\t' * level + f'{amount:g}×{mat} make (unknown recipe)') return 0 else: building = recipe['building_ticker'] production[building][mat] += amount liquid = '\033[31mnot liquid\033[0m' if traded > amount * 2: liquid = '\033[32mliquid\033[0m' print('\t' * level + f'{amount:g}×{mat} make ({building}, {liquid})') total_cost = 0.0 for input_mat in recipe['inputs']: input_amount = input_mat['material_amount'] * amount / recipe['outputs'][0]['material_amount'] total_cost += analyze_mat(level + 1, input_mat['material_ticker'], input_amount, production, prices, recipes) print('\t' * level + f'\tcost: {total_cost:9,.2f}') return total_cost class RawPrice(typing.TypedDict): MaterialTicker: str ExchangeCode: str Ask: float | None AverageTraded30D: float | None # averaged daily traded volume over last 30 days Supply: int if __name__ == '__main__': main()