Files
accounted/lib/agent/intents/bokslut-step.ts
T
Jakob WennbergandClaude Sonnet 5 ec27228a8e style: remove em/en dashes repo-wide, add CLAUDE.md rule against them (#890)
Em dashes (—) and en dashes (–) had spread across comments, docs, tests,
and a few UI strings, reading as AI-generated boilerplate rather than
house style. Replaced each with punctuation matching its context: colon
for explanatory clauses, comma for asides, plain hyphen for numeric/legal
ranges (e.g. "21-23§"), "to"/"till" for date ranges, parentheses for
paired-dash asides. messages/en.json and messages/sv.json were fixed by
hand together to keep sv/en in sync.

Left untouched where the dash is the functional subject rather than
decorative punctuation: date-range-parser.ts's separator regex,
charset-repair.ts's CP1252 byte-mapping table (and its test), the SIE
encoding mojibake docs, generic-csv.ts's minus-sign normalizer, the
agent system-prompt files that already instruct against em dashes, and
a golden iXBRL test fixture compared byte-for-byte.

Also fixes two bugs surfaced along the way: an off-by-one in
ApiKeysPanel's scope-label split (a leftover from an earlier partial
pass), and a charset-repair test that had lost the literal en-dash it
exists to verify.

Regenerated the agent atom seed migration (skills:generate) since 27
SKILL.md files changed. Added a CLAUDE.md rule against em/en dashes,
with an explicit carve-out for the functional-dash cases above.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-07-04 15:58:06 +02:00

131 lines
5.2 KiB
TypeScript

import { defineAgentIntent } from './types'
import { OPUS_MODEL } from '@/lib/agent/composer/client'
import { renderAgentGroundRules } from './shared-rules'
// bokslut.step: "Fråga [namn]" inside the year-end (bokslut) wizard.
//
// Bokslut is where users feel the most stress: many decisions (periodisering,
// avskrivningar, dispositioner, tax provision), each with K2/K3 implications,
// and irreversible once locked. The agent explains the current step, the
// state, and what's recommended given the company's signals.
//
// Declarative atoms: year-end-closing + financial-reporting + tax-planning +
// asset-accounting. Heavy load by design; this is when the user wants the
// full reasoning depth.
//
// Opus per plan §8 V1 #6: multi-step reasoning across rules + balances.
interface BokslutStepArgs {
// The bokslut wizard's step id, e.g. 'accruals', 'depreciation',
// 'dispositioner', 'tax-provision', 'arsredovisning'. Empty = overview.
step_id?: string | null
fiscal_year_end?: string | null
}
interface CapturedBokslutStep {
step_id: string | null
fiscal_period: {
id: string | null
period_start: string | null
period_end: string | null
status: string | null
} | null
entity_type: string | null
}
export const bokslutStep = defineAgentIntent<BokslutStepArgs, CapturedBokslutStep>({
id: 'bokslut.step',
buttonLabel: 'Fråga om detta steg',
sheetTitle: 'Hjälp med bokslut',
atoms: {
mode: 'declarative',
horizontal: [
'swedish-year-end-closing',
'swedish-financial-reporting',
'swedish-tax-planning',
'swedish-asset-accounting',
'swedish-accounting-compliance',
],
includeCompanyVertical: true,
includeCompanyModifiers: true,
},
tools: [
'gnubok_year_end_readiness',
'gnubok_propose_accruals',
'gnubok_propose_annual_depreciation',
'gnubok_propose_dispositioner',
'gnubok_preview_arsredovisning',
'gnubok_preview_ef_declaration',
'gnubok_get_trial_balance',
'gnubok_get_balance_sheet',
'gnubok_get_income_statement',
'gnubok_load_skill',
'gnubok_search_tools',
'gnubok_remember_fact',
'gnubok_forget_fact',
],
model: OPUS_MODEL,
capture: async ({ step_id, fiscal_year_end }, { supabase, companyId }) => {
// Find the latest non-locked fiscal period (the one being closed): or
// the one matching fiscal_year_end if supplied.
let query = supabase
.from('fiscal_periods')
.select('id, period_start, period_end, status')
.eq('company_id', companyId)
if (fiscal_year_end) query = query.eq('period_end', fiscal_year_end)
query = query.order('period_end', { ascending: false }).limit(1)
const { data: period } = await query.maybeSingle()
const { data: company } = await supabase
.from('companies')
.select('entity_type')
.eq('id', companyId)
.maybeSingle()
return {
step_id: step_id ?? null,
fiscal_period: period
? {
id: (period as { id: string }).id,
period_start: ((period as { period_start?: string | null }).period_start) ?? null,
period_end: ((period as { period_end?: string | null }).period_end) ?? null,
status: ((period as { status?: string | null }).status) ?? null,
}
: null,
entity_type: ((company as { entity_type?: string | null } | null)?.entity_type) ?? null,
}
},
promptTemplate: ({ captured, profileSummary }) => {
const lines: string[] = []
if (profileSummary) lines.push(`Företagets profil: ${profileSummary}`, '')
lines.push('Användaren är i bokslutsguiden och behöver hjälp.')
if (captured.step_id) lines.push(`Aktivt steg: ${captured.step_id}`)
if (captured.fiscal_period) {
lines.push(
`Räkenskapsår: ${captured.fiscal_period.period_start ?? '?'} → ${captured.fiscal_period.period_end ?? '?'} (status: ${captured.fiscal_period.status ?? '?'})`,
)
}
if (captured.entity_type) lines.push(`Företagsform: ${captured.entity_type}`)
lines.push('')
lines.push(renderAgentGroundRules())
lines.push('')
lines.push('Arbetssätt: hjälp användaren genom STEGET de står i:')
lines.push('1. Kör gnubok_year_end_readiness för att se vad som saknas.')
lines.push('2. Om steget är "accruals": använd gnubok_propose_accruals för periodiseringar och förklara varje förslag (när påverkar det BR/RR, varför detta belopp?).')
lines.push('3. Om steget är "depreciation": gnubok_propose_annual_depreciation. Förklara planenlig vs. överavskrivning, K2 schablonregler vs. K3 individual.')
lines.push('4. Om steget är "dispositioner": gnubok_propose_dispositioner. Periodiseringsfond, koncernbidrag (om holding), årets skatt.')
lines.push('5. Om steget är "arsredovisning": preview via gnubok_preview_arsredovisning, granska noter, förvaltningsberättelse, underskrifter, deadline.')
lines.push('6. Om EF: använd gnubok_preview_ef_declaration. Räntefördelning, expansionsfond, NE-bilaga.')
lines.push('')
lines.push('Var BFL-rigorös: bokslut är irreversibelt när det låses. Peka på risker innan du föreslår staging av en operation.')
lines.push('Svara på svenska. Ditt första svar är det första användaren ser: gå rakt på sak.')
return lines.join('\n')
},
})