# Contextual AI Wisdom System Plan

## Overview
AI-powered coaching that sees your numbers and calls you out. Not motivation - situation-specific wisdom that responds to what's happening RIGHT NOW.

**Status:** COMPLETE
**Created:** 2026-01-16

---

## Context Triggers

The AI analyzes the user's situation and identifies the primary trigger:

| Trigger | Condition | Example Output |
|---------|-----------|----------------|
| `streak_strong` | Streak >= 5 days | "5 days straight. You're building a reputation." |
| `streak_broken` | No calls in 3+ days | "The phone gets heavier every day you don't pick it up." |
| `zero_calls` | 0 calls today, after 12pm | "Half the day gone, zero dials. One call changes everything." |
| `behind_target` | Calls < 50% of target, after 2pm | "8 calls to target. Clock's ticking." |
| `overdue_callbacks` | 2+ overdue callbacks | "3 people waiting. They already said yes once." |
| `hot_leads_stale` | Hot leads not contacted 48hrs | "Hot leads go cold. 5 of yours are cooling." |
| `sms_waiting` | SMS replies waiting 2+ hrs | "Someone replied. Don't leave them hanging." |
| `friday_push` | Friday after 3pm | "Weekend's calling. But so are 46 leads." |
| `monday_start` | Monday before 11am | "Fresh week. Set the tone in the first hour." |
| `post_win` | Conversion in last 4 hours | "Blood in the water. Confidence peaks after a win." |
| `winning_streak` | 2+ wins this week | "2 wins this week. You're in the zone. Don't stop." |
| `afternoon_slump` | 2pm-4pm, fewer calls than morning | "Afternoon energy dip. This is when average reps stop." |
| `crushing_it` | Calls > target, wins today | "Target smashed. Now show them what extra looks like." |
| `end_of_day` | After 5pm | "Day's done. Tomorrow starts with momentum or excuses." |

---

## Database Schema

### Table: `wisdom`
```sql
CREATE TABLE wisdom (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  message TEXT NOT NULL,
  expanded TEXT,  -- Longer explanation for tap-to-expand
  context_trigger TEXT NOT NULL,
  source TEXT DEFAULT 'ai' CHECK (source IN ('ai', 'curated')),
  thumbs_up INTEGER DEFAULT 0,
  thumbs_down INTEGER DEFAULT 0,
  times_shown INTEGER DEFAULT 0,
  created_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX idx_wisdom_trigger ON wisdom(context_trigger);
CREATE INDEX idx_wisdom_rating ON wisdom((thumbs_up - thumbs_down) DESC);
```

### Table: `wisdom_shown`
```sql
CREATE TABLE wisdom_shown (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID REFERENCES auth.users(id),
  wisdom_id UUID REFERENCES wisdom(id),
  context_snapshot JSONB,  -- Store context at time of showing
  shown_at TIMESTAMPTZ DEFAULT NOW(),
  rating TEXT CHECK (rating IN ('up', 'down'))
);

CREATE INDEX idx_wisdom_shown_user ON wisdom_shown(user_id, shown_at DESC);
```

---

## Implementation Steps

### Step 1: Database Migration
**File:** `supabase/wisdom-migration.sql`

Create tables + seed curated fallbacks per trigger type.

### Step 2: Context Analysis Service
**File:** `src/services/wisdom.js`

