Add reminders & follow-ups (W1) (v0.1.0:92)
First-class reminders tied to the fundraising grid — foundation of the agreed reminders -> NL-search -> bot-mutations plan (keep LP data off third-party LLMs). - reminders table (migration 0006; logical FK to fundraising_investors.id + denormalized name), CRUD at /api/reminders (soft-delete; open/done/snoozed/ cancelled; assignee; source; source_row_id resolution) - read-only derived reminder_status grid column (overdue/due_soon/open), filterable; orphan reconciler cancels reminders when an investor leaves the grid - Reminders page, Dashboard "Reminders Due" card, daily-digest reminders section - per-investor last_activity_at recency rollup (shared block for the W2 NL query) - tests: test_reminders.py + digest reminders test (31/31 green, render-smoke green)
This commit is contained in:
@@ -193,6 +193,55 @@ def test_build_and_empty():
|
||||
conn.close()
|
||||
|
||||
|
||||
def test_reminders_due():
|
||||
"""The reminders-due section: overdue + due-today only (future / done / soft-deleted
|
||||
excluded), rendered even on an empty email window. Creates + drops the reminders table
|
||||
so the rest of the suite still exercises the table-absent path."""
|
||||
from datetime import date, timedelta
|
||||
conn = _conn()
|
||||
conn.execute("""CREATE TABLE reminders (id TEXT PRIMARY KEY, investor_id TEXT,
|
||||
investor_name TEXT, contact_id TEXT, title TEXT, details TEXT, due_date TEXT,
|
||||
status TEXT DEFAULT 'open', snoozed_until TEXT, assignee_id TEXT, created_by TEXT,
|
||||
source TEXT, completed_at TEXT, created_at TEXT, updated_at TEXT, deleted_at TEXT)""")
|
||||
today = date.today().isoformat()
|
||||
yest = (date.today() - timedelta(days=1)).isoformat()
|
||||
future = (date.today() + timedelta(days=30)).isoformat()
|
||||
conn.executemany(
|
||||
"INSERT INTO reminders (id,investor_name,title,due_date,status,assignee_id,deleted_at) "
|
||||
"VALUES (?,?,?,?,?,?,?)", [
|
||||
("r1", "Harbor & Vine", "Send wire instructions", yest, "open", "u1", None), # overdue
|
||||
("r2", "Brightwater Capital", "Call about allocation", today, "open", None, None), # due today
|
||||
("r3", "Vela Partners", "Quarterly touch", future, "open", "u1", None), # future -> hidden
|
||||
("r4", "Gone LP", "Done already", yest, "done", "u1", None), # done -> hidden
|
||||
("r5", "Deleted LP", "Tombstoned", yest, "open", "u1", "2026-06-01T00:00:00Z"), # deleted -> hidden
|
||||
])
|
||||
conn.commit()
|
||||
|
||||
due = digest_builder.collect_due_reminders(conn, today)
|
||||
titles = {r["title"] for r in due}
|
||||
check(titles == {"Send wire instructions", "Call about allocation"},
|
||||
f"due collector = overdue + due-today only (got {titles})")
|
||||
overdue = [r for r in due if r["overdue"]]
|
||||
check(len(overdue) == 1 and overdue[0]["title"] == "Send wire instructions", "overdue flagged")
|
||||
|
||||
stub = lambda prompt, system=None, max_tokens=220: "narrative"
|
||||
d = digest_builder.build_digest(conn, SINCE, UNTIL, chat_fn=stub)
|
||||
check(d["reminder_count"] == 2, f"reminder_count = 2 (got {d['reminder_count']})")
|
||||
check("REMINDERS DUE (2)" in d["body"], "body has reminders section header")
|
||||
check("Overdue (1):" in d["body"] and "Due today (1):" in d["body"], "body splits overdue / due today")
|
||||
check("Harbor & Vine — Send wire instructions" in d["body"]
|
||||
and "[Grant Gilliam]" in d["body"], "reminder line shows investor + title + resolved assignee")
|
||||
check("Quarterly touch" not in d["body"], "future reminder excluded from due section")
|
||||
|
||||
empty = digest_builder.build_digest(conn, "2030-01-01T00:00:00Z", "2030-01-02T00:00:00Z", chat_fn=stub)
|
||||
check("No tracked email activity" in empty["body"] and "REMINDERS DUE (2)" in empty["body"],
|
||||
"reminders render even on an empty email window (current-state addendum)")
|
||||
|
||||
conn.execute("DROP TABLE reminders")
|
||||
conn.commit()
|
||||
conn.close()
|
||||
|
||||
|
||||
def test_policy():
|
||||
conn = _conn()
|
||||
# No DB row yet: CRM_DIGEST_ENABLED=1 (set at import) seeds enabled; hour defaults 18.
|
||||
@@ -352,6 +401,7 @@ def main():
|
||||
print("collect_user_activity:"); test_collect()
|
||||
print("collect_investor_activity:"); test_investor()
|
||||
print("build_digest + empty:"); test_build_and_empty()
|
||||
print("reminders due:"); test_reminders_due()
|
||||
print("summary fallback:"); test_summary_fallback()
|
||||
print("digest policy:"); test_policy()
|
||||
print("window resolver:"); test_window_resolver()
|
||||
|
||||
Reference in New Issue
Block a user