from __future__ import annotations import dataclasses import sys import tomllib import typing import cache def main() -> None: planet_names = sys.argv[1:] planets = [Planet(fio_burn) for fio_burn in get_fio_burn(planet_names)] raw_materials: typing.Sequence[Material] = cache.get('https://rest.fnar.net/material/allmaterials') materials = {mat['Ticker']: mat for mat in raw_materials} target_days = float('inf') for planet in planets: vol_per_day = weight_per_day = 0 for consumption in planet.net_consumption: ticker = consumption['MaterialTicker'] vol_per_day += materials[ticker]['Volume'] * consumption['net_consumption'] weight_per_day += materials[ticker]['Weight'] * consumption['net_consumption'] days = planet.inventory.get(ticker, 0) / consumption['net_consumption'] if days < target_days: target_days = days print(planet.name, f'consumes {vol_per_day:.1f}㎥, {weight_per_day:.1f}t per day') load_more = True optimal = dict.fromkeys(p.name for p in planets) target_days = round(target_days + 0.05, 1) while load_more: total_weight_used = total_volume_used = 0 for planet in planets: buy, weight_used, volume_used = planet.buy_for_target(materials, target_days) total_weight_used += weight_used total_volume_used += volume_used if total_weight_used > 500 or total_volume_used > 500: load_more = False break optimal[planet.name] = buy target_days += 0.1 print('supply for', round(target_days, 1), 'days') for planet in planets: print('\n' + planet.name) for consumption in planet.net_consumption: ticker = consumption['MaterialTicker'] avail = planet.inventory.get(ticker, 0) daily_consumption = consumption['net_consumption'] days = avail / daily_consumption print(f'{ticker:>3}: {avail:5d} ({daily_consumption:8.2f}/d) {days:4.1f} d', end='') if need := optimal[planet.name].get(ticker): # pyright: ignore[reportOptionalMemberAccess] print(f' | {need:8.1f}') else: print() def get_fio_burn(planet_names: typing.Sequence[str]) -> typing.Iterator[FIOBurn]: with open('config.toml', 'rb') as f: config = tomllib.load(f) planets: list[FIOBurn] = cache.get('https://rest.fnar.net/fioweb/burn/user/' + config['username'], headers={'Authorization': config['fio_api_key']}) for name in planet_names: name = name.casefold() for planet_data in planets: if name in (planet_data['PlanetName'].casefold(), planet_data['PlanetNaturalId'].casefold()): assert planet_data['Error'] is None yield planet_data break else: raise ValueError(name + ' not found') class FIOBurn(typing.TypedDict): PlanetName: str PlanetNaturalId: str Error: typing.Any OrderConsumption: list[Amount] WorkforceConsumption: list[Amount] Inventory: list[Inventory] OrderProduction: list[Amount] @dataclasses.dataclass(init=False, eq=False, slots=True) class Planet: name: str inventory: dict[str, int] net_consumption: typing.Sequence[Amount] def __init__(self, fio_burn: FIOBurn) -> None: self.name = fio_burn['PlanetName'] or fio_burn['PlanetNaturalId'] self.inventory = {item['MaterialTicker']: item['MaterialAmount'] for item in fio_burn['Inventory']} producing = {item['MaterialTicker']: item for item in fio_burn['OrderProduction']} self.net_consumption = [] for c in fio_burn['OrderConsumption'] + fio_burn['WorkforceConsumption']: net = c['DailyAmount'] if production := producing.get(c['MaterialTicker']): net -= production['DailyAmount'] if net < 0: continue c['net_consumption'] = net self.net_consumption.append(c) def buy_for_target(self, materials: dict[str, Material], target_days: float) -> tuple[dict[str, float], float, float]: weight_used = volume_used = 0 buy: dict[str, float] = {} for consumption in self.net_consumption: ticker = consumption['MaterialTicker'] avail = self.inventory.get(ticker, 0) daily_consumption = consumption['net_consumption'] days = avail / daily_consumption if days < target_days: buy[ticker] = (target_days - days) * daily_consumption weight_used += buy[ticker] * materials[ticker]['Weight'] volume_used += buy[ticker] * materials[ticker]['Volume'] return buy, weight_used, volume_used class Amount(typing.TypedDict): MaterialTicker: str DailyAmount: float net_consumption: float class Inventory(typing.TypedDict): MaterialTicker: str MaterialAmount: int class Material(typing.TypedDict): Ticker: str Weight: float Volume: float if __name__ == '__main__': main()