Quellcode durchsuchen

plan: estimate shipping for SCBs

raylu vor 5 Tagen
Ursprung
Commit
e79d5b6f5f
1 geänderte Dateien mit 58 neuen und 6 gelöschten Zeilen
  1. 58 6
      ts/plan.ts

+ 58 - 6
ts/plan.ts

@@ -53,11 +53,13 @@ async function render(shareUUID: string, cx: string) {
 }
 
 async function _render(shareUUID: string, cx: string) {
-	const [plan, buildings, recipeList, exchanges, graph]: [Plan, Building[], Recipe[], Exchange[], Graph] = await Promise.all([
+	const [plan, buildings, recipeList, exchanges, materialList, graph]:
+			[Plan, Building[], Recipe[], Exchange[], Material[], Graph] = await Promise.all([
 		cachedFetchJSON('https://api.prunplanner.org/planning/shared/' + shareUUID),
 		cachedFetchJSON('https://api.prunplanner.org/data/buildings/'),
 		cachedFetchJSON('https://api.prunplanner.org/data/recipes/'),
 		cachedFetchJSON('https://api.prunplanner.org/data/exchanges/'),
+		cachedFetchJSON('https://api.prunplanner.org/data/materials/'),
 		cachedFetchJSON('/graph_data.json'),
 	]);
 	const planet: Planet = await cachedFetchJSON(`https://api.prunplanner.org/data/planet/${plan.plan_details.planet_natural_id}/`);
@@ -67,8 +69,9 @@ async function _render(shareUUID: string, cx: string) {
 	const recipes = new Map<string, Recipe>(recipeList.map(r => [r.recipe_id, r]));
 	const dailyTraded = new Map<string, number>(exchanges.filter((ex) => ex.exchange_code === cx)
 			.map((ex) => [ex.ticker, ex.sum_traded_7d / 7]));
+	const materials = new Map<string, Material>(materialList.map(m => [m.ticker, m]));
 
-	travel(graph.edges, planet.system_id, cx);
+	const oneWayHours = travel(graph.edges, planet.system_id, cx);
 
 	const planInput = new Counter();
 	const planOutput = new Counter();
@@ -108,14 +111,33 @@ async function _render(shareUUID: string, cx: string) {
 					<td style="color: ${color}">${pctFmt.format(netAmount / traded)}</td>
 				</tr>`;
 			}).join('')}
-		</table>
-	`;
+		</table>`;
+
+	if (oneWayHours !== null) {
+		const {shippingBottleneckType, shippingBottleneckAmount} = shippingBottleneck(materials, net);
+		const roundTripHours = oneWayHours * 2;
+		const scbThroughput = 24 / roundTripHours * 500;
+		renderTarget.innerHTML += `
+			<p>
+				shipping bottleneck: ${formatNumber(shippingBottleneckAmount)} (${shippingBottleneckType})
+			</p>
+			<p>
+				one-way to/from ${cx}: ${formatNumber(oneWayHours)} hours
+				<br>round-trip to ${cx}: ${formatNumber(roundTripHours)} hours
+				<br>non-stop SCB shipping throughput per day: 24 ÷ ${formatNumber(roundTripHours)} × 500 = ${formatNumber(scbThroughput)}
+				<br>non-stop SCBs needed: ${formatNumber(shippingBottleneckAmount)} ÷ ${formatNumber(scbThroughput)}
+				= ${formatNumber(shippingBottleneckAmount / scbThroughput)}
+			</p>
+		`;
+	}
 }
 
-function travel(edges: Edge[], system: string, cx: string): number {
+function travel(edges: Edge[], system: string, cx: string): number | null {
 	const {parsecs, jumps} = ftlDistance(edges, system, cx);
+	if (jumps === 0)
+		return null;
 	// SCB JMPs at 4pc/hr. RCT CHRGs in 7m30s. 30m DEP, 1.5hr APP+(TO/LAND)
-	return parsecs / 4 + (jumps - 1) * 0.125 + 2;
+	return Math.round(parsecs / 4 + (jumps - 1) * 0.125 + 2);
 }
 
 /** dijkstra to find the shortest FTL route from system to CX */
@@ -205,6 +227,30 @@ function calcBuilding(recipes: Map<string, Recipe>, building: PlanBuilding, cogc
 	return {input, output};
 }
 
+function shippingBottleneck(materials: Map<string, Material>, net: Counter):
+		{shippingBottleneckType: string, shippingBottleneckAmount: number} {
+	let importWeight = 0, importVol = 0;
+	let exportWeight = 0, exportVol = 0;
+	for (const [mat, amount] of net.entries()) {
+		const material = materials.get(mat)!;
+		if (amount < 0) {
+			importWeight -= amount * material.weight;
+			importVol -= amount * material.volume;
+		} else if (amount > 0) {
+			exportWeight += amount * material.weight;
+			exportVol += amount * material.volume;
+		}
+	}
+	const bottlenecks = [
+		{shippingBottleneckType: 'import weight', shippingBottleneckAmount: importWeight},
+		{shippingBottleneckType: 'import volume', shippingBottleneckAmount: importVol},
+		{shippingBottleneckType: 'export weight', shippingBottleneckAmount: exportWeight},
+		{shippingBottleneckType: 'export volume', shippingBottleneckAmount: exportVol},
+	];
+	bottlenecks.sort((a, b) => b.shippingBottleneckAmount - a.shippingBottleneckAmount);
+	return bottlenecks[0];
+}
+
 const numberFmt = new Intl.NumberFormat(undefined, {maximumFractionDigits: 2});
 const wholeFmt = new Intl.NumberFormat(undefined, {maximumFractionDigits: 0});
 const pctFmt = new Intl.NumberFormat(undefined, {style: 'percent', maximumFractionDigits: 2});
@@ -260,6 +306,12 @@ interface Exchange {
 	sum_traded_7d: number // avg_traded_7d is nonsense
 }
 
+interface Material {
+	ticker: string
+	weight: number
+	volume: number
+}
+
 interface Graph {
 	edges: Array<Edge>
 }