supply.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. from __future__ import annotations
  2. import sys
  3. import tomllib
  4. import typing
  5. import cache
  6. def main() -> None:
  7. planet = sys.argv[1].casefold()
  8. burn, raw_materials = fio_data(planet)
  9. inventory = {item['MaterialTicker']: item['MaterialAmount'] for item in burn['Inventory']}
  10. materials = {mat['Ticker']: mat for mat in raw_materials}
  11. producing = {item['MaterialTicker']: item for item in burn['OrderProduction']}
  12. net_consumption = []
  13. for c in burn['OrderConsumption'] + burn['WorkforceConsumption']:
  14. net = c['DailyAmount']
  15. if production := producing.get(c['MaterialTicker']):
  16. net -= production['DailyAmount']
  17. if net < 0:
  18. continue
  19. c['net_consumption'] = net
  20. net_consumption.append(c)
  21. vol_per_day = 0.0
  22. weight_per_day = 0.0
  23. target_days = float('inf')
  24. for consumption in net_consumption:
  25. ticker = consumption['MaterialTicker']
  26. vol_per_day += materials[ticker]['Volume'] * consumption['net_consumption']
  27. weight_per_day += materials[ticker]['Weight'] * consumption['net_consumption']
  28. days = inventory.get(ticker, 0) / consumption['net_consumption']
  29. if days < target_days:
  30. target_days = days
  31. print(f'consuming {vol_per_day:.1f}㎥/d')
  32. print(f'consuming {weight_per_day:.1f}t/d')
  33. target_days = round(target_days + 0.05, 1)
  34. while True:
  35. buy, weight_used, volume_used = buy_for_target(materials, net_consumption, inventory, target_days)
  36. if weight_used > 500 or volume_used > 500:
  37. break
  38. optimal = buy
  39. target_days += 0.1
  40. print('supply for', round(target_days, 1), 'days')
  41. for consumption in net_consumption:
  42. ticker = consumption['MaterialTicker']
  43. avail = inventory.get(ticker, 0)
  44. daily_consumption = consumption['net_consumption']
  45. days = avail / daily_consumption
  46. print(f'{ticker:>3}: {avail:5d} ({daily_consumption:8.2f}/d) {days:4.1f} d', end='')
  47. if need := optimal.get(ticker): # pyright: ignore[reportPossiblyUnboundVariable]
  48. print(f' | {need:8.1f}')
  49. else:
  50. print()
  51. def fio_data(planet: str) -> tuple[PlanetData, typing.Sequence[Material]]:
  52. with open('config.toml', 'rb') as f:
  53. config = tomllib.load(f)
  54. planets: list[PlanetData] = cache.get('https://rest.fnar.net/fioweb/burn/user/' + config['username'],
  55. headers={'Authorization': config['fio_api_key']})
  56. for planet_data in planets:
  57. name = planet_data['PlanetName']
  58. if name.casefold() == planet:
  59. assert planet_data['Error'] is None
  60. break
  61. else:
  62. raise ValueError(planet + ' not found')
  63. materials: list[Material] = cache.get('https://rest.fnar.net/material/allmaterials')
  64. return planet_data, materials
  65. def buy_for_target(materials: dict[str, Material], net_consumption: typing.Sequence[Amount], inventory: dict[str, int],
  66. target_days: float) -> tuple[dict[str, float], float, float]:
  67. weight_used = volume_used = 0
  68. buy: dict[str, float] = {}
  69. for consumption in net_consumption:
  70. ticker = consumption['MaterialTicker']
  71. avail = inventory.get(ticker, 0)
  72. daily_consumption = consumption['net_consumption']
  73. days = avail / daily_consumption
  74. if days < target_days:
  75. buy[ticker] = (target_days - days) * daily_consumption
  76. weight_used += buy[ticker] * materials[ticker]['Weight']
  77. volume_used += buy[ticker] * materials[ticker]['Volume']
  78. return buy, weight_used, volume_used
  79. class PlanetData(typing.TypedDict):
  80. PlanetName: str
  81. Error: typing.Any
  82. OrderConsumption: list[Amount]
  83. WorkforceConsumption: list[Amount]
  84. Inventory: list[Inventory]
  85. OrderProduction: list[Amount]
  86. class Amount(typing.TypedDict):
  87. MaterialTicker: str
  88. DailyAmount: float
  89. net_consumption: float
  90. class Inventory(typing.TypedDict):
  91. MaterialTicker: str
  92. MaterialAmount: int
  93. class Material(typing.TypedDict):
  94. Ticker: str
  95. Weight: float
  96. Volume: float
  97. if __name__ == '__main__':
  98. main()