feat(mcp): P2 hygiene — honest category suggestions, skill-reference lint, cadence copy (#882)
* feat(mcp): counterparty-tied category suggestions + no_signal (P2-1)
suggest_categories padded every transaction with a company-wide
category-frequency fallback at <=0.5 confidence — an identical four-way
spread on 20+/24 items that agents correctly reported as pure noise
(agent.feedback). Real signal came from memory atoms and query_journal.
- History is now counterparty-keyed: buildMerchantHistory groups past
categorized transactions by normalized merchant; the engine only
surfaces history for THIS transaction's merchant, with provenance
('Bokförd N gånger tidigare för denna motpart') and occurrence-scaled
confidence (0.56 at 1x, capped 0.85). No global padding — an empty
list is the honest answer.
- The MCP tool returns no_signal_transaction_ids for transactions where
NO source matched, steering agents to investigate (query_journal)
instead of pattern-matching on unrelated rows.
- Both callers (REST suggest-categories route + MCP tool) share the new
helpers, so web UI and agents improve together.
Part of dev_docs/mcp_optimization_plan.md (P2-1).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(skills): dangling-reference validation in skills:check + fix 10 dangling links (P2-2)
skills:generate/check now fail when an atom SKILL.md links a
references/*.md that does not exist on disk — a dangling pointer ships
a 404 to every agent that follows it (the weekly-booking-check
incident, agent.feedback).
The validator immediately caught 10 live dangling links in 4 atoms,
three distinct flavors:
- filename typo: swedish-asset-accounting/references/depreciaton.md
renamed to depreciation.md (the link was right, the file misspelled)
- link mismatch: swedish-e-invoicing linked market-providers-pricing.md;
the file is market-provider-pricing.md (link fixed)
- unauthored plans: single-shareholder-ab-fmb TODOs and reklambyra's
'planerad utbyggnad' section used resolvable references/ paths for
files that were never written — rephrased as plans without paths
Seed migration regenerated (4 atoms bumped, renamed reference child).
Part of dev_docs/mcp_optimization_plan.md (P2-2).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(events): align agent-feedback review cadence copy (P2-4)
gnubok_feedback replies 'we aggregate signal weekly'; the event-log
handler comment said quarterly. One of them was lying — weekly wins
(the mcp_optimization_plan triage is the living example).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
b27f6cdb04
commit
678f2ccffd
@@ -1,6 +1,6 @@
|
||||
import { createClient } from '@/lib/supabase/server'
|
||||
import { NextResponse } from 'next/server'
|
||||
import { getSuggestedCategories, getSuggestedTemplates, type SuggestedCategory, type SuggestedTemplate } from '@/lib/transactions/category-suggestions'
|
||||
import { getSuggestedCategories, getSuggestedTemplates, buildMerchantHistory, merchantHistoryFor, type SuggestedCategory, type SuggestedTemplate } from '@/lib/transactions/category-suggestions'
|
||||
import { findCounterpartyTemplatesBatch, formatCounterpartyName, toCounterpartyTemplateId } from '@/lib/bookkeeping/counterparty-templates'
|
||||
import { requireCompanyId } from '@/lib/company/context'
|
||||
import type { Transaction, EntityType } from '@/types'
|
||||
@@ -48,22 +48,19 @@ export async function POST(request: Request) {
|
||||
.eq('is_active', true)
|
||||
.order('priority', { ascending: false })
|
||||
|
||||
// Build category history from user's past categorized transactions
|
||||
// Counterparty-keyed history from past categorized transactions — the
|
||||
// suggestion engine only surfaces history tied to the SAME merchant
|
||||
// (global frequency padding produced identical low-confidence spreads).
|
||||
const { data: historicalTxns } = await supabase
|
||||
.from('transactions')
|
||||
.select('category')
|
||||
.select('category, merchant_name')
|
||||
.eq('company_id', companyId)
|
||||
.not('is_business', 'is', null)
|
||||
.neq('category', 'uncategorized')
|
||||
.neq('category', 'private')
|
||||
.limit(200)
|
||||
|
||||
const categoryHistory: Record<string, number> = {}
|
||||
if (historicalTxns) {
|
||||
for (const tx of historicalTxns) {
|
||||
categoryHistory[tx.category] = (categoryHistory[tx.category] || 0) + 1
|
||||
}
|
||||
}
|
||||
const merchantHistory = buildMerchantHistory(historicalTxns ?? [])
|
||||
|
||||
// Fetch entity type for template matching
|
||||
const { data: settings } = await supabase
|
||||
@@ -84,7 +81,7 @@ export async function POST(request: Request) {
|
||||
suggestions[tx.id] = getSuggestedCategories(
|
||||
tx as Transaction,
|
||||
mappingRules || [],
|
||||
categoryHistory
|
||||
merchantHistoryFor(merchantHistory, (tx as Transaction).merchant_name)
|
||||
)
|
||||
template_suggestions[tx.id] = await getSuggestedTemplates(tx as Transaction, entityType, mappingRules || undefined)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user