The store never canonicalises the business day convention

Table of Contents

This page is a capture in the inbox bucket of the product backlog — a pre-sprint idea, not yet pulled into a sprint as a story.

1. What

The store keeps whichever business day convention spelling a document states, so the same adjustment reaches the table as F, Following or FOLLOWING, while ores.refdata.core's curve-configuration round trip expects one code.

2. Why

bc083d5498 states the invariant — "the mapping layer converts the stated spelling to the code before a row is written, so the table holds one representation" — and adds stored_business_day_convention, but never wires it into the write path. Its only caller is the round-trip test, which therefore asserts behaviour nothing implements.

Two assertions fail on origin/main and on branches that hold no codec work:

  • Example_63 ... CapFloorVolatilities.CapFloorVolatility[1]: BusinessDayConvention was lost
  • Input/curveconfig.xml ... InflationCapFloorVolatilities .InflationCapFloorVolatility[0]: BusinessDayConvention: 'ModifiedFollowing' became 'MF'

Both come from the test's own expectation, as_stored in curve_configuration_database_roundtrip_tests.cpp:

  1. It assigns the helper's std::string into an std::optional<std::string> member. For a row that states no convention the helper answers "", so the expectation gains an empty convention where the document states none. The store gives none back, and the comparison reads that as a lost value. Keeping the empty optional instead makes the case pass, which was verified by rebuilding the test that way.
  2. It canonicalises a spelling the store never canonicalises. The document says MF, as_stored maps it to ModifiedFollowing, the database holds MF. The SQL value guard (refdata_curve_configuration_columns_validate_create.sql) only refuses a value outside the set; it never rewrites one.

3. What to decide

Either the mapping layer canonicalises, which is what its commit says it does, or the round trip stops expecting it and states that the table may hold every spelling a document uses. The first keeps the invariant the SQL guard and the grouped queries rely on; the second is honest about today's behaviour.

4. References

  • projects/ores.refdata/core/include/ores.refdata.core/repository/business_day_convention_code.hpp
  • projects/ores.refdata/core/tests/curve_configuration_database_roundtrip_tests.cpp
  • projects/ores.sql/create/refdata/refdata_curve_configuration_columns_validate_create.sql

5. Resolution (2026-11-03)

Both defects are fixed. This capture was filed against a tree whose guards refused a value outside the set but stored a rendering as it stood, and both halves landed after it was written.

  • The store canonicalises. Every write path reaches a guard that asks ores_reporting_value_in_set_fn whether the value is one the set states, then ores_reporting_canonical_value_fn for the form it holds (refdata_curve_configuration_columns_validate_create.sql:71). Measured on the running environment: select count(*) from ores_reporting_canonical_value_violations_fn() answers 0, and the function names every row holding a rendering instead of its form.
  • The round trip no longer states as_stored. It writes through the store and states the promise on what the store returns: the document the rows hold exports and reads back unchanged (curve_configuration_database_roundtrip_tests.cpp:259).
  • stored_business_day_convention is deleted, with the header this page names. The value set's ore_renderings.json is the only statement of the mapping, and the reader's tables are generated from it.

Worked in Normalise every ORE parameter representation.

Emacs 29.3 (Org mode 9.6.15)