Forráskód Böngészése

Update_Percentile_Formatting_and_Logic_Inversion

Changed percentile format to standard percentage syntax (`99.0%`) and inverted cost metrics so `100.0%` universally equates to the best outcome.

        PURPOSE:
        To establish logical consistency and readability. Previously, percentiles merely mapped to numerical size (`0.00` to `1.00`), causing "bad" metrics like massive capital expenditures to boast a 99th percentile rating, which contradicts established data analysis conventions. By inverting the math strictly for cost-based metrics, users can now quickly skim the dashboard knowing that `99.0%` is an objectively excellent outcome across every single column.

        IMPLEMENTATION DETAILS:
        - `ts/roi.ts`: Modified `getPercentile()` to accept an `invert: boolean = false` parameter.
        - `ts/roi.ts`: When `invert` is active, the function calculates `1.0 - decimal` before formatting.
        - `ts/roi.ts`: Updated the return string from `.toFixed(2)` to `(decimal * 100).toFixed(1) + "%"`.
        - `ts/roi.ts`: Explicitly routed the six calls: `profit_per_base` and `market_capacity_base` remain `false` (highest numerical value is best), while `break_even`, `capex_val`, `opex_val`, and `normalized_logistics_per_base` are passed as `true` (lowest numerical value is best).
        - `ts/roi.ts`: Updated the "Profit/Base" tooltip legend to explicitly clarify the new 100.0% desirability metric for the user.
Thomas Knott 2 hete
szülő
commit
f6f142c571
1 módosított fájl, 22 hozzáadás és 18 törlés
  1. 22 18
      ts/roi.ts

+ 22 - 18
ts/roi.ts

@@ -57,8 +57,6 @@ let opexMetric: MetricType = (localStorage.getItem('roi-opex-metric') as MetricT
 let revenueMetric: MetricType = (localStorage.getItem('roi-revenue-metric') as MetricType) || 'vwap';
 let includeShips: boolean = localStorage.getItem('roi-include-ships') === 'true';
 
-// EXTREME DETAIL: Added state tracking for the negative profit toggle. 
-// It defaults to 'true' (showing negatives) to preserve previous user experience until toggled.
 let showNegativeProfit: boolean = localStorage.getItem('roi-show-negative') !== 'false';
 let workingCapitalDays: number = parseInt(localStorage.getItem('roi-working-capital') || '3', 10);
 let targetPermit: number = parseInt(localStorage.getItem('roi-target-permit') || '2', 10);
@@ -152,9 +150,10 @@ async function render() {
 				th.style.cursor = 'pointer';
 				th.title = ''; 
 				
+				// EXTREME DETAIL: Updated the tooltip legend to explain that 100.0% is universally good.
 				if (keys[i] === 'profit_per_base') {
 					th.textContent = 'Profit/Base';
-					th.dataset.tooltip = 'Click to sort.\n\nDaily profit scaled to a full 500-area planetary base.\n(Percentiles rank 0.00 as lowest numerical value to 1.00 as highest)';
+					th.dataset.tooltip = 'Click to sort.\n\nDaily profit scaled to a full 500-area planetary base.\n(Percentiles rank 100.0% as the most desirable outcome, i.e., highest profit or lowest cost.)';
 				} else if (keys[i] === 'capex_val') {
 					th.textContent = 'CapEx/Base';
 					th.dataset.tooltip = 'Click to sort.\n\nTotal capital expenditure scaled to a full 500-area planetary base.\nIncludes base construction, working capital (days of OpEx), optional HQ Upgrade materials for the target permit, and optional Ship CapEx.';
@@ -219,8 +218,6 @@ async function render() {
 		return true;
 	});
 
-	// EXTREME DETAIL: Notice that this is initialized as 'let' instead of 'const'.
-	// This allows us to re-assign the array post-mapping if the negative profit toggle is active.
 	let profitsWithMetrics: ProfitWithMetrics[] = filteredProfits.map(p => {
 		const bases = p.area / 500;
 		const opex_val = p.opex[opexMetric] / bases;
@@ -247,10 +244,6 @@ async function render() {
 		return { ...p, capex_val, opex_val, revenue_val, profit_per_base, break_even, hq_capex };
 	});
 
-	// EXTREME DETAIL: We filter the array again AFTER mapping the dynamic pricing logic.
-	// This ensures we are testing the user's specific VWAP/Bid/Ask permutation, and doing
-	// this before the percentile arrays are generated guarantees money-losing bases are entirely
-	// excluded from the active percentile population.
 	if (!showNegativeProfit) {
 		profitsWithMetrics = profitsWithMetrics.filter(p => p.profit_per_base > 0);
 	}
@@ -280,12 +273,15 @@ async function render() {
 	for (const p of profitsWithMetrics) {
 		const tr = document.createElement('tr');
 		
-		const pctProfit = getPercentile(p.profit_per_base, arrProfit);
-		const pctBreak = getPercentile(p.break_even, arrBreak);
-		const pctCapex = getPercentile(p.capex_val, arrCapex);
-		const pctOpex = getPercentile(p.opex_val, arrOpex);
-		const pctLog = getPercentile(p.normalized_logistics_per_base, arrLog);
-		const pctCap = getPercentile(p.market_capacity_base, arrCap);
+		// EXTREME DETAIL: We explicitly route each column to its correct `invert` state.
+		// Profit and Market Cap use `false` (highest numerical value = 100%).
+		// Break Even, CapEx, OpEx, and Logistics use `true` (lowest numerical value = 100%).
+		const pctProfit = getPercentile(p.profit_per_base, arrProfit, false);
+		const pctBreak = getPercentile(p.break_even, arrBreak, true);
+		const pctCapex = getPercentile(p.capex_val, arrCapex, true);
+		const pctOpex = getPercentile(p.opex_val, arrOpex, true);
+		const pctLog = getPercentile(p.normalized_logistics_per_base, arrLog, true);
+		const pctCap = getPercentile(p.market_capacity_base, arrCap, false);
 		
 		tr.innerHTML = `
 			<td>${p.outputs.map(o => o.ticker).join(', ')}</td>
@@ -331,14 +327,22 @@ async function render() {
 	saveState();
 }
 
-function getPercentile(val: number, sortedArr: number[]): string {
-	if (sortedArr.length < 2) return "1.00";
+// EXTREME DETAIL: Added 'invert' boolean to flip logic for cost metrics.
+// Returns a heavily formatted string e.g., '99.0%'.
+function getPercentile(val: number, sortedArr: number[], invert: boolean = false): string {
+	if (sortedArr.length < 2) return "100.0%";
 	let less = 0;
 	for (let i = 0; i < sortedArr.length; i++) {
 		if (sortedArr[i] < val) less++;
 		else break; 
 	}
-	return (less / (sortedArr.length - 1)).toFixed(2);
+	
+	let decimal = less / (sortedArr.length - 1);
+	if (invert) {
+		decimal = 1.0 - decimal;
+	}
+	
+	return (decimal * 100).toFixed(1) + "%";
 }
 
 function saveState() {