Story: Normalise every ORE parameter representation
Table of Contents
- 1. Goal
- 2. Status
- 3. Acceptance
- 4. Tasks
- 5. Analysis
- 6. Census of the renderings ORE accepts
- 7. Inventory of the ORE XML mappings
- 8. Decisions
- 9. Out of scope
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,modelParameterandglobalParametercall 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_valueis 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_tblfor a parameter andores_reporting_ore_parameter_vocabularies_tblfor 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,trueandfalse; 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,Engineand 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.