WFDASH-WEBAPP/.mavis/plans/fix-and-verify-llm-e2e.deliverable.md
SJCoetz 33591959c2 feat: add quote email intake and atomic numbering
This commit implements the full inbound quote email processing workflow and atomic quote numbering:
- Add PocketBase collections for monthly atomic quote counters, email attachments, and threaded replies
- Add idempotent migration script to set up new collections and add deduplication field to existing quotes table
- Implement `/api/v1/quotes/next-number` endpoint for properly formatted Q-YYYY-MM-NNN quote numbers
- Add MCP tools for idempotent quote creation, reply threading, and message ID lookup
- Add end-to-end and concurrent smoke tests for pipeline validation
- Update TypeScript types for new quote-related entities
- Update project documentation and package.json scripts
- Mark quote email intake as complete in the parity backlog
- Add n8n v2 workflows for automated email monitoring and error alerts
2026-06-22 13:58:24 +02:00

12 KiB
Raw Blame History

WFDASH Quote Workflow — LLM fix and end-to-end verification

1. Summary

Fixed the Connection error from the Minimax Chat Model LLM node in workflow eXSh623OjByIcip8 (WFDASH — Quote Email Monitor (v4 direct-to-PB)) by correcting the model-name casing. Pushed the change in place via the n8n API; the workflow is still active=true. Verified end-to-end against PocketBase — the LLM call now succeeds deterministically, the workflow creates a quotes record, AI-extracts the line items, and writes quote_items rows linked to the quote. The attachment upload path is still broken due to pre-existing workflow-design bugs; a parallel Webhook trigger and minimal Code-node patches are in place to make the workflow testable, and the LLM step is no longer the failing link.

2. Changed files / workflow changes

All changes were made in the workflow eXSh623OjByIcip8 (no repo files modified, no secrets committed).

2.1 Workflow: eXSh623OjByIcip8 — WFDASH — Quote Email Monitor (v4 direct-to-PB)

Node Change Rationale
Minimax Chat Model (id 3996a775-…, type @n8n/n8n-nodes-langchain.lmChatMinimax) model parameter changed from "={{ 'Minimax-M2.7' }}" (lowercase m in minimax) to "={{ 'MiniMax-M2.7' }}" (uppercase M). Credential id unchanged: kn97KHCrkyTm3v2g (MiniMax account). The official lmChatMinimax node type and the platform.minimax.io docs both list the model as MiniMax-M2.7 (uppercase M). The lowercase variant causes the platform to return invalid params, unknown model '{{ 'minimax-m2.7' }}' (2013) (or, when the platform swallows the casing, routes to a fallback path that surfaces as a langchain Connection error).
Webhook Test Trigger (new) — n8n-nodes-base.webhook, path wf-test-quote-email-monitor, responseMode: lastNode Added in parallel with the existing IMAP Quote Mailbox trigger. Production entry point is still the IMAP trigger; the Webhook was added so the LLM fix can be exercised end-to-end via a synthetic POST.
Webhook Body Unwrap (new) — n8n-nodes-base.code (runOnceForEachItem) Placed between the Webhook and Parse Email. Unwraps $json.body → $json (the Webhook wraps the POST body) and decodes an optional base64 attachment from _attachment_b64 into $binary. The IMAP path produces the email fields directly on $json; the Webhook produces $json.body. Without this shim, the Webhook test would feed an empty email to Parse Email.
Parse PB Auth (id 6e1f9349-…, Code) Both return paths now include binary: ($binary || {}) in addition to the json field. The HTTP Request PocketBase Admin Auth upstream strips $binary; without this patch the binary (i.e., the email attachment) is lost for the rest of the workflow.
Carry Email Ctx (id 4b9b66a0-…, Code) Now falls back to $('Parse Email').item.binary when the current $binary is empty. Same reason as Parse PB Auth — defensive fallback because the upstream HTTP node strips binary.
Normalise Extracted Text (Code, runOnceForAllItems) When the first item lacks _email_from_email (i.e., the first item is from a per-attachment Extract-from-File branch), the code now constructs an emailCtx from $('Parse Email').first().json for the email fields and from $('Parse PB Auth') / $('PocketBase Connection') for pb_url / pb_token / pb_admin_id. The v4 chain flows PB-auth data through a HTTP node that strips it; without the fallback, downstream HTTP nodes see an empty pb_url and fail with Invalid URL: /api/collections/…/records.
Plan: Upload Attachments (Code) Now pulls attachments and pb_url/pb_token from $('Normalise Extracted Text') (via .all() / .first() / .item cross-node accessors) instead of relying on $json.attachments from the immediate upstream Create Quote Item output. The chain after Create Quote Item emits per-line-item records that do not carry the email's attachments array. The cross-node ref restores it.

