Przeglądaj źródła

roi: handle multi-output recipes

raylu 1 tydzień temu
rodzic
commit
20fb4c0e3a
2 zmienionych plików z 28 dodań i 16 usunięć
  1. 26 14
      roi.py
  2. 2 2
      ts/roi.ts

+ 26 - 14
roi.py

@@ -39,37 +39,44 @@ def main() -> None:
 def calc_profit(recipe: Recipe, buildings: typing.Mapping[str, Building], hab_area_cost: typing.Mapping[Worker, float],
 		hab_capex: typing.Mapping[Worker, float], materials: typing.Mapping[str, Material],
 		prices: typing.Mapping[str, Price]) -> Profit | None:
-	try:
-		(output,) = recipe['Outputs']
-	except ValueError: # skip recipes that don't have exactly 1 output
-		return
-	output_price = prices[output['Ticker']]
-	if output_price.vwap_7d is None or output_price.average_traded_7d is None: # skip recipes with thinly traded output
+	if len(recipe['Outputs']) == 0:
 		return
+	output_prices: dict[str, PriceNonNull] = {}
+	for output in recipe['Outputs']:
+		price = prices[output['Ticker']]
+		if price.vwap_7d is None or price.average_traded_7d is None:
+			return # skip recipes with thinly traded outputs
+		output_prices[output['Ticker']] = typing.cast(PriceNonNull, price)
+
 	cost = 0
 	for input in recipe['Inputs']:
 		if (input_cost := prices[input['Ticker']].vwap_7d) is None:
 			return # skip recipes with thinly traded inputs
 		cost += input_cost * input['Amount']
-	revenue = output_price.vwap_7d * output['Amount']
+	revenue = sum(output_prices[output['Ticker']].vwap_7d * output['Amount'] for output in recipe['Outputs'])
+	profit_per_run = revenue - cost
+
 	building = buildings[recipe['BuildingTicker']]
 	area = building['AreaCost'] + sum(hab_area_cost[worker] * building[worker] for worker in hab_area_cost)
 	capex = building_construction_cost(building, prices) + \
 		sum(hab_capex[worker] * building[worker] for worker in hab_capex)
-	profit_per_run = revenue - cost
 	runs_per_day = 24 * 60 * 60 * 1000 / recipe['TimeMs']
 	if building['Ticker'] in ('FRM', 'ORC'):
 		runs_per_day *= 1.1212 # promitor's fertility
 	worker_consumable_daily_cost = building_daily_cost(building, prices)
 	cost_per_day = cost * runs_per_day + worker_consumable_daily_cost
-	output_per_day = output['Amount'] * runs_per_day
+
+	lowest_liquidity = min(recipe['Outputs'],
+			key=lambda output: output['Amount'] / output_prices[output['Ticker']].average_traded_7d)
+	output_per_day = lowest_liquidity['Amount'] * runs_per_day
+
 	logistics_per_area = max(
 		sum(materials[input['Ticker']]['Weight'] * input['Amount'] for input in recipe['Inputs']),
 		sum(materials[input['Ticker']]['Volume'] * input['Amount'] for input in recipe['Inputs']),
-		materials[output['Ticker']]['Weight'] * output['Amount'],
-		materials[output['Ticker']]['Volume'] * output['Amount'],
+		sum(materials[output['Ticker']]['Weight'] * output['Amount'] for output in recipe['Outputs']),
+		sum(materials[output['Ticker']]['Volume'] * output['Amount'] for output in recipe['Outputs']),
 	) * runs_per_day / area
-	return Profit(output['Ticker'], recipe['RecipeName'],
+	return Profit([output['Ticker'] for output in recipe['Outputs']], recipe['RecipeName'],
 			expertise=building['Expertise'],
 			profit_per_day=(profit_per_run * runs_per_day - worker_consumable_daily_cost),
 			area=area,
@@ -77,7 +84,7 @@ def calc_profit(recipe: Recipe, buildings: typing.Mapping[str, Building], hab_ar
 			cost_per_day=cost_per_day,
 			logistics_per_area=logistics_per_area,
 			output_per_day=output_per_day,
-			average_traded_7d=output_price.average_traded_7d)
+			average_traded_7d=output_prices[lowest_liquidity['Ticker']].average_traded_7d)
 
 def building_construction_cost(building: Building, prices: typing.Mapping[str, Price]) -> float:
 	return sum(bc['Amount'] * prices[bc['CommodityTicker']].vwap_7d for bc in building['BuildingCosts']) # pyright: ignore[reportOperatorIssue]
@@ -144,9 +151,14 @@ class Price:
 	average_traded_7d: float | None
 	vwap_30d: float | None
 
+@dataclasses.dataclass(eq=False, frozen=True, slots=True)
+class PriceNonNull:
+	vwap_7d: float
+	average_traded_7d: float
+
 @dataclasses.dataclass(eq=False, frozen=True, slots=True)
 class Profit:
-	output: str
+	output: typing.Collection[str]
 	recipe: str
 	expertise: str
 	profit_per_day: float

+ 2 - 2
ts/roi.ts

@@ -46,7 +46,7 @@ async function render() {
 		const profit_per_area = p.profit_per_day / p.area;
 		const break_even = p.profit_per_day > 0 ? p.capex / p.profit_per_day : Infinity;
 		tr.innerHTML = `
-			<td>${p.output}</td>
+			<td>${p.output.join(', ')}</td>
 			<td>${expertise[p.expertise]}</td>
 			<td style="color: ${color(profit_per_area, 0, 250)}">${formatDecimal(profit_per_area)}</td>
 			<td><span style="color: ${color(break_even, 30, 3)}">${formatDecimal(break_even)}</span>d</td>
@@ -78,7 +78,7 @@ expertiseSelect.addEventListener('change', render);
 render();
 
 interface Profit {
-	output: string
+	output: string[]
 	recipe: string
 	expertise: keyof typeof expertise
 	profit_per_day: number