UX: owner answers round 2: no suggestion caption, most-often history, 14 default categories, finer import mapping

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-18 07:51:05 +02:00
parent 4c1a4b5a9d
commit 4d3c343f16
3 changed files with 132 additions and 77 deletions
+26 -27
View File
@@ -1,20 +1,28 @@
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
bowling,token,entertainment,curated
entre,token,entertainment,data
entré,token,entertainment,data
filmstaden,token,entertainment,curated
gröna lund,phrase,entertainment,curated
karaoke,token,entertainment,data
konsert,prefix,entertainment,data
liseberg,token,entertainment,curated
museet,token,entertainment,curated
museum,token,entertainment,data
netflix,token,entertainment,curated
padel,token,entertainment,curated
spotify,token,entertainment,data
teater,prefix,entertainment,curated
viaplay,token,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
@@ -47,9 +55,7 @@ sjukhus,token,health,curated
tandläkare,token,health,curated
tandvård,token,health,curated
vårdcentral,token,health,curated
amortering,prefix,home,curated
bauhaus,token,home,data
brf,token,home,data
byggmax,token,home,curated
clas ohlson,phrase,home,data
elgiganten,token,home,curated
@@ -57,21 +63,26 @@ färg,token,home,data
garderob,prefix,home,data
gardin,prefix,home,data
hylla,token,home,data
hyra,token,home,data
ikea,token,home,data
jula,token,home,curated
jysk,token,home,curated
lamp,prefix,home,data
lgh,token,home,data
möbler,token,home,curated
ohlson,token,home,data
rusta,token,home,data
ränta,token,home,data
barnförsäkring,token,other,data
förskola,token,other,data
försäkring,prefix,other,data
hemförsäkring,token,other,data
ränteåterbäring,token,other,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
@@ -90,38 +101,29 @@ max,token,restaurants,data
mcdonalds,token,restaurants,curated
middag,token,restaurants,data
pizz,prefix,restaurants,data
pub,token,restaurants,curated
ramen,token,restaurants,data
restaurang,prefix,restaurants,curated
seml,prefix,restaurants,data
streetfood,token,restaurants,data
sushi,token,restaurants,data
takeaway,token,restaurants,curated
thai,token,restaurants,data
uber eats,phrase,restaurants,curated
wolt,token,restaurants,curated
amazon,token,shopping,curated
h&m,token,shopping,curated
intersport,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
zara,token,shopping,curated
bensin,token,transport,curated
bil,token,transport,data
biltvätt,token,transport,curated
buss,prefix,transport,curated
diesel,token,transport,curated
easypark,token,transport,curated
flygbuss,token,transport,curated
färj,prefix,transport,data
laddning,prefix,transport,data
okq8,token,transport,curated
parkering,prefix,transport,data
pendeltåg,token,transport,curated
preem,token,transport,curated
sj,token,transport,curated
sl,token,transport,curated
@@ -130,7 +132,6 @@ taxi,token,transport,data
trängselskatt,token,transport,curated
tåg,token,transport,data
uber,token,transport,curated
västtrafik,token,transport,curated
airbnb,token,travel,data
boende,token,travel,data
booking,token,travel,curated
@@ -138,8 +139,6 @@ flyg,token,travel,curated
hotel,token,travel,data
hotell,token,travel,data
sas,token,travel,curated
scandic,token,travel,curated
vandrarhem,token,travel,curated
bredband,token,utilities,curated
elräkning,token,utilities,curated
internet,token,utilities,data
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
bowling token entertainment curated
10 entre token entertainment data
11 entré token entertainment data
12 filmstaden token entertainment curated
gröna lund phrase entertainment curated
13 karaoke token entertainment data
14 konsert prefix entertainment data
liseberg token entertainment curated
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 viaplay bankavgift token entertainment 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
55 tandläkare token health curated
56 tandvård token health curated
57 vårdcentral token health curated
amortering prefix home curated
58 bauhaus token home data
brf token home data
59 byggmax token home curated
60 clas ohlson phrase home data
61 elgiganten token home curated
63 garderob prefix home data
64 gardin prefix home data
65 hylla token home data
hyra token home data
66 ikea token home data
jula token home curated
67 jysk token home curated
68 lamp prefix home data
lgh token home data
69 möbler token home curated
70 ohlson token home data
71 rusta token home data
72 ränta amortering token prefix home housing data curated
73 barnförsäkring avgift token other housing data
74 förskola bolån token other housing data curated
75 försäkring brf prefix token other housing data
76 hemförsäkring hyra token other housing data
77 ränteåterbäring lgh token other 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
101 mcdonalds token restaurants curated
102 middag token restaurants data
103 pizz prefix restaurants data
pub token restaurants curated
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
takeaway token restaurants curated
109 thai token restaurants data
110 uber eats phrase restaurants curated
wolt token restaurants curated
111 amazon token shopping curated
112 h&m token shopping curated
intersport 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
zara token shopping curated
119 bensin token transport curated
120 bil token transport data
biltvätt token transport curated
121 buss prefix transport curated
diesel token transport curated
easypark token 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
pendeltåg token transport curated
127 preem token transport curated
128 sj token transport curated
129 sl token transport curated
132 trängselskatt token transport curated
133 tåg token transport data
134 uber token transport curated
västtrafik token transport curated
135 airbnb token travel data
136 boende token travel data
137 booking token travel curated
139 hotel token travel data
140 hotell token travel data
141 sas token travel curated
scandic token travel curated
vandrarhem token travel curated
142 bredband token utilities curated
143 elräkning token utilities curated
144 internet token utilities data
+43 -38
View File
@@ -19,12 +19,12 @@ Proposal, 2026-10-18. The owner decided that the expense editor (S-7) fills in t
Diacritics are kept (å, ä, ö and é stay distinct; the keyword table lists both spellings where it matters). Digits are kept. The **tokens** are the normalised description split on spaces; the **first token** is the first of them. An empty normalised description gives no history or keyword match.
**CS-5 Order.** The suggested category is the first that applies of:
1. **History, exact:** among the non-deleted expenses on the phone (every account, either user, including imported and recurring ones) whose normalised description equals the normalised typed description, the most recent one's category.
2. **History, first token:** among the non-deleted expenses whose first token equals the typed description's first token, the most recent one's category.
1. **History, exact:** among the non-deleted expenses on the phone (every account, either user, including imported and recurring ones) whose normalised description equals the normalised typed description, the category used most often among them; on a tie, the one used most recently.
2. **History, first token:** among the non-deleted expenses whose first token equals the typed description's first token, the category used most often among them; on a tie, the one used most recently.
3. **Keywords:** the category of the winning entry of the built-in keyword table (CS-6).
4. **Default:** the AC-8.6 default (the category last used in the selected account on this phone if still active, else `other`).
"Most recent" is the expense-list order of AC-8.10: `expense_date` descending, then `created_at` descending. A history candidate whose category is archived or deleted is skipped and the next most recent candidate of the same step is tried; when none is left, the next step applies.
"Used most often" counts the matching expenses per category; "most recently" is the expense-list order of AC-8.10 (`expense_date` descending, then `created_at` descending), applied to the latest expense of each tied category (owner, 2026-10-18: most often, not most recent). Categories that are archived or deleted are left out of the count; when no category is left, the next step applies.
**CS-6 Keyword table.** Each entry has `keyword` (already normalised), `match` and `default_key`. An entry matches when:
* `token`: some token equals the keyword;
@@ -33,58 +33,64 @@ Diacritics are kept (å, ä, ö and é stay distinct; the keyword table lists bo
When several entries match, the longest keyword (in code points, spaces included) wins; on equal length, the entry earlier in the table wins (the table is sorted by `default_key`, then `keyword`). The winning entry gives the category with that `default_key` (renamed defaults included, since the key stays). If that category is archived, the next matching entry in the same order is tried; when none is left, step 4 applies. The table is fixed data shipped with the app; users do not edit it.
**CS-7 What the user sees.** The suggestion replaces the category shown in the Kategori field and needs no confirmation. While a category comes from step 1, 2 or 3 and the flag of CS-2 is false, a one-line caption under the category field says "Vald utifrån beskrivningen" / "Chosen from the description" (string `expense_editor_category_suggested`); it disappears when the user picks a category by hand or when step 4 applies. Clearing the description returns the field to the step-4 default.
**CS-7 What the user sees.** The suggestion simply replaces the category shown in the Kategori field: no caption, no marker, no confirmation (owner, 2026-10-18: a wrong guess is corrected with one tap). Clearing the description returns the field to the step-4 default.
## Evidence
Source: the household's two Splitwise exports, read-only on the owner's Mac. 1 775 expense rows from 2020-11 to 2026-10 (payment rows, blank lines and the total row excluded), mapped to Kvitt categories with the §10.8 table. True categories: groceries 662, home 315, restaurants 301, other 171, entertainment 111, transport 89, utilities 58, travel 35, health 17, shopping 16. 21 rows (1.2 %) had a Swedish Splitwise category name missing from the §10.8 table and count as `other` (see open points).
Source: the household's two Splitwise exports, read-only on the owner's Mac. 1 775 expense rows from 2020-11 to 2026-10 (payment rows, blank lines and the total row excluded), mapped to the 14 Kvitt categories with the owner's revised import mapping of 2026-10-18 (§10.8 plus Rent, Mortgage, Hyra, Avbetalning/Amortering → `housing`; Insurance, Försäkringar → `insurance`; Childcare, Barnomsorg → `children`; Taxes, Skatter → `fees`; and the nine Swedish names that were missing). No row is unmapped. True categories: groceries 662, restaurants 301, home 164, housing 154, entertainment 113, transport 90, children 73, utilities 61, other 39, travel 37, insurance 26, shopping 19, fees 19, health 17.
Method: chronological simulation. Rows are walked in date order; each row's category is predicted from earlier rows only and compared with its true category. The step-4 default is approximated as the previous row's category in the same file (one file is one account), `other` for the first row.
**History variants alone** (coverage = share of rows with a prediction; accuracy among those):
**History variants alone** (coverage = share of rows with a prediction; accuracy among those; "most often" breaks ties by most recent):
| Variant | Coverage | Accuracy (most recent) | Accuracy (most frequent) |
| Variant | Coverage | Most often | Most recent |
|---|---|---|---|
| Exact normalised description | 56.2 % | 94.9 % | 95.9 % |
| First two tokens | 57.2 % | 94.8 % | 95.8 % |
| First token | 71.5 % | 92.0 % | 93.0 % |
| Exact, then first token (chosen) | 71.5 % | 92.3 % | 93.2 % |
| Any shared token of ≥ 3 letters | 79.0 % | 86.2 % | 89.4 % |
| Any shared token of ≥ 4 letters | 63.7 % | 85.4 % | 88.3 % |
| Exact normalised description | 56.2 % | 95.6 % | 94.6 % |
| First two tokens | 57.2 % | 95.5 % | 94.5 % |
| First token | 71.5 % | 92.6 % | 91.4 % |
| **Exact, then first token (chosen)** | 71.5 % | **92.8 %** | 91.9 % |
| Any shared token of ≥ 3 letters | 79.0 % | 89.0 % | 84.8 % |
| Any shared token of ≥ 4 letters | 63.7 % | 88.0 % | 83.9 % |
Exact-then-first-token keeps the owner's "matching description" literally as step 1 and adds the first token for coverage. "Most frequent" is about one point better but harder to explain; the owner chose most recent. Dropping digit-only tokens changed nothing.
Exact-then-first-token keeps the owner's "matching description" literally as step 1 and adds the first token for coverage. "Most often" is about one point better than "most recent" in every variant. Dropping digit-only tokens changed nothing.
**Full rule (CS-5, chosen variant, keyword table as committed):**
| Stage | Share of rows | Accuracy |
|---|---|---|
| History (exact 998 rows at 94.9 %, first token 272 rows at 82.7 %) | 71.5 % | 92.3 % |
| Keywords | 7.8 % | 94.2 % |
| Default (approximated) | 20.7 % | 30.0 % |
| **Overall** | 100 % | **79.5 %** |
| History, exact (998 rows) | 56.2 % | 95.6 % |
| History, first token (272 rows) | 15.3 % | 82.7 % |
| Keywords (145 rows) | 8.2 % | 92.4 % |
| Default, approximated (360 rows) | 20.3 % | 28.3 % |
| **Overall** | 100 % | **79.7 %** |
For comparison: the AC-8.6 default alone 31.9 %; history and default without keywords 74.6 %. Putting keywords before history would give 80.7 %, exact history then keywords then first token 79.9 %; the differences are small, so the owner's order stands.
For comparison: the AC-8.6 default alone 30.5 %; history and default without keywords 74.7 %; the full rule with "most recent" history 79.0 %.
**Cold start** (keywords and default only, no history): first 10 % of rows (177): keywords cover 78.0 % at 96.4 % accuracy, 85.3 % overall. Over all rows: 72.2 % coverage at 96.7 %. These are in-sample for the `data` entries. An out-of-sample check (keep a `data` entry only if it reaches ≥ 2 rows and ≥ 80 % precision in the first half; test on the second half) gives 58.1 % coverage at 98.4 % accuracy.
**Cold start** (keywords and default only, no history): first 10 % of rows (177): keywords cover 77.4 % at 95.6 % accuracy, 83.6 % overall. Over all rows: 72.7 % coverage at 96.0 %. These are in-sample for the `data` entries. An out-of-sample check (keep a `data` entry only if it reaches ≥ 2 rows and ≥ 80 % precision in the first half; test on the second half) gives 57.9 % coverage at 97.5 % accuracy.
**Most common wrong suggestions** (suggested → true, full rule, 363 wrong of 1 775; 257 of them at the default step): groceries → restaurants 55, groceries → other 38, restaurants → groceries 35, groceries → home 23, home → other 18, restaurants → other 14, other → groceries 13, other → home 12. History errors are mostly the groceries/restaurants pair on a shared first token such as "mat". Keyword errors (8 in total) are mostly `apotek` (the household filed pharmacy purchases under other as often as under health) and `ikea` (the IKEA restaurant).
**Most common wrong suggestions** (suggested → true, full rule, 360 wrong of 1 775: 258 at the default step, 91 at history, 11 at keywords): groceries → restaurants 53, restaurants → groceries 30, groceries → home 24, groceries → other 15, groceries → children 14, transport → restaurants 9, housing → home 9, groceries → entertainment 8. History errors are mostly the groceries/restaurants pair on a shared first token such as "mat". Keyword errors are spread thin (`apotek` and `mat` 2 each, the rest 1).
**Keyword table:** 149 entries, 76 `data` and 73 `curated`; 119 `token`, 24 `prefix`, 6 `phrase`.
**Keyword table:** 148 entries, 79 `data` and 69 `curated`; 114 `token`, 28 `prefix`, 6 `phrase`. No entry maps to `other`.
| `default_key` | data | curated |
|---|---|---|
| groceries | 20 | 4 |
| restaurants | 17 | 11 |
| home | 14 | 6 |
| restaurants | 17 | 8 |
| home | 10 | 4 |
| housing | 5 | 2 |
| utilities | 1 | 7 |
| transport | 6 | 15 |
| travel | 4 | 5 |
| entertainment | 7 | 9 |
| transport | 6 | 10 |
| travel | 4 | 3 |
| entertainment | 7 | 5 |
| health | 1 | 7 |
| shopping | 1 | 9 |
| other | 5 | 0 |
| shopping | 1 | 7 |
| insurance | 2 | 5 |
| children | 4 | 3 |
| fees | 1 | 4 |
`data` entries predicted one category with high precision in the export (most ≥ 90 %, `ikea` 79 %, `apotek` 55 % kept because the meaning is unambiguous). `curated` entries are well-known Swedish chains and services and unambiguous words, absent or rare in the export.
`data` entries predicted one category with high precision in the export (most ≥ 90 %; `ikea` 79 %, the misses being the IKEA restaurant; `apotek` 55 %, kept because the meaning is unambiguous, the household filed pharmacy purchases under health, children, other and home). `curated` entries are well-known Swedish chains and services and unambiguous words, absent or rare in the export. Two curated entries go against the few rows in the export, by the owner's examples: `barnförsäkring` → insurance (3 rows filed under childcare) and `blöjor` → children (2 of 3 rows filed under groceries).
The five former `other` entries were moved: `försäkring`, `hemförsäkring`, `barnförsäkring` → insurance; `ränteåterbäring` → fees (17 of 18 rows were filed under taxes); `förskola` → children (now the prefix `förskol`). `hyra`, `ränta`, `lgh`, `brf` and `amortering` moved from home to housing, and `avgift` was added (2 of 2 rows housing).
## Excluded on purpose
@@ -93,14 +99,13 @@ Kinds of word left out of the table even when they predicted well:
* town, city and country names (they predict a trip, not a category);
* names of single local restaurants, cafés and shops whose identity was unclear;
* function words and abbreviations (och, till, för, på, mm, etc, o, &);
* words the household used against their dictionary meaning: `el` (always part of "hyra och el", filed under home), `kläder` (filed under other); and words split between categories: `frukost`, `vin`, `öl`, `blommor`, `brunch`, `cykel`.
* words the household used against their dictionary meaning: `el` (always part of "hyra och el", filed under housing), `kläder` (filed under childcare in the export; generic clothes belong to shopping); and words split between categories: `frukost`, `vin`, `öl`, `blommor`, `brunch`, `cykel`, `ansiktstvätt`.
## Open points
1. **`other` entries.** The five `data` entries for `other` (insurance words, ränteåterbäring, förskola) are kept because they beat the default; the brief said the curated set never targets `other`. Drop them if keyword hits should never select Övrigt.
2. **Missing Swedish Splitwise names in §10.8.** Nine Swedish category names in the export are not in the mapping table (21 rows): Städning, Verktyg - Övrigt, Tjänster, Cykel, Livet - Övrigt, Flyg, Spel, Kläder, Utbildning. By their English equivalents they would map to utilities, utilities ("Utilities - Other"), home, transport, other, travel, entertainment, shopping and other. The importer would count them as unmapped; the history step inherits whatever the import stores.
3. **Imported history.** After M11 the whole Splitwise history is in Room, so for this household history decides most descriptions from day one; the keyword table matters for new words and after a fresh install.
4. **Recurring template editor.** Not covered; the same rule could apply to new templates.
5. **Recency key.** CS-5 uses the list order (`expense_date`). Using `updated_at` instead would let a re-categorised old expense teach the rule; not measurable from the export.
6. **Where the table lives.** Proposed: `:domain` as data plus a unit test that loads `docs/ux/category-keywords.csv` (or its successor) and checks every keyword is already normalised and unique.
7. **English keywords.** The table is Swedish plus brand names; English words are not covered.
1. **Import mapping.** The replay uses the owner's revised mapping (14 categories, nine added Swedish names); SPEC §10.8 and AC-7.2 still list ten categories and must be updated with it.
2. **Imported history.** After M11 the whole Splitwise history is in Room, so for this household history decides most descriptions from day one; the keyword table matters for new words and after a fresh install.
3. **Recurring template editor.** Not covered; the same rule could apply to new templates.
4. **Recency key.** CS-5 breaks ties by list order (`expense_date`). Using `updated_at` instead would let a re-categorised old expense count as recent; not measurable from the export.
5. **Where the table lives.** Proposed: `:domain` as data plus a unit test that loads `docs/ux/category-keywords.csv` (or its successor) and checks every keyword is already normalised, unique, and maps to an existing `default_key`.
6. **English keywords.** The table is Swedish plus brand names; English words are not covered.
+63 -12
View File
@@ -13,10 +13,15 @@ Every entry has: the owner's answer or the finding, the proposed decision text f
| Category from the description | **Yes:** the category is auto-selected from the description and the user can still change it. Rule: history first, then a built-in keyword list, then the AC-8.6 default. The keyword list is learned from the household's Splitwise export. |
| S-1 "Ditt namn" field (partner types the name on S-1 and confirms it on S-2) | **OK** |
| Swedish wording "Ni är kvitt", "Gör upp", "Du lånade ut / Du lånade", "Inte inblandad" | **Yes** |
| Muted Kalas (2026-10-18, second round) | **Approved** ("let's go with that for now"). |
| Mark the suggested category ("Vald utifrån beskrivningen")? | **No:** the category is simply selected; a wrong guess is changed with one tap; simpler UI. |
| History match: most recent or most often? | **Most often.** |
| Import granularity | Splitwise's categories are finer than Kvitt's ten defaults (several "Övrigt" under different groups); map them better. "Hemförsäkring" must not end up in Övrigt. |
| Which new default categories? | **All four proposed:** Boende, Försäkringar, Barn, Avgifter & skatter. |
## UX-1 Direction: Kalas, muted
* **Decision:** the app's visual direction is U1's direction 3 "Kalas" (cards, very round shapes, a solid category-colour tile on every expense, the seesaw balance), with a muted palette (status: being revised; see `directions/3-kalas/`). Typeface per D-020 (Material 3 default, Roboto); Kalas's preview font Nunito is dropped.
* **Decision:** the app's visual direction is U1's direction 3 "Kalas" (cards, very round shapes, a solid category-colour tile on every expense, the seesaw balance), with the muted palette of `directions/3-kalas/` (owner approved 2026-10-18). Typeface per D-020 (Material 3 default, Roboto); Kalas's preview font Nunito is dropped.
* **Why:** owner's pick; muted because bright colours tire over years of daily use.
* **Spec:** none beyond UX-5 (category colours). U2's `design-spec.md` carries the tokens.
@@ -27,22 +32,67 @@ Every entry has: the owner's answer or the finding, the proposed decision text f
## UX-3 Category suggested from the description
* **Decision (owner, 2026-10-18):** for a new expense, the editor selects a category from the description as the user types, while the user has not picked a category by hand in this editor session: (1) history match, (2) built-in keyword list, (3) the AC-8.6 default. The user can always change it. Editing an existing expense never changes its category automatically.
* **Decision (owner, 2026-10-18):** for a new expense, the editor selects a category from the description as the user types, while the user has not picked a category by hand in this editor session: (1) history match, the category used **most often** among matching expenses, (2) built-in keyword list, (3) the AC-8.6 default. The selected category is not marked as a suggestion (no caption); the user changes it with one tap. Editing an existing expense never changes its category automatically.
* **Why:** most expenses repeat the same shops; fewer taps in the ten-second flow (§13.1).
* **Evidence** (chronological replay of the household's 1 775 Splitwise expenses, aggregates only, `category-suggestion.md`): history step right 92.3 % of the time and used for 71.5 % of rows; keyword step 94.2 % on 7.8 %; overall 79.5 % correct versus 31.9 % for today's AC-8.6 default alone. At cold start (no history) keywords fire on 78 % of rows and are right 96 % of the time.
* **Evidence** (chronological replay of the household's 1 775 Splitwise expenses with the 14 categories of UX-8 and the mapping of UX-7, aggregates only, `category-suggestion.md`): history step ("most often") used for 71.5 % of rows and right 92.8 % of the time; keyword step 92.4 % on 8.2 %; overall **79.7 %** correct versus 30.5 % for today's AC-8.6 default alone ("most recent" would give 79.0 %). Cold start (no history): keywords fire on 77 % of rows and are right 96 % of the time.
* **Spec changes:**
* AC-8.6: the category default becomes "the suggestion of §9.11, which falls back to the current rule".
* New §9.11 "Category suggestion": the text CS-1 to CS-7 of `category-suggestion.md`.
* New contract file `contracts/category-keywords.csv` (columns `keyword,match,default_key,source`; draft `docs/ux/category-keywords.csv`, 149 entries), listed in §14.8.
* New contract file `contracts/category-keywords.csv` (columns `keyword,match,default_key,source`; draft `docs/ux/category-keywords.csv`, 148 entries, none mapping to `other`), listed in §14.8.
* §14.2.4: `ExpenseRepository` gains `suspend fun suggestCategory(accountId: AccountId?, description: String): CategoryId?` (computed in `:data` from Room and the keyword table; the editor calls it on description and account changes while the category was not picked by hand).
* §14.5: a pure Kotlin function `suggestCategory(description, history, keywords, activeCategories, fallback)` in `:domain` with test vectors (Kotlin only, like analytics).
* `strings.csv`: `expense_editor_category_suggested` "Vald utifrån beskrivningen" / "Chosen from the description".
* **Open points** (owner, low stakes; defaults in brackets): keep the five keywords that pick Övrigt, e.g. insurance words [keep]; "most recent" vs "most frequent" history match, the latter about one point better [most recent, as chosen].
## UX-7 §10.8 misses nine Swedish Splitwise category names
## UX-7 Finer Splitwise import mapping (§10.8)
* **Finding** (from the export analysis): 21 rows use Swedish Splitwise category names that the §10.8 mapping table lacks: Utbildning (4), Tjänster (3), Livet - Övrigt (3), Kläder (3), Städning (2), Flyg (2), Spel (2), Verktyg - Övrigt (1), Cykel (1). They would import as `other` with a warning.
* **Proposed decision:** add them to the §10.8 table and `server/crates/kvitt-import/data/splitwise_categories.csv` by their English equivalents: Utbildning → other, Tjänster → home, Livet - Övrigt → other, Kläder → shopping, Städning → utilities, Flyg → travel, Spel → entertainment, Verktyg - Övrigt → utilities, Cykel → transport. (Needed by M9.)
* **Finding** (export analysis): 21 rows use Swedish Splitwise category names that the §10.8 table lacks (Utbildning 4, Tjänster 3, Livet - Övrigt 3, Kläder 3, Städning 2, Flyg 2, Spel 2, Verktyg - Övrigt 1, Cykel 1); and §10.8 collapses Splitwise's finer categories, so e.g. Försäkringar (26 rows), Barnomsorg (73) and Skatter (19) land in `other` and Hyra (106) and Avbetalning/Amortering (48) in `home`.
* **Decision (owner, 2026-10-18):** map Splitwise categories as finely as Kvitt's categories allow, with the four new defaults of UX-8.
* **Spec:** the §10.8 table and `server/crates/kvitt-import/data/splitwise_categories.csv` become:
| Splitwise category (English) | Splitwise category (Swedish) | Kvitt `default_key` |
|---|---|---|
| Groceries, Food and drink - Other, Liquor | Livsmedel, Mat och dryck - Övrigt, Alkohol | `groceries` |
| Dining out | Restaurangbesök | `restaurants` |
| Rent, Mortgage | Hyra, Avbetalning/Amortering | `housing` |
| Electronics, Furniture, Household supplies, Maintenance, Services, Pets, Home - Other | Elektronik, Möbler, Förbrukningsvaror, Skötsel/underhåll, Tjänster, Husdjur, Hem - Övrigt | `home` |
| Electricity, Heat/gas, Water, Trash, TV/Phone/Internet, Cleaning, Utilities - Other | El, Värme/gas, Vatten, Sophämtning, TV/telefon/internet, Städning, Verktyg - Övrigt | `utilities` |
| Insurance | Försäkringar | `insurance` |
| Bicycle, Bus/train, Car, Gas/fuel, Parking, Taxi, Transportation - Other | Cykel, Buss/tåg, Bil, Bensin/bränsle, Parkering, Taxi, Transport - Övrigt | `transport` |
| Hotel, Plane | Hotell, Flyg | `travel` |
| Games, Movies, Music, Sports, Entertainment - Other | Spel, Filmer, Musik, Sport, Underhållning - Övrigt | `entertainment` |
| Medical expenses | Sjukvård/medicin | `health` |
| Childcare | Barnomsorg | `children` |
| Clothing, Gifts | Kläder, Presenter | `shopping` |
| Taxes | Skatter | `fees` |
| General, Education, Life - Other, Other | Allmänt, Utbildning, Livet - Övrigt | `other` |
| any other value | any other value | `other`, counted as unmapped |
Swedish names not seen in the export (El, Värme/gas, Vatten, Sophämtning, Husdjur) are Splitwise's Swedish labels by analogy and must be checked against Splitwise before M9 relies on them; an unseen name simply stays unmapped.
## UX-8 Four new default categories
* **Decision (owner, 2026-10-18):** Kvitt seeds 14 default categories instead of 10. New: Boende, Försäkringar, Barn, Avgifter & skatter. Hem keeps furniture, supplies, maintenance, electronics and services; rent and mortgage move to Boende.
* **Why:** the household's history shows these as separate, large groups (Hyra + Amortering 154 rows, Barnomsorg 73, Försäkringar 26, Skatter 19) that were collapsed into Hem and Övrigt; separate categories make analytics and the category suggestion meaningful.
* **Spec (AC-7.2 table, full new list in sort order; new rows in bold):**
| `default_key` | English name | Swedish name | `icon` | `color` | `sort_order` |
|---|---|---|---|---|---|
| `groceries` | Groceries | Mat & hushåll | `shopping_cart` | `green` | 10 |
| `restaurants` | Restaurants | Restaurang & café | `restaurant` | `orange` | 20 |
| **`housing`** | **Housing** | **Boende** | **`key`** | **`indigo`** | **25** |
| `home` | Home | Hem | `home` | `brown` | 30 |
| `utilities` | Utilities | Räkningar | `bolt` | `yellow` | 40 |
| **`insurance`** | **Insurance** | **Försäkringar** | **`shield`** | **new token (UX-5)** | **45** |
| `transport` | Transport | Transport | `directions_car` | `blue` | 50 |
| `travel` | Travel | Resor | `flight` | `teal` | 60 |
| `entertainment` | Entertainment | Nöje | `movie` | `purple` | 70 |
| `health` | Health | Hälsa | `medical_services` | `red` | 80 |
| **`children`** | **Children** | **Barn** | **`child_care`** | **`lime`** | **85** |
| `shopping` | Shopping | Shopping | `shopping_bag` | `pink` | 90 |
| **`fees`** | **Fees & taxes** | **Avgifter & skatter** | **`account_balance`** | **new token (UX-5)** | **95** |
| `other` | Other | Övrigt | `category` | `grey` | 100 |
* **Contract consequences** (one landing, numbered by the docs agent): AC-7.2 table; §9.7 icon set gains `key`, `shield`, `account_balance` (33 tokens) and the colour set gains two tokens (UX-5); §14.1 seed rows with four new fixed ids and a server schema **migration to version 2** inserting them (existing databases, including the development stack, get them on upgrade); `DefaultCategoryKey` and `ColorToken` in §14.2.4 gain the new values and `CategoryIcons.ALL` the new icons; the OpenAPI enums; default-name strings in both languages (`strings.csv`); test vectors that enumerate categories, if any. The Room schema is unchanged (category rows arrive through sync).
* **Order matters for existing milestones:** M3 (server) and M5 (`:data`) already landed with ten defaults, so this is a contract change against code on `main`, not only the spec.
## UX-4 S-1 asks for the display name
@@ -53,7 +103,7 @@ Every entry has: the owner's answer or the finding, the proposed decision text f
## UX-5 Category colour values (§9.7)
* **Finding:** several provisional §9.7 values fail contrast as icon colours on tinted containers (light orange, yellow, lime).
* **Decision:** replace the §9.7 hex values with the muted Kalas values (pending; filled in when the revision is approved). Token names unchanged.
* **Decision:** replace the §9.7 hex values with the muted Kalas values, and add two colour tokens for the new categories of UX-8 (values and names from the Kalas revision in progress; filled in when it reports). Existing token names unchanged.
* **Spec:** §9.7 table values.
## UX-6 Smaller UX rules found during U1 (no owner question needed)
@@ -69,5 +119,6 @@ Every entry has: the owner's answer or the finding, the proposed decision text f
## Status
* U1: delivered (`d27f92e` rebased on `main`), direction picked.
* Done: muted Kalas revision (`directions/3-kalas/`, awaiting the owner's look); keyword list and suggestion analysis (`category-keywords.csv`, `category-suggestion.md`).
* Next: owner approves the muted palette → U2 `design-spec.md`, renamed `strings.csv`, per-screen PNGs, and the final values for UX-3 and UX-5.
* Done: muted Kalas revision (approved); keyword list and replay for 14 categories and "most often".
* In progress: Kalas mocks with the 14 categories and two new colour tokens;
* Next: owner approves the muted palette → U2 `design-spec.md`, renamed `strings.csv`, per-screen PNGs, and the final values for UX-3, UX-5 and UX-8.