5291806c37
Migrants arrive with vouchers, not bank transactions, so the bank-keyed ledger context is empty for them. This adds the description-keyed twin. - public.ledger_key(text): legibility key on top of the frozen normalize_counterparty_key mirror: strips AP-register prefixes (levfakt, leverantörsfaktura från N, levbet, faktura, kvitto, utgift), the supplier number that follows them, and trailing 1-3 digit runs, never "inköp". Mirrored by lib/parties/ledger-key.ts; the pair is pinned by a shared fixture list in the pg test. - public.get_observed_parties(company, from_date, limit): posted vouchers grouped by ledger_key(description) with occurrences, variants, expense and revenue SEK from the lines, first/last seen, median cadence and the Laplace-smoothed dominant result account. Excludes storno, opening balance, year-end and VAT settlement, and vouchers that carry a bank merchant name (those stay with get_ledger_deep_context). SECURITY INVOKER, so RLS scopes it. Never stored. - lib/parties/classify.ts: the deterministic pre-classifier moved out of the evaluation script so product and evaluation share one implementation (0.965 agreement with the founder labels, party recall 0.99). - lib/parties/observed.ts: RPC wrapper that classifies each row and derives a display rhythm from the cadence. Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
41 lines
1.6 KiB
TypeScript
41 lines
1.6 KiB
TypeScript
import { describe, expect, it } from 'vitest'
|
|
import { classifyKey } from '../classify'
|
|
|
|
// Founder-labelled examples from the 2026-09-02 golden set, one per rule.
|
|
describe('classifyKey', () => {
|
|
it.each([
|
|
['levfakt beijer byggmaterial 097', '4000', 'party'],
|
|
['leverantörsfaktura från 157 råå bryggeri', '4010', 'party'],
|
|
['loopia', '6542', 'party'],
|
|
['taxi stockholm', '5800', 'party'],
|
|
['inköp av varor', '4010', 'category'],
|
|
['bankkostnad', '6570', 'bank'],
|
|
['baspaket bank', '6570', 'bank'],
|
|
['fika', '7690', 'category'],
|
|
['löneutbetalning anställd 15', '7210', 'payroll'],
|
|
['transaktion betalning mot rapport', '7200', 'payroll'],
|
|
['periodisering av verifikation d19', '5010', 'adjustment'],
|
|
['lagerförändring', '4000', 'adjustment'],
|
|
['bolagsverket ändra bolagsordning', '6991', 'authority'],
|
|
['skattekonto', '6992', 'authority'],
|
|
['klarna', '6570', 'intermediary'],
|
|
['diesel', '5611', 'category'],
|
|
['telefon', '6210', 'category'],
|
|
] as const)('%s (%s) -> %s', (key, acct, expected) => {
|
|
expect(classifyKey({ key, acct })).toBe(expected)
|
|
})
|
|
|
|
it('treats a card-platform line with a person suffix as a party', () => {
|
|
expect(classifyKey({ key: 'pleo andreas', acct: '4535' })).toBe('party')
|
|
})
|
|
|
|
it('does not let BAS description examples make Google look generic', () => {
|
|
expect(classifyKey({ key: 'cc google co', acct: '5420' })).toBe('party')
|
|
})
|
|
|
|
it('returns unsure for an empty or all-noise key', () => {
|
|
expect(classifyKey({ key: '' })).toBe('unsure')
|
|
expect(classifyKey({ key: '12 34' })).toBe('unsure')
|
|
})
|
|
})
|