Home / Intelligence Log / Software Development Software Development

Using LLMs to Automate Deal Flow Intelligence in Your CRM

The Deal Intelligence Gap

Most CRM systems are excellent at storing what happened — call logged, email sent, stage updated — and poor at capturing what was learned. A sales call produces qualitative intelligence that is genuinely valuable for deal strategy: what objections surfaced, how strongly the prospect signaled interest, what next steps were agreed to, and what risk flags the conversation revealed. That intelligence almost never makes it into the CRM because it requires someone to spend 15 minutes synthesizing unstructured notes into structured fields.

Large language models change this equation. Given a call transcript, Claude can extract structured deal intelligence in seconds — categorizing sentiment, identifying specific objections, recommending stage movement, and flagging risk signals — with accuracy that equals or exceeds what a well-trained sales analyst would produce manually.

Data Model Design

The data model centers on two tables. The deals table stores core deal attributes as a JSONB column, which allows flexible schema evolution without migrations as the intelligence fields change over time. The deal_activities table records each interaction — calls, emails, meetings — with the raw content in TEXT and the extracted intelligence in a separate JSONB column. A GIN index on both JSONB columns enables fast attribute queries across the deal pipeline.

SQL schema.sql
CREATE TABLE deals (
    id          UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    company     TEXT NOT NULL,
    contact     TEXT,
    stage       TEXT,
    attributes  JSONB DEFAULT '{}',
    created_at  TIMESTAMPTZ DEFAULT now(),
    updated_at  TIMESTAMPTZ DEFAULT now()
);

CREATE TABLE deal_activities (
    id           UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    deal_id      UUID REFERENCES deals(id),
    activity_type TEXT,
    raw_content  TEXT,
    intelligence JSONB,
    created_at   TIMESTAMPTZ DEFAULT now()
);

CREATE INDEX ON deals USING GIN (attributes);
CREATE INDEX ON deal_activities USING GIN (intelligence);

Transcript Analysis with Claude

The DealIntelligence class wraps the Anthropic API call and parses the structured JSON response. The prompt instructs the model to return a fixed schema — sentiment, objections, next steps, deal signals, risk flags, recommended stage, and a summary — which can be validated and stored directly without post-processing.

Python deal_intelligence.py
import anthropic, json, psycopg2
from dataclasses import dataclass
from typing import Dict, List

@dataclass
class DealRecord:
    deal_id: str
    company: str
    contact: str
    stage: str

class DealIntelligence:
    def __init__(self, db_conn: str):
        self.client = anthropic.Anthropic()
        self.db     = psycopg2.connect(db_conn)

    def analyze_transcript(self, transcript: str, deal: DealRecord) -> Dict:
        prompt = f"""Analyze this sales call transcript for deal intelligence.
Return valid JSON only, with these exact keys:
- sentiment: "positive" | "neutral" | "negative"
- objections: list of specific objections raised
- next_steps: list of agreed action items
- deal_signals: list of positive buying signals
- risk_flags: list of concerns or blockers identified
- recommended_stage: suggested CRM stage based on this conversation
- summary: 2-3 sentence executive summary

Company: {deal.company}
Contact: {deal.contact}
Current Stage: {deal.stage}

Transcript:
{transcript}"""

        response = self.client.messages.create(
            model="claude-opus-4-7", max_tokens=1500,
            messages=[{"role": "user", "content": prompt}]
        )
        return json.loads(response.content[0].text)

    def draft_followup_email(self, intelligence: Dict, deal: DealRecord) -> str:
        objections_text = ", ".join(intelligence.get('objections', []))
        next_steps_text = "\n- ".join(intelligence.get('next_steps', []))

        prompt = f"""Draft a professional follow-up email for this sales conversation.

Contact: {deal.contact} at {deal.company}
Call Summary: {intelligence.get('summary', '')}
Objections Raised: {objections_text}
Next Steps Agreed:
- {next_steps_text}

Write a concise, personalized follow-up that confirms next steps and addresses
the main objections without being pushy. Professional but warm tone."""

        response = self.client.messages.create(
            model="claude-opus-4-7", max_tokens=600,
            messages=[{"role": "user", "content": prompt}]
        )
        return response.content[0].text

    def save_activity(self, deal_id: str, transcript: str, intelligence: Dict):
        with self.db.cursor() as cur:
            cur.execute(
                """INSERT INTO deal_activities
                   (deal_id, activity_type, raw_content, intelligence)
                   VALUES (%s, 'call', %s, %s)""",
                (deal_id, transcript, psycopg2.extras.Json(intelligence))
            )
            # Update stage if recommended stage differs
            recommended = intelligence.get('recommended_stage')
            if recommended:
                cur.execute(
                    "UPDATE deals SET stage = %s WHERE id = %s",
                    (recommended, deal_id)
                )
        self.db.commit()

Automated Follow-Up Drafting

The follow-up email draft uses the intelligence output as its input — pulling objections, next steps, and the summary to generate a personalized message that is specific to the actual conversation rather than a generic template. The draft goes into a review queue rather than sending automatically. A human confirms and sends. This keeps the LLM in an assistive role and preserves the personal judgment that high-stakes business development requires.

PostgreSQL JSONB Storage

Storing intelligence as JSONB rather than normalized columns is deliberate. The schema of deal intelligence evolves as the LLM prompt evolves — new keys appear, existing ones are renamed, arrays grow. JSONB absorbs these changes without schema migrations. The GIN index makes these fields queryable: finding all deals where a pricing objection was flagged is a straightforward WHERE intelligence @> '{"objections": ["pricing"]}' query.

Putting It Together

The complete workflow is: transcript arrives (from a recording integration, a manual paste, or an email thread) → analyze_transcript() extracts structured intelligence → save_activity() persists it and updates the deal stage → draft_followup_email() generates a draft for human review. The entire flow completes in under 10 seconds per call, turning what was a 15-minute manual task into a background process that surfaces better intelligence than most teams produce by hand.

Ready to Stop Losing Deal Intelligence?

We build AI-augmented CRM systems that capture and structure deal intelligence from every conversation — automatically.

Talk to Our Team