Story: Normalise every ORE parameter representation

Table of Contents

This page documents a story in Sprint 29. It captures the goal, current status, acceptance criteria, and the tasks that compose it.

1. Goal

Every ORE parameter whose reader accepts more than one rendering of a value is stored in one canonical representation, and the map from a rendering to that representation is data the database holds, not a table compiled into a binary. The value set states both what is accepted and what is canonical, so the screen offers one form and the store keeps one form, and a spelling ORE adds later is a row rather than a release.

2. Status

Field Value
State DONE
Parent sprint Sprint 29
Now Nothing.
Waiting on Nothing.
Next Nothing.
Last touched 2026-11-08

3. Acceptance

The story closes on the tasks it finished:

  • Consolidate the ORE element maps.
  • Take the census of the representations ORE accepts.
  • Scaffold story: Normalise every ORE parameter representation.

4. Tasks

Task State Start End Description
Consolidate the ORE element maps DONE 2026-10-31 2026-10-31 Runs first. Three tables name a leaf column and the set that governs it; measured, the two column maps are disjoint by schema, the third duplicates four rows of the contract, and 54 leaves the guards check have no rule. The story makes one map keyed by the ORE element, which every later task reads a set through.
Scaffold story: Normalise every ORE parameter representation DONE 2026-10-30 2026-10-30 Story scaffolding rides this task: documents, sprint wiring, and the scaffold PR. Close it before merging that PR.
Take the census of the representations ORE accepts DONE 2026-10-30 2026-10-30 Initial task for: Normalise every ORE parameter representation

5. Analysis

5.1. What a "rendering" is

A rendering is a string a document or a caller states for a value whose meaning is one of a closed set. ORE accepts more than one rendering for four kinds of value, and the store keeps whichever arrived:

Class Example What ORE accepts Where the difference shows
Alias spellings business day convention 27 strings for 7 conventions a query groups into 27 rows for 7 facts
Case-insensitive words ReversionType, VolatilityType, IrVolatilityType, SensitivityDecomposition any case, because the parser upper-cases before comparing bootstrap and Bootstrap are two rows
Flags every boolean setup and product parameter Y, YES, TRUE, 1, true, yes at different layers Y, true and yes are three rows for one switch
Index and code lists day counters, currencies aliases as separate registry rows (ACT/360, Act/360, A360) the picker offers the alias and the store keeps it

A fifth kind looks the same but is not this class: Model, Engine and a sensitivity template code name a thing the document defines, so their valid set is open and nothing about them can be canonicalised.

The count of case-insensitive comparisons in the ORE source is twenty across irmodeldata.cpp, lgmdata.cpp and bondutils.cpp, against the three the value set audit marked, so the audit under-counted that class.

5.2. Where the knowledge lives today, and why that is the defect

projects/ores.refdata/core/include/ores.refdata.core/repository/business_day_convention_code.hpp holds the 27-to-7 crosswalk as a compiled table. It solved the one case that broke a test and a picker, and it is exactly the shape the story forbids for the general case: ORE adds a spelling, and a release has to follow. Twenty-seven strings of engine knowledge in a binary is a copy that will drift.

5.3. The shape the data should take

Three placements were considered.

5.3.1. A. The value set states the canonical form (recommended)

The value set already enumerates what is accepted. Adding one nullable column to ores_reporting_ore_parameter_value_set_values_tbl — canonical_value, empty when the value is its own canonical form — turns the enumeration into a crosswalk without a second statement. The same attribute answers the question the business day convention picker raised: a value is offered when its canonical is empty, and an alias is accepted but not offered. One attribute, one rule, and the change to ORE is a row.

The mapping tables the tree already holds select the set: the row in ores_reporting_ore_parameter_setting_value_sets_tbl resolves a parameter, and the row in ores_reporting_ore_parameter_vocabularies_tbl resolves a curve or convention leaf element, to the one set that states its values. So the conversion needs no new mapping table and no compiled map: it needs the canonical pointer inside the values of the set the link already names. That is why this placement is a column and not a table.

5.3.2. B. A crosswalk table per entity

A *_spellings_tbl beside each registry, in the shape of ores_refdata_curve_configuration_columns_tbl. Precise, but it states the accepted enumerations a second time.

5.3.3. C. One generic alias table

(domain, alias, canonical) for everything. Small, but it loses the tie to the value set, so a spelling could exist with no set that accepts it.

5.4. Where the conversion runs

5.4.1. The repository hands the crosswalk to the mapper (recommended)

