Add business-card photo intake to the Matrix bot (M3)
The intake bot now accepts a photo of a business card in the intake room and turns it into the same new-investor proposal a typed note would. The only new step is image -> text; everything downstream (parse, fuzzy match, in-thread approval, log-communication write) is reused unchanged. M3 was deferred only because Spark Control had no vision model. That blocker is gone: the daily-driver Qwen is vision-capable under the same model id, and the gateway forwards OpenAI multimodal content untouched, so no gateway/server/s9pk change is needed -- this ships bot-only (git pull + rebuild on the Spark). Transcribe-then-reuse (not vision-straight-to-JSON) is deliberate: the transcription becomes the source text the email-integrity rule checks against, so a mis-read address can't reach the CRM unapproved -- same guarantee as the text path. Card commits tag source="matrix_card" for the audit log. - llm.chat_vision: multimodal /v1/chat/completions, same model, same gateway - spark.transcribe_card: faithful card->text, "" on a non-card (NONE sentinel) - bot.on_image/handle_card: download image, transcribe, hand to handle_intake - crm_client: source provenance overridable via the proposal's _source key - tests: test_spark.py + a provenance case; 41/41 suite green
This commit is contained in:
@@ -58,6 +58,17 @@ def test_subject_blank_when_note_present_else_provenance_label():
|
||||
assert no_note["subject"] == "Intake (Matrix)"
|
||||
|
||||
|
||||
def test_source_defaults_to_intake_and_card_overrides():
|
||||
# Provenance: a typed note tags source="matrix_intake"; a scanned card rides in on
|
||||
# _source="matrix_card" (set by the bot's image handler) so the audit log distinguishes them.
|
||||
typed = crm_client.build_commit_payload(
|
||||
{"intent": "new_investor", "investor_name": "Acme", "note": "x"})
|
||||
assert typed["source"] == "matrix_intake"
|
||||
card = crm_client.build_commit_payload(
|
||||
{"intent": "new_investor", "investor_name": "Acme", "note": "x", "_source": "matrix_card"})
|
||||
assert card["source"] == "matrix_card"
|
||||
|
||||
|
||||
def _with_stub_authed(reply, capture=None):
|
||||
"""Swap crm_client._authed for a canned (status, data); return a restorer."""
|
||||
orig = crm_client._authed
|
||||
|
||||
Reference in New Issue
Block a user