Parcourir la source

Add methodology documentation block

Purpose: Add a persistent "How it Works" section to the bottom of the page to demystify the background calculations.
        - Created a new `initDocumentation()` function utilizing DOM manipulation to dynamically build a comprehensive `<details>` HTML block.
        - Styled the block to blend naturally with the existing UI, using standard CSS for spacing and borders.
        - Broken down the mathematical process behind Area normalization, CapEx/OpEx mapping, Logistics standardizations, and percentile weights into 6 readable sections.
        - Triggered the initialization exactly once at the end of the script execution loop.
Thomas Knott il y a 1 semaine
Parent
commit
40ce74ef3f
1 fichiers modifiés avec 59 ajouts et 0 suppressions
  1. 59 0
      ts/roi.ts

+ 59 - 0
ts/roi.ts

@@ -480,12 +480,71 @@ function formatMatPrices(matPrices: MatPrice[], metric: MetricType, runs_per_day
 	}).join('\n');
 }
 
+// EXTREME DETAIL: Injects a comprehensive, casually written documentation section at the bottom of the page.
+// This demystifies the PrUn API data pipeline and exposes the raw formulas used for the table's metrics.
+function initDocumentation() {
+	// Prevent duplicate documentation blocks if initialized multiple times (e.g. HMR or weird state)
+	if (document.getElementById('methodology-doc')) return;
+
+	const docDetails = document.createElement('details');
+	docDetails.id = 'methodology-doc';
+	docDetails.style.marginTop = '40px';
+	docDetails.style.marginBottom = '40px';
+	docDetails.style.padding = '15px';
+	docDetails.style.border = '1px solid #444';
+	docDetails.style.borderRadius = '8px';
+	docDetails.style.backgroundColor = 'rgba(128, 128, 128, 0.1)';
+	
+	docDetails.innerHTML = `
+		<summary style="cursor: pointer; font-size: 1.25em; font-weight: bold;">📖 Documentation: How These Numbers Are Calculated</summary>
+		<div style="margin-top: 20px; line-height: 1.6; font-size: 0.95em;">
+			<p>This tool pulls live data from the PrUn APIs and calculates the true profitability of every recipe in the game. Here is the step-by-step math from the raw data to your screen:</p>
+			
+			<h3 style="margin-top: 1.5em;">1. The "Base" Normalization</h3>
+			<p>To make fair comparisons between a tiny Farm and a massive Shipyard, all metrics are scaled to a standard <strong>500-area planetary base</strong>. First, we calculate the true area of a building by adding its base area footprint to the area of the habitation modules required for its specific workers. Then, we divide 500 by this true area to figure out exactly how many of these fully-staffed buildings fit on one planet.</p>
+
+			<h3 style="margin-top: 1.5em;">2. Revenue & OpEx (Operational Expenditure)</h3>
+			<p><strong>Revenue:</strong> Daily output amount × Runs per day × Selected Price (VWAP, Bid, or Ask).</p>
+			<p><strong>OpEx:</strong> (Daily input amount × Selected Price) + <strong>Worker Consumables</strong>. The consumables cost is calculated by looking up the exact daily drinking water, rations, luxury goods, etc., needed to keep your specific workforce alive and operating at 100%.</p>
+			<p><strong>Profit/Base:</strong> Total Daily Revenue - Total Daily OpEx.</p>
+
+			<h3 style="margin-top: 1.5em;">3. CapEx (Capital Expenditure) & Break Even</h3>
+			<p><strong>CapEx</strong> starts with the exact material costs to construct the buildings (plus MCG/BSE/etc. for the planetary surface). Using the dropdown toggles at the top, it dynamically expands to include:</p>
+			<ul style="margin-top: 0.5em; margin-bottom: 0.5em; padding-left: 20px;">
+				<li style="margin-bottom: 0.5em;"><strong>Working Capital:</strong> Adds your daily OpEx multiplied by the number of days you need to buffer in a warehouse.</li>
+				<li style="margin-bottom: 0.5em;"><strong>HQ Upgrades:</strong> Adds the material costs of upgrading your Headquarters to the selected Permit level.</li>
+				<li style="margin-bottom: 0.5em;"><strong>Ship CapEx:</strong> Adds a flat 800,000 CIS per ship required to move your goods (calculated dynamically based on your Logistics Bottleneck and the Round Trip time).</li>
+			</ul>
+			<p><strong>Break Even:</strong> Total CapEx ÷ Daily Profit. This tells you exactly how many days it takes for the entire setup to pay for itself.</p>
+
+			<h3 style="margin-top: 1.5em;">4. Logistics Bottleneck</h3>
+			<p>Every recipe requires shipping inputs in and outputs out. We calculate the total Weight (t) and Volume (m³) for both. We then divide these by a standard ship's capacity (3,000t or 1,000m³). The highest resulting number is your <strong>Logistics Bottleneck</strong>. For example, if a recipe fills 1.5 ships per day with output volume, your bottleneck is "1.5 m³ (O)".</p>
+
+			<h3 style="margin-top: 1.5em;">5. Market Capacity (Saturation)</h3>
+			<p>Even if a recipe is insanely profitable, it doesn't matter if nobody is buying it. We divide the <strong>7-Day Average Traded Volume</strong> on the selected exchange by the <strong>Daily Output of a Full 500-Area Base</strong>. If the Market Capacity is "0.5", it means building just half a base will completely saturate the market and crash the price.</p>
+
+			<h3 style="margin-top: 1.5em;">6. Percentiles (Normalized vs Absolute)</h3>
+			<p>Percentiles rank every recipe from 0.0% (worst) to 100.0% (best).<br>
+			<strong>Absolute</strong> percentiles treat every recipe equally (e.g., 1 vote per recipe).<br> 
+			<strong>Normalized</strong> percentiles weight each recipe by its <em>Market Cash Flow</em> (Revenue × Market Capacity). This prevents thinly traded, obscure recipes from artificially skewing the rankings, giving you a much more realistic view of the overall economy.</p>
+		</div>
+	`;
+
+	const table = document.querySelector('table');
+	if (table && table.parentNode) {
+		table.parentNode.appendChild(docDetails);
+	} else {
+		document.body.appendChild(docDetails);
+	}
+}
+
 setupPopover();
 lowVolume.addEventListener('change', render);
 cxSelect.addEventListener('change', render);
 expertiseSelect.addEventListener('change', render);
 buildingSelect.addEventListener('change', render);
 render();
+initDocumentation();
 
 interface Metrics {
 	vwap: number;