Skip to content

Interest Signals Detection

Overview

Interest Signals Detection analyzes visitor messages to identify buying signals and trigger demo proposals at the right moment. The system scores signals cumulatively across the conversation.

How It Works

flowchart TD A[Visitor message] --> B[interest_signals_detector] B --> C{Analyze for signals} C --> D[Score each signal 0-100] D --> E[Add to cumulative score] E --> F{Score > threshold?} F -->|Yes| G[Propose demo with 👇] F -->|No| H[Continue conversation]

Signal Categories

The detector looks for three categories of buying signals:

Engagement Signals

  • Message count and conversation depth
  • Time spent in conversation
  • Questions about specific features

Buying Signals

  • Pricing questions
  • Timeline/urgency mentions
  • Budget discussions
  • Decision-maker language

Context Signals

  • Company size mentions
  • Use case descriptions
  • Industry/sector information

Scoring System

Each detected signal gets a score from 0-100 based on evidence strength:

Signal Strength Score Range Example
Weak 0-30 "Just browsing"
Moderate 31-60 "Looking at options"
Strong 61-100 "Need this by Q2, budget approved"

Scores accumulate across turns until the threshold triggers a demo proposal.

Emoji Marker

  • 👇 (pointing down) marks a demo proposal
  • Detected by dialog_state_extractor.py
  • Triggers special handling in the frontend (shows CTA buttons)

Key Files

Backend

File Purpose
ixchat/nodes/interest_signals_detector.py Main detection logic
ixchat/pydantic_models/interest_signals.py Signal definitions and scoring
ixchat/pydantic_models/interest_signals_state.py State tracking
ixchat/utils/demo_proposal.py Threshold checking

State Tracking

class InterestSignalsState(BaseModel):
    cumulative_score: int = 0           # Current session score
    lifetime_interest_score: int = 0    # Persists across sessions
    detected_signals: list[KeySignal]   # Signals found
    demo_proposed: bool = False         # Whether demo was proposed this session

LLM Output

class InterestSignalsDetectionOutput(BaseModel):
    detected_signals: list[KeySignal] = Field(
        description="List of detected interest signals with scores"
    )

Demo Proposal Flow

When cumulative score exceeds the threshold:

  1. interest_signals_detector sets should_propose_demo = True
  2. Answer includes 👇 emoji marker
  3. dialog_state_extractor detects marker and updates state
  4. Frontend shows demo CTA buttons
  5. Score resets after demo is proposed

Lifetime Score Persistence

Interest scores persist across sessions via lifetime_interest_score:

  • Stored in Supabase conversations table
  • Loaded on session start via get_lifetime_interest_score()
  • Ensures returning high-interest visitors get appropriate treatment

Debugging

Backend logs use prefixes:

📊 [INTEREST SIGNALS] Analyzing conversation for buying signals
📊 [INTEREST SIGNALS] Detected signals: pricing_inquiry (85), timeline_mention (60)
📊 [INTEREST SIGNALS] Cumulative score: 145 (threshold: 100)
👇 [INTEREST SIGNALS] Proposing demo - score exceeds threshold

Configuration

Demo proposal threshold can be configured per site in custom_config:

{
  "interest_signals": {
    "demo_threshold": 100,
    "enabled": true
  }
}

Testing

poetry run pytest packages/ixchat/tests/test_interest_signals_detector.py -v