diff --git a/docs/ux/category-keywords.csv b/docs/ux/category-keywords.csv index ed6901a..c73444c 100644 --- a/docs/ux/category-keywords.csv +++ b/docs/ux/category-keywords.csv @@ -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 diff --git a/docs/ux/category-suggestion.md b/docs/ux/category-suggestion.md index 72964e7..f53a6a1 100644 --- a/docs/ux/category-suggestion.md +++ b/docs/ux/category-suggestion.md @@ -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. diff --git a/docs/ux/proposed-decisions.md b/docs/ux/proposed-decisions.md index 3b8d6b5..131cefe 100644 --- a/docs/ux/proposed-decisions.md +++ b/docs/ux/proposed-decisions.md @@ -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.