/*
BREAK-EVEN AUDITOR & ANALYZER
*/
const GEMINI_API_KEY = PropertiesService.getScriptProperties().getProperty(‘GEMINI_API_KEY’);
const SHEET_NAME = ‘Break-Even Analysis’;
if (!GEMINI_API_KEY) {
throw new Error(‘GEMINI_API_KEY is missing! Add it in Project properties > Script properties.’);
}
function doGet() {
return HtmlService.createHtmlOutput(`
`).setXFrameOptionsMode(HtmlService.XFrameOptionsMode.ALLOWALL);
}
function saveAndAnalyze(data) {
try {
if (!data || typeof data !== ‘object’) {
return { success: false, error: ‘No valid data received’ };
}
const fixed = Number(data.fixed) || 0;
const varCost = Number(data.varCost) || 0;
const price = Number(data.price) || 0;
const target = Number(data.target) || 0;
const weeklySales = Number(data.weeklySales) || 0;
const growthRate = Number(data.growthRate) || 0;
const cashOnHand = Number(data.cashOnHand) || 0;
const personalDraw = Number(data.personalDraw) || 0;
const planWeeks = Number(data.planWeeks) || 12;
const contrib = price – varCost;
if (contrib <= 0) return { success: false, error: 'Price must be > Variable Cost’ };
const contribPct = (contrib / price) * 100;
const beUnits = Math.ceil(fixed / contrib);
const beRevenue = beUnits * price;
const targetUnits = Math.ceil((fixed + target) / contrib);
const projProfit = (weeklySales * contrib) – fixed;
let safetyMargin = 0;
if (weeklySales > 0) {
safetyMargin = ((weeklySales – beUnits) / weeklySales) * 100;
}
let endingCash = cashOnHand;
let currentSales = weeklySales;
const weeklyDraw = personalDraw / 4.33;
for (let i = 1; i <= planWeeks; i++) {
endingCash += (currentSales * contrib) - fixed - weeklyDraw;
currentSales *= (1 + growthRate / 100);
}
function formatCurrency(value) { return '$' + Math.round(value).toLocaleString(); }
const prompt = `Analyze this business model in under 600 words:
METRICS:
- Fixed: ${formatCurrency(fixed)}/wk | Variable: ${formatCurrency(varCost)}/unit | Price: ${formatCurrency(price)}/unit
- Break-even: ${beUnits} units/wk (${formatCurrency(beRevenue)}/wk)
- Current sales: ${weeklySales} units/wk | Growth: ${growthRate}%/wk | Profit: ${formatCurrency(projProfit)}/wk
- Safety margin: ${safetyMargin.toFixed(1)}% | Cash: ${formatCurrency(cashOnHand)} → ${formatCurrency(endingCash)} (${planWeeks} weeks)
- Personal draw: ${formatCurrency(personalDraw)}/mo
PROVIDE:
1. VIABILITY: One sentence assessment
2. TOP RISK: What could break this business + impact
3. SENSITIVITY: New break-even if costs +15% or sales -20% (give exact units)
4. CASH: When does cash turn negative? Recommended reserve?
5. ACTION: One thing to do THIS WEEK with expected $ impact
Be quantitative. Use bullets.`;
// FIXED MODEL NAME - This resolves the 404 error
const model = 'gemini-2.5-flash';
const url = `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${GEMINI_API_KEY}`;
const payload = {
contents: [{ parts: [{ text: prompt }] }],
generationConfig: { temperature: 0.4 }
};
const response = UrlFetchApp.fetch(url, {
method: 'post',
contentType: 'application/json',
payload: JSON.stringify(payload),
muteHttpExceptions: true
});
const responseCode = response.getResponseCode();
if (responseCode !== 200) {
return { success: false, error: `Gemini API error ${responseCode}` };
}
const json = JSON.parse(response.getContentText());
let analysisText = 'AI Unavailable';
if (json.candidates && json.candidates.length > 0) {
if (json.candidates[0].content && json.candidates[0].content.parts) {
analysisText = json.candidates[0].content.parts[0].text;
}
}
const ss = SpreadsheetApp.getActiveSpreadsheet();
let sheet = ss.getSheetByName(SHEET_NAME);
if (!sheet) {
sheet = ss.insertSheet(SHEET_NAME);
const headers = [
‘Timestamp’, ‘Fixed Costs’, ‘Var Cost’, ‘Price’, ‘Target Profit’,
‘BE Units’, ‘Target Units’, ‘Contrib %’, ‘Weekly Sales’, ‘Growth %’,
‘Safety %’, ‘Proj Profit’, ‘Starting Cash’, ‘Ending Cash’, ‘Personal Draw’, ‘AI Analysis’
];
sheet.appendRow(headers);
sheet.setFrozenRows(1);
sheet.getRange(1, 1, 1, headers.length).setFontWeight(‘bold’)
.setBackground(‘#667eea’).setFontColor(‘#ffffff’).setHorizontalAlignment(‘center’);
}
sheet.appendRow([
new Date(), fixed, varCost, price, target, beUnits, targetUnits,
contribPct.toFixed(2) + ‘%’, weeklySales, growthRate + ‘%’,
safetyMargin.toFixed(2) + ‘%’, projProfit, cashOnHand, endingCash,
personalDraw, analysisText
]);
const lastRow = sheet.getLastRow();
sheet.getRange(lastRow, 2, 1, 1).setNumberFormat(‘$#,##0’);
sheet.getRange(lastRow, 3, 1, 1).setNumberFormat(‘$#,##0.00’);
sheet.getRange(lastRow, 4, 1, 1).setNumberFormat(‘$#,##0.00’);
sheet.getRange(lastRow, 12, 1, 3).setNumberFormat(‘$#,##0’);
sheet.autoResizeColumns(1, 16);
return {
success: true,
analysis: analysisText,
message: SHEET_NAME + ‘ (Row ‘ + lastRow + ‘)’,
metrics: { beUnits: beUnits, beRevenue: formatCurrency(beRevenue), projProfit: formatCurrency(projProfit), safetyMargin: safetyMargin.toFixed(1) }
};
} catch (error) {
return { success: false, error: ‘Server error: ‘ + error.toString() };
}
}