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
Workplace Incident Reporting
[YourCompany.com] · HR Department · Prepared by FullSpec · [Today's Date]
This document is the complete technical reference for the Workplace Incident Reporting automation build. It covers the current-state process map, all three agent specifications with IO contracts, the end-to-end data flow, and the recommended build stack. The FullSpec team uses this pack to configure, wire, and test every component. Nothing in this document requires action from [YourCompany.com] beyond confirming the pre-build requirements called out in each agent section.
01Process snapshot
02Agent specifications
This agent fires the moment a new Google Form response lands. It parses every field from the form payload, applies the severity classification logic (minor, moderate, or serious) against agreed rule criteria covering injury type, number of people affected, and regulatory threshold flags, assigns a padded sequential reference number (e.g. INC-0042), and writes a fully structured row to the Google Sheets incident register. It is the entry point for the entire workflow and all downstream agents depend on the data it writes. Estimated build time: 6 hours. Complexity: Moderate.
// Input
form_response: {
submitted_at: ISO8601 timestamp,
reporter_name: string,
reporter_email: string,
incident_date: date (YYYY-MM-DD),
incident_time: string,
incident_location: string,
incident_type: enum [near-miss, injury, property-damage, hazard-report],
injury_involved: boolean,
injured_employee_id: string | null,
description: string (free text),
witnesses: string | null
}
// Output
sheets_row: {
reference_number: string (e.g. INC-0042),
submitted_at: ISO8601,
reporter_name: string,
reporter_email: string,
incident_date: date,
incident_type: string,
injury_involved: boolean,
injured_employee_id: string | null,
severity: enum [minor, moderate, serious],
description: string,
status: 'open',
notification_sent: false,
investigation_due_date: date (incident_date + 5 business days for minor/moderate; + 2 for serious),
corrective_actions_closed: false,
bamboohr_updated: false
}- Severity classification rules must be documented and agreed with the HR manager before build begins. The rule set should map injury type and regulatory flag combinations to minor, moderate, or serious. A generic fallback is not acceptable for a compliance-sensitive process.
- Google Sheets register must use the exact column order specified in the IO contract above. The sheet ID and tab name must be provided as build credentials before work starts.
- Reference number format is INC-XXXX with zero-padding to four digits. The agent reads the last row to determine the next integer; a dedicated counter cell in a config tab is the preferred dedupe approach to avoid race conditions if two submissions arrive within the same second.
- If the form response is missing required fields (e.g. incident_date is blank), the agent must write the row with a 'data-incomplete' status flag and send an alert email to the HR coordinator via Gmail rather than silently failing.
- Google Workspace Business Starter or above is required for the Forms API trigger to function reliably at this volume. Confirm the account tier before build.
- injury_involved boolean must be mapped explicitly from the form field label, not inferred from the description, to ensure BambooHR update logic downstream fires correctly.
This agent watches the Google Sheets incident register for new confirmed rows written by the Intake Agent. On each new row it reads the department field and severity to determine the correct Slack recipient: the line manager for that department receives a channel notification for all incidents; the operations director receives an additional direct message for any incident classified as serious. The agent also writes a notification log entry (recipient, channel or DM ID, timestamp, incident reference) back to a dedicated Notifications tab in the same Google Sheet. Estimated build time: 5 hours. Complexity: Moderate.
// Input
sheets_row: {
reference_number: string,
severity: enum [minor, moderate, serious],
incident_type: string,
incident_date: date,
incident_location: string,
description: string (truncated to 300 chars for Slack preview),
reporter_name: string,
department: string // used for manager routing lookup
}
// Output
slack_message_manager: {
channel: '#incidents-[department]' or manager Slack user ID,
text: 'New incident [reference_number] logged: [severity] | [incident_type] | [incident_date]. Please review and investigate within [investigation_due_date].',
blocks: [incident summary card with reference, severity badge, due date, Notion link placeholder]
}
slack_message_director: { // serious incidents only
channel: '@ops-director-user-id',
text: 'SERIOUS incident [reference_number] requires your immediate awareness. HR has been notified.'
}
sheets_notification_log_row: {
reference_number: string,
notified_at: ISO8601,
manager_slack_id: string,
director_notified: boolean,
notification_sent: true
}- A department-to-Slack-channel mapping table must be provided before build. This should list every active department and its corresponding Slack channel ID or manager Slack user ID. Without this the routing logic cannot be built.
- The operations director's Slack user ID must be confirmed and stored as a build credential. Do not hardcode a name; use the Slack user ID to avoid breakage if the director changes.
- Slack Pro plan or above is required for reliable DM delivery and the Slack API scopes needed (chat:write, users:read, channels:read). Confirm the plan tier.
- After sending notifications, the agent must update notification_sent to true in the Sheets row immediately to prevent duplicate notifications if the trigger re-fires.
- If a Slack message fails to deliver (e.g. invalid channel ID), the agent must catch the error and log it to the error log table rather than silently dropping the notification.
- Slack message blocks should include a colour-coded severity badge: green for minor, amber for moderate, red for serious, using Slack Block Kit attachment color field.
This is the most complex agent in the workflow. It triggers on a confirmed incident row in Google Sheets and performs four distinct functions: creates a structured Notion investigation page with all incident fields pre-populated and a deadline set; monitors that page on a scheduled poll and sends Gmail deadline reminder emails to the assigned manager if the page status has not progressed to 'findings submitted' before the due date; once corrective actions are logged in Notion, monitors each action for completion and sends escalating Gmail reminders; and for rows where injury_involved is true, calls the BambooHR API to write the incident date, type, and reference number to the employee's record. HR retains responsibility for marking the incident formally closed in Notion, which triggers the final closure confirmation email to the reporter. Estimated build time: 11 hours. Complexity: Complex.
// Input
sheets_row: {
reference_number: string,
severity: string,
incident_type: string,
incident_date: date,
description: string,
reporter_name: string,
reporter_email: string,
injury_involved: boolean,
injured_employee_id: string | null,
investigation_due_date: date,
department: string
}
// Output: Notion page
notion_page: {
database_id: [INCIDENTS_DB_ID],
properties: {
'Reference': reference_number,
'Severity': severity,
'Incident Type': incident_type,
'Incident Date': incident_date,
'Description': description,
'Reporter': reporter_name,
'Department': department,
'Investigation Due': investigation_due_date,
'Status': 'Under Investigation',
'Findings': '' (blank, manager fills),
'Corrective Actions': [] (relation, manager populates)
}
}
// Output: Gmail reminder (deadline approaching, T-1 day)
gmail_reminder_1: {
to: manager_email,
subject: '[INC-XXXX] Investigation due tomorrow',
body: 'The investigation deadline for incident [reference_number] is tomorrow. Please update the Notion page: [notion_page_url]'
}
// Output: Gmail reminder (deadline passed, T+0 and T+3 escalation)
gmail_reminder_2: {
to: manager_email,
cc: hr_coordinator_email,
subject: '[INC-XXXX] Investigation overdue',
body: 'The investigation for [reference_number] is now overdue. Please submit findings immediately.'
}
// Output: BambooHR update (injury incidents only)
bamboohr_custom_field_patch: {
endpoint: 'PUT /v1/employees/{employee_id}/tables/customTabIncidents',
body: {
incident_reference: reference_number,
incident_date: incident_date,
incident_type: incident_type,
outcome_notes: severity
}
}
// On HR closes incident in Notion (status set to 'Closed')
gmail_closure: {
to: reporter_email,
subject: 'Incident [reference_number] closed',
body: 'Your incident report has been investigated and all corrective actions confirmed. Reference: [reference_number].'
}
sheets_status_update: {
row: matched by reference_number,
status: 'closed',
closed_at: ISO8601
}- The Notion incidents database ID must be provided before build. The database must already exist with the property schema listed in the IO block above. The FullSpec team will provide the exact property type definitions (select, date, text, relation) to configure before the build connection is made.
- BambooHR API access requires an API key generated from an account with admin or full API permissions. Some BambooHR plans (Essentials tier) restrict API access entirely; this must be confirmed before build starts. If the custom incident tab does not already exist in BambooHR, it must be created by the BambooHR admin before the field mapping can be completed.
- The BambooHR employee ID used in the API call must match the injured_employee_id field submitted on the Google Form. The form must use a field that captures a value the HR team can reliably map to BambooHR employee IDs (e.g. employee number, not a name).
- Reminder polling must run on a scheduled trigger (e.g. once per business day at 08:00 in the business's local timezone) rather than on a time-delay from the original trigger, to avoid issues with weekend deadlines.
- If the Notion page status is updated to 'Closed' by anyone other than the designated HR coordinator role, a guard condition should check that all corrective action sub-tasks are marked complete before the closure email fires. Partial closure must be blocked.
- For free-text incident descriptions, a human review step for any incident auto-classified as 'serious' is strongly recommended before the Notion investigation page is created and the director notified. A short approval gate (e.g. HR coordinator confirms severity in a Slack message before the agent proceeds) should be discussed with the process owner before the serious-incident branch is built.
- Gmail sending is performed via the Google Workspace Gmail API using a service account or OAuth credential authorised for the HR coordinator's mailbox. The scopes required are gmail.send and, for polling unread threads, gmail.readonly.
03End-to-end data flow
// ── TRIGGER ──────────────────────────────────────────────────────────────
// Employee submits Google Form
TRIGGER: google_forms.on_new_response(form_id: INCIDENT_FORM_ID)
-> form_response {
submitted_at, reporter_name, reporter_email,
incident_date, incident_time, incident_location,
incident_type, injury_involved, injured_employee_id,
description, witnesses
}
// ── AGENT 1: INCIDENT INTAKE AGENT ───────────────────────────────────────
// Input: raw form_response
// Action: apply severity classification rules
severity = classify(incident_type, injury_involved, description)
// rule: injury_involved = true AND incident_type = 'injury' -> 'serious'
// rule: incident_type = 'near-miss' AND injury_involved = false -> 'minor'|'moderate'
// rule: incident_type = 'hazard-report' -> 'minor' by default
// Action: assign reference number (read last INC-XXXX from config cell, increment)
reference_number = 'INC-' + zero_pad(last_ref + 1, 4)
// Action: compute investigation_due_date
investigation_due_date = add_business_days(
incident_date,
severity == 'serious' ? 2 : 5
)
// Action: write to Google Sheets register
google_sheets.append_row(sheet_id: REGISTER_SHEET_ID, tab: 'Incidents', row: {
reference_number, submitted_at, reporter_name, reporter_email,
incident_date, incident_type, injury_involved, injured_employee_id,
severity, description, status: 'open',
notification_sent: false, investigation_due_date,
corrective_actions_closed: false, bamboohr_updated: false
})
// Output: confirmed row in Google Sheets with reference_number, severity, due_date
// ── AGENT 2: NOTIFICATION AND ESCALATION AGENT ───────────────────────────
// Input: new Google Sheets row where notification_sent = false
// Trigger: polled on new row append OR event-driven via webhook from Sheets
manager_slack_id = routing_table.lookup(department)
// routing_table: [department -> slack_channel_id | slack_user_id]
// must be pre-configured as a static lookup in the automation platform
// Action: send manager Slack notification (all severities)
slack.post_message(channel: manager_slack_id, blocks: [
severity_badge(severity), // green|amber|red
text: '[reference_number] | [incident_type] | Due: [investigation_due_date]',
text: description[0:300]
])
// Action: send director DM (serious only)
IF severity == 'serious':
slack.post_message(channel: OPS_DIRECTOR_SLACK_ID,
text: 'SERIOUS: [reference_number] requires immediate awareness.'
)
// Action: log notification and update sheet
google_sheets.update_row(reference_number, {
notification_sent: true,
notified_at: now()
})
google_sheets.append_row(tab: 'NotificationLog', row: {
reference_number, notified_at, manager_slack_id,
director_notified: severity == 'serious'
})
// Output: Slack messages delivered; notification_sent = true in register
// ── AGENT 3: INVESTIGATION AND CORRECTIVE ACTION AGENT ───────────────────
// Input: confirmed Sheets row, notification_sent = true
// Action: create Notion investigation page
notion_page_id = notion.create_page(database_id: INCIDENTS_DB_ID, properties: {
'Reference': reference_number,
'Severity': severity,
'Incident Type': incident_type,
'Incident Date': incident_date,
'Description': description,
'Reporter': reporter_name,
'Department': department,
'Investigation Due': investigation_due_date,
'Status': 'Under Investigation'
})
// Store notion_page_id back to Sheets row for reference
google_sheets.update_row(reference_number, { notion_page_url: notion_page_id })
// ── SCHEDULED POLL: daily at 08:00 local time ────────────────────────────
// Action: check each open Notion page against today's date
FOR each open_incident IN google_sheets.get_rows(status: 'open'):
notion_status = notion.get_page(notion_page_id).properties['Status']
IF today == investigation_due_date - 1 AND notion_status == 'Under Investigation':
gmail.send(to: manager_email,
subject: '[reference_number] Investigation due tomorrow',
body: 'Please update: [notion_page_url]'
)
IF today >= investigation_due_date AND notion_status == 'Under Investigation':
gmail.send(to: manager_email, cc: hr_email,
subject: '[reference_number] Investigation OVERDUE',
body: 'Immediate action required.'
)
// ── BambooHR UPDATE (injury incidents only) ───────────────────────────────
IF injury_involved == true AND bamboohr_updated == false:
bamboohr.put('/v1/employees/{injured_employee_id}/tables/customTabIncidents', {
incident_reference: reference_number,
incident_date: incident_date,
incident_type: incident_type,
outcome_notes: severity
})
google_sheets.update_row(reference_number, { bamboohr_updated: true })
// ── CLOSURE TRIGGER (HR sets Notion page Status to 'Closed') ─────────────
// Trigger: Notion webhook on page property change OR daily poll detects 'Closed'
IF notion_status == 'Closed' AND corrective_actions_all_complete:
gmail.send(to: reporter_email,
subject: 'Incident [reference_number] closed',
body: 'Your report has been investigated and corrective actions confirmed.'
)
google_sheets.update_row(reference_number, {
status: 'closed',
closed_at: now()
})
// ── END OF FLOW ──────────────────────────────────────────────────────────04Recommended build stack
More documents for this process
Every document generated for Workplace Incident Reporting.