Skip to content

Instantly share code, notes, and snippets.

@zackiles
Created January 11, 2025 08:08
Show Gist options
  • Save zackiles/047434eadb0b65aaae0e16a39fa995e8 to your computer and use it in GitHub Desktop.
Save zackiles/047434eadb0b65aaae0e16a39fa995e8 to your computer and use it in GitHub Desktop.
Appscript for Google Sheets that build a comprehensive and interactive financial model for a GPU rental business on a decentralized rental network
/*****************************************************
* Google Apps Script for a Comprehensive
* GPU Rental Business Financial Model in Google Sheets
*
* This script:
* 1. Creates/clears four sheets:
* - "Inputs"
* - "Calculations"
* - "Dashboard"
* - "Scenario Results"
* 2. Inserts default values (including multiple GPU variants, overhead, depreciation,
* quantity options, etc.)
* 3. Builds row-by-row calculations for all variants and quantity options
* 4. Creates a Dashboard summarizing key metrics (with charts)
* 5. Stores scenario toggles ("Best", "Worst", "LowRevenue", "HighRevenue")
* and a function (runAllScenarios) that systematically sets each scenario,
* logs the results, and writes them to the "Scenario Results" sheet
*
* HOW TO USE:
* 1. Create a new, blank Google Sheet.
* 2. Open Extensions → Apps Script (or Tools → Script Editor in older UIs).
* 3. Delete any existing code in the script editor and paste this entire script.
* 4. Click Save (you may be prompted to name your project).
* 5. In the toolbar, select "setupModel" from the function dropdown and click "Run".
* 6. Authorize if prompted. Return to the sheet to see your new tabs and custom menu.
*
* NOTE: The code attempts to be as comprehensive as possible, but feel free to extend
* scenario toggles, depreciation calculations, or advanced rate logic as needed.
*****************************************************/
/**
* onOpen() - Automatically adds our custom menu for quick actions
*/
function onOpen() {
SpreadsheetApp.getUi()
.createMenu('GPU Rental Model')
.addItem('Set Up Model', 'setupModel')
.addItem('Load Best Case Scenario', 'loadBestCase')
.addItem('Load Worst Case Scenario', 'loadWorstCase')
.addItem('Load LowRevenue Scenario', 'loadLowRevenueScenario')
.addItem('Load HighRevenue Scenario', 'loadHighRevenueScenario')
.addItem('Run All Scenarios', 'runAllScenarios')
.addToUi();
}
/**
* setupModel() - Main entry point to configure or refresh the Sheets
*/
function setupModel() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
// Create or clear each sheet we'll use
createOrClearSheet(ss, 'Inputs');
createOrClearSheet(ss, 'Calculations');
createOrClearSheet(ss, 'Dashboard');
createOrClearSheet(ss, 'Scenario Results');
// Fill out the sheets
const inputsSheet = ss.getSheetByName('Inputs');
fillInputsSheet(inputsSheet);
formatInputsSheet(inputsSheet);
const calculationsSheet = ss.getSheetByName('Calculations');
setupCalculationsSheet(calculationsSheet);
formatCalculationsSheet(calculationsSheet);
const dashboardSheet = ss.getSheetByName('Dashboard');
setupDashboardSheet(dashboardSheet);
formatDashboardSheet(dashboardSheet);
const scenarioResultsSheet = ss.getSheetByName('Scenario Results');
setupScenarioResultsSheet(scenarioResultsSheet);
formatScenarioResultsSheet(scenarioResultsSheet);
// (Optional) Load a default scenario to populate sample data
loadBestCase();
}
/**
* createOrClearSheet() - Utility to create a sheet if non-existent,
* or clear it if it already exists
*/
function createOrClearSheet(ss, sheetName) {
let sheet = ss.getSheetByName(sheetName);
if (!sheet) {
ss.insertSheet(sheetName);
} else {
sheet.clear();
sheet.clearFormats();
sheet.clearNotes();
}
}
/**
* fillInputsSheet() - Inserts default input values, including
* different GPU variants, overhead, depreciation, etc.
*
* Rows 1-...: General parameters
* Following rows: GPU Variants table
* Another section: Quantity options
*/
function fillInputsSheet(sheet) {
// High-level inputs
const inputsData = [
// The columns are: [ Label, Default Value ]
['Electricity Rate (USD/kWh)', 0.12],
['Additional System Overhead (W)', 50],
['Monthly Depreciation (Low)', 0.015],
['Monthly Depreciation (High)', 0.05],
['Annual Depreciation (Best)', 0.10],
['Annual Depreciation (Worst)', 0.25],
['Platform Fee (%) Single GPU', 10], // Updated to a realistic default (10%)
['Platform Fee (%) Eight GPUs', 25], // Updated to a realistic default (25%)
['Maintenance Cost/Month (USD)', 0],
['Hours of Operation per Day', 24],
['Days in Month for Calc', 30],
['Break-even Rate (Single GPU)', 0.25],
['Break-even Rate (Eight GPUs)', 2.00],
];
sheet.getRange(1, 1, inputsData.length, 2).setValues(inputsData);
// Label GPU Variants section
const startRow = inputsData.length + 2; // 13 + 2 = 15
sheet.getRange(startRow, 1).setValue('GPU Variants (Name, Price USD, Power Draw W)');
// Define GPU variants
const gpuVariants = [
['Variant A (Low)', 1800, 350],
['Variant B (Mid)', 2800, 400],
['Variant C (High)', 3000, 400],
];
// Place them in the sheet: columns A, B, C
sheet.getRange(startRow + 1, 1, gpuVariants.length, 3).setValues(gpuVariants);
// Label Quantity Options
const qtyStartRow = startRow + gpuVariants.length + 3; // 15 + 3 + 3 = 21
sheet.getRange(qtyStartRow, 1).setValue('Quantity Options');
const quantityData = [
['Single GPU', 1],
['Eight GPUs', 8]
];
sheet.getRange(qtyStartRow + 1, 1, quantityData.length, 2).setValues(quantityData);
}
/**
* formatInputsSheet() - Applies formatting to the Inputs sheet
*/
function formatInputsSheet(sheet) {
// Apply bold to general input headers
const inputsHeaderRange = sheet.getRange(1, 1, 13, 2);
inputsHeaderRange.setFontWeight('bold');
// Define rows based on inputsData length
const inputsDataLength = 13;
const startRow = inputsDataLength + 2; // 15
const gpuVariantsStartRow = startRow;
const gpuVariantsCount = 3;
const qtyStartRow = gpuVariantsStartRow + gpuVariantsCount + 3; // 15 + 3 + 3 = 21
// Apply borders to GPU Variants section
const gpuVariantsRange = sheet.getRange(gpuVariantsStartRow + 1, 1, gpuVariantsCount, 3);
gpuVariantsRange.setBorder(true, true, true, true, true, true);
// Apply borders to Quantity Options section
const quantityCount = 2;
const quantityRange = sheet.getRange(qtyStartRow + 1, 1, quantityCount, 2);
quantityRange.setBorder(true, true, true, true, true, true);
// Bold section headers
sheet.getRange(gpuVariantsStartRow, 1).setFontWeight('bold');
sheet.getRange(qtyStartRow, 1).setFontWeight('bold');
// Enable text wrapping for all cells
sheet.getRange(1, 1, sheet.getMaxRows(), sheet.getMaxColumns()).setWrap(true);
// Auto-resize columns
sheet.autoResizeColumns(1, 3);
// Set number formats
// General Parameters (Column B)
// Rows 1: Electricity Rate (USD/kWh) - Currency
// Row 2: Overhead - Number
// Rows 3-6: Depreciation - Percentage
// Rows 7-8: Platform Fee (%) - Percentage
// Row 9: Maintenance Cost - Currency
// Rows 10-11: Hours and Days - Number
// Rows 12-13: Break-even Rates - Number
// Apply percentage format to depreciation and fees
sheet.getRange(3, 2, 4, 1).setNumberFormat('0.00%'); // Rows 3-6
sheet.getRange(7, 2, 2, 1).setNumberFormat('0.00%'); // Rows 7-8
// Apply currency format to relevant cells
sheet.getRange(1, 2, 1, 1).setNumberFormat('$0.00'); // Electricity Rate
sheet.getRange(9, 2, 1, 1).setNumberFormat('$0.00'); // Maintenance Cost
// Apply number format to other numerical inputs
sheet.getRange(2, 2, 1, 1).setNumberFormat('0'); // Overhead
sheet.getRange(10, 2, 1, 1).setNumberFormat('0'); // Hours of Operation
sheet.getRange(11, 2, 1, 1).setNumberFormat('0'); // Days in Month
sheet.getRange(12, 2, 2, 1).setNumberFormat('0.00'); // Break-even Rates
// GPU Variants Table Formatting
// Bold headers and background color
sheet.getRange(gpuVariantsStartRow + 1, 1, 1, 3)
.setFontWeight('bold')
.setBackground('#D9E1F2');
// Set number formats for GPU Variants
sheet.getRange(gpuVariantsStartRow + 1, 2, gpuVariantsCount, 1).setNumberFormat('$#,##0');
sheet.getRange(gpuVariantsStartRow + 1, 3, gpuVariantsCount, 1).setNumberFormat('0');
// Quantity Options Table Formatting
// Bold headers and background color
sheet.getRange(qtyStartRow + 1, 1, 1, 2)
.setFontWeight('bold')
.setBackground('#D9E1F2');
// Set number formats for Quantity Options
sheet.getRange(qtyStartRow + 1, 2, quantityCount, 1).setNumberFormat('0');
}
/**
* setupCalculationsSheet() - Builds a table of calculations referencing
* the GPU variants and quantity options from the Inputs sheet, computing
* monthly revenue, electricity cost, net revenue, profit, depreciation, ROI, etc.
*/
function setupCalculationsSheet(sheet) {
// Header row
const headers = [
'GPU Variant',
'Quantity',
'GPU Price (USD)',
'Power Draw (W)',
'Rental Rate (USD/hour)',
'Monthly Hours',
'Monthly Revenue',
'Electricity Cost',
'Platform Fee',
'Net Revenue',
'Profit',
'Monthly Dep (Low)',
'Value after 1 month (Low)',
'ROI (Low Dep) %'
];
sheet.getRange(1, 1, 1, headers.length).setValues([headers]);
const ss = SpreadsheetApp.getActiveSpreadsheet();
const inputsSheet = ss.getSheetByName('Inputs');
// Define where GPU Variants are located in Inputs sheet
const inputsDataLength = 13;
const gpuVariantsStartRow = inputsDataLength + 2; // 15
const gpuVariantsCount = 3;
const variantsRange = inputsSheet.getRange(gpuVariantsStartRow + 1, 1, gpuVariantsCount, 3);
const variantsData = variantsRange.getValues();
// Define where Quantity Options are located in Inputs sheet
const qtyStartRow = gpuVariantsStartRow + gpuVariantsCount + 3; // 15 + 3 + 3 = 21
const qtyCount = 2;
const qtyRange = inputsSheet.getRange(qtyStartRow + 1, 1, qtyCount, 2);
const qtyData = qtyRange.getValues(); // (Label, quantity)
// Create Calculations table: for each variant, for each quantity => 1 row
let rowIndex = 2; // Start writing from row 2 (row 1 is headers)
variantsData.forEach(variant => {
qtyData.forEach(qty => {
// GPU variant data
const variantName = variant[0];
const variantPrice = variant[1];
const variantPower = variant[2];
// Put them into the sheet
sheet.getRange(rowIndex, 1).setValue(variantName); // GPU Variant
sheet.getRange(rowIndex, 2).setValue(qty[1]); // Quantity
sheet.getRange(rowIndex, 3).setValue(variantPrice); // GPU Price
sheet.getRange(rowIndex, 4).setValue(variantPower); // Power Draw
// Placeholder for Rental Rate
sheet.getRange(rowIndex, 5).setValue(0.45);
// Monthly Hours = Hours of Operation per Day * Days in Month
sheet.getRange(rowIndex, 6).setFormula('=Inputs!B10 * Inputs!B11');
// Monthly Revenue = Rental Rate * Monthly Hours * Quantity
sheet.getRange(rowIndex, 7).setFormula(
`=E${rowIndex} * F${rowIndex} * B${rowIndex}`
);
// Electricity Cost
// = (Power Draw + overhead) * monthly hours * (electricity rate / 1000) * quantity
sheet.getRange(rowIndex, 8).setFormula(
`= (D${rowIndex} + Inputs!B2) * F${rowIndex} * (Inputs!B1 / 1000) * B${rowIndex}`
);
// Platform Fee
// IF Quantity=1 => Single GPU fee => Inputs!B7
// ELSE => Eight GPUs fee => Inputs!B8
sheet.getRange(rowIndex, 9).setFormula(
`=IF(B${rowIndex}=1, (Inputs!B7/100)*G${rowIndex}, (Inputs!B8/100)*G${rowIndex})`
);
// Net Revenue = Monthly Revenue - Platform Fee - Maintenance
sheet.getRange(rowIndex, 10).setFormula(
`=G${rowIndex} - I${rowIndex} - Inputs!B9`
);
// Profit = Net Revenue - Electricity Cost
sheet.getRange(rowIndex, 11).setFormula(
`=J${rowIndex} - H${rowIndex}`
);
// Monthly Dep (Low)
sheet.getRange(rowIndex, 12).setFormula('=Inputs!B3');
// Value after 1 month (Low) = Price * (1 - monthly_depr)
sheet.getRange(rowIndex, 13).setFormula(
`=C${rowIndex}*(1 - L${rowIndex})`
);
// ROI (Low Dep) % = (Profit + Dep Value) / Price * 100
sheet.getRange(rowIndex, 14).setFormula(
`=((K${rowIndex} + M${rowIndex}) / C${rowIndex})*100`
);
rowIndex++;
});
});
// Apply formatting
sheet.setFrozenRows(1); // Freeze header row
sheet.getRange(1, 1, 1, headers.length)
.setFontWeight('bold')
.setBackground('#D9E1F2')
.setHorizontalAlignment('center');
// Enable text wrapping for all cells
sheet.getRange(1, 1, sheet.getMaxRows(), sheet.getMaxColumns()).setWrap(true);
// Auto-resize columns
sheet.autoResizeColumns(1, headers.length);
// Set number formats
// Identify columns:
// C: GPU Price (USD) - Currency
// D: Power Draw (W) - Number
// E: Rental Rate (USD/hour) - Currency
// F: Monthly Hours - Number
// G: Monthly Revenue - Currency
// H: Electricity Cost - Currency
// I: Platform Fee - Currency
// J: Net Revenue - Currency
// K: Profit - Currency
// L: Monthly Dep (Low) - Percentage
// M: Value after 1 month (Low) - Currency
// N: ROI (Low Dep) % - Percentage
// Apply currency format
sheet.getRange(2, 3, sheet.getLastRow() - 1, 1).setNumberFormat('$#,##0.00');
sheet.getRange(2, 5, sheet.getLastRow() - 1, 1).setNumberFormat('$#,##0.00');
sheet.getRange(2, 7, sheet.getLastRow() - 1, 1).setNumberFormat('$#,##0.00');
sheet.getRange(2, 8, sheet.getLastRow() - 1, 1).setNumberFormat('$#,##0.00');
sheet.getRange(2, 9, sheet.getLastRow() - 1, 1).setNumberFormat('$#,##0.00');
sheet.getRange(2, 10, sheet.getLastRow() - 1, 1).setNumberFormat('$#,##0.00');
sheet.getRange(2, 11, sheet.getLastRow() - 1, 1).setNumberFormat('$#,##0.00');
sheet.getRange(2, 13, sheet.getLastRow() - 1, 1).setNumberFormat('$#,##0.00');
// Apply percentage format
sheet.getRange(2, 12, sheet.getLastRow() - 1, 1).setNumberFormat('0.00%');
sheet.getRange(2, 14, sheet.getLastRow() - 1, 1).setNumberFormat('0.00%');
// Align numerical columns to the right
const numericalColumns = ['C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N'];
numericalColumns.forEach(col => {
sheet.getRange(`${col}2:${col}${sheet.getLastRow()}`).setHorizontalAlignment('right');
});
}
/**
* formatCalculationsSheet() - Applies additional formatting to the Calculations sheet
* (This function is kept for extensibility; current formatting is handled in setupCalculationsSheet)
*/
function formatCalculationsSheet(sheet) {
// Additional formatting can be added here if needed in the future
}
/**
* setupDashboardSheet() - Creates a summary view
* of the calculations, including some references and charts.
*/
function setupDashboardSheet(dashboardSheet) {
// Clear existing content
dashboardSheet.clear();
// Title
dashboardSheet.getRange('A1').setValue('GPU Rental Dashboard');
// Subtitle or description
dashboardSheet.getRange('A2').setValue('Example: Profit (Variant A, Single GPU)');
// Reference Profit from Calculations
dashboardSheet.getRange('B2').setFormula('=Calculations!K2');
// Insert a bar chart comparing "Profit" across all rows in Calculations
const ss = SpreadsheetApp.getActiveSpreadsheet();
const calcSheet = ss.getSheetByName('Calculations');
const lastCalcRow = calcSheet.getLastRow();
const chartRangeLabels = calcSheet.getRange(2, 1, lastCalcRow - 1, 1); // GPU Variant names
const chartRangeData = calcSheet.getRange(2, 11, lastCalcRow - 1, 1); // Profit in column K
let profitChart = dashboardSheet.newChart()
.setChartType(Charts.ChartType.BAR)
.addRange(chartRangeLabels)
.addRange(chartRangeData)
.setOption('title', 'Profit by GPU Variant & Quantity')
.setOption('hAxis', { title: 'Profit (USD)', format: '$#,##0.00' })
.setOption('vAxis', { title: 'GPU Variant & Quantity' })
.setOption('legend', { position: 'none' })
.setPosition(4, 1, 0, 0)
.build();
dashboardSheet.insertChart(profitChart);
// Process Scenario Results to create a time series data table
const resultsSheet = ss.getSheetByName('Scenario Results');
const lastResultsRow = resultsSheet.getLastRow();
if (lastResultsRow > 1) { // Ensure there is data beyond headers
const resultsData = resultsSheet.getRange(2, 1, lastResultsRow - 1, 9).getValues();
// Create a map to hold scenario profits by date
const scenarioMap = {};
resultsData.forEach(row => {
const timestamp = new Date(row[0]);
const scenarioName = row[1];
const profit = row[7];
// Normalize the date to the first day of the month for grouping
const month = timestamp.getMonth();
const year = timestamp.getFullYear();
const normalizedDate = new Date(year, month, 1);
const dateKey = Utilities.formatDate(normalizedDate, ss.getSpreadsheetTimeZone(), 'MM/dd/yyyy');
if (!scenarioMap[dateKey]) {
scenarioMap[dateKey] = {};
}
scenarioMap[dateKey][scenarioName] = profit;
});
// Convert the map to a sorted array
const sortedDates = Object.keys(scenarioMap).sort((a, b) => new Date(a) - new Date(b));
const timeSeriesData = [['Date', 'Best Case', 'Worst Case', 'LowRevenue', 'HighRevenue']];
sortedDates.forEach(date => {
const row = [date];
['Best Case', 'Worst Case', 'LowRevenue', 'HighRevenue'].forEach(scenario => {
row.push(scenarioMap[date][scenario] || 0); // Use 0 if no data
});
timeSeriesData.push(row);
});
// Write the time series data to the Dashboard sheet starting at A5
dashboardSheet.getRange(5, 1, timeSeriesData.length, timeSeriesData[0].length).setValues(timeSeriesData);
// Insert a time series chart for Profit over Time by Scenario
let timeSeriesChart = dashboardSheet.newChart()
.setChartType(Charts.ChartType.LINE)
.addRange(dashboardSheet.getRange(5, 1, timeSeriesData.length, timeSeriesData[0].length))
.setOption('title', 'Profit Over Time by Scenario')
.setOption('hAxis', { title: 'Date', format: 'MM/dd/yyyy' })
.setOption('vAxis', { title: 'Profit (USD)', format: '$#,##0.00' })
.setOption('curveType', 'function') // Smooth lines
.setOption('legend', { position: 'bottom' })
.setOption('series', {
0: { color: '#1c91c0' }, // Best Case
1: { color: '#e7711b' }, // Worst Case
2: { color: '#4374e0' }, // LowRevenue
3: { color: '#e2431e' } // HighRevenue
})
.setPosition(20, 1, 0, 0)
.build();
dashboardSheet.insertChart(timeSeriesChart);
}
// Enable text wrapping for all cells
dashboardSheet.getRange(1, 1, dashboardSheet.getMaxRows(), dashboardSheet.getMaxColumns()).setWrap(true);
// Auto-resize columns
dashboardSheet.autoResizeColumns(1, 5);
}
/**
* formatDashboardSheet() - Applies formatting to the Dashboard sheet
*/
function formatDashboardSheet(sheet) {
// Format Title
sheet.getRange('A1').setFontWeight('bold').setFontSize(16);
// Format Subtitle
sheet.getRange('A2').setFontStyle('italic');
// Format the referenced Profit cell
sheet.getRange('B2').setNumberFormat('$#,##0.00').setFontWeight('bold');
// Align numerical columns to the right
sheet.getRange('B2').setHorizontalAlignment('right');
}
/**
* setupScenarioResultsSheet() - Prepares the "Scenario Results" sheet
* to store scenario outcomes for each row in Calculations.
*/
function setupScenarioResultsSheet(resultsSheet) {
// Header row:
const headers = [
'Timestamp',
'Scenario Name',
'GPU Variant',
'Quantity',
'Rental Rate',
'Monthly Revenue',
'Electricity Cost',
'Profit',
'ROI (Low Dep) %'
];
resultsSheet.getRange(1, 1, 1, headers.length).setValues([headers]);
}
/**
* formatScenarioResultsSheet() - Applies formatting to the Scenario Results sheet
*/
function formatScenarioResultsSheet(sheet) {
// Apply bold to header row and set background color
sheet.getRange(1, 1, 1, sheet.getLastColumn()).setFontWeight('bold').setBackground('#D9E1F2');
// Enable text wrapping for all cells
sheet.getRange(1, 1, sheet.getMaxRows(), sheet.getMaxColumns()).setWrap(true);
// Apply alternate row shading using conditional formatting
const range = sheet.getRange(2, 1, sheet.getMaxRows() - 1, sheet.getLastColumn());
const rule = SpreadsheetApp.newConditionalFormatRule()
.whenFormulaSatisfied('=ISEVEN(ROW())')
.setBackground('#F9F9F9')
.setRanges([range])
.build();
const rules = sheet.getConditionalFormatRules();
rules.push(rule);
sheet.setConditionalFormatRules(rules);
// Auto-resize columns
sheet.autoResizeColumns(1, sheet.getLastColumn());
// Set number formats
// Assuming columns:
// A: Timestamp - Date
// B: Scenario Name - Text
// C: GPU Variant - Text
// D: Quantity - Number
// E: Rental Rate - Currency
// F: Monthly Revenue - Currency
// G: Electricity Cost - Currency
// H: Profit - Currency
// I: ROI (Low Dep) % - Percentage
sheet.getRange(2, 1, sheet.getMaxRows() - 1, 1).setNumberFormat('mm/dd/yyyy hh:mm:ss'); // Timestamp
sheet.getRange(2, 4, sheet.getMaxRows() - 1, 1).setNumberFormat('0'); // Quantity
sheet.getRange(2, 5, sheet.getMaxRows() - 1, 1).setNumberFormat('$#,##0.00'); // Rental Rate
sheet.getRange(2, 6, sheet.getMaxRows() - 1, 1).setNumberFormat('$#,##0.00'); // Monthly Revenue
sheet.getRange(2, 7, sheet.getMaxRows() - 1, 1).setNumberFormat('$#,##0.00'); // Electricity Cost
sheet.getRange(2, 8, sheet.getMaxRows() - 1, 1).setNumberFormat('$#,##0.00'); // Profit
sheet.getRange(2, 9, sheet.getMaxRows() - 1, 1).setNumberFormat('0.00%'); // ROI %
// Align numerical columns to the right
const numericalColumns = ['D', 'E', 'F', 'G', 'H', 'I'];
numericalColumns.forEach(col => {
sheet.getRange(`${col}2:${col}${sheet.getMaxRows()}`).setHorizontalAlignment('right');
});
// Freeze header row
sheet.setFrozenRows(1);
}
/**
* loadBestCase() - Quick scenario override for certain cells
* (e.g., lower overhead, higher rental rates, lower platform fee).
*/
function loadBestCase() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const inputsSheet = ss.getSheetByName('Inputs');
// Adjust data to reflect best-case scenario
inputsSheet.getRange('B1').setValue(0.10); // Electricity Rate (USD/kWh)
inputsSheet.getRange('B2').setValue(40); // Overhead (W)
inputsSheet.getRange('B7').setValue(10); // Platform Fee (%) Single GPU (10%)
inputsSheet.getRange('B8').setValue(25); // Platform Fee (%) Eight GPUs (25%)
// Optionally set default "high" rental rate in the Calculations
// The user can handle variant-specific rates differently if needed
}
/**
* loadWorstCase() - Quick scenario override for certain cells
* (e.g., higher overhead, lower rental rates, higher platform fee).
*/
function loadWorstCase() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const inputsSheet = ss.getSheetByName('Inputs');
// Adjust data for worst-case scenario
inputsSheet.getRange('B1').setValue(0.20); // Electricity Rate (USD/kWh)
inputsSheet.getRange('B2').setValue(100); // Overhead (W)
inputsSheet.getRange('B7').setValue(25); // Platform Fee (%) Single GPU (25%)
inputsSheet.getRange('B8').setValue(35); // Platform Fee (%) Eight GPUs (35%) - Adjusted below
// Enforce maximum platform fee of 25%
// Correct the Platform Fee (%) Eight GPUs to 25% if it exceeds
const feeEightGPU = inputsSheet.getRange('B8').getValue();
if (feeEightGPU > 25) {
inputsSheet.getRange('B8').setValue(25);
}
}
/**
* loadLowRevenueScenario() - Lower rental rates specifically,
* while leaving other aspects default or near-default.
*/
function loadLowRevenueScenario() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const calcSheet = ss.getSheetByName('Calculations');
const lastRow = calcSheet.getLastRow();
// Override the "Rental Rate (USD/hour)" column (col 5 => E)
// for each row to something low, e.g., 0.40
for (let row = 2; row <= lastRow; row++) {
calcSheet.getRange(row, 5).setValue(0.40);
}
}
/**
* loadHighRevenueScenario() - Higher rental rates across the board
*/
function loadHighRevenueScenario() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const calcSheet = ss.getSheetByName('Calculations');
const lastRow = calcSheet.getLastRow();
// Override the "Rental Rate (USD/hour)" column (col 5 => E) to 0.60
for (let row = 2; row <= lastRow; row++) {
calcSheet.getRange(row, 5).setValue(0.60);
}
}
/**
* runAllScenarios() - Demonstrates how to systematically apply each scenario,
* read the resulting data from "Calculations" sheet, and store
* them in "Scenario Results" sheet, so we can compare them side by side.
*/
function runAllScenarios() {
const scenarioList = [
{ name: 'Best Case', loader: loadBestCase },
{ name: 'Worst Case', loader: loadWorstCase },
{ name: 'LowRevenue', loader: loadLowRevenueScenario },
{ name: 'HighRevenue', loader: loadHighRevenueScenario }
];
scenarioList.forEach(s => {
// Set the scenario
s.loader();
SpreadsheetApp.flush(); // Force recalc
// Now read the calculations and log them
saveScenarioResults(s.name);
});
// Update the Dashboard with the new time series chart
const dashboardSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Dashboard');
setupDashboardSheet(dashboardSheet);
}
/**
* saveScenarioResults(scenarioName) - Reads the "Calculations" sheet row-by-row,
* and writes the relevant data to "Scenario Results" with a timestamp.
*/
function saveScenarioResults(scenarioName) {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const calcSheet = ss.getSheetByName('Calculations');
const resultsSheet = ss.getSheetByName('Scenario Results');
const lastRow = calcSheet.getLastRow();
// We know the columns in "Calculations":
// A: GPU Variant (1)
// B: Quantity (2)
// C: GPU Price (3)
// D: Power Draw (4)
// E: Rental Rate (5)
// F: Monthly Hours (6)
// G: Monthly Revenue (7)
// H: Electricity Cost (8)
// I: Platform Fee (9)
// J: Net Revenue (10)
// K: Profit (11)
// L: Monthly Dep (Low) (12)
// M: Value after 1 month (Low) (13)
// N: ROI (Low Dep) % (14)
// We'll gather for each row:
// [Timestamp, scenarioName, GPUVariant, Quantity, RentalRate, MonthlyRev, ElectricCost, Profit, ROI%]
const outputData = [];
const timeStamp = new Date();
for (let row = 2; row <= lastRow; row++) {
const gpuVariant = calcSheet.getRange(row, 1).getValue();
if (!gpuVariant) {
// skip empty row
continue;
}
const quantity = calcSheet.getRange(row, 2).getValue();
const rentalRate = calcSheet.getRange(row, 5).getValue();
const monthlyRev = calcSheet.getRange(row, 7).getValue();
const electCost = calcSheet.getRange(row, 8).getValue();
const profit = calcSheet.getRange(row, 11).getValue();
const roi = calcSheet.getRange(row, 14).getValue();
outputData.push([
timeStamp,
scenarioName,
gpuVariant,
quantity,
rentalRate,
monthlyRev,
electCost,
profit,
roi
]);
}
if (outputData.length > 0) {
const startRow = resultsSheet.getLastRow() + 1;
resultsSheet.getRange(startRow, 1, outputData.length, outputData[0].length)
.setValues(outputData);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment