supply.py 3.1 KB

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