The mapper stays a pure function of (domain, crosswalk) and keeps the conversion, which is the decision already taken: the mapping is responsible for converting between representations. The repository loads the crosswalk once per write batch, so the database is read, and the mapper remains testable without one.

5.4.2. A trigger normalises before the guard

Data-driven and impossible to bypass, but it puts a second rule where the guard already lives, and the domain never sees the conversion.

5.4.3. The XML codec normalises

Rejected: it breaks the corpus test that proves a document survives read and write untouched, and it would put database knowledge in a codec.

5.5. What it costs

  • A backfill: rows already stored under an alias must be rewritten to the canonical form, and the check in the acceptance is what finds them.
  • A read per write batch, which the repositories already do for other lookups.
  • The compiled table goes, so the one case the branch fixed moves to data.

5.6. How the census is taken

extract_parameter_types.py already reads the ORE source for the parser of every parameter. It gains three facts per parser: the accepted strings, whether the comparison upper-cases its input, and the spelling the engine's own writer emits (the canonical form). The census task runs it over ORE 1.8.17.0 and writes the table above with a row per parameter, which is the list the rest of the story works through.

6. Census of the renderings ORE accepts

The census is produced by projects/ores.seeder/datasets/ore_pricing_engines/extract_parameter_renderings.py, which reads the engine's own source for the renderings each parser accepts, whether the comparison ignores case, and the spelling the engine's writer emits. Its output is committed as parameter-renderings.json beside the extractor, so a later ORE version regenerates it and the diff is the change.

Against ORE 1.8.17.0, seventy-four parameters accept more than one rendering:

Class Count Parameters
Flags 52 every parseBool parameter, whose parser takes twelve renderings: Y, YES, TRUE, True, true, 1 and the six negatives
Case-insensitive words 6 Calibration, CalibrationStrategy, FloatSpreadMapping, IrVolatilityType, ReversionType, VolatilityType
Alias maps 16 BrownianBridgeOrdering, Heston.QuantoProcessDiscretization, OptionExpiryPosition, PolynomType, Pricing.Sequence, Regression.VarGroupMode, RegressorModel, SalvagingAlgorithm, Scheme, SensitivityDecomposition, SequenceType, SobolDirectionIntegers, SobolOrdering, Training.BasisFunction, Training.Sequence, TrainingSequenceType

6.1. The canonical form the engine writes

Five of the six case-insensitive words have a writer this scan found, and the spelling it emits is the canonical form the story adopts:

Parameter Canonical
Calibration BestFit, Bootstrap, FirstBestFitThenBootsrap, None
CalibrationStrategy CoterminalAtm, CoterminalDealStrike, DeltaGammaAdjusted, None, UnderlyingAtm, UnderlyingDealStrike
IrVolatilityType, ReversionType, VolatilityType Hagan, HullWhite

Two details matter here, and both are evidence a person reads rather than trusts. The engine writes FirstBestFitThenBootsrap for the enumerator it parses as FIRSTBESTFITTHENBOOTSTRAP — the writer is misspelt, and its own parser would refuse that spelling. And CalibrationStrategy writes CoterminalAtm where the value set states CoterminalATM.

The rule that follows: the canonical form must be a rendering the parser accepts, because a document the system exports has to be one ORE reads back. Where the writer and the parser disagree, the parser wins and the difference is recorded as an engine defect rather than copied. So FirstBestFitThenBootstrap is canonical and the writer's typo is noted; CoterminalAtm is canonical because the comparison ignores case and the writer states it.

6.2. Two gaps in the census

  • A writer the scan cannot see. Sixty-nine parameters have no writer in the engine's source: the enumeration belongs to QuantLib, and operator<< for it lives in the QuantLib tree, not this one. The canonical form for those must come from a person's reading of the engine's output, or from the value set the registry already states, and the census says so rather than guessing.
  • Leaves are not in the census. This scan reads engineParameter, modelParameter and globalParameter call sites, so it names the parameters of a pricing engine. The curve and convention leaves are a different family: the business day convention leaf was the case that started this story, and the day counter leaf is the same class. A later task extends the census to the leaves, or the value set's own links are taken as the list.

7. Inventory of the ORE XML mappings

The tree does not hold one mapping table. It holds eight that touch the ORE document, and they overlap on the leaf side. The count is from a recreated and populated database.

