roi.ts 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970
  1. import {setupPopover} from './popover';
  2. const roi: Promise<{lastModified: Date, profits: Profit[]}> = (async function () {
  3. const response = await fetch('https://prun.raylu.net/roi.json');
  4. const lastModified = new Date(response.headers.get('last-modified')!);
  5. const profits = await response.json();
  6. return {lastModified, profits};
  7. })();
  8. const lowVolume = document.querySelector('#low-volume') as HTMLInputElement;
  9. async function render() {
  10. const formatDecimal = new Intl.NumberFormat(undefined,
  11. {maximumFractionDigits: 2, maximumSignificantDigits: 6, roundingPriority: 'lessPrecision'}).format;
  12. const formatWhole = new Intl.NumberFormat(undefined, {maximumFractionDigits: 0}).format;
  13. const tbody = document.querySelector('tbody')!;
  14. tbody.innerHTML = '';
  15. const {lastModified, profits} = await roi;
  16. for (const p of profits) {
  17. const volumeRatio = p.output_per_day / p.average_traded_7d;
  18. if (!lowVolume.checked && volumeRatio > 0.05) {
  19. continue;
  20. }
  21. const tr = document.createElement('tr');
  22. const profit_per_area = p.profit_per_day / p.area;
  23. const break_even = p.profit_per_day > 0 ? p.capex / p.profit_per_day : Infinity;
  24. tr.innerHTML = `
  25. <td>${p.output}</td>
  26. <td>${p.expertise}</td>
  27. <td style="color: ${color(profit_per_area, 0, 250)}">${formatDecimal(profit_per_area)}</td>
  28. <td><span style="color: ${color(break_even, 30, 3)}">${formatDecimal(break_even)}</span>d</td>
  29. <td style="color: ${color(p.capex, 300_000, 40_000)}">${formatWhole(p.capex)}</td>
  30. <td style="color: ${color(p.cost_per_day, 40_000, 1_000)}">${formatWhole(p.cost_per_day)}</td>
  31. <td style="color: ${color(p.logistics_per_area, 2, 0.2)}">${formatDecimal(p.logistics_per_area)}</td>
  32. <td>
  33. ${formatDecimal(p.output_per_day)}<br>
  34. <span style="color: ${color(volumeRatio, 0.05, 0.002)}">${formatWhole(p.average_traded_7d)}</span>
  35. </td>
  36. `;
  37. const output = tr.querySelector('td')!;
  38. output.dataset.tooltip = p.recipe;
  39. tbody.appendChild(tr);
  40. }
  41. document.getElementById('last-updated')!.textContent =
  42. `last updated: ${lastModified.toLocaleString(undefined, {dateStyle: 'full', timeStyle: 'long', hour12: false})}`;
  43. }
  44. function color(n: number, low: number, high: number): string {
  45. // scale n from low..high to 0..1 clamped
  46. const scale = Math.min(Math.max((n - low) / (high - low), 0), 1);
  47. return `color-mix(in xyz, #0aa ${scale * 100}%, #f80)`;
  48. }
  49. setupPopover();
  50. lowVolume.addEventListener('change', render);
  51. render();
  52. interface Profit {
  53. output: string
  54. recipe: string
  55. expertise: string
  56. profit_per_day: number
  57. area: number
  58. capex: number
  59. cost_per_day: number
  60. logistics_per_area: number
  61. output_per_day: number
  62. average_traded_7d: number
  63. }