Task: Test ORE import/export of FX conventions against currency_pair

Table of Contents

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 example conventions.xml with an <FX> element is imported; every field lands on currency_pair=/=currency_pair_convention with 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 of ores.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:
    • Id is not preserved — reverse_fx regenerates it as <base>-<quote>-FX-CONVENTIONS rather than keeping the source XML's original Id (e.g. FX-ECB-EUR-USD becomes EUR-USD-FX-CONVENTIONS).
    • The real example file has two <FX> entries for the same pair (EUR/USD: EUR-USD-FX-CONVENTIONS with SpotDays=0 and FX-ECB-EUR-USD with SpotDays=2) — since currency_pair_convention is keyed 1:1 by pair_code (by design), only one survives import. Asserted explicitly in fx_convention_roundtrip_duplicate_pair_collision_is_documented.
    • pip_factor=/=PointsFactor reciprocal conversion (1/x then 1/that) round-trips cleanly for all 24 real-file entries (all use PointsFactor=10000) — verified via Approx().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:

  1. Field-by-field round-trip for all 24 <FX> entries in the real example file (external/ore/examples/Input/conventions.xml).
  2. 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.
  3. 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.

Emacs 29.3 (Org mode 9.6.15)