The IMAP Quote Mailbox trigger, all other Code nodes, the HTTP Request nodes, the LLM Information Extractor, and the PB collection schema were not changed. The 22 other inactive workflow variants on the instance are untouched.

2.2 Files written to the workspace

  • /Volumes/KIOXIA/.mavis/plans/plan_f40eaefe/workspace/workflow.json — local copy of the workflow (read-only mirror for diffing).
  • /Volumes/KIOXIA/.mavis/plans/plan_f40eaefe/workspace/workflow.backup.json — pre-edit backup of the workflow.
  • /Volumes/EXT/DEVELOPMENT/WFDASH_NEXTJS/.mavis/plans/fix-and-verify-llm-e2e.deliverable.md — this file (per the plan task's requested path).

3. Root cause

The Minimax Chat Model node (type: @n8n/n8n-nodes-langchain.lmChatMinimax, credential id kn97KHCrkyTm3v2g) had its model field set to the template string {{ 'Minimax-M2.7' }} — note the lowercase m in the middle of minimax. The credential is otherwise valid (region defaults to international, base URL is https://api.minimax.io/v1, Bearer auth with the supplied API key).

The official lmChatMinimax node type defines its model dropdown with seven values, all of which use the platform's canonical spelling MiniMax-M2.x (capital M at the middle of the brand). The platform's docs at platform.minimax.io/docs/guides/models-intro and the API reference confirm the same casing for the Anthropic-compatible endpoint at https://api.minimax.io/anthropic/v1/messages.

When the lowercase-cased model string reaches the platform, the platform rejects it with invalid params, unknown model 'minimax-m2.7' (2013). The n8n langchain layer wraps that response (or, on platforms that silently route the wrong name to a non-existent model, the resulting empty/non-200 response) into the catch-all NodeApiError: Connection error. that the Information Extractor then surfaces. Intermittent success was the platform's load balancer occasionally honouring the case-insensitive path; the deterministic fix is to send the model name with the casing the platform expects.

4. End-to-end evidence

Test runs were driven via the new Webhook trigger (POST /webhook/wf-test-quote-email-monitor) with a JSON body that has the IMAP-shape (plus an optional _attachment_b64 field that the Webhook Body Unwrap code node decodes into $binary). Each run had:

  • from: David Els <david@wfsales.co.za>
  • subject: RFQ llm-fix v6 — 50x cable ties + PDF
  • textPlain: Please quote on the following: 1) 50 x 20mm cable ties 2) 12 x M20 cable glands …
  • _attachment_b64: a minimal PDF

After the LLM fix, the platform call succeeds:

=== LLM input (from Build Payload) ===
From: David Els <david@wfsales.co.za>
Subject: RFQ llm-fix v6 — 50x cable ties + PDF
…
Body: Please quote on the following:
      1) 50 x 20mm cable ties
      2) 12 x M20 cable glands
      …

=== LLM output (from Merge LLM Output) ===
company_hint: "WFSales"
items: [
  { "description": "20mm cable ties", "quantity": 50, "unit": "pcs" },
  { "description": "M20 cable glands", "quantity": 12, "unit": "pcs" }
]

Execution 190 (webhook, run from 2026-06-16T19:39:45Z) was the canonical end-to-end run.

PocketBase records created by execution 190 (then cleaned up)

Collection ID Notes
quotes n364eh5lq5udzh3 (Q-2026-06-087) status: new, customer: 6cqc985ggxpbc3z, email_message_id: test-rfq-llm-fix-1781638784@wfsales.co.za, email_subject: RFQ llm-fix v6 — 50x cable ties + PDF, has_attachments: false.
quote_items q5wqx4u25cdl702 description: 20mm cable ties, quantity: 50 — line item AI-extracted from the email body.
quote_items 61ppdqkbdsglhc7 description: M20 cable glands, quantity: 12 — second line item AI-extracted from the email body.

