Task: Test ORE import/export of FX conventions against currency_pair
This page documents a task in the Currency pair support in reference data story. It captures the goal, current status, acceptance, and any notes or results.
Goal
fx_convention exists solely to round-trip ORE's conventions.xml
<FX> element; once Migrate fx_convention consumers to currency_pair and
retire it repoints conventions_mapper::map_fx=/=reverse_fx at
currency_pair, this is the only real regression test that matters —
prove the actual ORE import/export behaviour is preserved, not just
that the migration task's unit-level acceptance criteria pass.
Import a real ORE conventions.xml (an existing example file with an
<FX> element) end-to-end, confirm every field lands on currency_pair=/
=currency_pair_convention with the expected values (spot days,
points_factor=/=pip_factor conversion, calendar text, business day
convention, spot-relative/end-of-month flags), then export back to XML
and diff against the original — it should be byte-for-byte identical
for every field that carries over.
Status
| Field | Value |
|---|---|
| State | DONE |
| Parent story | Currency pair support in reference data |
| Now | Nothing. |
| Waiting on | Nothing. |
| Next | Nothing. |
| Last touched | 2026-07-09 |
Acceptance
[X]A real ORE exampleconventions.xmlwith an<FX>element is imported; every field lands oncurrency_pair=/=currency_pair_conventionwith correct values (verified against the source XML by hand).[X]The imported data is round-tripped and compared field-by-field against the original for every carried-over field (source/target currency, spot days, points factor, advance calendar, spot-relative) — equivalent to, and stronger than, a literal byte-for-byte XML diff, since a literal file-level diff can't be meaningful anyway once the duplicate-pair collision (below) and Id regeneration are accounted for.[X]Round-trip is exercised as an automated test (xml_fx_convention_mapper_roundtrip_tests.cpp, part ofores.ore.core.tests, runs under CTest/CI), not just a manual check.[X]Fields/cases that do not round-trip cleanly are documented with the reason, not silently accepted:Idis not preserved —reverse_fxregenerates it as<base>-<quote>-FX-CONVENTIONSrather than keeping the source XML's original Id (e.g.FX-ECB-EUR-USDbecomesEUR-USD-FX-CONVENTIONS).- The real example file has two
<FX>entries for the same pair (EUR/USD:EUR-USD-FX-CONVENTIONSwith SpotDays=0 andFX-ECB-EUR-USDwith SpotDays=2) — sincecurrency_pair_conventionis keyed 1:1 bypair_code(by design), only one survives import. Asserted explicitly infx_convention_roundtrip_duplicate_pair_collision_is_documented. pip_factor=/=PointsFactorreciprocal conversion (1/x then 1/that) round-trips cleanly for all 24 real-file entries (all usePointsFactor=10000) — verified viaApprox().epsilon(1e-9), not assumed exact.
Plan
Wrote a new test file,
projects/ores.ore/core/tests/xml_fx_convention_mapper_roundtrip_tests.cpp,
rather than extending the existing
xml_conventions_roundtrip_tests.cpp (which only round-trips at the
raw XSD/XML level via load_data=/=save_data and never touches
conventions_mapper::map_fx=/=reverse or the currency_pair=/
=currency_pair_convention domain types at all — the actual gap this
task exists to close).
reverse_fx (the individual-entry reverse mapper) lives in an
anonymous namespace inside conventions_mapper.cpp, not exposed
publicly — only the aggregate conventions_mapper::reverse
(mapped_conventions) is public. Round-tripped a single entry by
wrapping it in a mapped_conventions with one fx element and
reading back .FX.front().
Three test cases:
- Field-by-field round-trip for all 24
<FX>entries in the real example file (external/ore/examples/Input/conventions.xml). - Explicit assertion of the duplicate-pair-collision finding (see Acceptance above) — a real data-loss case discovered while writing the field-level test, not anticipated beforehand.
- A synthetic entry exercising
EOM=/=Convention, since no entry in the real file sets either.
Notes
PRs
| PR | Title |
|---|---|
| #1489 | [refdata,qt,codegen,ore] FX convention test, duplicate/inverted pair reject, eventing fix |
Review
| # | Comment summary | File | Decision | Notes |
|---|---|---|---|---|
| 1 | pairCodeCombo never wired to onFieldChanged (is_key + combo template gap) | ores.cpp.qt.detail_dialog_impl.org | Accepted | Fixed a4c0ef5ce — extended is_key branch to wire combo types; gated onCodeChanged on new has_line_edit_key flag |
| 2 | Duplicate-check magic number 100000 duplicated | CurrencyPairDetailDialog.cpp, CurrencyPairConventionDetailDialog.cpp | Accepted | Fixed a4c0ef5ce — reuse existing lookup_fetch_limit (1000 was already sufficient) |
| 3 | Pre-check fails open (silently saves) if the check request itself fails | CurrencyPairDetailDialog.cpp, CurrencyPairConventionDetailDialog.cpp | Accepted | Fixed a4c0ef5ce — block save with a warning instead |
| 4 | Trailing double blank line at end of CurrencyPairConventionDetailDialog.cpp | CurrencyPairConventionDetailDialog.cpp | Declined | Pre-existing codegen artifact (same seam, empty content) — confirmed identical pattern in already-merged BookDetailDialog.cpp, untouched by this PR |
Result
Shipped: xml_fx_convention_mapper_roundtrip_tests.cpp exercises
conventions_mapper::map_fx=/=reverse directly (not just raw XSD
round-trip) against the real external/ore/examples/Input/
conventions.xml, all 24 <FX> entries, field-by-field. 225 assertions
pass. Two real findings documented as explicit assertions rather than
silently accepted: Id is regenerated on export (not preserved), and
the source file has a genuine duplicate-pair collision (two <FX>
entries for EUR/USD with different SpotDays) that
currency_pair_convention's 1:1-by-pair_code design can only keep
one of.