Add Matrix NL-query Q&A surface (W2 step 5)

Read-only natural-language query over the curated nl_query endpoint, answered
in-thread. Two entry points (room-per-purpose model): a dedicated Q&A room
(MATRIX_QUERY_ROOM) where every top-level message is a question, plus the
?/@bot trigger in the intake room as a cross-room convenience. Both routes hit
the same handle_query -> crm_client.nl_query -> POST /api/query/nl; translation
runs on the box's local model, nothing leaves the box, and there is no write
path so no approval gate applies.

Pure logic (trigger parsing, answer rendering) in query.py with offline tests;
async room wiring in bot.py (live-smoke only, per the bot's convention).

Bot-side only, ships on the Spark via git pull + restart. Depends on the
box-side /api/query/nl endpoint, which lands with the v93 s9pk (reminders + W2):
until v93 is installed the Q&A surface 404s, so the bot deploy is staged to
follow that install.
This commit is contained in:
Keysat
2026-06-18 19:46:54 -05:00
parent 6c29c22601
commit 68106d7a5a
10 changed files with 458 additions and 5 deletions
+13
View File
@@ -139,6 +139,19 @@ def decide_email_proposal(proposal_id, decision, note=None):
return data.get("data") or {}
def nl_query(question):
"""Ask the read-only NL-query endpoint (POST /api/query/nl). Translation runs on the box's
LOCAL model — the question never leaves the box and no write is possible. Returns the
endpoint's structured result dict ({intent, slots, rows, summary, ...} or {error, detail});
the server returns that same body on a hit AND on the soft 503 (model down) / 500 (query
fault) status codes, so we hand it straight to the renderer. Any OTHER status — auth (403),
a malformed request (400), an unexpected shape — raises so the caller posts a brief error."""
status, data = _authed("POST", "/api/query/nl", {"question": question, "source": "matrix"})
if status not in (200, 500, 503):
raise RuntimeError(f"nl-query failed ({status}): {data.get('error') or data}")
return data.get("data") or {}
def build_commit_payload(proposal):
"""Pure: map a proposal to the /api/fundraising/log-communication request body.