Table Rows Maps Read by Verdict
ores_reporting_ore_parameters_tbl the per-tenant contract (scope, subtype, name) and a document xpath to a domain and its text the shell check, the screens the parameter catalogue: keep
ores_dq_ore_parameters_artefact_tbl 2295 the same data, staged from the guide the publish functions that fill the contract a staging area, not a second mapping: keep, and say so
ores_reporting_ore_parameter_setting_value_sets_tbl 756 (scope, name) to a value set the parameter trigger, the screens, the shell a link: keep
ores_reporting_ore_parameter_vocabularies_tbl 4 an ORE element (schema, element) to a value set nothing at runtime: the seed writes it, no reader consults it a statement with no enforcer: consolidate
ores_refdata_curve_configuration_columns_tbl 212 a curve column (entity, table, column) to a document element and xpath the curve value guard overlaps the vocabularies: consolidate
ores_refdata_convention_kind_terms_tbl 270 a convention kind term to (schema, xpath) the convention CRUD and the ORE import overlaps the vocabularies: consolidate
ores_reporting_engine_settings_group_settings_tbl 15 a part's column to the Setup ORE name the engine run settings screen a screen inventory, not a document map: keep
ores_analytics_pricing_pair_settings_tbl 834 (type, model, engine, scope, name) to the setting a pair takes the pricing screen a screen inventory: keep

7.1. What the overlap costs

Three tables answer "which ORE element is this column, and which value set states its values": the curve columns table, the convention kind terms table and the vocabularies table. Measured, they are not three statements of one leaf. The curve columns name only curveconfig.xsd leaves and the convention terms only conventions.xsd ones, so the two are disjoint; the vocabularies table names four curveconfig.xsd leaves, and the contract path resolves the same four sets to the same codes. What the tree holds is two column maps, one per schema, and a four-row duplicate of what the contract already states.

The defect is coverage rather than duplication. Of the 299 distinct leaves the two guards check, 245 resolve to a value set and 54 resolve to none, because the parameter contract holds no row for them. A leaf the map names and the contract does not is silently unguarded. The evidence is evidence/leaf_maps_overlap.py, with its output beside it.

The overlap is worse than duplication in one direction. Both guards resolve a value set through the parameter contract — curve column or convention term to (schema_name, xpath), then to the parameter, then to its setting link — so the vocabularies table is not on any path a write takes. A rule stated only there is stated and not enforced. That is measured, not inferred: grep -rn "ore_parameter_vocabularies" --include=*.cpp --include=*.hpp --include=*.ts --include=*.tsx projects/ apps/ returns nothing, and both guards' joins are projects/ores.sql/create/refdata/refdata_curve_configuration_columns_validate_create.sql:43 and refdata_convention_terms_validate_create.sql:51.

7.2. What this story needs, and what it does not

For the canonical form the story needs exactly one thing: given a column or a parameter, find its value set. Two links already do that, one for a parameter and one for a leaf. The canonical pointer goes on the values those links find, so no new map is added and none of the three leaf tables is a prerequisite.

7.3. What the consolidation is

The first task of the story, not a later clean-up: one leaf map keyed by the ORE element, with the projection columns attached to it, and the two links collapsed into one shape. It runs before the canonical form, because every later task reads a value set through a link, and a link with three homes is what makes a reader guess which one wins.

8. Decisions

Proposed, and confirmed or corrected when the census lands.

  • The value set states the canonical form. A value is canonical when its canonical_value is empty, and an alias states the value it means. The set therefore answers three questions with one statement: what is accepted, what a rendering means, and what a screen offers. The links already in the tree — ores_reporting_ore_parameter_setting_value_sets_tbl for a parameter and ores_reporting_ore_parameter_vocabularies_tbl for a leaf — are what find the set, so no new mapping table is added.
  • The canonical form is the spelling ORE's own writer emits. For a convention that is Following; for a flag, true and false; for a case-insensitive word, the mixed case the engine's enum states. The engine's writer is the authority because a document the system exports is one ORE reads back.
  • The mapping keeps the conversion. The repository loads the crosswalk for the values a write touches and hands it to the mapper, which stays a pure function of (domain, crosswalk). The database functions keep enforcing; they do not acquire a second rule.
  • The codec keeps the document's rendering. It is a codec, not a converter, and the corpus test that proves a document survives read and write untouched stays as it is.
  • A redelivery is a row, not a release. Adding a spelling ORE accepts is a row in the value set and nothing else.
  • Closed on 2026-11-08 at sprint 29 close with its done tasks. The open tasks and the original acceptance moved to Normalise every ORE parameter representation — the remaining work.

9. Out of scope

  • Model, Engine and a sensitivity template code: the document defines the thing they name, so their set is open and nothing about them is canonicalised.
  • Free text parameters, whose set says so.
  • Currencies, whose registry already holds the ISO code and nothing else.
  • Rewriting ORE's semantics: the canonical form is a rendering, not a new value.

Emacs 29.3 (Org mode 9.6.15)