```javascript
function analyzeContext(dashboardData) {
  const context = {
    // Time context
    hour: new Date().getHours(),
    dayOfWeek: new Date().getDay(), // 0=Sun, 5=Fri
    timeOfDay: getTimeOfDay(), // morning/midday/afternoon/evening

    // Activity context
    callsToday: dashboardData.stats.callsToday,
    callTarget: dashboardData.mission.callTarget,
    callsToTarget: dashboardData.mission.callsToTarget,
    percentToTarget: (dashboardData.stats.callsToday / dashboardData.mission.callTarget) * 100,

    // Streak context
    streak: dashboardData.stats.streak,
    daysSinceLastCall: calculateDaysSinceLastCall(),

    // Urgent context
    overdueCallbacks: dashboardData.urgent.overdueCallbacks.length,
    hotLeadsStale: dashboardData.urgent.hotLeadsStale.length,
    smsWaiting: dashboardData.notifications.smsReplies.length,

    // Wins context
    winsToday: dashboardData.stats.winsToday,
    winsThisWeek: dashboardData.stats.winsThisWeek,
    lastWinHoursAgo: calculateLastWinHoursAgo(),

    // Leads context
    hotLeadsWaiting: dashboardData.mission.hotLeadsWaiting,
    totalLeadsWaiting: dashboardData.leadCounts.total,
  };

  // Determine primary trigger
  context.trigger = determineTrigger(context);

  return context;
}

function determineTrigger(ctx) {
  // Priority order - most urgent/impactful first

  // Post-win energy (highest priority if recent)
  if (ctx.lastWinHoursAgo !== null && ctx.lastWinHoursAgo < 4) {
    return 'post_win';
  }

  // Broken streak (needs intervention)
  if (ctx.daysSinceLastCall >= 3) {
    return 'streak_broken';
  }

  // Overdue callbacks (people waiting)
  if (ctx.overdueCallbacks >= 2) {
    return 'overdue_callbacks';
  }

  // SMS waiting (quick wins)
  if (ctx.smsWaiting >= 2) {
    return 'sms_waiting';
  }

  // Zero calls late in day
  if (ctx.callsToday === 0 && ctx.hour >= 12) {
    return 'zero_calls';
  }

  // Friday push
  if (ctx.dayOfWeek === 5 && ctx.hour >= 15) {
    return 'friday_push';
  }

  // Monday start
  if (ctx.dayOfWeek === 1 && ctx.hour < 11) {
    return 'monday_start';
  }

  // Behind target afternoon
  if (ctx.hour >= 14 && ctx.percentToTarget < 50) {
    return 'behind_target';
  }

  // Afternoon slump
  if (ctx.hour >= 14 && ctx.hour <= 16) {
    return 'afternoon_slump';
  }

  // Hot leads going stale
  if (ctx.hotLeadsStale >= 3) {
    return 'hot_leads_stale';
  }

  // Strong streak
  if (ctx.streak >= 5) {
    return 'streak_strong';
  }

  // Crushing it
  if (ctx.callsToday >= ctx.callTarget && ctx.winsToday > 0) {
    return 'crushing_it';
  }

  // Winning streak
  if (ctx.winsThisWeek >= 2) {
    return 'winning_streak';
  }

  // End of day
  if (ctx.hour >= 17) {
    return 'end_of_day';
  }

  // Default
  return 'general';
}
```

### Step 3: OpenAI Wisdom Generation
**File:** `src/services/wisdom.js`

```javascript
async function generateWisdom(context) {
  const prompt = `You're an elite sales coach for a construction industry rep in Australia.

Their current situation:
- Time: ${context.timeOfDay} on ${getDayName(context.dayOfWeek)}
- Calls today: ${context.callsToday} / ${context.callTarget} target
- Streak: ${context.streak} consecutive days (${context.daysSinceLastCall} days since last call)
- Overdue callbacks: ${context.overdueCallbacks} people waiting
- Hot leads going cold: ${context.hotLeadsStale}
- SMS replies waiting: ${context.smsWaiting}
- Wins today: ${context.winsToday}, this week: ${context.winsThisWeek}
- Last win: ${context.lastWinHoursAgo ? context.lastWinHoursAgo + ' hours ago' : 'not recently'}
- Hot leads waiting: ${context.hotLeadsWaiting}

Primary situation: ${context.trigger}

Generate ONE line of wisdom specific to their situation right now.
Not generic motivation. Speak directly to what they're facing.
Tone: Direct, wise, slightly tough love. Like a mentor who's been there.
No emojis. No fluff. Make it hit.

Also provide a 2-3 sentence expansion that explains the concept deeper.

Return JSON:
{
  "message": "The one-liner",
  "expanded": "The deeper explanation"
}`;

  // Call OpenAI...
}
```

### Step 4: Caching Logic
**File:** `src/services/wisdom.js`

