|
|
@@ -0,0 +1,82 @@
|
|
|
+from __future__ import annotations
|
|
|
+
|
|
|
+import collections
|
|
|
+import typing
|
|
|
+
|
|
|
+import cache
|
|
|
+import roi
|
|
|
+
|
|
|
+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()
|
|
|
+
|
|
|
+ buildings: dict[str, set[str]] = collections.defaultdict(set)
|
|
|
+ cost = 0.0
|
|
|
+ for mat, amount in blueprint.items():
|
|
|
+ cost += analyze_mat(0, mat, amount, buildings, prices, recipes)
|
|
|
+ print()
|
|
|
+ print(f'total cost: {cost:,}')
|
|
|
+ for building, mats in buildings.items():
|
|
|
+ print(f'{building:3}: {", ".join(mats)}')
|
|
|
+
|
|
|
+def recipe_for_mats() -> dict[str, roi.Recipe]:
|
|
|
+ all_recipes: list[roi.Recipe] = cache.get('https://api.prunplanner.org/data/recipes/')
|
|
|
+ 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, buildings: dict[str, set[str]],
|
|
|
+ prices: typing.Mapping[str, RawPrice], recipes: dict[str, roi.Recipe]) -> float:
|
|
|
+ price = prices[mat]
|
|
|
+ traded = price['AverageTraded30D'] or 0
|
|
|
+ if (price['Supply'] > amount * 10 and traded > 0) or (price['Supply'] > amount * 4 and amount < traded):
|
|
|
+ print('\t' * level + f'{amount:g}×{mat} buy: {price["Ask"]:9,}, daily traded {traded:5.1f}, supply {price["Supply"]:4}')
|
|
|
+ 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']
|
|
|
+ print('\t' * level + f'{amount:g}×{mat} make ({building})')
|
|
|
+ buildings[building].add(mat)
|
|
|
+ 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, buildings, 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()
|