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:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user