Skip to content

feat: convert sent and received exports into trades and events - #13

Merged
kelsos merged 1 commit into
mainfrom
feat/recipe-model
Oct 15, 2026
Merged

kelsos merged 1 commit into
mainfrom
feat/recipe-model

Conversation

@kelsos

@kelsos kelsos commented Oct 15, 2026

Copy link
Copy Markdown
Member

Exports of other tax tools, such as Koinly, CoinTracker, Awaken and ZenLedger files, record each transaction as what it sent and what it received: both sides make a trade, one side a deposit or a withdrawal. Until now a recipe wrote either trades or events for a whole file. This lets one recipe write both, and is what presets for those tools will be built on.

Recipe format, version 2

Version 1 recipe files keep loading, and are upgraded on load.

  • format: 'both' writes a trades file and an events file from one export. route holds the conditions a row has to meet to become a trade; every other row becomes an event.
  • Fallbacks: a field can list sources to try in order when its own source is empty, each under conditions. An event's currency comes from Received Currency, or from Sent Currency when the row received nothing.
  • Conditions test one column of a row: filled, empty, or one_of a list of values. A condition list the reader cannot fully read is dropped whole rather than loosened.

Wizard

  • Format step: a third choice, "Both, from one file", suggested when the header has sent and received amount and currency columns (Sent Amount, Received Quantity, IN Amount, Out Currency and similar).
  • Mapping step: for both, a "Trades and events" section with the rule ("a row is a trade when … and also … have a value") and how it splits the sample; the event fields grouped as "Deposits and withdrawals"; "If empty, use" for an event's currency and amount; and a checkbox that types an event with an empty label by its side, Deposit when it received and Withdrawal when it sent. A label nobody mapped is still rejected, not typed by its side. Rules and fallbacks from a recipe file that the guided controls cannot edit are described instead.
  • Samples per file: event type guesses and examples come from the rows that go to each file, so a trade's "Buy" label is not offered as an event type.
  • Preview: a table per file, each with its own columns.
  • Convert step: a download for each file that got rows, each with where to import it in rotki; the summary names the formats in words instead of their ids.

Worker

The start request names a file per format, all checked and all distinct. The worker opens only the files the recipe writes, closes every one of them on failure, and counts converted rows per format.

Testing

  • 260 unit tests, among them: version 1 upgrade and version 2 round trip, unreadable conditions, validation with fallbacks and the route; the converter routing rows, taking either side, typing by label then side, and rejecting an unmapped label; sided header detection for Koinly, CoinTracker and ZenLedger headers; the stream and the worker writing two files; the wizard routing a sample and guessing types from event rows only; the mapping step's rule section and the fallback controls.
  • 36 Playwright tests, with a new end to end run of a sided export: one trade, a deposit and a withdrawal typed by side, a staking label, and an unmapped label rejected, downloaded as two files.
  • Checked by eye at desktop and phone width: the three format choices, the mapping step for both, the preview tables and the two downloads.

Exports of other tax tools, such as Koinly, CoinTracker, Awaken and
ZenLedger files, record each transaction as what it sent and what it
received: both sides make a trade, one side a deposit or a withdrawal.
A recipe could only write trades or events for a whole file.

Recipes are now version 2, and version 1 files load unchanged:

- format `both` writes a trades file and an events file from one
  export; `route` holds the conditions that make a row a trade
- a field can have fallbacks, tried in order when its source is
  empty, each under conditions, as an event's currency from the sent
  side when the received side is empty
- a condition tests one column: filled, empty, or one of a list

The wizard offers Both as a third format and suggests it for sent and
received headers. The mapping step shows the rule for trades with how
it splits the sample, a fallback column for an event's currency and
amount, and types an event by its side when its label is empty. A label
nobody mapped is still rejected rather than typed by its side. Event
types and samples come from the rows that go to each file. The preview
shows a table per file, and the convert step offers each file that got
rows with where to import it in rotki.

The worker writes one file per format and counts rows per format. The
README describes the recipe file format.
@kelsos
kelsos requested a review from a team as a code owner October 15, 2026 11:46
@codecov

codecov Bot commented Oct 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 81.85053% with 51 lines in your changes missing coverage. Please review.
✅ Project coverage is 69.12%. Comparing base (d0292d8) to head (d40051e).

Files with missing lines Patch % Lines
src/components/wizard/StepConvert.vue 0.00% 11 Missing ⚠️
src/modules/convert/recipe.ts 83.07% 5 Missing and 6 partials ⚠️
src/components/wizard/StepMapping.vue 72.72% 3 Missing and 6 partials ⚠️
src/components/wizard/FieldMappingEditor.vue 82.85% 1 Missing and 5 partials ⚠️
src/modules/convert/row-converter.ts 86.66% 0 Missing and 4 partials ⚠️
src/modules/convert/suggest.ts 91.17% 0 Missing and 3 partials ⚠️
src/modules/convert/protocol.ts 77.77% 1 Missing and 1 partial ⚠️
src/modules/wizard/use-wizard.ts 86.66% 0 Missing and 2 partials ⚠️
src/workers/convert.worker.ts 84.61% 1 Missing and 1 partial ⚠️
src/components/wizard/StepPreview.vue 90.00% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #13      +/-   ##
==========================================
+ Coverage   66.20%   69.12%   +2.91%     
==========================================
  Files          40       40              
  Lines        1595     1807     +212     
  Branches      373      427      +54     
==========================================
+ Hits         1056     1249     +193     
+ Misses        472      471       -1     
- Partials       67       87      +20     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@kelsos
kelsos merged commit cf8b871 into main Oct 15, 2026
9 checks passed
@kelsos
kelsos deleted the feat/recipe-model branch October 15, 2026 11:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant