import {setupPopover} from './popover';
const roi: Promise<{lastModified: Date, profits: Profit[]}> = (async function () {
const response = await fetch('/roi.json');
const lastModified = new Date(response.headers.get('last-modified')!);
const profits = await response.json();
return {lastModified, profits};
})();
const lowVolume = document.querySelector('input#low-volume') as HTMLInputElement;
const expertise = {
AGRICULTURE: 'agri',
CHEMISTRY: 'chem',
CONSTRUCTION: 'const',
ELECTRONICS: 'elec',
FOOD_INDUSTRIES: 'food ind',
FUEL_REFINING: 'fuel',
MANUFACTURING: 'mfg',
METALLURGY: 'metal',
RESOURCE_EXTRACTION: 'res ext',
} as const;
const expertiseSelect = document.querySelector('select#expertise') as HTMLSelectElement;
for (const key of Object.keys(expertise)) {
const option = document.createElement('option');
option.value = key;
option.textContent = key.replace('_', ' ').toLowerCase();
expertiseSelect.appendChild(option);
}
const formatDecimal = new Intl.NumberFormat(undefined,
{maximumFractionDigits: 2, maximumSignificantDigits: 6, roundingPriority: 'lessPrecision'}).format;
const formatWhole = new Intl.NumberFormat(undefined, {maximumFractionDigits: 0}).format;
async function render() {
const tbody = document.querySelector('tbody')!;
tbody.innerHTML = '';
const {lastModified, profits} = await roi;
for (const p of profits) {
const volumeRatio = p.output_per_day / p.average_traded_7d;
if (!lowVolume.checked && volumeRatio > 0.05)
continue;
if (expertiseSelect.value !== '' && p.expertise !== expertiseSelect.value)
continue;
const tr = document.createElement('tr');
const profitPerArea = p.profit_per_day / p.area;
const breakEven = p.profit_per_day > 0 ? p.capex / p.profit_per_day : Infinity;
tr.innerHTML = `
${p.outputs.map(o => o.ticker).join(', ')} |
${expertise[p.expertise]} |
${formatDecimal(profitPerArea)} |
${formatDecimal(breakEven)}d |
${formatWhole(p.capex)} |
${formatWhole(p.cost_per_day)} |
${formatDecimal(p.logistics_per_area)} |
${formatDecimal(p.output_per_day)}
${formatWhole(p.average_traded_7d)}
|
`;
const output = tr.querySelector('td')!;
output.dataset.tooltip = p.recipe;
const profitCell = tr.querySelectorAll('td')[2];
const revenue = p.outputs.reduce((sum, o) => sum + o.amount * o.vwap_7d, 0);
const inputCost = p.input_costs.reduce((sum, o) => sum + o.amount * o.vwap_7d, 0);
profitCell.dataset.tooltip = formatMatPrices(p.outputs) + '\n\n' +
formatMatPrices(p.input_costs) + '\n' +
'worker consumables: ' + formatWhole(p.worker_consumable_cost_per_day) + '\n\n' +
`(${formatWhole(revenue)} - ${formatWhole(inputCost)}) × ${formatDecimal(p.runs_per_day)} runs ` +
`- ${formatWhole(p.worker_consumable_cost_per_day)} = ${formatDecimal(p.profit_per_day)}\n` +
`${formatDecimal(p.profit_per_day)} / ${formatWhole(p.area)} area = ${formatDecimal(profitPerArea)}`;
tbody.appendChild(tr);
}
document.getElementById('last-updated')!.textContent =
`last updated: ${lastModified.toLocaleString(undefined, {dateStyle: 'full', timeStyle: 'long', hour12: false})}`;
}
function color(n: number, low: number, high: number): string {
// scale n from low..high to 0..1 clamped
const scale = Math.min(Math.max((n - low) / (high - low), 0), 1);
return `color-mix(in xyz, #0aa ${scale * 100}%, #f80)`;
}
function formatMatPrices(matPrices: MatPrice[]): string {
return matPrices.map(({ticker, amount, vwap_7d}) =>
`${ticker}: ${amount} × ${formatDecimal(vwap_7d)} = ${formatWhole(amount * vwap_7d)}`).join('\n');
}
setupPopover();
lowVolume.addEventListener('change', render);
expertiseSelect.addEventListener('change', render);
render();
interface Profit {
outputs: MatPrice[]
recipe: string
expertise: keyof typeof expertise
profit_per_day: number
area: number
capex: number
cost_per_day: number
input_costs: MatPrice[]
worker_consumable_cost_per_day: number
runs_per_day: number
logistics_per_area: number
output_per_day: number
average_traded_7d: number
}
interface MatPrice {
ticker: string
amount: number
vwap_7d: number
}