ORE Import Wizard Design
Table of Contents
Overview
A multi-page import wizard that scans a directory tree of ORE XML files and imports currencies, portfolios, books, and trades into the database in a single guided operation. The wizard eliminates the need to manually import files one at a time, and is designed to handle large ORE example directories (300+ portfolio files) without becoming unmanageable.
Goals
- Point at any ORE example directory and import currencies, portfolios, books, and trades with minimal manual input.
- Derive the portfolio/book hierarchy automatically from the directory structure.
- Reuse existing importers in
ores.orewithout duplicating parsing logic. - Keep all reusable logic outside of Qt so the same pipeline can be driven from the shell in a future story.
Entry Points
- Main menu:
File → Import ORE Data... - Portfolio explorer toolbar button.
Success Criteria
- User points at any ORE example directory and the wizard guides them to a fully imported set of currencies, portfolios, books, and trades.
- If the user cancels before the import page, nothing is sent to the server.
- The wizard is extensible — future ORE entity types slot in as new pages.
Prerequisites
Protocol Refactoring: Batch Save Messages
A non-backwards-compatible sweep of the binary protocol: every save_*_request
is changed from carrying a single entity to std::vector<entity>. The response
changes to std::vector<save_result> (one result per item).
This is implemented as a separate story before the wizard work begins.
Repository Layer
Each repository gets a bulk write overload:
repo_.write(ctx_, std::vector<entity> entities);
This executes a single multi-row INSERT inside one transaction. The existing
single-entity write delegates to the bulk overload with a one-element vector —
no duplicated SQL logic.
Client-Side Helpers
Each request type provides a static factory to avoid scattered vector construction at call sites:
// Used by import wizard save_currency_request::from(std::vector<currency> currencies); // Used by all existing single-entity callers (edit dialogs, etc.) save_currency_request::from(currency c); // wraps in one-element vector
Existing callers change from save_currency_request{c} to
save_currency_request::from(c) — a mechanical sweep with no logic change.
Protocol Version
Bumped once for the entire sweep.
Architecture
Component Responsibilities
ores.ore (pure logic, no Qt, reusable from shell) ore_directory_scanner -- file tree classification ore_hierarchy_builder -- directory paths → portfolio/book tree ore_import_planner -- user choices → ore_import_plan ores.qt (presentation only) OreImportWizard -- QWizard, 7 pages, holds shared state OreImportController -- wires up menu + explorer entry points
All planning and data transformation happens in ores.ore. The Qt layer
collects user choices and executes the plan via ClientManager. The shell
command (future story) will collect choices from command-line flags and execute
the same plan the same way.
New Classes in ores.ore
ore_directory_scanner
Location: ores.ore/include/ores.ore/scanner/
Takes a root path and an exclusion list. Walks the file tree and returns a
scan_result struct:
struct scan_result { std::vector<std::filesystem::path> currency_files; // currencyconfig.xml std::vector<std::filesystem::path> portfolio_files; // portfolio*.xml std::vector<std::filesystem::path> ignored_files; };
Classification rules:
- Files named
currencyconfig.xml→ currency. - Files matching
portfolio*.xml→ portfolio. - Everything else → ignored.
- Files inside excluded folder names are treated as ignored.
ore_hierarchy_builder
Location: ores.ore/include/ores.ore/hierarchy/
Takes portfolio_files, the root path, and the exclusion list. For each file:
- Compute the path relative to root.
- Strip any path components that appear in the exclusion list.
- All remaining components except the last become nested portfolios.
- The last component becomes a book.
Returns a tree of import_node structs:
struct import_node { std::string name; enum class node_type { portfolio, book } type; std::optional<std::size_t> parent_index; // index into flat node list std::vector<std::filesystem::path> source_files; // portfolio XMLs (books only) };
Re-runs synchronously when the exclusion list changes — cheap enough to not require async.
ore_import_planner
Location: ores.ore/include/ores.ore/planner/
Takes a scan_result, a set of existing ISO codes (fetched from server), and
user choices:
struct import_choices { currency_import_mode currency_mode; // all | missing_only std::string parent_portfolio_name; // existing name or new name bool create_parent_portfolio; // true = create new std::vector<std::string> exclusions; // folder name exclusion list trade_defaults defaults; // dates, lifecycle event, counterparty }; enum class currency_import_mode { all, missing_only };
Returns an ore_import_plan: a pure data structure describing exactly what will
be created — no server calls, no Qt.
struct ore_import_plan { std::vector<refdata::domain::currency> currencies; std::vector<refdata::domain::portfolio> portfolios; std::vector<refdata::domain::book> books; std::vector<ore::xml::trade_import_item> trades; };
New Classes in ores.qt
OreImportWizard
QWizard subclass. Holds all shared wizard state:
- Root path, scan result, exclusion list
- Hierarchy tree (
std::vector<import_node>) - Currency import mode
- Trade defaults
- Import results (counts, failure details)
Seven QWizardPage subclasses, one per page (see Wizard Page Flow).
OreImportController
Wired to the main menu action and the portfolio explorer toolbar signal. Creates and shows the wizard. No other responsibilities.
Wizard Page Flow
[1. Welcome] → [2. Directory] → [3. Scan Summary] → [4. Currencies]
→ [5. Hierarchy] → [6. Trades] → [7. Done]
Page 1 — Welcome
Brief explanation of what the wizard does and what it will create. No inputs.
Page 2 — Directory Selection
Folder picker. On Next, an async scan runs (progress spinner) using
ore_directory_scanner. Result stored in wizard state. Next is disabled until
the scan completes.
Page 3 — Scan Summary
Read-only counts: "Found N currency files, M portfolio files, K files ignored."
Editable exclusion list (default: [Input]) — add/remove folder name entries.
Changing the list re-runs ore_hierarchy_builder live to update the hierarchy
shown on page 5.
Next is disabled until the scan result is available.
Page 4 — Currency Import
Fetches existing ISO codes from the server asynchronously when the page is shown.
Shows a table of all unique currencies found across all currency files.
Toggle:
- Import all — sends all currencies (temporal DB creates new versions for existing ones).
- Import missing only — skips ISO codes already in the database.
Page 5 — Portfolio Hierarchy
Parent portfolio selector: dropdown of existing portfolios fetched from server, plus a "Create new top-level portfolio…" option that prompts for a name.
Below the selector: a read-only tree view showing exactly which portfolios and
books will be created, derived by ore_hierarchy_builder from the directory
structure and current exclusion list.
Page 6 — Trade Import
Global defaults panel:
- Trade date, effective date, termination date
- Lifecycle event (New, Amendment, Novation, etc.)
- Default counterparty
Progress bar and cancel button. Import starts automatically when the page is shown:
- Build the
ore_import_planviaore_import_planner. - Send
save_currencies_request(one batch message). - Send
save_portfolios_request(one batch message). - Send
save_books_request(one batch message). - Send
save_trades_request(one batch message — potentially very large).
Each step updates the progress bar. Cancel is available between steps; since each step is a single atomic server call, cancelling between steps leaves only fully-committed entity types in the DB (currencies only, or currencies + portfolios, etc.). A future enhancement could add a rollback call.
Page 7 — Done
Summary: "Imported N currencies, created M portfolios, K books, P trades. Q trades failed."
Expandable "View failures" section showing per-trade error details.
Data Flow
User picks directory
↓
ore_directory_scanner(root, exclusions)
↓
scan_result{currency_files, portfolio_files, ignored_files}
↓
User reviews summary, edits exclusion list
↓
ore_hierarchy_builder(portfolio_files, root, exclusions)
↓
std::vector<import_node> (tree of portfolios + books)
↓
User selects parent portfolio, currency mode, trade defaults
↓
ore_import_planner(scan_result, existing_iso_codes, choices)
↓
ore_import_plan{currencies, portfolios, books, trades}
↓
ClientManager → save_currencies_request (one DB transaction)
→ save_portfolios_request (one DB transaction)
→ save_books_request (one DB transaction)
→ save_trades_request (one DB transaction)
Testing Strategy
ores.ore Unit Tests (Catch2, no server required)
ore_directory_scanner: given a synthetic in-memory file tree, verify correct classification; verify exclusion list suppresses matching folder names.ore_hierarchy_builder: given a set of file paths + exclusion list, verify the correct portfolio/book tree is produced; test edge cases (single-level path, all segments excluded, path with only excluded segments).ore_import_planner: given a scan result + choices, verify theore_import_plancontains the expected currencies (all vs missing), correct portfolio/book nesting, and correct trade count.
Protocol Unit Tests
save_*_request::from(single)andfrom(vector)round-trip serialisation.- Verify
from(single)produces a one-element vector identical tofrom(vector_of_one).
Repository Integration Tests
- Bulk
write(ctx_, vector<entity>)for currencies, portfolios, books, trades. - Verify all-or-nothing: if one entity in the vector violates a constraint, none are committed.
Qt Wizard
No automated tests for the UI pages (consistent with existing wizard pattern).
Logic is fully covered by the ores.ore unit tests above.
Open Questions
- Should cancelling mid-import (e.g. after currencies but before trades) trigger a compensating delete call? Deferred to a future story.
- The trade batch may be very large (tens of thousands of rows). If the server imposes a message size limit, the trade batch may need to be chunked. To be investigated during implementation.