Files
ten31-database/backend/matrix_intake
Keysat a10889b10b Refine email-proposal review UX (v0.1.0:91)
Three post-smoke refinements to the Matrix email-proposal review:

1. Dash separators (bot): every card/reply is framed with a dash rule top and
   bottom so threads stop bleeding together vertically on mobile.

2. Remove decided threads (bot): on a conclusive approve/dismiss from either
   surface, the bot redacts the card (client.room_redact) so the room clears
   down to only undecided items. Redacting the bot's own card needs no power;
   the web->Matrix path now redacts instead of posting a closure note.

3. Clearer note wording (server v91 + bot): the proposed grid note now names who
   emailed whom -- "{teammate} emailed {investor}" (outbound) / "{sender} emailed
   the team" (inbound) -- instead of an ambiguous "Sent"/"Received". Outbound
   detection also matches our corporate domain (public providers excluded), so a
   teammate's mail from a non-enrolled @ten31.xyz address no longer reads as
   "Received". Going-forward only; no schema change. The card drops its bare
   direction label since the note now carries the relationship.

Tests updated; 30/30 green, render-smoke green.
2026-06-18 11:59:38 -05:00
..

Matrix intake bot

Turns a typed message in a dedicated Matrix room into a proposed fundraising-grid add/edit, gated on in-thread human approval before any write. Runs as its own process (on the Spark), separate from the CRM. Full design + rules: docs/guides/matrix-intake.md.

Run

# 1. Install the one third-party dep (isolated to this component — NOT the CRM runtime)
python3 -m pip install -r requirements.txt          # matrix-nio

# 2. Fill the MATRIX_* and CRM_BOT_* vars in the repo .env (see ../../.env.example),
#    and create a dedicated CRM user for CRM_BOT_USERNAME/PASSWORD (admin → invite user).

# 3. Start the listener
python3 bot.py

It primes the Matrix sync past history (no backlog replay), then listens. Post a message in the intake room; it replies in a thread with the parsed proposal. Reply yes to commit, edit field=value to change a field, or no to discard.

Layout

  • bot.py — entrypoint: connect, prime-then-listen, dispatch (lifts matrix-bridge's plumbing).
  • parse.py — message → structured proposal via local Qwen (spark.pybackend/ingest/llm.py).
  • proposals.py — in-memory pending-proposal store + the yes/edit/no state machine.
  • crm_client.py — login + GET /api/intake/match + write via POST /api/fundraising/log-communication.
  • matrix_io.py — message splitting, thread-root detection, threaded-reply sender.
  • settings.py — Matrix + CRM-API config (named settings, not config, to avoid shadowing ingest/config).

Test (offline)

python3 test_parse.py && python3 test_proposals.py && python3 test_crm_client.py
# endpoint + create→match contract (boots the real server against a temp DB):
cd ../ && python3 test_intake_endpoints.py

Live Matrix behavior needs creds + matrix-nio and can only be smoke-tested on the Spark.