shipbuilding.py 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. from __future__ import annotations
  2. import collections
  3. import typing
  4. import cache
  5. import roi
  6. def main() -> None:
  7. blueprint = {
  8. 'FFC': 1,
  9. 'FSE': 1,
  10. 'LFE': 2,
  11. 'MFE': 2,
  12. 'QCR': 1,
  13. 'SFE': 1,
  14. 'LCB': 1,
  15. 'MFL': 1,
  16. 'MSL': 1,
  17. 'LHP': 94,
  18. 'SSC': 128,
  19. 'BR1': 1,
  20. 'CQM': 1,
  21. }
  22. prices: typing.Mapping[str, RawPrice] = {p['MaterialTicker']: p
  23. for p in cache.get('https://refined-prun.github.io/refined-prices/all.json') if p['ExchangeCode'] == 'IC1'}
  24. recipes = recipe_for_mats()
  25. buildings: dict[str, set[str]] = collections.defaultdict(set)
  26. cost = 0.0
  27. for mat, amount in blueprint.items():
  28. cost += analyze_mat(0, mat, amount, buildings, prices, recipes)
  29. print()
  30. print(f'total cost: {cost:,}')
  31. for building, mats in buildings.items():
  32. print(f'{building:3}: {", ".join(mats)}')
  33. def recipe_for_mats() -> dict[str, roi.Recipe]:
  34. all_recipes: list[roi.Recipe] = cache.get('https://api.prunplanner.org/data/recipes/')
  35. mat_recipes = collections.defaultdict(list) # all ways to make a mat
  36. for recipe in all_recipes:
  37. for output in recipe['outputs']:
  38. mat_recipes[output['material_ticker']].append(recipe)
  39. mat_recipe: dict[str, roi.Recipe] = {} # mats for which there's only one recipe to make
  40. for mat, recipes in mat_recipes.items():
  41. if len(recipes) == 1:
  42. mat_recipe[mat] = recipes[0]
  43. return mat_recipe
  44. def analyze_mat(level: int, mat: str, amount: float, buildings: dict[str, set[str]],
  45. prices: typing.Mapping[str, RawPrice], recipes: dict[str, roi.Recipe]) -> float:
  46. price = prices[mat]
  47. traded = price['AverageTraded30D'] or 0
  48. if (price['Supply'] > amount * 10 and traded > 0) or (price['Supply'] > amount * 4 and amount < traded):
  49. print('\t' * level + f'{amount:g}×{mat} buy: {price["Ask"]:9,}, daily traded {traded:5.1f}, supply {price["Supply"]:4}')
  50. assert price['Ask'] is not None
  51. return price['Ask'] * amount
  52. else:
  53. if (recipe := recipes.get(mat)) is None:
  54. print('\t' * level + f'{amount:g}×{mat} make (unknown recipe)')
  55. return 0
  56. else:
  57. building = recipe['building_ticker']
  58. print('\t' * level + f'{amount:g}×{mat} make ({building})')
  59. buildings[building].add(mat)
  60. total_cost = 0.0
  61. for input_mat in recipe['inputs']:
  62. input_amount = input_mat['material_amount'] * amount / recipe['outputs'][0]['material_amount']
  63. total_cost += analyze_mat(level + 1, input_mat['material_ticker'], input_amount, buildings, prices, recipes)
  64. print('\t' * level + f'\tcost: {total_cost:9,.2f}')
  65. return total_cost
  66. class RawPrice(typing.TypedDict):
  67. MaterialTicker: str
  68. ExchangeCode: str
  69. Ask: float | None
  70. AverageTraded30D: float | None # averaged daily traded volume over last 30 days
  71. Supply: int
  72. if __name__ == '__main__':
  73. main()