Cut core/model, reclaim, json, util comment bloat ~26% (comments only, zero code change)

This commit is contained in:
2026-07-29 20:49:06 -04:00
parent 1f24c4b095
commit 65ca1e1f9d
16 changed files with 400 additions and 681 deletions
+108 -208
View File
@@ -1,41 +1,13 @@
#pragma once
// bank_book — the pure core of the multi-bank phase (Phase B), deliberately free
// of any REAPER type so it compiles and unit-tests OUTSIDE the DAW. It is the
// third instance of the same "pure registry + JSON round-trip, unit-tested outside
// the DAW" pattern as bank_model and view_mode_model.
// bank_book — pure multi-bank registry: wraps N BankModel instances (bank_model
// itself is untouched — additive, no bankId on Sample). Movement between banks is
// index-only; files never relocate, banks are logical groupings over one shared pool.
//
// PURE MODULE (CLAUDE.md §load-bearing split): NO REAPER types, NO SWELL, NO
// vendor/ includes. Standard library only.
//
// -- What it is --------------------------------------------------------------
//
// An ordered registry of banks. Each bank = { stable id, display name, ordinal,
// BankModel }. The book WRAPS N BankModel instances — bank_model / BankModel are
// UNTOUCHED (additive: no bankId on Sample). Movement of samples between banks is
// index-only (remove from source's BankModel, add to destination's); files never
// relocate — banks are logical groupings over one shared file pool.
//
// -- The pool (privileged, not special-cased) --------------------------------
//
// Structurally the pool is bank-zero — one Bank among many, seeded on construction
// with a fixed id (kPoolBankId) and fixed display name (kPoolBankName), ordinal 0.
// Semantically it is privileged, and the privileges are enforced HERE in the pure
// rules layer (CONTEXT.md §Multi-bank guardrail — not deferred to a shell):
// * always exists (seeded on construction; the book never reaches zero banks)
// * un-deletable (deleteBank rejects the pool)
// * un-renamable (renameBank rejects the pool)
// * un-evacuable (evacuate rejects the pool — the pool is evacuation's
// destination, not a source)
//
// -- Id minting is the CALLER'S job (design decision) ------------------------
//
// createBank takes a caller-supplied stable id, mirroring bank_model's "id
// assigned by the caller" and view_mode_model's mode ids. The pure core has no
// REAPER genGuid / RNG and deliberately introduces none: a fake in-model id source
// would not be a real GUID anyway, and keeping ids caller-supplied lets the B2
// shell mint a genuine REAPER GUID while the model stays pure and deterministically
// testable. The model still enforces the invariants: non-empty, unique, not the
// reserved pool id.
// The pool is bank-zero (fixed id/name, ordinal 0), privileged and enforced HERE:
// always exists, un-deletable, un-renamable, un-evacuable (evacuate's destination
// only). createBank takes a caller-supplied id — REAPER GUID minting stays in the
// shell so this model stays pure and deterministic; the model still enforces
// non-empty/unique/not-reserved.
#include <optional>
#include <string>
@@ -47,26 +19,22 @@
namespace reasampler {
// Q-W1 interim: this god module re-namespaces in its own split wave; until then the
// clean model types it wraps live in reasampler::model.
// Interim: this module re-namespaces later; the model types it wraps live in
// reasampler::model.
using namespace model;
// The pool's fixed identity. The id is reserved: createBank rejects it, and the
// pool is always bank-zero. The name is fixed: renameBank rejects the pool.
// The pool's fixed identity: createBank rejects this id; renameBank rejects this name.
inline constexpr const char* kPoolBankId = "pool";
inline constexpr const char* kPoolBankName = "Pool";
// SlotMap — extracted to its own TU/header pair (Q-W1, T4-05): core/model/slot_map.h.
// Included above because Bank carries one per bank.
// One bank: a stable id, a display name, an ordinal (tab/display order), and its
// own BankModel. The pool is the bank whose id == kPoolBankId.
// One bank: id/display/ordinal/BankModel/SlotMap. The pool is the bank whose
// id == kPoolBankId.
struct Bank {
std::string id; // stable, persisted; the pool's is kPoolBankId
std::string displayName; // mutable for named banks; fixed "Pool" for the pool
int ordinal = 0; // display order; pool is 0, named banks 1..N
BankModel index; // this bank's samples
SlotMap slots; // L7 display positions of this bank's samples (gap-preserving)
SlotMap slots; // display positions of this bank's samples (gap-preserving)
bool isPool() const { return id == kPoolBankId; }
@@ -76,16 +44,12 @@ struct Bank {
}
};
// Outcome of a cross-bank sample move/copy. Mirrors AddResult's honesty: the op
// reports what happened rather than silently mutating on a bad request.
// - Moved / Copied: the sample was transferred to the destination as a new entry.
// - Collapsed: the destination already held the hash; it collapsed onto the
// existing entry (a no-op add on the destination side). For a
// MOVE the source entry is STILL removed; for a COPY the source
// entry is (as always) retained.
// - RejectedUnknownBank: a source or destination id named no bank.
// - RejectedSampleAbsent: the sample id was not in the source bank.
// - RejectedSameBank: source and destination were the same bank (no-op).
// Outcome of a cross-bank move/copy — reports what happened rather than mutating
// silently on a bad request.
// - Moved / Copied: transferred to the destination as a new entry.
// - Collapsed: destination already held the hash, collapsed onto it (move
// still removes the source; copy keeps it, as always).
// - RejectedUnknownBank / RejectedSampleAbsent / RejectedSameBank: no-op guards.
enum class TransferResult {
Moved,
Copied,
@@ -95,21 +59,18 @@ enum class TransferResult {
RejectedSameBank,
};
// Scope of a sample-remove (fork R-A, settled 2026-07-24). ThisBank is the default
// and the ONLY behavior surfaced in the UI/action layer; AllBanks is a latent seam
// live and tested at the model level, promotable later behind this parameter without
// a rewrite, but never wired to an affordance in B5.
// - ThisBank: drop the entry from the one named source bank only. A same-hash entry
// in another bank survives (no cross-bank cascade — dedup is per-bank).
// - AllBanks: drop the sample's entry from EVERY bank that holds the source id
// ("purge from the library"). Latent; unsurfaced.
// Scope of a sample-remove. ThisBank is the only behavior surfaced in the UI;
// AllBanks is a tested latent seam, not wired to any affordance.
// - ThisBank: drop the entry from the one named source bank only (no cross-bank
// cascade — dedup is per-bank).
// - AllBanks: drop the sample's entry from every bank holding it ("purge from
// the library").
enum class RemoveScope {
ThisBank,
AllBanks,
};
// Outcome of BankBook::removeSample. Mirrors TransferResult's honesty: the op reports
// what happened rather than silently mutating on a bad request.
// Outcome of BankBook::removeSample — same honesty as TransferResult.
// - Removed: at least one index entry was dropped.
// - RejectedUnknownBank: the source bank id named no bank (ThisBank scope only).
// - RejectedSampleAbsent: the sample id was in no bank in scope (nothing removed).
@@ -120,8 +81,7 @@ enum class RemoveResult {
};
// An ordered registry of banks with the pool seeded as bank-zero, per-bank sample
// indices, an active-bank pointer, and lossless JSON round-trip. The heart of the
// multi-bank phase — mirror of bank_model / view_mode_model.
// indices, an active-bank pointer, and lossless JSON round-trip.
class BankBook {
public:
BankBook(); // seeds the pool (id kPoolBankId, name kPoolBankName, ordinal 0);
@@ -129,34 +89,29 @@ public:
// -- Bank lifecycle ------------------------------------------------------
// Creates a named bank with the caller-supplied stable id and display name,
// assigning the next ordinal. Rejects (returns false, no mutation) an empty id,
// a duplicate id, the reserved pool id, or a display name that duplicates an
// existing bank's name (including the pool's "Pool"). Display-name uniqueness is
// trimmed + case-insensitive (ASCII): "Drums", "drums", and " Drums " collide.
// Creates a named bank with a caller-supplied id/display name (next ordinal
// assigned automatically). Rejects (false, no mutation) an empty/duplicate id,
// the reserved pool id, or a duplicate display name (trimmed + case-insensitive,
// ASCII — "Drums"/"drums"/" Drums " collide, including against the pool's "Pool").
bool createBank(const std::string& id, const std::string& displayName);
// Renames a named bank. Rejects (false, no mutation) an unknown id, the pool, or a
// target name already used by a DIFFERENT bank (trimmed + case-insensitive, as
// createBank). Renaming a bank to its own current name is a no-op success.
// Renames a named bank. Rejects (false, no mutation) an unknown id, the pool, or
// a name already used by another bank. Renaming to its own current name is a
// no-op success.
bool renameBank(const std::string& id, const std::string& displayName);
// Deletes a NAMED bank, removing it (and its member index entries) from the
// registry. Files are a shell/prune concern and are NOT touched here. Rejects
// (false, no mutation) an unknown id or the pool. Remaining banks' ordinals are
// compacted so the pool stays 0 and named banks stay contiguous 1..N. If the
// deleted bank was active, the active bank falls back to the pool.
// Deletes a named bank and its member entries (files untouched — a shell/prune
// concern). Rejects (false, no mutation) an unknown id or the pool. Remaining
// ordinals compact after; if the deleted bank was active, falls back to the pool.
bool deleteBank(const std::string& id);
// Reorders a NAMED bank to `newOrdinal` (clamped into the named-bank range),
// shifting the others to keep ordinals contiguous. The pool is pinned at 0 and
// cannot be reordered. Rejects (false, no mutation) an unknown id or the pool.
// Reorders a named bank to newOrdinal (clamped into range, others shift to stay
// contiguous). The pool is pinned at 0. Rejects an unknown id or the pool.
bool reorderBank(const std::string& id, int newOrdinal);
// Moves EVERY member of a named bank into the pool (index-only, observing the
// same destination-collapse-by-hash as a move), leaving the bank empty. Rejects
// (false, no mutation) an unknown id or the pool (the pool is the destination,
// never a source). Returns true on success even if the bank was already empty.
// Moves every member of a named bank into the pool (index-only, same
// destination-collapse-by-hash as a move). Rejects an unknown id or the pool
// (the pool is only ever a destination). Returns true even if already empty.
bool evacuate(const std::string& id);
// -- Active bank ---------------------------------------------------------
@@ -164,123 +119,83 @@ public:
// The active bank's id (the capture target). Defaults to the pool.
const std::string& activeBankId() const { return activeBankId_; }
// Sets the active bank. Rejects (returns false, no change) an id that names no
// bank — an invalid set never corrupts state.
// Sets the active bank. Rejects (false, no change) an id that names no bank.
bool setActiveBank(const std::string& id);
// The active bank's BankModel — the index the capture layer adds to. Always
// valid (the active id always names a live bank; it falls back to the pool).
// The active bank's BankModel — always valid (falls back to the pool).
BankModel& activeIndex();
const BankModel& activeIndex() const;
// -- Sample movement (index-only; files never relocate) ------------------
// Moves a sample by id from `fromBankId` to `toBankId`: removes it from the
// source index and adds it to the destination (observing destination
// collapse-by-hash). See TransferResult for the full outcome set.
// Moves a sample by id between banks (destination collapse-by-hash observed).
// See TransferResult for the full outcome set.
TransferResult moveSample(const std::string& sampleId,
const std::string& fromBankId,
const std::string& toBankId);
// Copies a sample by id from `fromBankId` to `toBankId`: the source entry is
// retained, the destination gains it (observing destination collapse-by-hash).
// Same hash may then live in both banks — cross-bank dedup is NOT enforced.
// Copies a sample by id between banks, source retained (destination
// collapse-by-hash observed). Cross-bank dedup is NOT enforced — the same hash
// may then live in both banks.
TransferResult copySample(const std::string& sampleId,
const std::string& fromBankId,
const std::string& toBankId);
// -- Sample removal (index-only; the file is NEVER touched — orphaned until prune) --
// Drops a sample's index entry (the sample-level sibling of move/copy/evacuate).
// Index-only and non-destructive to the file: a last-reference remove leaves the
// file on disk, orphaned until Phase R prune — remove NEVER deletes bytes.
//
// Scope (fork R-A): ThisBank (default, the only surfaced verb) drops the entry from
// `fromBankId` alone; AllBanks (latent seam) drops the sample id from every bank
// that holds it. See RemoveResult for the outcome set.
// * ThisBank: RejectedUnknownBank if `fromBankId` names no bank; RejectedSampleAbsent
// if that bank does not hold the id; Removed on a drop.
// * AllBanks: `fromBankId` is ignored (the id is purged book-wide);
// RejectedSampleAbsent if NO bank held the id; Removed otherwise.
// No mutation occurs on any Rejected outcome (no-op guardrail for the undo layer).
// Drops a sample's index entry — non-destructive to the file (a last-reference
// remove leaves it on disk, orphaned until prune reclaims it). See RemoveScope/
// RemoveResult for scope and outcome. No mutation on any Rejected outcome.
RemoveResult removeSample(const std::string& sampleId,
const std::string& fromBankId,
RemoveScope scope = RemoveScope::ThisBank);
// -- Sample display order (L7; index membership untouched) ---------------
// -- Sample display order (index membership untouched) -------------------
// The bank's sample ids in DISPLAY (slot) order — the deterministic order the grid
// iterates, sourced from the bank's SlotMap. Reconciles the map against live index
// membership first (drops stale markers, appends unmapped samples densely), so a
// freshly-migrated or out-of-band-mutated bank always yields a complete order. An
// unknown bank id yields an empty vector. Const-logical but reconciles lazily, so
// it is a non-const member.
// The bank's sample ids in display (slot) order, reconciled against live index
// membership first (drops stale markers, appends unmapped samples densely). An
// unknown bank id yields an empty vector.
std::vector<std::string> orderedSampleIds(const std::string& bankId);
// Ensures every bank's SlotMap is consistent with its index membership: seeds a
// map that has NO overlap with its index from insertion order (the pre-L7 migration
// default — dense, no gaps), and reconciles a partially-populated map (drop stale,
// append unmapped). Idempotent. Called after deserialize and after any capture/
// transfer that added samples out-of-band of the L7 reorder path.
// Ensures every bank's SlotMap is consistent with its index membership (seeds a
// dense order, or reconciles a partial map). Idempotent — call after deserialize
// or any out-of-band membership change.
void reconcileSlots();
// Reorders sample `id` within `bankId` to `targetSlot` (gap-preserving; see
// SlotMap::reorder). INDEX-ONLY of positions — the sample's membership, file, and
// metadata are untouched (capture != placement holds). Reconciles the bank's slots
// first so the target space is complete. Returns false (no mutation) on an unknown
// bank or an id the bank does not hold.
// Reorders sample `id` within `bankId` to targetSlot (gap-preserving; index/file/
// metadata untouched). Returns false (no mutation) on an unknown bank or id.
bool reorderSample(const std::string& id, const std::string& bankId, int targetSlot);
// Alt-replace (L7 F3): the dragged sample `newId` (already a member of `bankId`)
// takes the slot of the occupant `oldId`, and `oldId` is REMOVED from `bankId`'s
// index (index-only, same semantics as removeSample ThisBank — the file stays on
// disk; owned-manifest/prune govern bytes; hashReferencedElsewhere handles the
// last-reference case). Position of the slot is preserved; only its occupant changes.
// Alt-replace: the dragged `newId` (already a member of bankId) takes the slot of
// `oldId`, and `oldId` is removed from the index (same semantics as removeSample
// ThisBank). Position is preserved; only the occupant changes.
//
// POOL GUARD (settled): the index-removal of `oldId` passes the SAME guard the
// remove verb applies — removeSample(oldId, bankId, ThisBank) must return Removed.
// For the pool this is permitted whenever the occupant exists (per-sample removal
// is not a pool privilege violation — the pool's guards are un-delete/rename/evacuate,
// never per-sample remove). If the removal would be rejected (occupant absent), the
// whole replace is rejected: false, NO mutation (neither the index nor the slots
// change), so the shell can fall back to the default insert-shift or a no-op.
// Rejects (false, no mutation) an unknown bank, a `newId`/`oldId` the bank does not
// hold, or `newId == oldId`. NEVER touches disk; introduces no new deletion authority.
// Applies the same pool guard as removeSample — per-sample removal from the pool
// is allowed (the pool's guards are un-delete/rename/evacuate, never per-sample
// remove). Rejects (false, no mutation of either index or slots) an unknown bank,
// a newId/oldId the bank doesn't hold, or newId == oldId.
bool replaceSample(const std::string& newId, const std::string& oldId,
const std::string& bankId);
// Refreshes a sample IN PLACE wherever it lives in the book (M10 re-capture):
// finds the bank holding `sampleId` and replaces its entry with `updated`
// (order-preserving, no dedup — see BankModel::updateInPlace). Scans banks in
// ordinal order and updates the FIRST holder (a sample id is unique within a
// bank; the same id living in two banks via copy would update the earliest, which
// is acceptable — re-capture operates on the panel's focused single selection).
// Returns false (no mutation) if no bank holds the id or the replacement's path
// is absolute. Index-only and non-destructive to the timeline.
// Refreshes a sample in place wherever it lives (re-capture): finds the bank
// holding sampleId and replaces its entry with `updated` (order-preserving, no
// dedup). Updates the FIRST holder in ordinal order if the id lives in multiple
// banks via copy. Returns false (no mutation) if no bank holds the id or the
// replacement's path is absolute.
bool updateSampleInPlace(const std::string& sampleId, const Sample& updated);
// Reference-count query backing the confirm-on-last-reference guardrail: does any
// bank OTHER than `exceptBankId` still hold an entry whose contentHash == `hash`?
//
// Identity is the CONTENT HASH, not the file path: hash is the canonical dedup key
// the whole model already reasons in (findByHash / collapse-by-hash), and two
// entries that share content share one file — so "some other bank still references
// this hash" is exactly "removing here does not orphan the file." An EMPTY hash is
// never matched (it does not participate in dedup, mirroring findByHash), so an
// empty-hash sample reads as referenced-nowhere-else — the safe, confirm-eliciting
// direction (we cannot prove another bank shares an unhashed file).
// Does any bank other than exceptBankId still hold an entry whose contentHash
// == hash? Backs the confirm-on-last-reference guardrail: two entries sharing a
// hash share one file, so this answers "would removing here orphan the file."
// An empty hash never matches (mirrors findByHash) — reads as
// referenced-nowhere-else, the safe confirm-eliciting default.
bool hashReferencedElsewhere(const std::string& hash,
const std::string& exceptBankId) const;
// Every project-relative file path referenced by ANY bank in the book, pool
// included — the union across the whole book (Phase R, prune). This is the
// safety-critical referenced-set the prune core subtracts: a file referenced by
// any bank (INCLUDING via a copy into a second bank) appears here, so prune never
// reclaims it. Paths are returned VERBATIM (Sample.relativePath, exact strings —
// no normalization), first-seen order across banks in ordinal order then sample
// insertion order, and DE-DUPLICATED (one file referenced by N banks appears
// once). An empty relativePath is skipped (it references no file). Additive
// read-only query; adds no mutation and no coupling to Phase R.
// Every project-relative path referenced by any bank (pool included) — the union
// prune subtracts against. Paths are verbatim (no normalization), first-seen
// order across banks in ordinal then insertion order, de-duplicated. An empty
// relativePath is skipped.
std::vector<std::string> referencedPaths() const;
// -- Query ---------------------------------------------------------------
@@ -308,33 +223,25 @@ public:
// -- Persistence ---------------------------------------------------------
// Serializes the whole book to a JSON string (lossless round-trip): the pool
// folded in as bank-zero + named banks + per-bank indices + ordinals + active
// id. deserialize(serialize(x)) == x.
// Serializes the whole book to JSON (lossless): pool as bank-zero + named banks
// + per-bank indices + ordinals + active id. deserialize(serialize(x)) == x.
std::string serialize() const;
// Parses a book JSON produced by serialize(). std::nullopt on malformed input.
//
// LEGACY MIGRATION: a bare legacy bank_index JSON (the pre-multi-bank shape, an
// object with a "samples" array and no "banks" key) is promoted into the pool's
// index, yielding a book of { pool } with zero named banks — one-way, lossless.
// After migration the book blob is authoritative (the caller persists the book
// shape going forward; the legacy key is retired by the B2 shell).
// A bare legacy bank_index JSON (pre-multi-bank shape: a "samples" array, no
// "banks" key) is promoted into the pool's index — one-way, lossless — yielding
// a book of { pool } with zero named banks.
static std::optional<BankBook> deserialize(const std::string& json);
// Resolve a BankBook from the two persisted ext-state values a project may carry:
// the authoritative `banks` blob and the retired-but-possibly-present legacy
// `bank_index` blob. The persist shell (B2) hands both raw strings straight here so
// the load-source decision stays REAPER-free and unit-tested. Precedence:
// 1. non-empty `banksJson` present -> deserialize it (authoritative). If it is
// MALFORMED, do NOT silently fall back to the legacy blob — a corrupt `banks`
// blob is an error, not an absence; return an empty book so a stale legacy key
// can never resurrect a superseded single-bank state over a broken book.
// 2. else non-empty `legacyJson` -> deserialize it (one-way pool migration).
// 3. else (both absent/empty) -> a fresh empty book (pool only).
// Never returns nullopt: an unloadable input degrades to the empty book (matching
// the shell's existing "malformed -> ignore, start empty" behaviour), so the caller
// has one branchless install path.
// Resolves a BankBook from the two persisted ext-state values a project may
// carry: the authoritative `banksJson` and the retired legacy `bank_index` blob.
// 1. non-empty banksJson -> deserialize it. If malformed, do NOT fall back to
// legacy — a corrupt banks blob is an error, not an absence; returns an
// empty book so a stale legacy key can never resurrect superseded state.
// 2. else non-empty legacyJson -> deserialize it (pool migration).
// 3. else -> a fresh empty book (pool only).
// Never returns nullopt — an unloadable input degrades to the empty book.
static BankBook loadFromPersisted(const std::string& banksJson,
const std::string& legacyJson);
@@ -343,41 +250,34 @@ private:
std::string activeBankId_; // always names a live bank; defaults to pool
// Folds a display name to its uniqueness key: strip leading/trailing ASCII
// whitespace, lower-case ASCII letters. So "Drums", "drums", and " Drums " share
// one key and cannot coexist. ASCII-only by design — the pure core carries no
// locale facility and must not grow one. A private STATIC member (Q-W5, settled)
// because BOTH halves of the split implementation need the ONE folding rule: the
// rules TU (bank_book.cpp, displayNameTaken) and the JSON TU (bank_book_json.cpp,
// deserialize's duplicate-display-name coalesce) — a drifted second copy would let
// a parsed book violate the create/rename uniqueness invariant.
// whitespace, lower-case ASCII letters — so "Drums"/"drums"/" Drums " share one
// key. ASCII-only by design — the pure core carries no locale facility. Private
// static because both halves of the split implementation (rules + JSON) need the
// one folding rule; a drifted second copy would let a parsed book violate the
// create/rename uniqueness invariant.
static std::string nameKey(const std::string& s);
// True if a bank OTHER than `exceptId` already carries `name`'s uniqueness key
// (trimmed + case-insensitive, ASCII). Backs the create/rename uniqueness check;
// pass exceptId=id to let a bank keep (or re-case/-space) its own name.
// True if a bank other than exceptId already carries name's uniqueness key.
// Backs the create/rename uniqueness check; pass exceptId=id to let a bank keep
// (or re-case/-space) its own name.
bool displayNameTaken(const std::string& name, const std::string& exceptId) const;
// Re-sorts banks_ by ordinal (pool pinned first) and rewrites ordinals to a
// contiguous 0..N-1 so the pool is 0 and named banks are 1..N. Called after any
// structural change (create / delete / reorder).
// contiguous 0..N-1. Called after any structural change (create/delete/reorder).
void normalizeOrdinals();
// Replaces the book's banks with a parsed set, normalizes ordinals, and resolves
// the active bank (falling back to the pool if the id names no bank). Used only
// by deserialize; kept private so the public surface stays create/rename/etc.
// by deserialize.
void adoptBanks(std::vector<Bank>&& banks, const std::string& activeBank);
};
// The next bank id to activate when cycling the active bank forward, in ordinal
// order (the ids arrive pool-first, named 1..N, matching banks()). Wraps: the id
// after the last returns the first (pool → named → … → pool). This is the pure
// decision behind the "cycle active bank" action — the shell reads the book's
// ordered bank ids + current active id, asks for the next, and activates it.
// order (pool -> named -> ... -> pool, wraps). Free function (not a member) so it's
// unit-testable against a bare id vector without a full book.
// * empty list -> "" (nothing to cycle to)
// * single id (pool-only) -> that id (a one-bank book stays put)
// * currentBankId not present -> the first id (a sane home to jump to)
// Exposed as a free function (not a BankBook member) so it is unit-testable against
// a bare id vector without a full book. Mirror of view_mode_model's nextModeId.
std::string nextBankId(const std::vector<std::string>& orderedBankIds,
const std::string& currentBankId);