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
@@ -141,6 +141,35 @@ describe('buildAssistantSnapshot', () => {
|
||||
expect(snap).toContain('Moms (2026-09-12)')
|
||||
})
|
||||
|
||||
it('lists the räkenskapsår newest first with their period_id (#2185)', async () => {
|
||||
const periods = [
|
||||
{ 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 },
|
||||
]
|
||||
const supabase = {
|
||||
from: (table: string) => {
|
||||
const chain = {
|
||||
select: () => chain,
|
||||
eq: () => chain,
|
||||
order: () => chain,
|
||||
limit: () => chain,
|
||||
maybeSingle: async () => ({ data: null, error: null }),
|
||||
then: (onFulfilled: (v: unknown) => unknown) =>
|
||||
Promise.resolve(
|
||||
table === 'fiscal_periods'
|
||||
? { data: periods, error: null }
|
||||
: { count: null, error: null },
|
||||
).then(onFulfilled),
|
||||
}
|
||||
return chain
|
||||
},
|
||||
} as unknown as SupabaseClient
|
||||
const snap = await buildAssistantSnapshot(supabase, 'c1')
|
||||
expect(snap).toContain(
|
||||
'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).',
|
||||
)
|
||||
})
|
||||
|
||||
it('returns an empty string when there is nothing to say', async () => {
|
||||
const snap = await buildAssistantSnapshot(supabaseWith(null), 'c1')
|
||||
expect(snap).toBe('')
|
||||
|
||||
@@ -4,6 +4,7 @@ import { createLogger } from '@/lib/logger'
|
||||
import { EmptyModelAnswerError } from './errors'
|
||||
import { buildLedgerTools } from './ledger-tools'
|
||||
import { buildAssistantSnapshot } from './snapshot'
|
||||
import { FISCAL_YEAR_RULE } from '@/lib/agent/fiscal-years'
|
||||
|
||||
const log = createLogger('agent.ask')
|
||||
|
||||
@@ -89,7 +90,8 @@ Regler:
|
||||
// With tools: the model can and should fetch the real figures itself.
|
||||
const TOOL_RULES = `
|
||||
Du har läsverktyg för bolagets faktiska bokföring: resultatrapport, balansrapport, momsrapport, huvudbok, transaktioner (query_journal), kund- och leverantörsreskontra, lönejournal, kontoplan, fakturor, dokumentinkorg med mera. När användaren frågar om siffror, belopp, poster, kategorier eller en period: ANROPA rätt verktyg och svara med de faktiska siffrorna, inte uppskattningar. Verktygen är skrivskyddade; för att bokföra eller ändra något hänvisar du användaren till rätt sida i appen.
|
||||
"Nuläge"-blocket nedan är bara grunddata (moms, deadlines), inte hela bokföringen: använd verktygen för siffror.`
|
||||
"Nuläge"-blocket nedan är bara grunddata (moms, deadlines, räkenskapsår), inte hela bokföringen: använd verktygen för siffror.
|
||||
${FISCAL_YEAR_RULE}`
|
||||
|
||||
// Without tools (core-only build, or a text-only model): answer from what is
|
||||
// in the prompt and be honest about the rest.
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
resolveEmployeeFacts,
|
||||
type EmployeeVerdict,
|
||||
} from '@/lib/agent/composer/employee-facts'
|
||||
import { loadFiscalYearInventory, renderFiscalYearInventory } from '@/lib/agent/fiscal-years'
|
||||
|
||||
/**
|
||||
* A compact, always-on grounding block for the single-call assistant.
|
||||
@@ -58,6 +59,8 @@ export async function buildAssistantSnapshot(
|
||||
companyId: string,
|
||||
): Promise<string> {
|
||||
const lines: string[] = []
|
||||
// Kicked off with the first round so it costs no extra latency; never rejects.
|
||||
const fiscalYears = loadFiscalYearInventory(supabase, companyId)
|
||||
|
||||
try {
|
||||
const [{ data }, activeEmployees] = await Promise.all([
|
||||
@@ -118,5 +121,10 @@ export async function buildAssistantSnapshot(
|
||||
// best-effort: skip the deadlines line
|
||||
}
|
||||
|
||||
// Which years the ledger holds and how to address them: a company with
|
||||
// several imported years otherwise reads as single-year (#2185).
|
||||
const fiscalYearLine = renderFiscalYearInventory(await fiscalYears)
|
||||
if (fiscalYearLine) lines.push(fiscalYearLine)
|
||||
|
||||
return lines.join('\n')
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user