CampaignBrain AI Chat¶
Version: 0.4.81+ Last Updated: 2025-12-28
The AI Chat provides a natural language interface for querying voter data and campaign engagement records. Powered by Claude, it understands campaign terminology and converts plain English questions into executable queries.
Table of Contents¶
- Overview
- Architecture
- Data Sources
- Query Types
- Configuration
- API Endpoints
- Example Queries
- Technical Details
Overview¶
The AI Chat combines two powerful data sources:
| Source | Data Type | Query Method |
|---|---|---|
| i360 Voters | Voter demographics, scores, contact info | QueryIR (structured JSON) |
| Campaign Data | Donors, volunteers, event attendees | Tool Calling |
Users can ask questions in natural language, and Claude determines the best approach to answer:
- Voter queries → Structured JSON output → DuckDB SQL execution
- Campaign queries → Native tool calls → cbmodels API
Architecture¶
┌─────────────────────────────────────────────────────────────────┐
│ User Message │
└─────────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ Claude AI (Sonnet) │
│ ┌─────────────────────────────────────────────────────────────┐│
│ │ System Prompt: ││
│ │ - Data Dictionary (voter columns, stats, examples) ││
│ │ - QueryIR format specification ││
│ │ - Campaign tool descriptions ││
│ └─────────────────────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────┘
│
┌───────────────┴───────────────┐
│ │
▼ ▼
┌───────────────────────────┐ ┌───────────────────────────────┐
│ QueryIR (JSON) │ │ Tool Use (API) │
│ │ │ │
│ { │ │ list_campaign_sources() │
│ "type": "query", │ │ lookup_campaign_contact() │
│ "intent": "list", │ │ find_super_supporters() │
│ "steps": [...] │ │ enrich_with_campaign_data() │
│ } │ │ │
└───────────────────────────┘ └───────────────────────────────┘
│ │
▼ ▼
┌───────────────────────────┐ ┌───────────────────────────────┐
│ CBQueryEngine │ │ campaign_data_service │
│ (DuckDB SQL) │ │ (httpx → cbmodels API) │
└───────────────────────────┘ └───────────────────────────────┘
│ │
▼ ▼
┌───────────────────────────┐ ┌───────────────────────────────┐
│ i360_voters table │ │ cbmodels Campaign API │
│ (DuckDB) │ │ (Tier 1 endpoints) │
└───────────────────────────┘ └───────────────────────────────┘
│ │
└───────────────┬───────────────┘
│
▼
┌─────────────────────────────────────────────────────────────────┐
│ ChatResponse │
│ - message: Formatted results │
│ - type: results | clarify | error │
│ - data: { count, samples, tool_results } │
│ - actions: [ Save as Segment, View in Search, Refine ] │
└─────────────────────────────────────────────────────────────────┘
Data Sources¶
1. i360 Voter Data¶
The voter database contains 27+ columns of voter information:
| Category | Fields |
|---|---|
| Identity | svid, first_name, last_name, suffix, gender, birth_year |
| Location | address, city, state, zip, county, congressional_district, state_house, state_senate, precinct |
| Contact | email_mydata, cell_phone_mydata, home_phone_mydata |
| Political | party, voter_status |
| Scores | turnout_score, trump_support_score, gop_primary_score, fiscal_score, 2a_score, pro_life_score |
| Demographics | ethnicity, religion, marital_status, occupation |
2. Campaign Data (cbmodels)¶
Campaign engagement data from imported files:
| Data Type | Examples |
|---|---|
| Donors | 2022-Donor-MI.xlsx, WinRed donors |
| Volunteers | Weekly volunteer lead files |
| Event Attendees | Rally signups, town halls |
| Captains | Trump Force captains, team leads |
| Commits | BYV commits, pledge cards |
Query Types¶
Voter Queries (QueryIR)¶
For demographic, geographic, and score-based filtering:
"Find Republicans in Wayne County with high turnout scores"
"Women over 65 who support Trump"
"Count Democrats in Congressional District 13"
"Young voters under 35 with cell phones"
Result: Returns voter count, sample records, and actions (Save as Segment, View in Search).
Campaign Data Queries (Tools)¶
For engagement history and cross-source analysis:
"What campaign data do we have?"
"Show me everything on john@example.com"
"Find our super supporters"
"Who appears in 5+ sources?"
Result: Returns campaign source listings, engagement records, or contact match data.
Combined Queries¶
For enriching voter results with campaign data:
Flow: First queries voters (QueryIR), then enriches with campaign data (tool).
Configuration¶
Required Environment Variables¶
# Claude AI (required)
ANTHROPIC_API_KEY=sk-ant-...
CB_CHAT_MODEL=claude-sonnet-4-20250514 # optional, defaults to sonnet
# Campaign Data (optional - enables campaign tools)
CBMODELS_BASE_URL=http://localhost:8001
CBMODELS_TENANT_ID=mi20
Integration Settings¶
Configure via the Integrations page (/integrations) or database:
| Integration | Setting | Description |
|---|---|---|
| anthropic | api_key | Claude API key |
| anthropic | model | Model to use (claude-sonnet-4-20250514) |
| cbmodels | base_url | Campaign data API URL |
| cbmodels | tenant_id | Tenant slug (e.g., mi20) |
API Endpoints¶
POST /api/cb-chat/message¶
Send a message to the AI chat.
Request:
{
"message": "Find Republicans in Wayne County",
"conversation_id": "optional-uuid",
"history": [
{"role": "user", "content": "previous message"},
{"role": "assistant", "content": "previous response"}
]
}
Response:
{
"message": "Finding registered Republican voters in Wayne County\n\n**Found:** 12,345 voters",
"type": "results",
"data": {
"count": 12345,
"samples": [...],
"intent": "list"
},
"actions": [
{
"label": "Save as Segment",
"action": "save_segment",
"data": {"filters": {...}, "count": 12345}
}
],
"conversation_id": "uuid",
"sql": "SELECT * FROM i360_voters WHERE party = 'R' AND county = 'Wayne' LIMIT 100"
}
GET /api/cb-chat/suggestions¶
Get example queries for the UI.
Response:
{
"suggestions": [
{"text": "How many voters do we have?", "category": "Overview"},
{"text": "Find Republicans with high turnout scores", "category": "Targeting"},
{"text": "What campaign data do we have?", "category": "Campaign"}
]
}
GET /api/cb-chat/health¶
Check chat system health.
Response:
{
"status": "healthy",
"anthropic_configured": true,
"model": "claude-sonnet-4-20250514",
"data_dictionary_loaded": true,
"query_engine_available": true,
"campaign_data_available": true
}
Example Queries¶
Voter Queries¶
| Query | What it does |
|---|---|
| "How many voters do we have?" | Count all voters |
| "Find Republicans in Wayne County" | Filter by party + county |
| "Women over 65 who support Trump" | Gender + age + score filter |
| "Voters with email addresses in Detroit" | Contact + city filter |
| "High turnout Independents" | Score + party filter |
| "Young voters under 35 with cell phones" | Age + contact filter |
Campaign Data Queries¶
| Query | Tool Called | What it does |
|---|---|---|
| "What campaign data do we have?" | list_campaign_sources | Lists all imported files with row counts |
| "Show me everything on john@example.com" | lookup_campaign_contact | All records for that email across sources |
| "Find super supporters" | find_super_supporters | Contacts in 2+ sources |
| "Who appears in 5+ sources?" | find_super_supporters(min_sources=5) | High-engagement contacts |
Clarification Queries¶
When the chat needs more information:
| Query | Clarification |
|---|---|
| "Show me swing voters" | "How would you like to define 'swing voter'?" with options |
| "Find engaged voters" | May ask which score to use |
Technical Details¶
Query Intermediate Representation (QueryIR)¶
The structured format for voter queries:
{
"type": "query",
"intent": "list",
"explanation": "Finding registered Republican voters in Wayne County",
"confidence": 0.95,
"steps": [
{"operation": "filter", "field": "party", "operator": "=", "value": "R"},
{"operation": "filter", "field": "county", "operator": "=", "value": "Wayne"},
{"operation": "limit", "value": 100}
]
}
Operations:
- filter - WHERE clause (=, !=, >, <, >=, <=, LIKE, IN, IS NULL)
- limit - LIMIT clause
- order - ORDER BY clause (ASC/DESC)
Campaign Tools¶
Defined in cb_campaign_tools.py:
{
"name": "list_campaign_sources",
"description": "List all campaign data sources...",
"input_schema": {"type": "object", "properties": {}, "required": []}
}
{
"name": "lookup_campaign_contact",
"description": "Find all campaign records for a contact...",
"input_schema": {
"type": "object",
"properties": {
"email": {"type": "string"},
"phone": {"type": "string"}
}
}
}
{
"name": "find_super_supporters",
"description": "Find contacts in multiple sources...",
"input_schema": {
"type": "object",
"properties": {
"field": {"type": "string", "enum": ["email", "phone"]},
"min_sources": {"type": "integer", "minimum": 2},
"limit": {"type": "integer", "maximum": 100}
}
}
}
{
"name": "enrich_with_campaign_data",
"description": "Add campaign data to a list of contacts...",
"input_schema": {
"type": "object",
"properties": {
"emails": {"type": "array", "items": {"type": "string"}},
"phones": {"type": "array", "items": {"type": "string"}}
}
}
}
Data Dictionary¶
Generated dynamically from i360_voters table with:
- Column names and types
- Value distributions (top values, counts)
- Range statistics (min/max for scores, birth years)
- Example queries for each column
This grounds Claude in actual data to prevent hallucination.
File Locations¶
| File | Purpose |
|---|---|
src/api/routes/cb_chat.py |
Main chat endpoint, tool execution |
src/api/services/cb_prompts.py |
System prompt template |
src/api/services/cb_query_ir.py |
QueryIR models |
src/api/services/cb_query_engine.py |
SQL generation + execution |
src/api/services/cb_data_dictionary.py |
Dynamic data dictionary |
src/api/services/cb_campaign_tools.py |
Tool definitions |
src/api/services/campaign_data_service.py |
cbmodels API client |
src/app/templates/cb_chat/index.html |
Chat UI |
Segment Creation¶
Chat results can be saved as segments:
- User asks a voter query
- Chat returns results with "Save as Segment" action
- User clicks action, enters segment name
- Frontend calls
POST /api/i360/save-as-segment - Segment created with filters stored in
filter_criteriaJSON
Dynamic segments re-run the query on each execution. Static segments snapshot the voter IDs.
Performance Notes¶
- Voter queries: Typically < 500ms (DuckDB is fast)
- Campaign data: 20-50ms per tool call
- Claude response: 1-3 seconds depending on complexity
- Token usage: ~500-1000 tokens per query (input), ~200-500 tokens (output)
Troubleshooting¶
"Anthropic API key not configured"¶
Set ANTHROPIC_API_KEY in .env or via Integrations page.
"Campaign data not available"¶
Set CBMODELS_BASE_URL and CBMODELS_TENANT_ID in .env.
Query returns no results¶
- Check if the voter database exists (
db/i360.db) - Verify column names match the data dictionary
- Try a simpler query to test connectivity
Claude returns clarification when not expected¶
- The query may be ambiguous
- Check if the field name is exact (e.g., "trump_support_score" not "trump score")
- Provide more specific criteria
Version History¶
| Version | Changes |
|---|---|
| 0.4.81 | Added campaign data tools (Issue #62) |
| 0.3.5 | Initial AI Chat implementation |