fix(agent): the assistant reaches earlier räkenskapsår: period resolved from dates, years listed in the grounding (#2436)
Part 3 of #2185. A user with several imported years concluded the assistant "only reads the period I am standing in". Nothing restricted it: the report tools defaulted to the most recent fiscal period when no period_id was given and then rejected any from_date/to_date/as_of_date outside it, and neither the snapshot nor the chat identity block told the model which years existed or how to address them. - extensions/general/mcp-server/server.ts: resolveReportPeriod takes a date hint; without period_id, a date in the call resolves the fiscal period that contains it (findFiscalPeriodContaining, company-scoped), and a date no period covers fails with the company's span instead of the latest year's bounds. Income statement (from_date or to_date), balance sheet (as_of_date) and dimension P&L (to_date) use it. The range guard is unchanged. No schema change: tools/list sits at its token ceiling. - lib/agent/fiscal-years.ts: one query and one line, "Räkenskapsår (senaste först): ... period_id=<uuid> (senaste|avslutat)", plus the rule on addressing an earlier year, shared by the single-call snapshot and the streaming chat's always-on identity block. - lib/agent/intents/shared-rules.ts and TOOL_RULES: pass that year's period_id, one call per year, and say which räkenskapsår the answer covers. Claude-Session: https://claude.ai/code/session_0179bdetHyofL6ATfQxB5wP5 Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Jakob Wennberg
Claude Fable 5.1
parent
20925f6c65
commit
500806f001
@@ -0,0 +1,67 @@
|
||||
import { describe, it, expect } from 'vitest'
|
||||
import type { SupabaseClient } from '@supabase/supabase-js'
|
||||
import {
|
||||
FISCAL_YEAR_INVENTORY_CAP,
|
||||
loadFiscalYearInventory,
|
||||
renderFiscalYearInventory,
|
||||
} from '../fiscal-years'
|
||||
|
||||
function supabaseReturning(result: { data?: unknown; error?: unknown } | Error): {
|
||||
supabase: SupabaseClient
|
||||
calls: { method: string; args: unknown[] }[]
|
||||
} {
|
||||
const calls: { method: string; args: unknown[] }[] = []
|
||||
const chain: Record<string, unknown> = {}
|
||||
for (const method of ['select', 'eq', 'order', 'limit']) {
|
||||
chain[method] = (...args: unknown[]) => {
|
||||
calls.push({ method, args })
|
||||
return chain
|
||||
}
|
||||
}
|
||||
chain.then = (onFulfilled: (v: unknown) => unknown, onRejected?: (e: unknown) => unknown) =>
|
||||
(result instanceof Error ? Promise.reject(result) : Promise.resolve(result)).then(
|
||||
onFulfilled,
|
||||
onRejected,
|
||||
)
|
||||
return { supabase: { from: () => chain } as unknown as SupabaseClient, calls }
|
||||
}
|
||||
|
||||
describe('loadFiscalYearInventory', () => {
|
||||
it('reads the company periods newest first, capped', async () => {
|
||||
const rows = [
|
||||
{ id: 'a', name: '2026', period_start: '2026-01-01', period_end: '2026-12-31', is_closed: false },
|
||||
]
|
||||
const { supabase, calls } = supabaseReturning({ data: rows, error: null })
|
||||
expect(await loadFiscalYearInventory(supabase, 'c1')).toEqual(rows)
|
||||
expect(calls).toContainEqual({ method: 'eq', args: ['company_id', 'c1'] })
|
||||
expect(calls).toContainEqual({ method: 'order', args: ['period_start', { ascending: false }] })
|
||||
expect(calls).toContainEqual({ method: 'limit', args: [FISCAL_YEAR_INVENTORY_CAP] })
|
||||
})
|
||||
|
||||
it('is best-effort: a failing query yields an empty list', async () => {
|
||||
const { supabase } = supabaseReturning(new Error('db down'))
|
||||
expect(await loadFiscalYearInventory(supabase, 'c1')).toEqual([])
|
||||
const nulled = supabaseReturning({ data: null, error: { message: 'x' } })
|
||||
expect(await loadFiscalYearInventory(nulled.supabase, 'c1')).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('renderFiscalYearInventory', () => {
|
||||
it('marks the newest and the closed years and carries the period_id', () => {
|
||||
expect(
|
||||
renderFiscalYearInventory([
|
||||
{ id: 'fp-2026', name: '2026', period_start: '2026-01-01', period_end: '2026-12-31', is_closed: false },
|
||||
{ id: 'fp-2025', name: '2025', period_start: '2025-01-01', period_end: '2025-12-31', is_closed: true },
|
||||
{ id: 'fp-2024', name: '2024', period_start: '2024-05-01', period_end: '2025-04-30', is_closed: false },
|
||||
]),
|
||||
).toBe(
|
||||
'Räkenskapsår (senaste först): 2026-01-01..2026-12-31 period_id=fp-2026 (senaste); ' +
|
||||
'2025-01-01..2025-12-31 period_id=fp-2025 (avslutat); ' +
|
||||
'2024-05-01..2025-04-30 period_id=fp-2024.',
|
||||
)
|
||||
})
|
||||
|
||||
it('is null for a company without periods, so the caller drops the line', () => {
|
||||
expect(renderFiscalYearInventory([])).toBeNull()
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user