|
|
@@ -1,5 +1,6 @@
|
|
|
from __future__ import annotations
|
|
|
|
|
|
+import dataclasses
|
|
|
import sys
|
|
|
import tomllib
|
|
|
import typing
|
|
|
@@ -7,95 +8,112 @@ import typing
|
|
|
import cache
|
|
|
|
|
|
def main() -> None:
|
|
|
- planet = sys.argv[1].casefold()
|
|
|
- burn, raw_materials = fio_data(planet)
|
|
|
-
|
|
|
- inventory = {item['MaterialTicker']: item['MaterialAmount'] for item in burn['Inventory']}
|
|
|
+ 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}
|
|
|
- producing = {item['MaterialTicker']: item for item in burn['OrderProduction']}
|
|
|
- net_consumption = []
|
|
|
- for c in burn['OrderConsumption'] + burn['WorkforceConsumption']:
|
|
|
- net = c['DailyAmount']
|
|
|
- if production := producing.get(c['MaterialTicker']):
|
|
|
- net -= production['DailyAmount']
|
|
|
- if net < 0:
|
|
|
- continue
|
|
|
- c['net_consumption'] = net
|
|
|
- net_consumption.append(c)
|
|
|
-
|
|
|
- vol_per_day = 0.0
|
|
|
- weight_per_day = 0.0
|
|
|
+
|
|
|
target_days = float('inf')
|
|
|
- for consumption in net_consumption:
|
|
|
- ticker = consumption['MaterialTicker']
|
|
|
- vol_per_day += materials[ticker]['Volume'] * consumption['net_consumption']
|
|
|
- weight_per_day += materials[ticker]['Weight'] * consumption['net_consumption']
|
|
|
- days = inventory.get(ticker, 0) / consumption['net_consumption']
|
|
|
- if days < target_days:
|
|
|
- target_days = days
|
|
|
-
|
|
|
- print(f'consuming {vol_per_day:.1f}㎥/d')
|
|
|
- print(f'consuming {weight_per_day:.1f}t/d')
|
|
|
+ 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 True:
|
|
|
- buy, weight_used, volume_used = buy_for_target(materials, net_consumption, inventory, target_days)
|
|
|
- if weight_used > 500 or volume_used > 500:
|
|
|
- break
|
|
|
- optimal = buy
|
|
|
- target_days += 0.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 consumption in net_consumption:
|
|
|
- ticker = consumption['MaterialTicker']
|
|
|
- avail = 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.get(ticker): # pyright: ignore[reportPossiblyUnboundVariable]
|
|
|
- print(f' | {need:8.1f}')
|
|
|
- else:
|
|
|
- print()
|
|
|
-
|
|
|
-def fio_data(planet: str) -> tuple[PlanetData, typing.Sequence[Material]]:
|
|
|
+ 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[PlanetData] = cache.get('https://rest.fnar.net/fioweb/burn/user/' + config['username'],
|
|
|
+ planets: list[FIOBurn] = cache.get('https://rest.fnar.net/fioweb/burn/user/' + config['username'],
|
|
|
headers={'Authorization': config['fio_api_key']})
|
|
|
- for planet_data in planets:
|
|
|
- name = planet_data['PlanetName']
|
|
|
- if name.casefold() == planet:
|
|
|
- assert planet_data['Error'] is None
|
|
|
- break
|
|
|
- else:
|
|
|
- raise ValueError(planet + ' not found')
|
|
|
-
|
|
|
- materials: list[Material] = cache.get('https://rest.fnar.net/material/allmaterials')
|
|
|
- return planet_data, materials
|
|
|
-
|
|
|
-def buy_for_target(materials: dict[str, Material], net_consumption: typing.Sequence[Amount], inventory: dict[str, int],
|
|
|
- target_days: float) -> tuple[dict[str, float], float, float]:
|
|
|
- weight_used = volume_used = 0
|
|
|
- buy: dict[str, float] = {}
|
|
|
- for consumption in net_consumption:
|
|
|
- ticker = consumption['MaterialTicker']
|
|
|
- avail = 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 PlanetData(typing.TypedDict):
|
|
|
+ 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
|