Cache invalidation when context changes significantly:
```javascript
function hasContextChangedSignificantly(oldCtx, newCtx) {
  // Trigger changed
  if (oldCtx.trigger !== newCtx.trigger) return true;

  // Calls jumped by 3+
  if (Math.abs(newCtx.callsToday - oldCtx.callsToday) >= 3) return true;

  // New win
  if (newCtx.winsToday > oldCtx.winsToday) return true;

  // New overdue callback
  if (newCtx.overdueCallbacks > oldCtx.overdueCallbacks) return true;

  // Time period changed
  if (newCtx.timeOfDay !== oldCtx.timeOfDay) return true;

  // 2 hours elapsed
  if (Date.now() - oldCtx.timestamp > 2 * 60 * 60 * 1000) return true;

  return false;
}
```

### Step 5: API Endpoint
**File:** `src/routes/dashboard.js`

```javascript
GET /api/dashboard/wisdom

Response:
{
  message: "3 people waiting. They already said yes once.",
  context_trigger: "overdue_callbacks",
  expanded: "Callbacks aren't cold calls. These people already showed interest...",
  wisdomId: "uuid"
}

POST /api/dashboard/wisdom/rate
Body: { wisdomId: "uuid", rating: "up" | "down" }
```

### Step 6: Curated Fallbacks (per trigger)
**File:** `supabase/wisdom-migration.sql`

```sql
-- streak_strong
INSERT INTO wisdom (message, expanded, context_trigger, source) VALUES
('You''re not building a streak. You''re building a reputation.', 'Consistency compounds. Every day you show up, you''re proving to yourself and your pipeline that you''re the kind of rep who doesn''t quit. That reputation follows you into every conversation.', 'streak_strong', 'curated'),

-- streak_broken
('The phone gets heavier every day you don''t pick it up.', 'Avoidance creates anxiety. The longer you wait, the harder it feels. But here''s the truth: the first call breaks the spell. Just one dial and you''re back in the game.', 'streak_broken', 'curated'),

-- zero_calls
('Half the day gone, zero dials. One call changes the energy.', 'Starting is the hardest part. Your brain is making it bigger than it is. Pick up the phone, make one call, and watch how quickly the momentum builds.', 'zero_calls', 'curated'),

-- overdue_callbacks
('They already said yes once. Don''t make them regret it.', 'A callback isn''t a cold call. They''re expecting you. They want to hear from you. Every hour you delay, they''re wondering if you''re the right person to work with.', 'overdue_callbacks', 'curated'),

-- friday_push
('Weekend''s calling. But so are your leads. 30 more minutes.', 'Friday afternoon separates the professionals from the clock-watchers. While everyone else is mentally checked out, you''re stacking next week''s pipeline.', 'friday_push', 'curated'),

-- post_win
('Blood in the water. Confidence peaks after a win. Use it now.', 'Your voice sounds different after a win. Prospects can hear certainty. This is your window - the next 2-3 calls have the highest conversion probability of your day.', 'post_win', 'curated'),

-- monday_start
('Fresh week. The tone you set in the first hour carries all week.', 'Monday morning is a statement. Start slow and you''ll chase all week. Start strong and you''ll cruise. Your choice.', 'monday_start', 'curated'),

-- behind_target
('Clock''s ticking. But you only need one good conversation.', 'Targets aren''t about quantity. They''re about getting enough at-bats. One connected call can change your whole day. Focus on the next dial, not the number.', 'behind_target', 'curated'),

-- sms_waiting
('Someone replied. Fastest path to a deal is someone who''s already engaged.', 'An inbound reply is the warmest lead you''ll get all day. Don''t let it go cold. Strike while they''re thinking about you.', 'sms_waiting', 'curated'),

-- hot_leads_stale
('Hot leads go cold. 5 of yours are cooling right now.', 'A hot lead today is a warm lead tomorrow and a cold lead next week. Speed to contact is the single biggest factor in conversion.', 'hot_leads_stale', 'curated'),

-- afternoon_slump
('2pm energy dip. This is when average reps coast.', 'Your competitors are checking emails and planning tomorrow. You''re on the phone. That''s the edge.', 'afternoon_slump', 'curated'),

-- crushing_it
('Target smashed. Now show them what extra looks like.', 'You hit your number. Most would coast. But you''re not most. The difference between good and elite is what you do after you''ve already won.', 'crushing_it', 'curated'),

-- winning_streak
('2 wins this week. You''re in the zone. Don''t stop.', 'Winning creates winning. Your confidence is high, your pitch is tight, your timing is on. This is the week to go hard.', 'winning_streak', 'curated'),

-- end_of_day
('Day''s done. Tomorrow starts with momentum or excuses.', 'How you end today determines how you start tomorrow. Leave something in the chamber - a hot lead to call first thing, a callback scheduled. Give tomorrow-you a head start.', 'end_of_day', 'curated'),

-- general
('The money''s in the follow-up. Always has been.', 'Most deals don''t close on the first call. They close on the fifth, the eighth, the twelfth. The rep who follows up wins.', 'general', 'curated');
```

