Email search/query + windowed digest preview (v0.1.0:83)
Communications tab (search/query roadmap items 1 & 2): - Fix the investor dropdown: the facet only listed grid investors, so it came back empty whenever email matched a classic contact or org domain (no grid id — the common case). It now mirrors the email list, resolving each link to a typed identity (fund:/org:/contact:/addr:) with precedence grid -> org -> contact -> address; investor_id accepts the typed key (bare id = fund: for back-compat) and an unknown prefix matches nothing. - Add a date-range filter and a click-to-expand full-body view (GET /api/email/detail, admin, soft-delete-gated; body_text only, never raw remote HTML). - Add a "Search content" mode: GET /api/email/search wraps the ingest hybrid_search over the Qdrant email index (doc_type=email), hydrated and soft-delete-filtered against SQLite (canonical), 503 if Spark/Qdrant down. Daily digest: - Settings -> Admin builds a digest over a chosen window (last 24h or since a date) as an in-app preview before sending (POST /api/admin/digest/preview), so the local-Spark summarizer can be verified on demand even on a quiet day. Manual send uses the same window; neither advances the daily cursor, so a preview never suppresses the scheduled digest. Code-only, migrations no-op. 22/22 backend tests, render-smoke pass.
This commit is contained in:
@@ -100,15 +100,36 @@ def _build_and_send(conn, since_iso, until_iso, *, build_fn=None, send_fn=None):
|
||||
}
|
||||
|
||||
|
||||
def send_digest_window(conn_factory=None, *, since_iso, until_iso,
|
||||
build_fn=None, send_fn=None):
|
||||
"""Build the digest for an explicit (since_iso, until_iso] window and send it
|
||||
to the active-admin set now, WITHOUT advancing the daily cursor — a manual or
|
||||
preview send must never suppress the scheduled daily digest. Same transport +
|
||||
recipient rules as the daily path (raises digest_mailer.NoTransport when none
|
||||
is configured / no admin has an address). Backs the admin 'send now' endpoint.
|
||||
|
||||
No DB writes happen here (the cursor is deliberately untouched), so the connection
|
||||
is opened and closed without a commit — don't add one without revisiting that."""
|
||||
factory = conn_factory or _conn_factory_from_env()
|
||||
conn = factory()
|
||||
try:
|
||||
result = _build_and_send(conn, since_iso, until_iso,
|
||||
build_fn=build_fn, send_fn=send_fn)
|
||||
return {"status": "sent", **result}
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def maybe_send_digest(conn_factory=None, *, force=False,
|
||||
now_local=None, now_utc=None, build_fn=None, send_fn=None):
|
||||
"""Send the daily digest if it is due (or unconditionally when force=True).
|
||||
|
||||
Daily path: skips before the send hour and if already sent today; content
|
||||
window runs from the last send to now and the cursor advances on success.
|
||||
force path (the admin 'send now' endpoint): ignores the policy and the guards,
|
||||
uses a fixed last-24h window, and does NOT advance the daily cursor — so an
|
||||
on-demand preview never suppresses the scheduled send."""
|
||||
Daily path (the scheduler loop): skips before the send hour and if already sent
|
||||
today; content window runs from the last send to now and the cursor advances on
|
||||
success. force path: ignores the policy and the guards, uses a fixed last-24h
|
||||
window, and does NOT advance the daily cursor. (The admin 'send now' / preview
|
||||
endpoints now use send_digest_window for an arbitrary window; force is retained
|
||||
for the fixed last-24h case and its tests.)"""
|
||||
import digest_builder
|
||||
|
||||
factory = conn_factory or _conn_factory_from_env()
|
||||
|
||||
Reference in New Issue
Block a user