|
@@ -71,6 +71,7 @@ async function render() {
|
|
|
for (const [mat, amount] of Object.entries(buildingProduction))
|
|
for (const [mat, amount] of Object.entries(buildingProduction))
|
|
|
requiredMats[mat] = (requiredMats[mat] ?? 0) + amount;
|
|
requiredMats[mat] = (requiredMats[mat] ?? 0) + amount;
|
|
|
|
|
|
|
|
|
|
+ const buildingsByTicker = Object.fromEntries(buildings.map((b) => [b.building_ticker, b]));
|
|
|
const expertiseGroups: Record<string, string[]> = {};
|
|
const expertiseGroups: Record<string, string[]> = {};
|
|
|
for (const building of buildings) {
|
|
for (const building of buildings) {
|
|
|
if (!(building.building_ticker in production))
|
|
if (!(building.building_ticker in production))
|
|
@@ -84,7 +85,7 @@ async function render() {
|
|
|
main.append(
|
|
main.append(
|
|
|
renderAnalysis(analysisNodes),
|
|
renderAnalysis(analysisNodes),
|
|
|
element('p', {textContent: `total cost: ${formatWhole(cost)}`}),
|
|
element('p', {textContent: `total cost: ${formatWhole(cost)}`}),
|
|
|
- renderProduction(expertiseGroups, production, prices, recipes),
|
|
|
|
|
|
|
+ renderProduction(expertiseGroups, production, prices, recipes, buildingsByTicker),
|
|
|
renderMatList('extract', extract),
|
|
renderMatList('extract', extract),
|
|
|
renderMatList('buy', buy),
|
|
renderMatList('buy', buy),
|
|
|
renderShipbuilders(requiredMats, knownCompanies, companyProductionById),
|
|
renderShipbuilders(requiredMats, knownCompanies, companyProductionById),
|
|
@@ -239,11 +240,34 @@ function renderAnalysisNode(node: AnalysisNode, level = 0): HTMLElement {
|
|
|
return el;
|
|
return el;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+const WORKER_CONSUMPTION: Record<'pioneers' | 'settlers' | 'technicians' | 'engineers' | 'scientists', Record<string, number>> = {
|
|
|
|
|
+ pioneers: {'COF': 0.5, 'DW': 4, 'RAT': 4, 'OVE': 0.5, 'PWO': 0.2},
|
|
|
|
|
+ settlers: {'DW': 5, 'RAT': 6, 'KOM': 1, 'EXO': 0.5, 'REP': 0.2, 'PT': 0.5},
|
|
|
|
|
+ technicians: {'DW': 7.5, 'RAT': 7, 'ALE': 1, 'MED': 0.5, 'SC': 0.1, 'HMS': 0.5, 'SCN': 0.1},
|
|
|
|
|
+ engineers: {'DW': 10, 'MED': 0.5, 'GIN': 1, 'FIM': 7, 'VG': 0.2, 'HSS': 0.2, 'PDA': 0.1},
|
|
|
|
|
+ scientists: {'DW': 10, 'MED': 0.5, 'WIN': 1, 'MEA': 7, 'NST': 0.1, 'LC': 0.2, 'WS': 0.05},
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+function buildingDailyCost(building: Building, prices: Record<string, RawPrice>): number {
|
|
|
|
|
+ let cost = 0;
|
|
|
|
|
+ for (const [workerType, mats] of Object.entries(WORKER_CONSUMPTION)) {
|
|
|
|
|
+ const workers = building[workerType as keyof typeof WORKER_CONSUMPTION];
|
|
|
|
|
+ for (const [mat, per100] of Object.entries(mats)) {
|
|
|
|
|
+ const price = prices[mat].VWAP30D;
|
|
|
|
|
+ if (price == null) throw new Error(`no price for ${mat}`);
|
|
|
|
|
+ cost += price * workers * per100 / 100;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return cost;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
function renderProduction(expertiseGroups: Record<string, string[]>, production: Production,
|
|
function renderProduction(expertiseGroups: Record<string, string[]>, production: Production,
|
|
|
- prices: Record<string, RawPrice>, recipes: Record<string, Recipe>): HTMLElement {
|
|
|
|
|
|
|
+ prices: Record<string, RawPrice>, recipes: Record<string, Recipe>,
|
|
|
|
|
+ buildingsByTicker: Record<string, Building>): HTMLElement {
|
|
|
const section = element('section');
|
|
const section = element('section');
|
|
|
section.append(element('h2', {textContent: 'production'}));
|
|
section.append(element('h2', {textContent: 'production'}));
|
|
|
|
|
|
|
|
|
|
+ let totalConsumablesCost = 0;
|
|
|
for (const [expertise, buildings] of Object.entries(expertiseGroups)) {
|
|
for (const [expertise, buildings] of Object.entries(expertiseGroups)) {
|
|
|
section.append(element('h3', {textContent: expertise}));
|
|
section.append(element('h3', {textContent: expertise}));
|
|
|
for (const building of buildings) {
|
|
for (const building of buildings) {
|
|
@@ -265,12 +289,17 @@ function renderProduction(expertiseGroups: Record<string, string[]>, production:
|
|
|
buildingMins += amount / outputPerRun * (recipe.time_ms / 1000 / 60);
|
|
buildingMins += amount / outputPerRun * (recipe.time_ms / 1000 / 60);
|
|
|
}
|
|
}
|
|
|
const numBuildings = buildingMins / (24*60) / 5 / 1.605; // one ship every 5 days, 160.5% efficiency
|
|
const numBuildings = buildingMins / (24*60) / 5 / 1.605; // one ship every 5 days, 160.5% efficiency
|
|
|
- const buildingRow = element('div', {className: 'building-row', textContent: `${formatFixed(numBuildings, 1)}x${building}`});
|
|
|
|
|
|
|
+ const consumablesCost = buildingDailyCost(buildingsByTicker[building], prices) * Math.round(Math.max(1, numBuildings));
|
|
|
|
|
+ totalConsumablesCost += consumablesCost;
|
|
|
|
|
+ const buildingRow = element('div', {className: 'building-row',
|
|
|
|
|
+ textContent: `${formatFixed(numBuildings, 1)}x${building} (${formatWhole(consumablesCost)}/d)`});
|
|
|
buildingRow.append(buildingMats);
|
|
buildingRow.append(buildingMats);
|
|
|
section.append(buildingRow);
|
|
section.append(buildingRow);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ section.append(element('h4', {textContent: `total consumables cost: ${formatWhole(totalConsumablesCost)}/day,
|
|
|
|
|
+ ${formatWhole(totalConsumablesCost * 5)}/ship`}));
|
|
|
return section;
|
|
return section;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -319,12 +348,18 @@ interface RawPrice {
|
|
|
ExchangeCode: string
|
|
ExchangeCode: string
|
|
|
Ask: number | null
|
|
Ask: number | null
|
|
|
AverageTraded30D: number | null
|
|
AverageTraded30D: number | null
|
|
|
|
|
+ VWAP30D: number | null
|
|
|
Supply: number
|
|
Supply: number
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
interface Building {
|
|
interface Building {
|
|
|
building_ticker: string
|
|
building_ticker: string
|
|
|
expertise: string
|
|
expertise: string
|
|
|
|
|
+ pioneers: number
|
|
|
|
|
+ settlers: number
|
|
|
|
|
+ technicians: number
|
|
|
|
|
+ engineers: number
|
|
|
|
|
+ scientists: number
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
type PMMGCompanyProduction = Record<string, Record<string, {amount: number}>>;
|
|
type PMMGCompanyProduction = Record<string, Record<string, {amount: number}>>;
|