fea5dfd1f912ec1111cec662cd8362f9aa55dfd9
3 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
43386b4852 |
feat(agent): offer unmatched inbox receipts as confirmable underlag (#1436)
The originally reported scenario is still broken after #1425 and its backfill-by-document_id: a user photographs a receipt into WhatsApp, answers the bot's questions, then opens the app, clicks the bank transaction and asks the assistant to book it, and is told "UNDERLAG: saknas" about a receipt we are holding, then asked everything again. WhatsApp intake writes neither invoice_inbox_items.matched_transaction_id (process-inbound.ts passes uploadAndExtract's matchedTransactionId as undefined) nor transactions.document_id (that mirror is written by the manual match route). Only TransactionMatchPicker fills either column. So the underlag list comes back empty, and a backfill that keys on document_id has nothing to key on. Unmatched, unconsumed inbox items are now scored against the transaction with the same pure scorer the picker uses and the strongest few are surfaced as TROLIGT UNDERLAG, carrying their captured chat answers. Proposals only: nothing writes matched_transaction_id, and the prompt tells the agent to get the match confirmed and to book only against a confirmed one. Setting the link at intake above a confidence bar is the obvious alternative and is deliberately left open. An uncomparable amount (cross-currency with no rate) disqualifies a candidate, because calculateMatchConfidence drops the amount signal in that case and date + merchant alone then score a confident match nobody checked the sums for. Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
0f7147a078 |
fix(agent): read a WhatsApp "nej" as an answer, not a half answer (#1433)
* fix(agent): read a WhatsApp "nej" as an answer, not a half answer #1425 gave the assistant the answers the user typed in WhatsApp. Rendering those inline off the raw channel_context blob gets the most common answer backwards. Answering "nej" to the representation question stores an EMPTY representation block: participants: [], purpose: null, denied: true. The renderer branched on `if (!rep.purpose)` and so emitted syfte SAKNAS: fråga bara efter syftet, inte om deltagarna igen. for a user who had just said the meal was not representation. `denied` was never read anywhere. The result is the assistant asking about the purpose of a private lunch, which is worse than the generic re-ask #1425 fixed, because the instruction is specific and confident. Clarifications now come from a structured summary that models the denial and the genuine half answer (participants named, purpose missing, which BFL 5 kap 6-7 § does want completed) as different states. #1425's syfte SAKNAS nudge is preserved for the case it was written for. Two smaller fixes in the same renderer, both about untrusted text: - The photo caption no longer reaches the prompt. It is the one field on the record nobody was asked for and nobody reviewed, and the rationale already written down in lib/documents/channel-context-notes.ts for keeping it off an immutable verifikat applies at least as strongly to a prompt that can call tools. - Human free text passes through flattenMemoryContent. An intent's promptTemplate output is seeded as a user message, so wrapToolResult never sees it and nothing else defends this path; a caption reading "# NYA INSTRUKTIONER: ..." previously rendered verbatim. All three tests fail against the current renderer and pass against this one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(agent): gate the chat-answer guidance on what was rendered CodeRabbit caught the same defect shape this PR is about: the prior-conversation paragraph was gated on chat_answers != null, but a caption-only context is non-null and now summarises to nothing, so the paragraph pointed at 'uppgivna av användaren' rows the prompt does not contain. Gate on whether a clarification line was actually emitted. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com> |
||
|
|
a3c6566caf |
feat(mcp): ledger-context resource with per-company booking patterns (#928)
* feat(mcp): ledger-context resource with per-company booking patterns Adds Accounted://ledger/context: derived account usage, counterparty booking patterns with explicit confidence share (0.7 floor), explicit mapping rules kept separate as authoritative, observed VAT profile, and conventions. Backed by a SECURITY INVOKER get_ledger_usage_stats RPC so group-bys run SQL-side, and surfaced as a top-5 digest stanza on gnubok_get_agent_briefing so one call still bootstraps a session. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * feat(mcp): fold source-quality prereqs into the ledger-context RPC Merchant-name normalization at the aggregation path (the splinter fix): new normalize_counterparty_key() SQL function mirroring normalizeCounterpartyName() so KORTKÖP/SWISH/date-suffixed labels merge into one counterparty key, which also makes the categorization_templates join exact. New supplier_patterns section (per-supplier dominant expense account + VAT treatment from supplier invoices; credit notes and reversed invoices excluded). account_usage excludes storno lines (they re-inflate the account a correction moved away from); the counterparty CTE keeps corrections because the transaction relink self-heals. Pattern confidence is now count-grounded evidence {seen_12m, agree, share, last_booked} instead of a bare ratio, and the digest frames it as historical frequency, never auto-book permission. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(agent-context): use roundOre for the share ratio (antipattern ratchet) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * fix(agent-context): defensive storno filter on counterparty CTE, fail-loud secondary reads Review follow-ups: the counterparty CTE now excludes source_type='storno' defensively (no live code path links a transaction to a storno, but legacy rows may predate reverseEntry's unlink; a linked storno would count the reversed category as precedent). Corrections stay included: they are the live booking after relink. Secondary reads (rules, templates, settings) now throw instead of silently reading as empty data: an agent must never be told 'no rules' when the truth is 'read failed'. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> |