Contracts for 14 default categories and the category suggestion (UX-3, UX-8)

- contracts/schema/migrations/0002_categories_v2.sql: server migration 0002 rebuilds categories with the
  14-key default_key CHECK, inserts housing, insurance, children and fees with server_versions taken from
  the global sequence, sets schema_version 2. server-v1.sql unchanged.
- contracts/category-keywords.csv: the keyword table of the suggestion (148 entries, from docs/ux).
- contracts/test-vectors/suggest_category.json: 65 Kotlin-only cases of CS-4 to CS-6.
- contracts/examples/E-1-health: schema_version 2.
- tools/contracts-check.py: reference suggestCategory, keyword-table checks, 0002 load checks on a fresh
  and an in-use database, plain icon/color check, new icon and colour tokens.
- android/domain: suggestCategory (CS-4 to CS-6, code points, platform NFC and categories through
  UnicodeText), vector runner for suggest_category, eight vector files.
- server/crates/kvitt-domain/tests/vectors.rs: suggest_category is Kotlin-only.

The OpenAPI DefaultCategoryKey extension and the two new category examples are held back until the
server's wire types know the new keys (M12b).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TQd3pEP9UeN2uifbHknQ7B
This commit is contained in:
2026-10-18 08:10:57 +02:00
parent 86e08a12cf
commit aa765bbb48
12 changed files with 8295 additions and 13 deletions
+149
View File
@@ -0,0 +1,149 @@
keyword,match,default_key,source
barn,prefix,children,data
blöjor,token,children,curated
fritids,token,children,curated
förskol,prefix,children,data
leksak,prefix,children,curated
napp,prefix,children,data
resevagn,token,children,data
bio,token,entertainment,data
entre,token,entertainment,data
entré,token,entertainment,data
filmstaden,token,entertainment,curated
karaoke,token,entertainment,data
konsert,prefix,entertainment,data
museet,token,entertainment,curated
museum,token,entertainment,data
netflix,token,entertainment,curated
padel,token,entertainment,curated
spotify,token,entertainment,data
teater,prefix,entertainment,curated
bankavgift,token,fees,curated
kvarskatt,token,fees,curated
ränteåterbäring,token,fees,data
skatt,token,fees,curated
skatteverket,token,fees,curated
alkohol,token,groceries,data
bondens marknad,phrase,groceries,data
bröd,token,groceries,data
bubbel,token,groceries,data
city gross,phrase,groceries,curated
coop,token,groceries,data
frukt,token,groceries,curated
grönsaker,token,groceries,data
handling,prefix,groceries,data
hemköp,token,groceries,data
ica,token,groceries,data
julmat,token,groceries,data
lidl,token,groceries,curated
livs,token,groceries,data
livsmedel,prefix,groceries,curated
mat,token,groceries,data
mjölk,token,groceries,data
netto,token,groceries,data
ost,token,groceries,data
snacks,token,groceries,data
storhandling,token,groceries,data
systembolag,prefix,groceries,data
systemet,token,groceries,data
willys,token,groceries,data
apotek,prefix,health,data
kry,token,health,curated
läkare,token,health,curated
medicin,prefix,health,curated
sjukhus,token,health,curated
tandläkare,token,health,curated
tandvård,token,health,curated
vårdcentral,token,health,curated
bauhaus,token,home,data
byggmax,token,home,curated
clas ohlson,phrase,home,data
elgiganten,token,home,curated
färg,token,home,data
garderob,prefix,home,data
gardin,prefix,home,data
hylla,token,home,data
ikea,token,home,data
jysk,token,home,curated
lamp,prefix,home,data
möbler,token,home,curated
ohlson,token,home,data
rusta,token,home,data
amortering,prefix,housing,curated
avgift,token,housing,data
bolån,token,housing,curated
brf,token,housing,data
hyra,token,housing,data
lgh,token,housing,data
ränta,token,housing,data
barnförsäkring,token,insurance,curated
bilförsäkring,token,insurance,curated
folksam,token,insurance,curated
försäkring,prefix,insurance,data
hemförsäkring,token,insurance,data
länsförsäkringar,token,insurance,curated
trygg hansa,phrase,insurance,curated
bagel,prefix,restaurants,data
bageri,prefix,restaurants,data
bar,token,restaurants,curated
burger king,phrase,restaurants,curated
cafe,token,restaurants,curated
café,token,restaurants,data
dumplings,token,restaurants,data
fika,prefix,restaurants,data
foodora,token,restaurants,curated
glass,token,restaurants,data
hamburgare,token,restaurants,curated
kaffe,token,restaurants,data
kebab,token,restaurants,data
lunch,token,restaurants,data
max,token,restaurants,data
mcdonalds,token,restaurants,curated
middag,token,restaurants,data
pizz,prefix,restaurants,data
ramen,token,restaurants,data
restaurang,prefix,restaurants,curated
seml,prefix,restaurants,data
streetfood,token,restaurants,data
sushi,token,restaurants,data
thai,token,restaurants,data
uber eats,phrase,restaurants,curated
amazon,token,shopping,curated
h&m,token,shopping,curated
kappahl,token,shopping,curated
lindex,token,shopping,curated
present,prefix,shopping,data
skor,token,shopping,curated
stadium,token,shopping,curated
zalando,token,shopping,curated
bensin,token,transport,curated
bil,token,transport,data
buss,prefix,transport,curated
flygbuss,token,transport,curated
färj,prefix,transport,data
laddning,prefix,transport,data
okq8,token,transport,curated
parkering,prefix,transport,data
preem,token,transport,curated
sj,token,transport,curated
sl,token,transport,curated
tanka,token,transport,curated
taxi,token,transport,data
trängselskatt,token,transport,curated
tåg,token,transport,data
uber,token,transport,curated
airbnb,token,travel,data
boende,token,travel,data
booking,token,travel,curated
flyg,token,travel,curated
hotel,token,travel,data
hotell,token,travel,data
sas,token,travel,curated
bredband,token,utilities,curated
elräkning,token,utilities,curated
internet,token,utilities,data
städning,prefix,utilities,curated
tele2,token,utilities,curated
telenor,token,utilities,curated
telia,token,utilities,curated
vattenfall,token,utilities,curated
1 keyword match default_key source
2 barn prefix children data
3 blöjor token children curated
4 fritids token children curated
5 förskol prefix children data
6 leksak prefix children curated
7 napp prefix children data
8 resevagn token children data
9 bio token entertainment data
10 entre token entertainment data
11 entré token entertainment data
12 filmstaden token entertainment curated
13 karaoke token entertainment data
14 konsert prefix entertainment data
15 museet token entertainment curated
16 museum token entertainment data
17 netflix token entertainment curated
18 padel token entertainment curated
19 spotify token entertainment data
20 teater prefix entertainment curated
21 bankavgift token fees curated
22 kvarskatt token fees curated
23 ränteåterbäring token fees data
24 skatt token fees curated
25 skatteverket token fees curated
26 alkohol token groceries data
27 bondens marknad phrase groceries data
28 bröd token groceries data
29 bubbel token groceries data
30 city gross phrase groceries curated
31 coop token groceries data
32 frukt token groceries curated
33 grönsaker token groceries data
34 handling prefix groceries data
35 hemköp token groceries data
36 ica token groceries data
37 julmat token groceries data
38 lidl token groceries curated
39 livs token groceries data
40 livsmedel prefix groceries curated
41 mat token groceries data
42 mjölk token groceries data
43 netto token groceries data
44 ost token groceries data
45 snacks token groceries data
46 storhandling token groceries data
47 systembolag prefix groceries data
48 systemet token groceries data
49 willys token groceries data
50 apotek prefix health data
51 kry token health curated
52 läkare token health curated
53 medicin prefix health curated
54 sjukhus token health curated
55 tandläkare token health curated
56 tandvård token health curated
57 vårdcentral token health curated
58 bauhaus token home data
59 byggmax token home curated
60 clas ohlson phrase home data
61 elgiganten token home curated
62 färg token home data
63 garderob prefix home data
64 gardin prefix home data
65 hylla token home data
66 ikea token home data
67 jysk token home curated
68 lamp prefix home data
69 möbler token home curated
70 ohlson token home data
71 rusta token home data
72 amortering prefix housing curated
73 avgift token housing data
74 bolån token housing curated
75 brf token housing data
76 hyra token housing data
77 lgh token housing data
78 ränta token housing data
79 barnförsäkring token insurance curated
80 bilförsäkring token insurance curated
81 folksam token insurance curated
82 försäkring prefix insurance data
83 hemförsäkring token insurance data
84 länsförsäkringar token insurance curated
85 trygg hansa phrase insurance curated
86 bagel prefix restaurants data
87 bageri prefix restaurants data
88 bar token restaurants curated
89 burger king phrase restaurants curated
90 cafe token restaurants curated
91 café token restaurants data
92 dumplings token restaurants data
93 fika prefix restaurants data
94 foodora token restaurants curated
95 glass token restaurants data
96 hamburgare token restaurants curated
97 kaffe token restaurants data
98 kebab token restaurants data
99 lunch token restaurants data
100 max token restaurants data
101 mcdonalds token restaurants curated
102 middag token restaurants data
103 pizz prefix restaurants data
104 ramen token restaurants data
105 restaurang prefix restaurants curated
106 seml prefix restaurants data
107 streetfood token restaurants data
108 sushi token restaurants data
109 thai token restaurants data
110 uber eats phrase restaurants curated
111 amazon token shopping curated
112 h&m token shopping curated
113 kappahl token shopping curated
114 lindex token shopping curated
115 present prefix shopping data
116 skor token shopping curated
117 stadium token shopping curated
118 zalando token shopping curated
119 bensin token transport curated
120 bil token transport data
121 buss prefix transport curated
122 flygbuss token transport curated
123 färj prefix transport data
124 laddning prefix transport data
125 okq8 token transport curated
126 parkering prefix transport data
127 preem token transport curated
128 sj token transport curated
129 sl token transport curated
130 tanka token transport curated
131 taxi token transport data
132 trängselskatt token transport curated
133 tåg token transport data
134 uber token transport curated
135 airbnb token travel data
136 boende token travel data
137 booking token travel curated
138 flyg token travel curated
139 hotel token travel data
140 hotell token travel data
141 sas token travel curated
142 bredband token utilities curated
143 elräkning token utilities curated
144 internet token utilities data
145 städning prefix utilities curated
146 tele2 token utilities curated
147 telenor token utilities curated
148 telia token utilities curated
149 vattenfall token utilities curated
+1 -1
View File
@@ -1,6 +1,6 @@
{
"status": "ok",
"server_version": "0.1.0",
"schema_version": 1,
"schema_version": 2,
"db_ok": true
}
@@ -0,0 +1,64 @@
-- Kvitt server schema v2: four new default categories (housing, insurance, children, fees). Do not edit; add a new
-- migration and bump the schema version (SPEC §14.8). Applied on top of server-v1.sql (0001_init.sql).
--
-- The default_key CHECK of categories cannot be altered in place in SQLite, so the table is rebuilt: copy the rows
-- aside, drop the table, recreate it with the new CHECK and copy the rows back, ids and server_version unchanged.
-- The migration runs inside one transaction (sqlx wraps every migration in one) with foreign_keys = ON (SPEC §8.9).
-- defer_foreign_keys = ON lets expenses and recurring_templates point at categories while the table is briefly
-- absent; the deferred check at commit still fails the migration if any reference is left dangling. It resets
-- itself when the transaction ends. No other table, index or trigger refers to categories by anything but name.
PRAGMA defer_foreign_keys = ON;
CREATE TABLE categories_v1_copy AS SELECT * FROM categories;
DROP TABLE categories;
CREATE TABLE categories (
id TEXT PRIMARY KEY NOT NULL,
default_key TEXT UNIQUE CHECK (default_key IS NULL OR default_key IN
('groceries', 'restaurants', 'home', 'utilities', 'transport',
'travel', 'entertainment', 'health', 'shopping', 'other',
'housing', 'insurance', 'children', 'fees')),
name TEXT CHECK (name IS NULL OR length(name) BETWEEN 1 AND 40),
icon TEXT NOT NULL,
color TEXT NOT NULL,
sort_order INTEGER NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
archived_at TEXT,
deleted_at TEXT,
server_version INTEGER NOT NULL UNIQUE,
CHECK (name IS NOT NULL OR default_key IS NOT NULL)
) STRICT;
INSERT INTO categories (id, default_key, name, icon, color, sort_order, created_at, updated_at, archived_at, deleted_at, server_version)
SELECT id, default_key, name, icon, color, sort_order, created_at, updated_at, archived_at, deleted_at, server_version
FROM categories_v1_copy;
DROP TABLE categories_v1_copy;
-- Each new row takes the next server_version from the global sequence, as every server write does (SPEC §14.1), so
-- phones receive the rows on their next pull whatever their cursor.
UPDATE sync_sequence SET value = value + 1 WHERE id = 1;
INSERT INTO categories (id, default_key, name, icon, color, sort_order, created_at, updated_at, archived_at, deleted_at, server_version)
SELECT '01920000-0000-7000-8000-000000000011', 'housing', NULL, 'key', 'indigo', 25,
'2026-10-18T00:00:00.000Z', '2026-10-18T00:00:00.000Z', NULL, NULL, value
FROM sync_sequence WHERE id = 1;
UPDATE sync_sequence SET value = value + 1 WHERE id = 1;
INSERT INTO categories (id, default_key, name, icon, color, sort_order, created_at, updated_at, archived_at, deleted_at, server_version)
SELECT '01920000-0000-7000-8000-000000000012', 'insurance', NULL, 'shield', 'slate', 45,
'2026-10-18T00:00:00.000Z', '2026-10-18T00:00:00.000Z', NULL, NULL, value
FROM sync_sequence WHERE id = 1;
UPDATE sync_sequence SET value = value + 1 WHERE id = 1;
INSERT INTO categories (id, default_key, name, icon, color, sort_order, created_at, updated_at, archived_at, deleted_at, server_version)
SELECT '01920000-0000-7000-8000-000000000013', 'children', NULL, 'child_care', 'lime', 85,
'2026-10-18T00:00:00.000Z', '2026-10-18T00:00:00.000Z', NULL, NULL, value
FROM sync_sequence WHERE id = 1;
UPDATE sync_sequence SET value = value + 1 WHERE id = 1;
INSERT INTO categories (id, default_key, name, icon, color, sort_order, created_at, updated_at, archived_at, deleted_at, server_version)
SELECT '01920000-0000-7000-8000-000000000014', 'fees', NULL, 'account_balance', 'amber', 95,
'2026-10-18T00:00:00.000Z', '2026-10-18T00:00:00.000Z', NULL, NULL, value
FROM sync_sequence WHERE id = 1;
UPDATE meta SET value = '2' WHERE key = 'schema_version';
File diff suppressed because it is too large Load Diff