feat(onboarding): create_sample_import_folders command, sample CSV templates and detection test (#354) #380
No reviewers
Labels
No labels
autopilot:pending-human
source:analyste
source:defenseur
source:human
source:medic
status:approved
status:blocked
status:in-progress
status:needs-clarification
status:needs-fix
status:ready
status:review
status:triage
type:bug
type:feature
type:infra
type:refactor
type:schema
type:security
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: maximus/Simpl-Resultat#380
Loading…
Reference in a new issue
No description provided.
Delete branch "issue-354-sample-import-folders"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Fixes #354
Stacked on PR #379 (
issue-353-transactions-budget-empty) — link 4 of theplanned-2026-10-03-onboarding-phase1chain. Only the commits above #379's head belong to this PR. The files touched here are disjoint from the previous links.What
create_sample_import_folders(import_folder, today) -> Result<Vec<String>, String>insrc-tauri/src/commands/fs_commands.rs, registered inlib.rs. WritesExemple Desjardins/releve-YYYY-MM.csvandExemple Tangerine/releve-YYYY-MM.csvinto the import folder, with{{M1}}/{{M2}}rendered asMM/YYYYof the two complete months beforetoday. Refuses a missing folder and refuses, before any write, when either sample folder already exists. Returns the two folder names.src/__fixtures__/samples/exemple-desjardins.csvandexemple-tangerine.csv, byte-identical to the issue body (41 lines each, trailing newline), embedded withinclude_str!and read by vitest from the same files. Synthetic data only.src/__fixtures__/samples/index.ts:readSampleTemplate+renderSampleTemplate.src/__fixtures__/csv/index.tsandCsvFixtureNameare untouched (corpus still frozen at 15).src/__integration__/onboarding-samples.test.ts: detection (detectImportFormat→ok, bankdesjardins/tangerine,negative_expense,single, 40/40), full parse with the productionmapRow(0 rows in error, sums +740.00 / −368.46), thencategorizeBatchagainst the 180 seed keywords read fromconsolidated_schema.sql→ exactly 33 / 36 categorised, ≥ 80 %, and the off-seed lines are the only uncategorised ones.Why
The onboarding checklist needs a source the user can import right away and a few lines left to classify by hand. The command never receives a file path to write: it takes the import folder and a date, and the names it writes are fixed in Rust.
todaycomes from the webview because chrono is compiled withstd+serdeonly (Local::now()unavailable), which also makes the command deterministic and testable.Decisions (autopilot)
replaceAllavoided inindex.ts(tsconfig targets ES2020,npm run buildtype-checkssrc/tests included); regexreplace(/…/g)instead.docs/architecture.mdand CHANGELOG are left to #357, which centralises them.How tested
cargo check: clean, no warning.cargo testandcargo test -- --test-threads=4: 192 passed (187 + 5 new), 0 failed.npm test: 1420 passed (76 files), including the 9 new cases;csvAutoDetect.test.tsandbankSignatures.test.tsunchanged.npm run build: green (tsc + vite).cmp.Generated autonomously by /autopilot run of 2026-10-03
🤖 Generated with Claude Code
Add a Tauri command that writes two synthetic bank statements (Desjardins chequing account, Tangerine credit card) into the import folder, one sub-folder each, so a new user has sources to import on the first run. The command takes the import folder and the webview's local date only (chrono is compiled without `clock`), renders the `{{M1}}` / `{{M2}}` tokens as the two complete months before that date, names each file `releve-YYYY-MM.csv` after the most recent one, and refuses to run when the folder is missing or when either sample folder already exists - checked before anything is written. It returns the two folder names. The templates live under `src/__fixtures__/samples/` so the Rust command (`include_str!`) and vitest read the same bytes. The integration test proves detection recognises each file as its bank with a signed single amount and reads 40/40 rows, that the production `mapRow` parses every row and sums to +740.00 / -368.46, and that the 180 seed keywords categorise exactly 33 / 36 lines while leaving the hand-classified ones untouched. The frozen detection corpus (`src/__fixtures__/csv`) is not modified. Five Rust tests cover the missing folder, the existing folder, the success path, an unparseable date and the year boundary, each in its own sandbox so they run in parallel. CHANGELOG and architecture docs are centralised in #357. Resolves #354 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>Self-review (autopilot, link 4/7) — Verdict: APPROVE
Reviewed the full diff of PR #380 (
GET /pulls/380.diff, 661 lines, 6 files) against issue #354.Summary
The command writes only two fixed folder names and one fixed file name under the folder it is given;
todayis parsed as aNaiveDateand only ever re-emitted throughformat("%m/%Y")/format("%Y-%m"), so no byte of it reaches a path. Both templates are byte-identical to the issue body (checked withcmpon the extracted code blocks). Measured categorisation matched the frozen 33 / 36 on the first run, so no expectation was adjusted. The frozen detection corpus is untouched.Criteria
cmpagainst the issue's code blocks;wc -l= 41 eachcreate_sample_import_folders(import_folder, today) -> Result<Vec<String>, String>, doc commentfs_commands.rs, registered inlib.rsafterpick_folderNaiveDate::parse_from_str, M1/M2 viachecked_sub_monthswith_day(1)first sochecked_sub_monthsnever clamps on a 29-31Erron missing folder;Errnaming the folder when either exists, before any writeexists()checks run before the firstcreate_dir; testrefuses_when_desjardins_folder_exists_and_writes_nothingasserts Tangerine absent and the existing folder still emptyinclude_str!relative tosrc-tauri/src/commands/../../../src/__fixtures__/samples/*.csvreleve-YYYY-MM.csv(M1), returns the two folder nameswrites_both_folders_for_the_two_previous_monthsasserts["Exemple Desjardins", "Exemple Tangerine"]and exactly one file per foldersandbox(name)per case, cleanup onErrpathsunwrap_err/expect;cargo test -- --test-threads=4192 passedsrc/__fixtures__/samples/index.ts, corpuscsv/index.tsuntouchedgit diffoncsvAutoDetect.test.ts,bankSignatures.test.ts,csv/index.tsis emptynegative_expense,single, 40/40, 0 errors, +740.00 / −368.46, 33 / 36, ≥ 80 %, off-seed not categorisedcheck-rustandcheck-frontendboth run on the PRfrontendrun 673 started one4a0f10;rustqueues behind it (runner capacity 1). Both path filters match (src/andsrc-tauri/)Security
today: a string that is notYYYY-MM-DDfailsparse_from_str, and the only derived path component isreleve-YYYY-MM.csvbuilt from the parsed date.import_folderis the same trust boundary asscan_import_folder/write_export_file(a folder the webview obtained from the picker). The command cannot overwrite:create_dirfails on an existing folder, and both folders are checked first.Concerns (non-blocking)
create_dir/writefails after the first succeeded (disk full): a singleExemple Desjardinsfolder may remain. Decided in planning and documented in the doc comment.(keyword, category_id, priority) VALUES (...)shape ofconsolidated_schema.sql; the test asserts the 180 count so a shape change surfaces as a failure rather than a silent shortfall.categorizationService.test.ts:9that does not exist (line 9 is an import); a dedicated regex was written instead.Generated autonomously by /autopilot run of 2026-10-03
Revue adversariale (
/pr-review, passe indépendante) — Verdict : APPROVERevue de la PR contre sa base
issue-353-transactions-budget-empty(1 commit, 6 fichiers,8eac47f..e4a0f10). Les 5 étapes ci-dessous ont été exécutées, pas lues.Ce qui a été vérifié
exemple-desjardins.csvetexemple-tangerine.csvidentiques au byte au corps de #354 (cmp), LF, 41 lignes, saut de ligne final. Sommes recalculées depuis les fichiers : +740,00 / −368,46.INSERT INTO keywordsdansconsolidated_schema.sql, les 180 matchent la regex du test, aucune ligne hors forme.e4a0f10:PR Check — Rust(3m38) etPR Check — Frontend(1m46) ont tous deux tourné et sont verts — la pile stackée déclenche bien la CI (aucun filtrebranches:), critère d'acceptation de #354 satisfait.git archivedu head) :tsc --noEmitexit 0 ; vitestonboarding-samples+csvAutoDetect+bankSignatures= 153 verts / 3 fichiers ;cargo test sample_folder_tests= 5/5, 187 filtrés (192 au total, chiffre de la PR confirmé).pull_refde #380 aveccloses— l'issue se fermera au merge. Pas de secret, pas de migration, pas de chaîne UI (commande Rust seule, consommée par un maillon suivant).chrono:default-features = false, features = ["serde", "std"]confirmé dansCargo.toml:47→ l'argumenttodayest fondé, la commande ne lit jamais l'horloge.Mutations du relecteur (copie hors dépôt)
{{M1}}/{{M2}}(Rust)DOLLARAMA→METRO(Tangerine)expected 38 to be 36) — le gel des comptes fonctionnem1/m2dansrenderSampleTemplate{{M2}}laissé non renduSuggestions (non bloquantes — code correct, propriétés non épinglées)
fs::create_diréchoue par lui-même avec un message contenant le nom du dossier et Tangerine n'est jamais atteint : l'état observable est le même avec ou sans le contrôle. Le cas qui discrimine est « Exemple Tangerine existe → Exemple Desjardins non créé ». Sonde ajoutée dans la copie : verte sur le code de la PR, rouge sans la boucle. Le code tient ; c'est le test qui manque. Un cas de 10 lignes, à ajouter ici ou en suivi.{{M1}}= le mois le plus récent » (M2 + B2).assert_dates_inet le test vitestmonthsvérifient l'ensemble des deux mois, pas l'affectation. Sans effet pour l'utilisateur (mêmes 40 lignes), mais une assertion sur la première ligne (01/07/2026) et la dernière (28/08/2026) suffirait.onboarding-samples.test.ts:239— « the seed is all priority 0, so the SQL order is the file order » est faux :COSTCOetWALMARTsont à 50,COUCHE-TARD ESSENCEà 10. Le test trie bien parprioritydécroissante, donc le mock est juste ; seul le commentaire ment. À corriger au passage.ORDER BY priority DESC. Sans conséquence ici (aucune description des gabarits ne matche deux mots-clés de même priorité vers des catégories différentes), à garder en tête si les gabarits bougent.Actions faites
scratchpad/pr380, fichiersissue-*.csv/head-*.csv), aucun fichier du dépôt touché, aucun résidusimpl-resultat-samples-*dans/tmp.status:approved(self-review autopilot) — inchangé, verdict cohérent.maximus referenced this pull request2026-10-04 12:52:55 +00:00