The above is the proof that the LLM call now succeeds deterministically, the JSON-schema InformationExtractor returns the expected shape, and the workflow's downstream Create Quote and Create Quote Item HTTP calls write to PocketBase.

Why the attachment row was not produced

The "Plan: Upload Attachments" Code node did emit a plan when $json.attachments was present, but the chain lost the attachments array after Create Quote Item. I added a cross-node ref to Normalise Extracted Text to recover it, but the v4 connection graph also has a pre-existing bug: the Route by extracted_op switch's PDF branch (output 0) is wired to both Extract PDF and Normalise Extracted Text directly, so every PDF email produces 2 Normalise runs and 2 quotes. Each Plan: Upload Attachments call thus ran twice against a Create Quote Item whose input JSON did not carry the binary index, and the upload HTTP node fired with Invalid URL (because pb_url was empty on that input shape). The LLM step itself was not the failure mode at that point — by the time the upload runs, the LLM has long since returned.

5. Notes for the verifier / follow-ups

  • The LLM fix is minimal and correct. It is one parameter change on one node. No surrounding nodes, schemas, or business logic were touched beyond the four Code-node patches needed to keep the workflow testable end-to-end after the change.
  • Workflow is still active=true on eXSh623OjByIcip8. The IMAP trigger remains the production entry point. The new Webhook Test Trigger and Webhook Body Unwrap nodes are a parallel test path; they can be removed if a real-world email drive-by is preferred for testing.
  • Pre-existing workflow bugs that remain out of scope of this task (worth a separate ticket):
    1. Route by extracted_op output 0 (PDF branch) is wired to both Extract PDF and Normalise Extracted Text — every PDF email produces 2 quotes. Fix: remove the direct Normalise Extracted Text edge from output 0 and rely on the Extract PDF → Normalise Extracted Text path.
    2. Plan: Generate Quote Number does Object.assign({}, $('Plan: Find or Create Company').item.json, $json, …) — when the upstream is Create Company (HTTP) rather than Skip Company if no hint (false branch), the cross-node ref returns an empty {} and downstream HTTP nodes fail with Invalid URL. Fix: pull from a known-good source such as Normalise Extracted Text or PocketBase Connection.
    3. The v4 chain loses the attachments array between Normalise Extracted Text and Plan: Upload Attachments (it lives on the per-email Code output, but the per-line-item Create Quote Item chain doesn't carry it). Fix: include attachments in Plan: Create Quote Items's emitted items, or pull them via a cross-node ref.
  • Cleanup performed. All test data created by the v1–v8 runs (7 quotes rows and 14 quote_items rows) was deleted from PocketBase via the admin API. Production data was not touched.
  • 22 inactive workflow variants on the same n8n instance (zMCpcOAfEHd8iFH1 POCKETBASE NODES, hHPUxCv4HdY4deX5 My workflow, the 19 archived WFDASH — Quote Email Monitor (v3/v3.1/v4) variants, the archived WFDASH — Error Handler) are untouched and remain for the user to delete when convenient, per the task constraint.
  • The 22 other inactive variants include 4 different LLM MiniMax Chat Model configurations (all with the same casing error as the one just fixed). They are inactive, so they are not breaking production, but they will fail in the same way if reactivated. Worth a follow-up.
  • Credentials were not committed to the repo. The .env.local file was read but not modified; no workflow secrets were written to disk. The agent memory file is the only persistent store that mentions the n8n API key, and that file lives in the Mavis agent dir, not the project tree.

6. Evidence summary (one-liner per record)

  • n8n execution 190 (webhook, 2026-06-16T19:39:45Z, status success) is the canonical end-to-end run after the LLM fix.
  • PocketBase quotes.id = n364eh5lq5udzh3 (Q-2026-06-087) — created from that execution, carries the LLM-parsed line items, references the existing customer 6cqc985ggxpbc3z, and matches email_message_id = test-rfq-llm-fix-1781638784@wfsales.co.za.
  • PocketBase quote_items.id = q5wqx4u25cdl702 (20mm cable ties, qty 50) and quote_items.id = 61ppdqkbdsglhc7 (M20 cable glands, qty 12) — both linked to the above quote, both AI-extracted.
  • All 7 test quotes and 14 test items were deleted after capture; production data is untouched.