Back to Competitor Intelligence Tracking

Developer Handover Pack

Everything needed to build the automation from scratch: current state, full agent specs, data flow, and the build stack.

4 pagesPDF · Technical
FS-DOC-04Technical

Developer Handover Pack

Competitor Intelligence Tracking

[YourCompany.com] · Sales Department · Prepared by FullSpec · [Today's Date]

This document gives the FullSpec build team everything needed to construct, connect, and validate the Competitor Intelligence Tracking automation. It covers the current-state step map with bottleneck flags, full agent specifications with IO contracts, the end-to-end data flow, and the recommended build stack. You and your team are responsible for confirming the competitor source list in Google Sheets, aligning HubSpot field naming, and reviewing the weekly digest output during the parallel QA run. FullSpec handles all build, wiring, testing, and handoff.

01Process snapshot

Step
Name
Description
1
Identify Competitors to Monitor
Sales Manager maintains a manual spreadsheet of rivals. New competitors added ad hoc after lost deals. (20 min/cycle)
2 BOTTLENECK
Check Competitor Websites
Rep visits each competitor site looking for pricing changes, feature announcements, or messaging updates. Inconsistent and rarely thorough. (45 min/cycle)
3
Review Google Alerts and News
Rep skims Google Alerts inbox each morning. High noise-to-signal ratio forces manual filtering before anything useful is found. (30 min/cycle)
4
Check Review Sites for New Ratings
Rep checks G2, Capterra, and similar platforms for new competitor reviews revealing product weaknesses. (25 min/cycle)
5
Monitor Competitor LinkedIn and Job Posts
Rep checks LinkedIn company pages and job listings for signals about competitor investment areas. (20 min/cycle)
6 BOTTLENECK
Record Findings in a Shared Doc or Sheet
Useful findings are typed up into a Notion page or Google Sheet. Format and detail vary by rep and week. (30 min/cycle)
7
Tag Relevant Deals in CRM
Rep manually adds a note to open HubSpot deal records if a finding is relevant to an active opportunity. (15 min/cycle)
8 BOTTLENECK
Compile Weekly Competitive Digest
Sales Manager collects findings and writes a summary email or Slack message for the full team before Monday meeting. (40 min/cycle)
9
Present Findings in Team Meeting
Manager or rep presents updates verbally in weekly sales meeting. Follow-up actions rarely written down systematically. (20 min/cycle)
Time cost summary: Total manual time per weekly cycle is 245 minutes (approximately 4 hours 5 minutes), equivalent to 5 hours per week when accounting for context-switching and rework. At $50/hour blended rate, this costs $13,000/year in staff time. The automation replaces steps 2, 3, 4, 5 (Intelligence Agent: daily scan, scoring, Notion logging, HubSpot notes) and step 8 (Digest Agent: Friday morning digest compiled and posted to Slack). Steps 1, 6 (maintenance-only), 7 (exception override), and 9 remain as light human touchpoints.
Developer Handover PackPage 1 of 4
FS-DOC-04Technical

02Agent specifications

Intelligence Agent

Runs on a daily 6 AM schedule. Reads the current competitor list and associated source URLs from the Google Sheets config tab, queries web monitoring and news aggregation endpoints for each competitor, and passes each raw signal through an AI relevance scoring step. Each signal is scored 1 to 5 for sales relevance and tagged with a category (pricing, product, messaging, market expansion, or hiring). Signals scoring below the configured threshold (default: 3) are discarded without being written anywhere. Signals at or above the threshold are written to the Notion intelligence database with structured fields, and any signal that matches an open HubSpot deal's competitor field triggers an appended timestamped note on that deal record.

Trigger
Daily schedule: 6:00 AM, every day. Fires regardless of weekend; threshold filtering prevents noise accumulation.
Tools
Google Sheets (competitor config read), Notion (signal write), HubSpot (deal note append)
Replaces steps
2 (website checks), 3 (Google Alerts review), 4 (review site checks), 5 (LinkedIn and job post monitoring)
Estimated build
22 hours — Complex
// Input
google_sheets.config_tab -> {
  competitor_name: string,
  website_url: string,
  pricing_page_url: string,
  linkedin_url: string,
  g2_profile_url: string,
  keyword_list: string[],
  alert_feed_url: string
}

// Raw signal shape (per source, before scoring)
raw_signal -> {
  competitor_name: string,
  source_type: 'website' | 'news' | 'review_site' | 'linkedin' | 'job_post',
  source_url: string,
  raw_text: string,
  captured_at: ISO8601 timestamp
}

// Output (scored, written to Notion)
notion.intelligence_db -> {
  competitor_name: string,
  category: 'pricing' | 'product' | 'messaging' | 'market_expansion' | 'hiring',
  relevance_score: integer (1-5),
  summary: string (max 200 chars),
  source_url: string,
  source_type: string,
  signal_date: ISO8601 date,
  logged_at: ISO8601 timestamp,
  hubspot_deal_ids: string[] | null
}

// HubSpot deal note (conditional, when competitor match found)
hubspot.deal_note -> {
  deal_id: string,
  note_body: string (timestamped competitor signal summary),
  associated_contact_ids: string[]
}
  • Google Sheets access requires the config sheet to use exact column headers: competitor_name, website_url, pricing_page_url, linkedin_url, g2_profile_url, keyword_list (comma-separated), alert_feed_url. Any deviation breaks the row parser. Confirm column naming with Tom Aldridge (RevOps Lead) before build begins.
  • Web monitoring queries must be rate-limited to avoid triggering bot-detection on competitor sites. Use a minimum 3-second delay between page fetches per competitor domain. Rotate User-Agent strings across requests.
  • AI scoring prompt must be versioned and stored in the orchestration layer config, not hardcoded in the workflow step. The relevance threshold (default: 3) must be a configurable environment variable (INTEL_SCORE_THRESHOLD) so it can be adjusted without redeployment.
  • Notion write requires a Notion Integration token with Insert Content and Read Content permissions on the target database. The database must be shared with the integration explicitly. Confirm the Notion workspace plan (Plus or above) supports API-created pages.
  • HubSpot deal note append uses the Engagements API v1 (POST /engagements/v1/engagements). The competitor_name value in Google Sheets must match exactly how the competitor appears in the HubSpot deal property (hs_competitor or a custom field). A name normalisation mapping step must be built before the HubSpot write. Confirm field name and exact values with Tom Aldridge before wiring.
  • Dedupe: before writing to Notion, check for an existing entry with the same competitor_name, source_url, and signal_date within the past 48 hours. If a match exists, skip the write. Use a Notion filter query (filter by competitor_name AND source_url AND signal_date) to perform this check on each signal.
  • Fallback: if the Notion write fails (timeout or permission error), log the failed signal payload to the error table in Supabase and send a Slack alert to the ops channel. Do not retry automatically more than twice.
Digest Agent

Runs on a weekly Friday 8:00 AM schedule. Reads all Notion intelligence database entries logged in the past seven days, groups them by competitor and then by category, and uses an AI summarisation step to produce a concise plain-English briefing. The digest is structured as: one section per competitor (ordered by total signal count descending), with bullet points per category. The formatted digest is posted to the designated Slack sales channel and optionally emailed to the sales manager via Gmail. Each Slack message includes a deep link back to the filtered Notion database view for that week.

Trigger
Weekly schedule: Friday 8:00 AM. Fires after the daily Intelligence Agent run has completed (minimum 30-minute offset from the 6 AM daily trigger to ensure Notion writes are settled).
Tools
Notion (weekly signal read), Slack (digest post), Gmail (optional email to sales manager)
Replaces steps
8 (Compile Weekly Competitive Digest)
Estimated build
10 hours — Moderate
// Input
notion.intelligence_db query -> {
  filter: { logged_at: >= (today - 7 days) },
  sort: { logged_at: descending },
  fields: [ competitor_name, category, relevance_score, summary, source_url, signal_date ]
}

// Grouped intermediate structure (in-memory, pre-summarisation)
digest_payload -> {
  week_ending: ISO8601 date,
  competitors: [
    {
      competitor_name: string,
      signal_count: integer,
      categories: {
        pricing: signal[],
        product: signal[],
        messaging: signal[],
        market_expansion: signal[],
        hiring: signal[]
      }
    }
  ]
}

// Output: Slack message
slack.post_message -> {
  channel: ENV:SLACK_SALES_CHANNEL_ID,
  text: string (formatted digest, plain text with markdown),
  blocks: [ section per competitor ],
  unfurl_links: false
}

// Output: Gmail (optional, controlled by ENV:DIGEST_EMAIL_ENABLED)
gmail.send -> {
  to: ENV:DIGEST_EMAIL_RECIPIENT,
  subject: 'Weekly Competitor Intelligence Digest - [week_ending date]',
  body: string (same content as Slack, HTML formatted)
}
  • Slack posting requires a Slack Bot Token (xoxb-) with chat:write and channels:read OAuth scopes. The bot must be invited to the target sales channel before the first run. Confirm channel ID (not channel name) and store as SLACK_SALES_CHANNEL_ID environment variable.
  • Gmail send uses OAuth 2.0 with the gmail.send scope only. Do not request broader Gmail scopes. The sending account must be a Google Workspace account. The sales manager email address is stored as DIGEST_EMAIL_RECIPIENT. The Gmail step is gated by ENV:DIGEST_EMAIL_ENABLED (boolean, default: true) so it can be disabled without touching the workflow.
  • If the Notion query returns zero signals for the week, the Digest Agent must still post a brief Slack message stating no new signals were logged this week and prompting the team to check the competitor source list. Do not silently skip the Friday post.
  • Digest AI summarisation prompt must cap output at 80 words per competitor section to keep the Slack post readable on mobile. This limit must be enforced in the prompt as a hard constraint, not a guideline.
  • The Notion deep-link URL in the Slack message must use a filtered view scoped to the current week. Pre-build the filtered Notion view and store the base URL as ENV:NOTION_DIGEST_VIEW_URL, appending the week-ending date as a query parameter where Notion's sharing URL supports it.
  • Confirm with Jordan Mercer (Sales Manager) that the Friday 8 AM posting time is correct before go-live. The schedule must be stored as a configurable cron expression (ENV:DIGEST_CRON) to allow adjustment without redeployment.
Developer Handover PackPage 2 of 4
FS-DOC-04Technical

03End-to-end data flow

Full trigger-to-output data flow — Competitor Intelligence Tracking
// ── TRIGGER ──────────────────────────────────────────────────────────────
SCHEDULE: daily 06:00 AM
  -> automation_platform.trigger()
  -> google_sheets.read(sheet='competitor_config', range='A2:H*')
     returns: competitor_rows[] {
       competitor_name, website_url, pricing_page_url,
       linkedin_url, g2_profile_url, keyword_list[], alert_feed_url
     }

// ── INTELLIGENCE AGENT: FETCH SIGNALS ────────────────────────────────────
FOR EACH competitor_row IN competitor_rows[]:
  web_monitor.fetch(url=competitor_row.website_url)
    -> raw_signal { source_type='website', raw_text, source_url, captured_at }
  web_monitor.fetch(url=competitor_row.pricing_page_url)
    -> raw_signal { source_type='website', raw_text, source_url, captured_at }
  news_aggregator.query(keywords=competitor_row.keyword_list[])
    -> raw_signal[] { source_type='news', raw_text, source_url, captured_at }
  rss_reader.fetch(url=competitor_row.alert_feed_url)
    -> raw_signal[] { source_type='news', raw_text, source_url, captured_at }
  web_monitor.fetch(url=competitor_row.g2_profile_url)
    -> raw_signal[] { source_type='review_site', raw_text, source_url, captured_at }
  web_monitor.fetch(url=competitor_row.linkedin_url)
    -> raw_signal[] { source_type='linkedin', raw_text, source_url, captured_at }

// ── INTELLIGENCE AGENT: SCORE AND FILTER ─────────────────────────────────
  FOR EACH raw_signal IN all_raw_signals[]:
    ai_scoring_step.run(
      input: { competitor_name, source_type, raw_text },
      prompt_version: ENV:INTEL_PROMPT_VERSION
    )
    -> scored_signal {
         competitor_name,
         category: 'pricing'|'product'|'messaging'|'market_expansion'|'hiring',
         relevance_score: integer (1-5),
         summary: string (max 200 chars),
         source_url,
         source_type,
         signal_date: date,
         captured_at: timestamp
       }
    IF scored_signal.relevance_score < ENV:INTEL_SCORE_THRESHOLD:
      DISCARD -> no write
    ELSE:
      CONTINUE to dedupe check

// ── INTELLIGENCE AGENT: DEDUPE CHECK ─────────────────────────────────────
    notion.query(
      database_id: ENV:NOTION_INTEL_DB_ID,
      filter: {
        competitor_name == scored_signal.competitor_name AND
        source_url == scored_signal.source_url AND
        signal_date >= (today - 2 days)
      }
    )
    IF results.count > 0:
      SKIP write (duplicate detected)
    ELSE:
      CONTINUE to Notion write

// ── INTELLIGENCE AGENT -> NOTION HANDOFF ─────────────────────────────────
    notion.create_page(
      database_id: ENV:NOTION_INTEL_DB_ID,
      properties: {
        competitor_name: scored_signal.competitor_name,
        category: scored_signal.category,
        relevance_score: scored_signal.relevance_score,
        summary: scored_signal.summary,
        source_url: scored_signal.source_url,
        source_type: scored_signal.source_type,
        signal_date: scored_signal.signal_date,
        logged_at: now()
      }
    )
    -> notion_page_id: string

// ── INTELLIGENCE AGENT -> HUBSPOT HANDOFF ────────────────────────────────
    hubspot.deals.search(
      filter: { competitor_field CONTAINS scored_signal.competitor_name }
    )
    -> matching_deals[] { deal_id, deal_name, associated_contact_ids[] }
    IF matching_deals.count > 0:
      FOR EACH deal IN matching_deals[]:
        hubspot.engagements.create(
          type: 'NOTE',
          deal_id: deal.deal_id,
          body: '[{scored_signal.signal_date}] {scored_signal.category}: {scored_signal.summary} | Source: {scored_signal.source_url}'
        )

// ── ERROR HANDLING (Intelligence Agent) ──────────────────────────────────
    ON error (Notion write fail | HubSpot timeout):
      supabase.insert(table='automation_errors', row={
        agent: 'intelligence_agent',
        error_type: error.type,
        payload: scored_signal,
        occurred_at: now()
      })
      slack.alert(channel: ENV:SLACK_OPS_CHANNEL_ID, message: 'Intelligence Agent write error - check Supabase')

// ── WEEKLY TRIGGER ────────────────────────────────────────────────────────
SCHEDULE: Friday 08:00 AM (ENV:DIGEST_CRON)
  -> automation_platform.trigger()

// ── DIGEST AGENT: READ NOTION ─────────────────────────────────────────────
  notion.query(
    database_id: ENV:NOTION_INTEL_DB_ID,
    filter: { logged_at >= (today - 7 days) },
    sort: { logged_at: 'descending' }
  )
  -> weekly_signals[] {
       competitor_name, category, relevance_score,
       summary, source_url, signal_date
     }

// ── DIGEST AGENT: GROUP AND SUMMARISE ────────────────────────────────────
  group_by(weekly_signals[], key=['competitor_name', 'category'])
  -> digest_payload {
       week_ending: date,
       competitors[]: {
         competitor_name,
         signal_count,
         categories: { pricing[], product[], messaging[], market_expansion[], hiring[] }
       }
     }
  ai_summarisation_step.run(
    input: digest_payload,
    max_words_per_competitor: 80,
    output_format: 'slack_markdown'
  )
  -> formatted_digest: string

// ── DIGEST AGENT -> SLACK HANDOFF ─────────────────────────────────────────
  slack.post_message(
    channel: ENV:SLACK_SALES_CHANNEL_ID,
    text: formatted_digest,
    blocks: [ per-competitor sections ],
    footer_link: ENV:NOTION_DIGEST_VIEW_URL + '?week=' + week_ending
  )

// ── DIGEST AGENT -> GMAIL HANDOFF (conditional) ──────────────��────────────
  IF ENV:DIGEST_EMAIL_ENABLED == true:
    gmail.send(
      to: ENV:DIGEST_EMAIL_RECIPIENT,
      subject: 'Weekly Competitor Intelligence Digest - ' + week_ending,
      body_html: html_format(formatted_digest)
    )

// ── END ───────────────────────────────────────────────────────────────────
Developer Handover PackPage 3 of 4
FS-DOC-04Technical

04Recommended build stack

Orchestration layer
A workflow automation tool (platform TBD). Implement one workflow per agent: 'intelligence_agent_daily' and 'digest_agent_weekly'. Both workflows share a single credential store scoped to the project. No credentials hardcoded in workflow nodes. All sensitive values (API keys, tokens, channel IDs, database IDs) stored as named environment variables in the platform credential/secret manager.
Webhook configuration
No inbound webhooks required for this process. Both agents are schedule-triggered. The daily trigger uses a cron expression '0 6 * * *' (06:00 UTC daily). The weekly digest trigger uses '0 8 * * 5' (08:00 UTC, Friday). Store both as ENV:INTEL_CRON and ENV:DIGEST_CRON to allow adjustment without workflow edits. Ensure the orchestration platform's scheduler accounts for UTC offset relative to the owner's local timezone (confirm with Jordan Mercer before go-live).
Templating approach
AI prompts (scoring and summarisation) are stored as versioned text templates in the orchestration platform's config or a dedicated config tab in Google Sheets. Prompt variables (competitor_name, raw_text, category list, max_words) are injected at runtime. Prompt version is tracked via ENV:INTEL_PROMPT_VERSION and ENV:DIGEST_PROMPT_VERSION. Slack message blocks use the Slack Block Kit layout. Gmail body uses a simple HTML template with inline styles for compatibility across email clients. No external templating service required.
Error logging
A Supabase table named 'automation_errors' captures all write failures and timeouts from both agents. Schema: id (uuid, auto), agent (text), error_type (text), payload (jsonb), occurred_at (timestamptz), resolved (boolean, default false). A Slack alert is sent to ENV:SLACK_OPS_CHANNEL_ID on each new error row insert. The FullSpec team reviews the error table during the two-cycle parallel QA run and again at the 30-day post-launch checkpoint. The ops channel must be a private Slack channel separate from the sales digest channel.
Testing approach
All builds run against sandbox credentials first: Notion test database (separate from production, same schema), HubSpot sandbox account with mirrored deal records, and a private Slack channel used only for QA output. Gmail sends during testing go to a dedicated test inbox (ENV:DIGEST_EMAIL_RECIPIENT set to internal FullSpec test address until go-live sign-off). Web monitoring and news aggregation queries run against a subset of two competitors only during initial build testing. Full competitor list is activated only after the first successful end-to-end sandbox cycle. No production Notion database or HubSpot deal records are touched until QA is passed.
Estimated total build time
Intelligence Agent: 22 hours (Complex). Digest Agent: 10 hours (Moderate). End-to-end integration testing, error handling, and parallel QA run: included in the 32-hour total build effort per the confirmed project snapshot. Build timeline: 4 weeks. Build cost: $4,200 (Standard tier, one-off). Any scope additions (e.g. LinkedIn scraping, battlecard generation, additional CRM fields) outside this specification are out of scope and subject to a separate change request.
Pre-build blockers: Three items must be resolved before the Intelligence Agent build can begin. First, the competitor source list in Google Sheets must be complete and use the exact column headers specified in the agent spec above. Second, the HubSpot competitor field name and accepted values must be confirmed with Tom Aldridge so the name normalisation mapping can be built. Third, Notion API access must be granted to the FullSpec integration token on the production intelligence database. Raise these with Jordan Mercer and Tom Aldridge at the April 10 source list review. Contact FullSpec at support@gofullspec.com with any access or credential questions.
Developer Handover PackPage 4 of 4

More documents for this process

Every document generated for Competitor Intelligence Tracking.

Launch Plan
Operations · Owner
View
ROI and Business Case
Finance · Owner
View
Process Runbook / SOP
Operations · Owner
View
Integration and API Spec
Technical · Developer
View
Test and QA Plan
Quality · Developer
View