Developer Handover Pack
Everything needed to build the automation from scratch: current state, full agent specs, data flow, and the build stack.
Developer Handover Pack
Budget vs Actuals Reporting
[YourCompany.com] · Finance Department · Prepared by FullSpec · [Today's Date]
This document gives the FullSpec build team everything needed to construct, configure, and validate the three-agent Budget vs Actuals Reporting automation. It covers the current-state step map with bottleneck flags, full agent specifications with IO contracts, the end-to-end data flow trace, and the recommended build stack. All build decisions, credential requirements, and error-handling rules are documented here so nothing requires a back-channel conversation before work begins.
01Process snapshot
02Agent specifications
Connects to QuickBooks via OAuth 2.0, pulls the closed-period P&L report on the scheduled period-close date, parses account-level figures across all cost centres, and writes them into the designated actuals columns of the master Google Sheets budget template. The agent validates each account code against the maintained mapping table before writing, flags unmapped codes into a separate error tab rather than skipping them silently, and under no circumstances overwrites formula cells or header rows. All writes use row-keyed matching against the account code column, not positional index.
// Input trigger.period_close_date // ISO 8601 date, e.g. '2025-02-01' trigger.entity_id // QuickBooks realm ID / Xero org ID config.mapping_table_sheet_id // Google Sheets ID of mapping reference tab config.actuals_sheet_tab_name // e.g. 'Feb 2025 Actuals' config.actuals_column_index // Column letter where actuals are written, e.g. 'D' // QuickBooks API response (ProfitAndLoss report) qb.report.Rows[].ColData[].value // Account name string qb.report.Rows[].ColData[].id // QuickBooks account ID qb.report.Rows[].ColData[].Amount // Period actuals figure, decimal // Output sheets.write_result.updated_rows // Count of rows successfully written sheets.write_result.unmapped_codes // Array of account IDs not found in mapping table sheets.write_result.skipped_rows // Rows skipped due to formula or header protection agent.status // 'complete' | 'partial' | 'failed' agent.timestamp // ISO 8601 completion timestamp
- QuickBooks API requires OAuth 2.0 with scopes com.intuit.quickbooks.accounting (read-only). Token expiry is 1 hour for access tokens; refresh tokens expire after 101 days of non-use. A token-refresh mechanism and an alert to support@gofullspec.com must be implemented before go-live.
- Google Sheets access must use a dedicated service account (not a personal Google account). The service account email must be granted Editor access on the budget spreadsheet.
- The account-to-budget-line mapping table must exist as a dedicated named tab in the budget spreadsheet with columns: qb_account_id, qb_account_name, budget_row_key, budget_line_label. This structure must be confirmed with the Finance Manager before build starts.
- Write operations must target cells by matching the value in the account-code column (column A by default), not by row number. Row-number targeting breaks if rows are inserted.
- If qb.report.Rows[].ColData[].id is absent from the mapping table, write the account to a dedicated 'Unmapped Accounts' tab and surface it in the Variance Analysis Agent's flagged list.
- Actuals column must be cleared before each write to avoid stale data from a prior partial run. Use a batch clear-then-write, not a cell-by-cell update.
- Confirm QuickBooks API rate limit: 500 requests per minute per realm. A report with 50 cost lines will not approach this, but confirm if multi-entity runs are added later.
- Confirm whether Xero fallback is required. The mapping table schema and API response structure differ between QuickBooks and Xero. Build for QuickBooks first; Xero adapter to be a separate branch if needed.
Reads the fully populated budget template immediately after the Actuals Sync Agent confirms a 'complete' or 'partial' status. Calculates dollar variance (actuals minus budget) and percentage variance ((actuals minus budget) divided by budget) for every mapped line. Compares each variance against the configured tolerance threshold (default: flag if absolute percentage variance exceeds 10%, or if dollar variance exceeds $500 on any single line). For flagged lines, the agent drafts plain-English commentary drawing on the variance direction, magnitude, and the same line's prior three periods if available. It then posts a targeted Slack message to each department head listing only the flagged lines relevant to their cost centre, with a 24-hour response window. Replies received within the window are appended to the commentary draft column before the Finance Manager review step.
// Input
upstream.agent.status // Must be 'complete' or 'partial' to proceed
sheets.budget_column // Column letter for approved budget figures, e.g. 'C'
sheets.actuals_column // Column letter written by Actuals Sync Agent, e.g. 'D'
sheets.variance_dollar_column // Target column for dollar variance output, e.g. 'E'
sheets.variance_pct_column // Target column for percentage variance output, e.g. 'F'
sheets.commentary_column // Target column for drafted commentary, e.g. 'G'
config.threshold_pct // Percentage threshold to flag, e.g. 0.10 (10%)
config.threshold_dollar // Dollar threshold to flag, e.g. 500
config.dept_head_slack_map // JSON object: { cost_centre_id: slack_user_id }
config.response_window_hours // e.g. 24
sheets.prior_periods_tab // Tab name holding prior 3-period actuals for trend
// Output
sheets.variance_rows // Array of { row_key, budget, actuals, var_dollar, var_pct, flagged }
sheets.commentary_draft // Array of { row_key, draft_text, source: 'agent' | 'slack_reply' }
slack.messages_sent // Array of { dept_head_slack_id, message_ts, lines_flagged }
slack.replies_received // Array of { message_ts, reply_text, received_at }
agent.flagged_line_count // Integer
agent.pending_commentary_count // Lines with no Slack reply after window closes
agent.status // 'complete' | 'partial_pending' | 'failed'- Slack integration requires a bot token with scopes: chat:write, channels:read, users:read. The bot must be invited to any private channel used by department heads before the first run.
- The dept_head_slack_map config object must be populated before build. Each cost_centre_id must match the budget_row_key values in the mapping table. Confirm these with the Finance Manager.
- If a department head does not reply within the configured response window, the commentary column for that line must be set to a standardised pending string (e.g. 'No response received within 24 hours. Finance Manager to review.') and the report must not be held. The Report Distribution Agent fires on approval regardless.
- Prior-period trend data is optional but improves commentary quality. If the prior_periods_tab does not exist on first build, the agent falls back to commentary based on variance magnitude only.
- Percentage variance formula must handle zero-budget lines (division-by-zero). If budget = 0 and actuals > 0, flag the line and set var_pct to null with a label of 'No budget set'.
- Threshold values (threshold_pct, threshold_dollar) must be stored in a config tab on the spreadsheet so the Finance Manager can adjust them without a code change.
- Slack message format must list: cost centre name, budget figure, actuals figure, variance dollar and percentage, and a plain-English request for a one-line explanation. Do not send raw account codes to department heads.
Fires after the Finance Manager explicitly approves the commentary in the budget sheet. Approval is detected by the presence of a defined approval marker (a specific cell value or a checkbox state) in the budget spreadsheet. On approval, the agent formats the finalised Google Sheet into a presentation-ready state, saves a snapshot copy to Google Drive under a date-stamped filename following the agreed naming convention, sets sharing permissions automatically, and posts a Slack notification to the configured finance channel with a direct link to the published file.
// Input
sheets.approval_cell_ref // e.g. 'Summary!B2', must contain string 'APPROVED'
sheets.source_spreadsheet_id // Google Sheets ID of the master budget template
config.drive_folder_id // Google Drive folder ID for published reports
config.file_naming_pattern // e.g. 'BudgetVsActuals_{YYYY}_{MM}_v{version}'
config.slack_channel_id // Finance channel ID for stakeholder notification
config.stakeholder_slack_ids // Array of Slack user IDs to mention in notification
// On approval
sheets.approval_timestamp // ISO 8601 timestamp when approval marker was set
sheets.approver_name // Value read from the approval row, e.g. 'Rachel Tung'
// Output
drive.published_file_id // Google Drive file ID of the saved snapshot
drive.published_file_url // Shareable link
drive.file_name // Final filename applied, e.g. 'BudgetVsActuals_2025_02_v1'
slack.notification_ts // Slack message timestamp of the stakeholder notification
slack.notification_text // Full message text posted to finance channel
agent.status // 'complete' | 'failed'- The approval detection mechanism must be confirmed with the Finance Manager before build. A named checkbox in a fixed cell is the lowest-friction option. An explicit string value ('APPROVED') is more robust for polling-based triggers. Decide and document this before the Report Distribution Agent build begins.
- Google Drive snapshot must be a copy of the sheet, not a shortcut or a link to the live document. Use the Drive API files.copy method to create a static snapshot.
- File naming convention must be agreed and stored in config. The version increment (v1, v2) must increment if the agent is triggered more than once in a single period, to avoid overwriting an already-published file.
- Sharing permissions on the Drive snapshot must be set at publish time using Drive API permissions.create. Default: anyone-with-link can view. Do not set edit access on the snapshot.
- The Slack notification must include: the period name (e.g. 'February 2025'), total variance summary (net dollar variance), count of flagged lines, count of lines still marked pending, and a direct URL to the Drive file.
- Google Drive API scopes required: https://www.googleapis.com/auth/drive.file (restrict to files created by the app). Do not request broader drive scope.
- If the approval marker is detected but the actuals column is empty or the Variance Analysis Agent status is 'failed', the Report Distribution Agent must abort and post an error alert to the finance Slack channel rather than publishing an incomplete report.
03End-to-end data flow
// ─────────────────────────────────────────────────────────────
// TRIGGER: Scheduled period-close date fires
// ─────────────────────────────────────────────────────────────
trigger.period_close_date = '2025-02-01' // ISO 8601
trigger.entity_id = 'qb_realm_id_xyz' // QuickBooks realm
// ─────��───────────────────────────────────────────────────────
// AGENT 1: Actuals Sync Agent
// ─────────────────────────────────────────────────────────────
// Step 1 replacement: QuickBooks API call — fetch P&L report
REQUEST GET /v3/company/{realmId}/reports/ProfitAndLoss
?start_date=2025-01-01&end_date=2025-01-31
&accounting_method=Accrual
&summarize_column_by=Total
Authorization: Bearer {access_token}
RESPONSE qb.report.Rows[].ColData[0].value // Account name, e.g. 'Payroll Expenses'
qb.report.Rows[].ColData[0].id // Account ID, e.g. '60'
qb.report.Rows[].ColData[1].value // Amount, e.g. '42500.00'
// Step 2 replacement: account-to-budget-line lookup
mapping_table[qb_account_id] -> budget_row_key, budget_line_label
unmapped_accounts[] -> write to 'Unmapped Accounts' tab
// Step 3 replacement: write actuals to Google Sheets
REQUEST sheets.spreadsheets.values.batchUpdate
spreadsheetId: config.source_spreadsheet_id
range: '{actuals_sheet_tab_name}!D{row}' per budget_row_key match
valueInputOption: RAW
values: [[qb.actuals_amount]]
// Step 4 replacement: formula integrity confirmed via row-key matching
sheets.write_result.updated_rows // e.g. 47
sheets.write_result.unmapped_codes // e.g. ['account_id_88', 'account_id_91']
agent1.status = 'complete' // or 'partial'
agent1.timestamp = '2025-02-01T07:14:22Z'
// ─────────────────────────────────────────────────────────────
// HANDOFF 1: Actuals Sync Agent -> Variance Analysis Agent
// Condition: agent1.status IN ('complete', 'partial')
// ─────────────────────────────────────────────────────────────
// ─────────────────────────────────────────────────────────────
// AGENT 2: Variance Analysis Agent
// ─────────────────────────────────────────────────────────────
// Step 5 replacement: read budget and actuals columns, compute variances
for each mapped row in sheets.budget_tab:
var_dollar = actuals_value - budget_value
var_pct = (actuals_value - budget_value) / budget_value
if budget_value == 0: var_pct = null, flag = true, label = 'No budget set'
if abs(var_pct) > config.threshold_pct OR abs(var_dollar) > config.threshold_dollar:
flagged = true
write var_dollar -> sheets.variance_dollar_column (e.g. col E)
write var_pct -> sheets.variance_pct_column (e.g. col F)
// Step 6 replacement: draft commentary per flagged line
for each flagged_row:
prior_3_periods = read sheets.prior_periods_tab for budget_row_key
draft_text = agent.generateCommentary(
budget_line_label, var_dollar, var_pct, prior_3_periods
)
write draft_text -> sheets.commentary_column (e.g. col G), source='agent'
// Step 7 replacement: Slack prompts to department heads
for each dept_head in config.dept_head_slack_map:
dept_flagged_lines = filter flagged_rows where cost_centre_id == dept_head.cost_centre_id
if dept_flagged_lines.length > 0:
REQUEST POST https://slack.com/api/chat.postMessage
channel: dept_head.slack_user_id
text: formatted_prompt(dept_flagged_lines)
slack.messages_sent[] = { dept_head_slack_id, message_ts, lines_flagged }
// Collect Slack replies within config.response_window_hours (e.g. 24h)
for each slack.reply received:
match reply to message_ts -> budget_row_key
update sheets.commentary_column value = reply.text, source='slack_reply'
// After window expires, unmatched flagged lines:
write 'No response received within 24 hours. Finance Manager to review.'
agent2.flagged_line_count // e.g. 8
agent2.pending_commentary_count // e.g. 2
agent2.status = 'complete'
// ─────────────────────────────────────────────────────────────
// HUMAN GATE: Finance Manager reviews and approves commentary
// Approval marker: sheets cell 'Summary!B2' set to 'APPROVED'
// Estimated time: under 20 minutes
// ─────────────────────────────────────────────────────────────
// ─────────────────────────────────────────────────────────────
// HANDOFF 2: Approval marker detected -> Report Distribution Agent
// Condition: sheets['Summary!B2'] == 'APPROVED'
// AND agent2.status == 'complete'
// AND sheets.actuals_column NOT empty
// ─────────────────────────────────────────────────────────────
// ─────────────────────────────────────────────────────────────
// AGENT 3: Report Distribution Agent
// ─────────────────────────────────────────────────────────────
// Step 8 replacement: snapshot and save to Google Drive
REQUEST POST https://www.googleapis.com/drive/v3/files/{fileId}/copy
name: 'BudgetVsActuals_2025_02_v1'
parents: [config.drive_folder_id]
RESPONSE drive.published_file_id // e.g. '1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs'
drive.published_file_url // shareable link
// Set sharing permissions — view-only for anyone with link
REQUEST POST https://www.googleapis.com/drive/v3/files/{fileId}/permissions
role: 'reader', type: 'anyone'
// Step 9 replacement: Slack notification to stakeholders
REQUEST POST https://slack.com/api/chat.postMessage
channel: config.slack_channel_id
text: 'Budget vs Actuals report for February 2025 is ready.'
+ ' Net variance: $-3,200.'
+ ' Flagged lines: 8 (2 pending Finance Manager review).'
+ ' View report: {drive.published_file_url}'
mentions: config.stakeholder_slack_ids
agent3.status = 'complete'
agent3.timestamp = '2025-02-01T09:41:05Z'
// ─────────────────────────────────────────────────────────────
// END: Report distributed. Cycle complete for period Feb 2025.
// ─────────────────────────────────────────────────────────────04Recommended build stack
More documents for this process
Every document generated for Budget vs Actuals Reporting.