Restrict comms_by_user/email_counts_by_user to matched-investor email
Both NL-query intents counted/listed a user's ENTIRE captured sent corpus (internal, vendor, personal mail) rather than only email to a matched investor — they were missing the `EXISTS email_investor_links` gate that recent_emails and the Communications panel's query_email_activity use. Their own docstrings said "investor emails", so the behavior was wrong, not just loose. Add the matched-only gate to both, mirroring query_email_activity. The runner test now seeds an unmatched sent email and asserts it is excluded (without the fix comms_by_user returns 3 not 2, this_week 2 not 1) — the prior fixture linked every email, so the leak went uncaught. Also documents the matched-only rule in the nl-query guide, and refreshes the AGENTS.md Current state (v93 deployed; this fix pending a v94 s9pk since the intents run on the box, not the bot).
This commit is contained in:
@@ -306,8 +306,11 @@ def run_investor_last_contact(conn, slots):
|
||||
|
||||
|
||||
def run_comms_by_user(conn, slots):
|
||||
"""The most recent `limit` outbound investor emails sent by a given user (matched by
|
||||
username or full name). Soft-delete-correct (live sighting, is_sent)."""
|
||||
"""The most recent `limit` outbound **investor** emails sent by a given user (matched by
|
||||
username or full name). MATCHED-ONLY: restricted to investor-linked email (an
|
||||
email_investor_links row exists), mirroring query_email_activity / recent_emails — NOT the
|
||||
user's entire sent corpus (internal/vendor/personal mail is captured but never surfaced
|
||||
here). Soft-delete-correct (live sighting, is_sent)."""
|
||||
n, pat = slots["limit"], like_contains(slots["user"])
|
||||
rows = _rows(conn.execute(
|
||||
"SELECT e.subject, e.sent_at, u.full_name AS sender, "
|
||||
@@ -318,6 +321,7 @@ def run_comms_by_user(conn, slots):
|
||||
"AND eam.deleted_at IS NULL AND eam.is_sent = 1 "
|
||||
"JOIN email_accounts ea ON ea.id = eam.account_id JOIN users u ON u.id = ea.user_id "
|
||||
"WHERE (u.username LIKE ? ESCAPE '\\' OR u.full_name LIKE ? ESCAPE '\\') "
|
||||
"AND EXISTS (SELECT 1 FROM email_investor_links l2 WHERE l2.email_id = e.id) "
|
||||
"ORDER BY e.sent_at DESC LIMIT ?", (pat, pat, n)))
|
||||
return {"columns": ["sent_at", "subject", "sender", "investor"], "rows": rows,
|
||||
"truncated": False,
|
||||
@@ -325,13 +329,16 @@ def run_comms_by_user(conn, slots):
|
||||
|
||||
|
||||
def run_email_counts_by_user(conn, slots):
|
||||
"""Per-user counts of outbound investor emails over this week / month / year-to-date.
|
||||
"""Per-user counts of outbound **investor** emails over this week / month / year-to-date.
|
||||
MATCHED-ONLY: counts only investor-linked email (an email_investor_links row exists),
|
||||
mirroring query_email_activity / recent_emails — not the user's entire sent corpus.
|
||||
Windows are calendar-based: week = since Monday, month = since the 1st, ytd = since Jan 1."""
|
||||
today = _today()
|
||||
wk = (today - timedelta(days=today.weekday())).isoformat()
|
||||
mo = today.replace(day=1).isoformat()
|
||||
yr = today.replace(month=1, day=1).isoformat()
|
||||
where = "WHERE eam.deleted_at IS NULL AND eam.is_sent = 1"
|
||||
where = ("WHERE eam.deleted_at IS NULL AND eam.is_sent = 1 "
|
||||
"AND EXISTS (SELECT 1 FROM email_investor_links l WHERE l.email_id = e.id)")
|
||||
params = [wk, mo, yr]
|
||||
if slots.get("user"):
|
||||
pat = like_contains(slots["user"])
|
||||
|
||||
Reference in New Issue
Block a user