Add Matrix intake bot (M1+M2): typed message → approved fundraising-grid write
New backend/matrix_intake/ runs as its own process (matrix-nio isolated from the stdlib CRM): local-Qwen parse via Spark Control → in-thread human approval (yes/edit/no) → write through the CRM's own log-communication endpoint, tagged source=matrix_intake. Adds read-only GET /api/intake/match (returns grid row id, no-duplicate contract); threads provenance through handle_log_fundraising_communication. Reviewer-passed: pop-before-commit closes a double-approve race; edit-grammar fix. Text-only v1; business-card photo (M3) deferred (no Spark vision model). 26/26 tests green; live Matrix smoke pending deploy.
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
"""Config for the Matrix intake bot — Matrix creds + the dedicated intake room.
|
||||
|
||||
Spark settings (SPARK_CONTROL_URL, CHAT_MODEL, …) are NOT read here; they come from the
|
||||
reused ingest client (see spark.py), which loads the same repo .env. This module only owns
|
||||
the Matrix connection and the CRM API target for the write-back leg (M2).
|
||||
"""
|
||||
import os
|
||||
|
||||
REPO_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
|
||||
def load_env(path=None):
|
||||
"""Populate os.environ from the repo .env (setdefault — never clobber a real env var)."""
|
||||
path = path or os.path.join(REPO_ROOT, ".env")
|
||||
if not os.path.exists(path):
|
||||
return
|
||||
with open(path, "r", encoding="utf-8") as fh:
|
||||
for line in fh:
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
k, v = line.split("=", 1)
|
||||
os.environ.setdefault(k.strip(), v.strip())
|
||||
|
||||
|
||||
load_env()
|
||||
|
||||
|
||||
def _require(name):
|
||||
val = os.environ.get(name, "").strip()
|
||||
if not val:
|
||||
raise RuntimeError(f"matrix_intake: required env var {name} is not set (see .env.example)")
|
||||
return val
|
||||
|
||||
|
||||
# Matrix connection (resolved lazily so importing this module for tests never requires creds).
|
||||
def matrix_settings():
|
||||
return {
|
||||
"homeserver": _require("MATRIX_HOMESERVER"),
|
||||
"user_id": _require("MATRIX_USER"),
|
||||
"token": _require("MATRIX_ACCESS_TOKEN"),
|
||||
"device_id": os.environ.get("MATRIX_DEVICE_ID", "ten31-intake-bot"),
|
||||
"intake_room": _require("MATRIX_INTAKE_ROOM"),
|
||||
}
|
||||
|
||||
|
||||
# CRM API target for the write-back leg (M2). The CRM has no service-key auth path — auth is
|
||||
# Bearer-JWT via /api/auth/login — so the bot logs in as a DEDICATED service user (a normal
|
||||
# CRM user, created by an admin) and reuses the existing auth. Creds live in .env, never code.
|
||||
def crm_settings():
|
||||
return {
|
||||
"base": os.environ.get("CRM_API_BASE", "http://127.0.0.1:8080").rstrip("/"),
|
||||
"username": os.environ.get("CRM_BOT_USERNAME", "").strip(),
|
||||
"password": os.environ.get("CRM_BOT_PASSWORD", ""),
|
||||
"verify_tls": os.environ.get("CRM_API_VERIFY_TLS", "true").lower() in ("1", "true", "yes", "on"),
|
||||
}
|
||||
Reference in New Issue
Block a user