grounding: wire matched email bodies into the LP-feedback corpus
_ground_feedback_corpus now pulls matched email bodies (the richest objection signal) alongside communications and grid notes, round-robin merged so email is never crowded out by a flat LIMIT, per-item capped at 4000 chars to keep the local minimize tractable on long threads, and degrading gracefully when the email tables are absent. Email remains Tier-2-sensitive: it only ever enters the redaction boundary, never Claude directly. Inert until Gmail capture is enrolled. Not yet deployed (bundles into the next release with the meeting-notes work). Test: test_ground_corpus.py. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+28
-9
@@ -3788,17 +3788,36 @@ class CRMHandler(BaseHTTPRequestHandler):
|
||||
return self.send_json({"data": res})
|
||||
|
||||
def _ground_feedback_corpus(self, conn, limit=60):
|
||||
"""Raw LP-feedback prose for grounding (communications + grid notes). Sensitive
|
||||
Tier-2-heavy text; ONLY ever passed into the redaction boundary, never to Claude
|
||||
directly."""
|
||||
items = []
|
||||
for q in ("SELECT body FROM communications WHERE body IS NOT NULL AND TRIM(body)<>'' ORDER BY communication_date DESC LIMIT ?",
|
||||
"SELECT notes FROM fundraising_investors WHERE notes IS NOT NULL AND TRIM(notes)<>'' LIMIT ?"):
|
||||
"""Raw LP-feedback prose for grounding, newest-first, balanced across sources:
|
||||
matched email bodies (the richest objection signal), logged communications, and
|
||||
fundraising grid notes. Sensitive Tier-2-heavy text; ONLY ever passed into the
|
||||
redaction boundary, never to Claude directly."""
|
||||
# Email bodies are capped per item (long threads/quote-chains) to keep the local
|
||||
# minimize tractable; only `matched` emails (tied to a known investor/contact) are
|
||||
# pulled. Sources are round-robin merged so email is always represented even when
|
||||
# communications/notes are plentiful, rather than crowded out by a flat LIMIT.
|
||||
sources = (
|
||||
"SELECT SUBSTR(body_text,1,4000) FROM emails WHERE match_status='matched' "
|
||||
"AND body_text IS NOT NULL AND TRIM(body_text)<>'' ORDER BY sent_at DESC LIMIT ?",
|
||||
"SELECT body FROM communications WHERE body IS NOT NULL AND TRIM(body)<>'' "
|
||||
"ORDER BY communication_date DESC LIMIT ?",
|
||||
"SELECT notes FROM fundraising_investors WHERE notes IS NOT NULL AND TRIM(notes)<>'' LIMIT ?",
|
||||
)
|
||||
buckets = []
|
||||
for q in sources:
|
||||
try:
|
||||
items += [r[0] for r in conn.execute(q, (limit,))]
|
||||
buckets.append([r[0] for r in conn.execute(q, (limit,))])
|
||||
except Exception:
|
||||
pass
|
||||
return items[:limit]
|
||||
buckets.append([]) # table absent (e.g. email integration not migrated) -> skip
|
||||
items, i = [], 0
|
||||
while len(items) < limit and any(i < len(b) for b in buckets):
|
||||
for b in buckets:
|
||||
if i < len(b):
|
||||
items.append(b[i])
|
||||
if len(items) >= limit:
|
||||
break
|
||||
i += 1
|
||||
return items
|
||||
|
||||
def handle_architect_ground(self, user, body):
|
||||
"""Ground an objection register in real LP feedback THROUGH the redaction boundary
|
||||
|
||||
Reference in New Issue
Block a user