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
Support Reporting and Insights
[YourCompany.com] · Customer Support Department · Prepared by FullSpec · [Today's Date]
This document is the primary technical reference for the FullSpec team building the Support Reporting and Insights automation pipeline. It covers the current-state process map, all three agent specifications, the end-to-end data flow, and the recommended build stack. Everything a builder needs to understand scope, replicate logic, and confirm constraints before writing a single line of configuration is contained here. The process owner's responsibilities are limited to confirming API credentials, approving the monthly commentary, and accepting the final handover.
01Process snapshot
02Agent specifications
Handles the scheduled retrieval of ticket data from Zendesk via API and writes the clean dataset to Google Sheets without any manual export or formatting step. This agent fires on the configured schedule (daily at 07:00, weekly on Monday at 08:00, and monthly on the first working day at 08:00) and is entirely autonomous. It replaces the two most time-intensive bottleneck steps: the manual CSV export and the formatting work that currently follows it. Build complexity is Moderate because the Zendesk API query must handle pagination, incremental date-range parameterisation, and a conditional field mapping layer to normalise column names before writing to the Sheet.
// Input
schedule_event: { cadence: 'daily' | 'weekly' | 'monthly', period_start: ISO8601, period_end: ISO8601 }
// Zendesk API query parameters
GET /api/v2/search.json
?query=type:ticket created>={period_start} created<={period_end}
&sort_by=created_at&sort_order=asc&per_page=100
Headers: { Authorization: 'Bearer {ZENDESK_API_TOKEN}' }
// Output
google_sheets_write: {
sheet_id: '{SHEET_ID}',
tab_name: 'raw_data_{cadence}',
rows: [
{ ticket_id, created_at, resolved_at, status, assignee_id,
category_tag, csat_score, first_response_time_min,
resolution_time_min, sla_breach: bool }
],
write_mode: 'overwrite',
confirmation_flag: 'data_fetch_complete'
}- Zendesk plan tier must be confirmed before build. Suite Team and above include API access; Support-only plans on legacy billing may restrict search API calls to 100/minute. Confirm with the process owner before configuring pagination logic.
- The Zendesk API token must be stored in the automation platform's shared credential store, not hard-coded. Token owner should be a dedicated service account, not a personal user login.
- The Google Sheet tab name must follow the naming convention 'raw_data_daily', 'raw_data_weekly', or 'raw_data_monthly'. Downstream formula ranges in the Reporting Agent depend on these exact tab names.
- Write mode is set to 'overwrite' on each run to prevent duplicate rows. If an incremental/append mode is needed in future, a deduplication key (ticket_id + period_end) must be added to the sheet schema before changing this setting.
- If the Zendesk API returns a 429 rate-limit response, the agent must pause for 60 seconds and retry up to three times before logging an error and halting the run.
- Historical data validation: before go-live, the FullSpec team will run three back-filled periods and compare row counts against a manual export to confirm the query is capturing the full dataset.
- Confirm whether Zendesk custom ticket fields (beyond standard schema) are included in scope. Custom fields require additional mapping and must be listed explicitly in the agreed metric definitions document.
Reads the refreshed Google Sheet populated by the Data Fetch Agent, calculates all core support metrics, drafts a plain-English commentary summarising key changes and anomalies for the period, and triggers a human review gate for monthly deep-dive reports. This agent is the most logic-intensive in the pipeline. It must handle three cadence-specific calculation templates, route the output correctly based on a monthly/non-monthly decision, and pass a ready-to-distribute summary to the Distribution Agent. For monthly reports, execution pauses at the commentary review step and waits for explicit manager approval before continuing. Complexity is rated Complex due to the conditional branching, metric formula definitions, and AI commentary generation layer.
// Input
trigger: { confirmation_flag: 'data_fetch_complete', cadence: 'daily' | 'weekly' | 'monthly',
sheet_id: '{SHEET_ID}', tab_name: 'raw_data_{cadence}' }
// Metric calculations (written to 'metrics_summary_{cadence}' tab)
metrics_output: {
period_start: ISO8601,
period_end: ISO8601,
ticket_volume: int,
avg_first_response_time_min: float,
avg_resolution_time_min: float,
csat_score_pct: float,
sla_breach_count: int,
sla_breach_rate_pct: float,
volume_by_category: { [category_tag]: int },
delta_vs_prior_period: { csat_score_pct: float, ticket_volume: int }
}
// Commentary generation prompt fields
ai_prompt_context: { metrics_output, prior_period_metrics, cadence, tone: 'concise and professional' }
commentary_output: { summary_text: string (2-3 sentences), anomalies_flagged: bool }
// On approval (monthly cadence only)
manager_review_gate: {
status: 'pending' | 'approved' | 'edited_and_approved',
reviewed_by: 'Support Manager',
approved_commentary: string,
approval_timestamp: ISO8601
}
// Output
reporting_agent_output: {
metrics_summary: metrics_output,
commentary: approved_commentary | commentary_output.summary_text,
looker_studio_url: string,
confirmation_flag: 'report_ready'
}- The metrics summary tab in Google Sheets must be pre-structured with named ranges matching the field names above. If the Support Team Lead's existing template tabs are reused, column headers must be normalised to the agreed schema before go-live.
- Looker Studio does not expose a push-refresh API. The dashboard updates automatically when the underlying Sheet is written; confirm the Sheets connector is connected to the correct Sheet ID and tab name during setup. Re-authorisation is required if the Sheet is ever moved or renamed.
- The AI commentary layer requires an API key for a language model service (for example, OpenAI GPT-4o or equivalent). The API key must be stored in the shared credential store. The prompt template must be version-controlled and reviewed by the Support Manager before go-live.
- The monthly review gate must be implemented as an approval step: the automation sends the draft commentary to the Support Manager via email or Slack, then waits up to 24 hours for a response. If no approval is received within 24 hours, the automation logs a timeout error and alerts the FullSpec monitoring table without sending the report.
- Prior-period metrics must be stored in a persistent 'metrics_history' tab (one row per period) so the delta calculations are reliable. The FullSpec team will pre-populate this tab with three months of historical data during the QA phase.
- Anomaly detection threshold: if CSAT drops more than 10 percentage points versus the prior period, or ticket volume increases more than 30% week-on-week, the commentary_output.anomalies_flagged field is set to true and the Distribution Agent must include a visual alert marker in the Slack and email output.
- Confirm which AI model tier and context window size is acceptable given ticket data volume. For monthly reports with 1,000-plus tickets, the prompt payload must summarise pre-aggregated metrics, not raw rows.
Takes the completed metrics summary and commentary produced by the Reporting Agent and delivers the finalised report across every configured channel: Gmail for stakeholder email, Slack for the real-time channel post, and Notion for the persistent reporting log. This agent handles three distinct write operations in sequence and logs the outcome of each. It replaces Steps 7, 8, and 9 entirely. Build complexity is Moderate: the logic is straightforward but three separate API integrations must be configured, templated, and tested independently, and each must handle a partial-failure scenario gracefully without re-sending to channels that already received the report.
// Input
trigger: { confirmation_flag: 'report_ready', cadence: 'daily' | 'weekly' | 'monthly' }
payload: {
period_label: string,
ticket_volume: int,
avg_resolution_time_min: float,
csat_score_pct: float,
sla_breach_rate_pct: float,
commentary: string,
looker_studio_url: string,
anomalies_flagged: bool
}
// Gmail send
gmail_send: {
to: ['{DISTRIBUTION_LIST_EMAILS}'],
subject: 'Support Report: {period_label}',
body_template: 'email_report_{cadence}.html',
fields_injected: [ period_label, ticket_volume, csat_score_pct,
avg_resolution_time_min, sla_breach_rate_pct,
commentary, looker_studio_url ]
}
// Slack post
slack_post: {
channel_id: '{SLACK_SUPPORT_CHANNEL_ID}',
blocks: [ header_block(period_label), metrics_block(ticket_volume, csat_score_pct,
sla_breach_rate_pct), commentary_block(commentary),
cta_block(looker_studio_url), alert_block(anomalies_flagged) ]
}
// Notion append
notion_append: {
database_id: '{NOTION_REPORTING_LOG_DB_ID}',
properties: {
report_date: date,
cadence: string,
period_label: string,
ticket_volume: number,
csat_score_pct: number,
sla_breach_rate_pct: number,
looker_studio_url: url,
sent_at: ISO8601
}
}
// Output
distribution_log: {
gmail_sent: bool,
slack_posted: bool,
notion_logged: bool,
errors: [ { channel: string, error_code: string, timestamp: ISO8601 } ]
}- Gmail must be authenticated via OAuth 2.0 using a Google Workspace service account with domain-wide delegation, or via a dedicated sending account with gmail.send scope. Personal OAuth tokens that expire must not be used as the primary credential.
- The distribution list for Gmail must be stored as a configurable environment variable, not hard-coded in the template. The process owner must confirm all recipient email addresses before go-live.
- Slack authentication requires a Bot Token (xoxb-) with chat:write and chat:write.public scopes. The Slack channel ID (not name) must be retrieved and stored during setup, as channel names can change.
- The Notion reporting log database must be created and shared with the Notion integration before build. The database schema (columns and property types) must match the notion_append fields exactly. Confirm whether the existing Notion workspace already has a reporting log or whether a new one must be created.
- Each of the three channel writes must be wrapped in independent error handling. A failure in the Notion append must not prevent the Gmail send or Slack post from completing. All failures are written to the error logging table.
- The anomaly alert block in Slack must be conditionally rendered: it only appears in the Slack message when anomalies_flagged is true. An untriggered alert block must not render as an empty block.
- Idempotency: if the Distribution Agent is re-triggered (for example, after a partial failure), it must check the distribution_log before sending to avoid duplicate emails or Slack posts within the same period.
03End-to-end data flow
// ============================================================
// SUPPORT REPORTING AND INSIGHTS: END-TO-END DATA FLOW
// ============================================================
// TRIGGER
// Automation platform fires cron schedule
schedule_event = {
cadence: 'daily' | 'weekly' | 'monthly',
period_start: ISO8601, // e.g. '2025-06-02T00:00:00Z'
period_end: ISO8601 // e.g. '2025-06-08T23:59:59Z'
}
// ============================================================
// AGENT 1: DATA FETCH AGENT
// ============================================================
// Step 1: Query Zendesk REST API v2
zendesk_request = {
method: 'GET',
endpoint: '/api/v2/search.json',
params: {
query: 'type:ticket created>={period_start} created<={period_end}',
sort_by: 'created_at',
sort_order: 'asc',
per_page: 100
},
auth: 'Bearer {ZENDESK_API_TOKEN}'
}
// Step 2: Paginate until all results fetched
zendesk_raw_response = [
{ id, created_at, updated_at, status, assignee_id, tags[],
custom_fields: { first_response_time_min, resolution_time_min,
csat_score, sla_breach }, via.channel }
]
// Step 3: Normalise and write to Google Sheets
// Target: Sheet ID = {SHEET_ID}, tab = 'raw_data_{cadence}'
sheets_write_payload = [
{ ticket_id, created_at, resolved_at, status, assignee_id,
category_tag, csat_score, first_response_time_min,
resolution_time_min, sla_breach: bool }
]
// write_mode: overwrite | confirmation_flag: 'data_fetch_complete'
// --- HANDOFF: Data Fetch Agent -> Reporting Agent ---
// Trigger: confirmation_flag == 'data_fetch_complete'
// ============================================================
// AGENT 2: REPORTING AGENT
// ============================================================
// Step 4: Read raw_data_{cadence} tab from Google Sheets
sheets_read = {
sheet_id: '{SHEET_ID}',
tab_name: 'raw_data_{cadence}',
fields: [ ticket_id, created_at, resolved_at, status, assignee_id,
category_tag, csat_score, first_response_time_min,
resolution_time_min, sla_breach ]
}
// Step 5: Calculate metrics and write to metrics_summary_{cadence} tab
metrics_output = {
period_start, period_end,
ticket_volume: COUNT(ticket_id),
avg_first_response_time_min: AVG(first_response_time_min),
avg_resolution_time_min: AVG(resolution_time_min),
csat_score_pct: AVG(csat_score) * 100,
sla_breach_count: COUNT(sla_breach == true),
sla_breach_rate_pct: (sla_breach_count / ticket_volume) * 100,
volume_by_category: GROUP_BY(category_tag, COUNT),
delta_vs_prior: {
csat_score_pct: metrics_output.csat_score_pct - prior_period.csat_score_pct,
ticket_volume: metrics_output.ticket_volume - prior_period.ticket_volume
}
}
// Append metrics_output to 'metrics_history' tab (one row per period)
// Step 6: Anomaly check
anomalies_flagged = (
delta_vs_prior.csat_score_pct < -10.0 ||
(delta_vs_prior.ticket_volume / prior_period.ticket_volume) > 0.30
)
// Step 7: Generate AI commentary
ai_prompt = {
system: 'You are a concise support analyst. Write 2-3 sentences.',
user: { metrics_output, prior_period_metrics, cadence, anomalies_flagged }
}
commentary_output = { summary_text: string, anomalies_flagged: bool }
// Step 8: Monthly gate (conditional branch)
IF cadence == 'monthly':
manager_review_gate = {
status: 'pending',
draft_commentary: commentary_output.summary_text,
timeout_hours: 24
}
// Automation pauses; Support Manager receives approval request
// On approval: approved_commentary = manager_edited_or_confirmed_text
// On timeout: log error to Supabase, halt, send alert
ELSE:
approved_commentary = commentary_output.summary_text
// Looker Studio dashboard updates automatically via Sheets connector
looker_studio_url = '{LOOKER_STUDIO_REPORT_URL}'
// confirmation_flag: 'report_ready'
// --- HANDOFF: Reporting Agent -> Distribution Agent ---
// Trigger: confirmation_flag == 'report_ready'
// ============================================================
// AGENT 3: DISTRIBUTION AGENT
// ============================================================
// Step 9: Send Gmail
gmail_send = {
to: '{DISTRIBUTION_LIST_EMAILS}',
subject: 'Support Report: {period_label}',
template: 'email_report_{cadence}.html',
inject: { period_label, ticket_volume, csat_score_pct,
avg_resolution_time_min, sla_breach_rate_pct,
approved_commentary, looker_studio_url }
}
// Step 10: Post Slack message
slack_post = {
channel_id: '{SLACK_SUPPORT_CHANNEL_ID}',
blocks: [ header(period_label), metrics(ticket_volume, csat_score_pct,
sla_breach_rate_pct), commentary(approved_commentary),
cta(looker_studio_url),
IF anomalies_flagged: alert_block() ]
}
// Step 11: Append Notion log entry
notion_append = {
database_id: '{NOTION_REPORTING_LOG_DB_ID}',
report_date: period_end,
cadence: cadence,
period_label: period_label,
ticket_volume: ticket_volume,
csat_score_pct: csat_score_pct,
sla_breach_rate_pct: sla_breach_rate_pct,
looker_studio_url: looker_studio_url,
sent_at: NOW()
}
// Final output: distribution_log
distribution_log = {
gmail_sent: bool,
slack_posted: bool,
notion_logged: bool,
errors: [ { channel, error_code, timestamp } ]
}
// All errors written to Supabase error_log table
// ============================================================04Recommended build stack
More documents for this process
Every document generated for Support Reporting & Insights.