### Step 7: Frontend - WisdomDisplay Component
**File:** `admin/src/components/WisdomDisplay.jsx`

```jsx
function WisdomDisplay({ wisdom, onRate, onExpand }) {
  const [expanded, setExpanded] = useState(false);

  return (
    <div
      className="bg-slate-900 text-white rounded-xl p-4 cursor-pointer"
      onClick={() => setExpanded(!expanded)}
    >
      <p className="text-sm font-medium leading-relaxed">
        "{wisdom.message}"
      </p>

      {expanded && (
        <p className="text-xs text-slate-400 mt-3 leading-relaxed">
          {wisdom.expanded}
        </p>
      )}

      <div className="flex items-center justify-between mt-3">
        <span className="text-xs text-slate-500">
          Tap for more
        </span>
        <div className="flex gap-2">
          <button onClick={(e) => { e.stopPropagation(); onRate('up'); }}>
            👍
          </button>
          <button onClick={(e) => { e.stopPropagation(); onRate('down'); }}>
            👎
          </button>
        </div>
      </div>
    </div>
  );
}
```

### Step 8: Update TimeAwareSection
**File:** `admin/src/components/TimeAwareSection.jsx`

Replace time mode indicator with WisdomDisplay component.

### Step 9: Update Dashboard.jsx
**File:** `admin/src/pages/Dashboard.jsx`

- Fetch wisdom on load
- Re-fetch when dashboard data changes significantly
- Handle rating submission
- Pass to TimeAwareSection

### Step 10: API Client Update
**File:** `admin/src/api/client.js`

```javascript
dashboardApi: {
  getStats: () => request('/api/dashboard/stats'),
  getWisdom: () => request('/api/dashboard/wisdom'),
  rateWisdom: (wisdomId, rating) => request('/api/dashboard/wisdom/rate', {
    method: 'POST',
    body: JSON.stringify({ wisdomId, rating })
  }),
}
```

---

## Files to Create

| File | Description |
|------|-------------|
| `supabase/wisdom-migration.sql` | Tables + curated fallbacks |
| `src/services/wisdom.js` | Context analysis + generation |
| `admin/src/components/WisdomDisplay.jsx` | Wisdom UI component |

## Files to Modify

| File | Changes |
|------|---------|
| `src/routes/dashboard.js` | Add wisdom endpoints |
| `admin/src/components/TimeAwareSection.jsx` | Replace time indicator |
| `admin/src/pages/Dashboard.jsx` | Add wisdom fetching + rating |
| `admin/src/api/client.js` | Add wisdom API methods |

---

## Build Order

1. [ ] Database migration with curated fallbacks
2. [ ] Wisdom service (context analysis + OpenAI)
3. [ ] API endpoints (get + rate)
4. [ ] WisdomDisplay component
5. [ ] Update TimeAwareSection
6. [ ] Update Dashboard.jsx + API client
7. [ ] Test all triggers

---

## Testing Scenarios

| Scenario | Expected Trigger |
|----------|-----------------|
| 0 calls at 2pm | `zero_calls` |
| 3 overdue callbacks | `overdue_callbacks` |
| Friday 4pm | `friday_push` |
| Monday 9am | `monday_start` |
| Win 2 hours ago | `post_win` |
| No calls in 4 days | `streak_broken` |
| 10-day streak | `streak_strong` |
| 3 SMS waiting | `sms_waiting` |
| Beat target + win today | `crushing_it` |

---

## Notes

- Use gpt-4o-mini for cost efficiency
- Context analysis runs on backend (not exposed to frontend)
- Wisdom ID used for rating correlation
- Store context snapshot for analytics on what triggers resonate
