On the 3rd of each month, emails you last month's profit and loss for each department, or for the whole business, with AI commentary.
Uses
Claude
Gmail
Google Sheets
Email last month's profit and loss for each department
On the 3rd of every month, the transactions tab of your Google Sheet is read and each cost center or department with transactions in the month just ended gets its own profit and loss report by email. A sheet with no cost center column gets one report for the whole business. The figures are calculated from the sheet: revenue and expenses by category against the month before, the net result, the year to date and the largest transactions. AI then writes a short commentary on top - the month's result, what drove it and what is worth a closer look - quoting only those figures.
A row that cannot be read is listed in the report instead of being counted as zero, and transactions with no cost center get a report of their own. A month with no transactions at all, or a tab missing its Date or Amount column, stops the run with an error instead of sending a report that looks like a quiet month. If the commentary cannot be written, the figures still go out, and a report that cannot be sent is named in the run's error.
You need a Google Sheets connection, a Gmail connection (the reports are sent from it) and an Anthropic connection (it writes the commentary), plus the sheet's link, the tab name and the address the reports go to. The tab needs Date and Amount columns; Cost Center (or Department), Type, Category and Description columns are used when present.
financeSheetUrl to blank.Set financeSheetUrl to blank.transactionsTab to Transactions.Set transactionsTab to Transactions.reportEmail to blank.Set reportEmail to blank.reportMonth to blank.Set reportMonth to blank.onlyCostCenters to an empty list.Set onlyCostCenters to an empty list.positiveAmountsAre to income.Set positiveAmountsAre to income.datesAreDayFirst to false.Set datesAreDayFirst to false.fiscalYearStartMonth to 1.Set fiscalYearStartMonth to 1.maxCostCenters to 25.Set maxCostCenters to 25.financeSheetUrl. Save it as financialData.Read the rows of financeSheetUrl. Save it as financialData.financialData.rows is emptyIf financialData.rows is emptyreportPlan to an empty record.Set reportPlan to an empty record.planFailedIf a step below fails, save the error as planFailedrows, headers, today, tab_name, report_email, report_month, only_cost_centers, positive_amounts_are, dates_are_day_first, fiscal_year_start_month, and max_cost_centers and save it as reportPlan.Run Python code on rows, headers, today, tab_name, report_email, report_month, only_cost_centers, positive_amounts_are, dates_are_day_first, fiscal_year_start_month, and max_cost_centers and save it as reportPlan.planFailed.message contains inputs are too largeIf planFailed.message contains inputs are too largeThe ', transactionsTab, ' tab holds more than one run can read at once (, the length of financialData.rows, rows), so nothing was sent. Move the transactions from earlier financial years into another tab - the reports only use this financial year and the month before it - then run it again.] joined by nothing.Stop and report a problem: [The ', transactionsTab, ' tab holds more than one run can read at once (, the length of financialData.rows, rows), so nothing was sent. Move the transactions from earlier financial years into another tab - the reports only use this financial year and the month before it - then run it again.] joined by nothing.planFailed.message.Stop and report a problem: The transactions could not be read, so nothing was sent: planFailed.message.reportPlan.error isn't emptyIf reportPlan.error isn't emptyreportPlan.error.Stop and report a problem: reportPlan.error.sentReports to an empty list.Set sentReports to an empty list.figuresOnly to an empty list.Set figuresOnly to an empty list.failedReports to an empty list.Set failedReports to an empty list.costCenter in reportPlan.cost_centers:For each costCenter in reportPlan.cost_centers:centerFigures to an empty record.Set centerFigures to an empty record.figuresFailedIf a step below fails, save the error as figuresFailedrows, center, and plan and save it as centerFigures.Run Python code on rows, center, and plan and save it as centerFigures.figuresFailed.message to 300 characters and save it as figuresProblem.Shorten figuresFailed.message to 300 characters and save it as figuresProblem.costCenter.name (its figures could not be worked out: figuresProblem) to the end of failedReports and save it as failedReports.Add costCenter.name (its figures could not be worked out: figuresProblem) to the end of failedReports and save it as failedReports.commentary to blank.Set commentary to blank.analysisFailedIf a step below fails, save the error as analysisFailedcenterFigures.facts
<<<END FIGURES>>>
WHAT TO WRITE
Plain text only: no markdown, no asterisks, no # headings and no tables. Write exactly these three parts, each starting with its label on a line of its own:
Summary
Two or three sentences: the month's net result, and how revenue and expenses moved against the previous month. For a cost center with no revenue, describe it by its costs.
What drove it
Two to four lines, each starting with - , naming the categories or transactions behind the biggest movements, with their figures.
Worth a closer look
One to three lines, each starting with - : what a finance lead would want to check, such as an unusually large transaction, a category that is new this month or has stopped, a cost growing faster than the rest, or rows that could not be counted. Word each one as something to check, not as a conclusion. If nothing stands out, write one line saying so.
RULES
- Quote only figures that appear above, exactly as they are written there. Do not add up, average, annualise, forecast or estimate anything, and do not work out a percentage that is not given.
- Do not guess at causes the figures cannot show, such as the market, the season, effort or staffing. Say what moved and point to the transactions behind it.
- If there is no previous month to compare with, say so once and do not describe changes.
- Keep it under 220 words.'. Save it as aiAnalysis.
Maximum response length: 900aiAnalysis.response_text and save it as commentary.Trim aiAnalysis.response_text and save it as commentary.costCenter.name could not be written, so its report carries the figures only: analysisFailed.message.Show in the output: The commentary for costCenter.name could not be written, so its report carries the figures only: analysisFailed.message.reportBody to blank.Set reportBody to blank.commentary isn't emptyIf commentary isn't empty** with in commentary and save it as commentary.Replace ** with in commentary and save it as commentary.### with in commentary and save it as commentary.Replace ### with in commentary and save it as commentary.## with in commentary and save it as commentary.Replace ## with in commentary and save it as commentary.@centerFigures.heading @commentary ──────────────────────────────── @centerFigures.statement ──────────────────────────────── About these figures @centerFigure... and save it as reportBody.Build the text @centerFigures.heading @commentary ──────────────────────────────── @centerFigures.statement ──────────────────────────────── About these figures @centerFigure... and save it as reportBody.costCenter.name to the end of figuresOnly and save it as figuresOnly.Add costCenter.name to the end of figuresOnly and save it as figuresOnly.@centerFigures.heading The written commentary could not be produced this month, so this report carries the figures only. ──────────────────────────────── @cent... and save it as reportBody.Build the text @centerFigures.heading The written commentary could not be produced this month, so this report carries the figures only. ──────────────────────────────── @cent... and save it as reportBody.sendFailedIf a step below fails, save the error as sendFailedreportPlan.recipients.Send an email to reportPlan.recipients.
Subject: centerFigures.subjectreportBodycostCenter.name to the end of sentReports and save it as sentReports.Add costCenter.name to the end of sentReports and save it as sentReports.sendFailed.message to 300 characters and save it as sendProblem.Shorten sendFailed.message to 300 characters and save it as sendProblem.costCenter.name (sendProblem) to the end of failedReports and save it as failedReports.Add costCenter.name (sendProblem) to the end of failedReports and save it as failedReports.sentReports with , and save it as sentList.Join sentReports with , and save it as sentList.sentList is emptyIf sentList is emptysentList to none.Change sentList to none.The @reportPlan.month_label P&L reports went to @reportPlan.recipients for: @sentList. @reportPlan.summary and save it as runSummary.Build the text The @reportPlan.month_label P&L reports went to @reportPlan.recipients for: @sentList. @reportPlan.summary and save it as runSummary.figuresOnly isn't emptyIf figuresOnly isn't emptyrunSummary, The AI commentary could not be written for , figuresOnly joined by , , , so those reports carry the figures only.] with nothing and save it as runSummary.Join [runSummary, The AI commentary could not be written for , figuresOnly joined by , , , so those reports carry the figures only.] with nothing and save it as runSummary.failedReports isn't emptyIf failedReports isn't emptyNot every report went out. Not sent: , failedReports joined by ; , . Sent: , sentList, . To send only the missing reports, put their cost centers in the onlyCostCenters step and set reportMonth to , reportPlan.month_setting, , then run it again.] joined by nothing.Stop and report a problem: [Not every report went out. Not sent: , failedReports joined by ; , . Sent: , sentList, . To send only the missing reports, put their cost centers in the onlyCostCenters step and set reportMonth to , reportPlan.month_setting, , then run it again.] joined by nothing.runSummary.Finish the run: runSummary.