outreach: follow-up radar — deterministic "needs attention" + one-click draft (v0.1.0:69)
The Outreach page now opens with a "Needs attention" list. A deterministic scan (outreach_agent.follow_up_radar) surfaces investors per the email history: tier 0 "you owe a reply" (their email is the most recent, unanswered, >=3d), tier 1 flagged + quiet, tier 2 warm lead gone quiet (no contact in >=45d). Most urgent first; every reason is verifiable from the data (no LLM in the surfacing — the deliberate fix for the trust problem that sank objection-grounding). Excludes graveyard; needs email history. One click sets the investor + suggested type (follow-up/nurture) and runs the existing outreach drafter. Route GET /api/outreach/radar. Test mcp/test_outreach.py extended (owe-reply/warm-quiet/recent/graveyard/order). Verified live in preview. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+37
-3
@@ -9956,6 +9956,7 @@
|
||||
const [drafting, setDrafting] = useState(false);
|
||||
const [result, setResult] = useState(null);
|
||||
const [draftText, setDraftText] = useState('');
|
||||
const [radar, setRadar] = useState([]);
|
||||
const TYPES = [
|
||||
['intro', 'Intro'],
|
||||
['follow_up', 'Warm follow-up'],
|
||||
@@ -9977,19 +9978,25 @@
|
||||
const r = await api('/api/outreach/investors', {}, token);
|
||||
if (!cancelled) setInvestors(Array.isArray(r?.investors) ? r.investors : []);
|
||||
} catch (_) { /* none */ }
|
||||
try {
|
||||
const rr = await api('/api/outreach/radar', {}, token);
|
||||
if (!cancelled) setRadar(Array.isArray(rr?.items) ? rr.items : []);
|
||||
} catch (_) { /* none */ }
|
||||
})();
|
||||
return () => { cancelled = true; };
|
||||
}, [token]);
|
||||
|
||||
const draft = async () => {
|
||||
const draft = async (ovInvestor, ovType) => {
|
||||
if (drafting) return;
|
||||
if (!investorId) { onShowToast('Pick an investor first', 'error'); return; }
|
||||
const inv = ovInvestor || investorId;
|
||||
const t = ovType || type;
|
||||
if (!inv) { onShowToast('Pick an investor first', 'error'); return; }
|
||||
try {
|
||||
setDrafting(true);
|
||||
setResult(null);
|
||||
const res = await api('/api/outreach/draft', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ investor_id: investorId, outreach_type: type, guidance }),
|
||||
body: JSON.stringify({ investor_id: inv, outreach_type: t, guidance }),
|
||||
}, token);
|
||||
const data = res.data || res;
|
||||
setResult(data);
|
||||
@@ -10013,6 +10020,33 @@
|
||||
return (
|
||||
<div className="page-container">
|
||||
<h2 className="section-title" style={{ marginBottom: '20px' }}>Outreach</h2>
|
||||
|
||||
{radar.length > 0 && (
|
||||
<div className="section">
|
||||
<div className="section-title" style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
||||
Needs attention
|
||||
<span className="approval-pill">{radar.length}</span>
|
||||
</div>
|
||||
<div className="index-action-hint" style={{ marginTop: 0, marginBottom: '12px' }}>
|
||||
Investors who are waiting on a reply or have gone quiet, most urgent first. Every reason is verifiable from your email history — no guesswork. Click Draft to compose in your voice.
|
||||
</div>
|
||||
{radar.map((it) => (
|
||||
<div key={it.investor_id} className="merge-candidate-card"
|
||||
style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', gap: '12px', marginBottom: '8px' }}>
|
||||
<div>
|
||||
<div style={{ fontWeight: 600 }}>{it.name}</div>
|
||||
<div className="kpi-subtitle">{it.reason}</div>
|
||||
</div>
|
||||
<button
|
||||
onClick={() => { setInvestorId(it.investor_id); setType(it.suggested_type); draft(it.investor_id, it.suggested_type); }}
|
||||
disabled={drafting}>
|
||||
{drafting ? '…' : `Draft ${it.suggested_type === 'nurture' ? 'nurture' : 'follow-up'}`}
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="section">
|
||||
<div className="index-action-hint" style={{ marginTop: 0, marginBottom: '12px' }}>
|
||||
Drafts a tailored LP email in Ten31's voice, grounded in the thesis and that investor's CRM notes + email history. The investor's details are de-identified before Claude sees them and restored locally, so the LP list never leaves Ten31. Drafts only — you review, edit, and send.
|
||||
|
||||
Reference in New Issue
Block a user