Task: Model the valid values of a parameter

Table of Contents

This page documents a task in the Make every configuration screen follow one standard story. It captures the goal, current status, acceptance, and any notes or results.

1. Goal

A parameter states its value type and its allowed values in the contract, and the allowed values may be the current rows of another ORE Studio catalogue, such as tenors, currencies, currency pairs, sensitivity templates or instrument codes. The screen draws the control from that statement and the write path enforces it, so no value list lives in a screen, a CSV on a per-pair row, or a code parser read at run time.

2. Status

Field Value
State DONE
Parent story Make every configuration screen follow one standard
Now Nothing.
Waiting on Nothing.
Next Nothing.
Last touched 2026-10-25

3. Acceptance

  • Every parameter states a value type: text, integer, decimal, boolean, date.
  • Every parameter whose value is not free text states a value set.
  • An enumerated set holds its values as rows, with a label, an order and an active flag, in one place and not in a comma separated column.
  • A catalogue set names a registered catalogue, and its values are the catalogue's current rows. No value is copied into the parameter tables.
  • The setting-to-set link is keyed by the setting, (scope, name), because the allowed values belong to the setting and not to one product type's copy of it.
  • The screen draws a combo for an enumerated set and for a catalogue set, a number field for a range, and a text field only for a free text parameter.
  • The write path refuses a value outside its set, for every client, with the value, the setting and the set named in the refusal.
  • The generator refuses a usual value that is not in its set.
  • pricing_pair_settings.options and kind are removed once the contract carries the same facts.
  • No value is backfilled from a code parser. A parser may be the evidence a curator reads; it is never the source of the data.

4. Plan

4.1. What is wrong today

Three layers answer three different questions and disagree. The contract carries a scalar domain and no allowed values. The per-pair settings table carries kind and options, a comma separated string, so the same list repeats on every pair that takes the setting, and its only consumer is the web client. Nothing enforces it: the analytics service accepts any string. A setting the guide does not list has no kind at all, which is why a curated setting such as Training.BasisFunction arrives as free text even though its values are known.

4.2. The tables

4.3. ores.reporting.ore_parameter_value_set

column holds
code the set's key, such as polynom_type
name a person's name for it
description what the values choose
source_kind enumerated, catalogue, range, regex or boolean
catalogue_code the catalogue, when the kind is catalogue
minimum, maximum the bounds, when the kind is range
pattern the regular expression, when the kind is regex
is_ordered whether the order of an enumerated set means something

4.4. ores.reporting.ore_parameter_value_set_value

column holds
set_code the set
value the string a configuration holds
label what a person reads
description what the value means
display_order where it sits in the combo
is_active whether it may still be chosen

The key is (set_code, value). The set is shared by every parameter that names it, so Chebyshev2nd is written once.

4.5. ores.reporting.ore_parameter_value_set_catalogue

column holds
code the catalogue's key, such as refdata.tenor
name a person's name
relation the view that presents its values
value_column, label_column which columns of that view carry the value and the label
is_active whether a set may still name it

Only a registered relation can be read, and its shape is checked when the registry is loaded. A catalogue is data: tenors, currencies, currency pairs, sensitivity templates, instrument codes, calendars, day counters, business day conventions and the rest. Each gets a view that presents (value, label) from the table that owns it, generated from the registry, so no hand-written join and no run-time table name sits in the read path.

4.6. The link, and why it is a junction

ores.reporting.ore_parameter_setting_value_set holds (scope, name, value_set_code) with the provenance fields. It is a junction between a setting and a set. It is keyed by the setting rather than by a parameter row because the contract holds one row per product type and the allowed values are the same for all of them; keying by the row would repeat the link hundreds of times. A setting has one set, so the pair is unique; the table is a junction and not a column so a setting may later draw from more than one set without a migration.

4.7. Reading

The contract serves, for each setting, its set and its resolved choices: the values rows for an enumerated set, the catalogue view's current rows for a catalogue, and the bounds or pattern for a range or a regex. The vocabulary reply carries them, and SettingValue chooses its control from the set's source kind, not from a CSV on a pair row.

4.8. Writing

One enforcement point, in the database, because every client writes through it. A trigger on the parameter table resolves (scope, name) to its set and refuses a value outside it, naming the value, the setting and the set. A catalogue set reads its registered view, so a value that has been retired from the catalogue is refused for the same reason. A range compares, a regex matches, a boolean takes two values. The analytics service and the BFF need no copy of the rules.

4.9. Migration

  1. Add the four tables and their generated views.
  2. Curate the sets and their values, starting with the sets the guide already lists as choices, then the settings whose values the code enumerates, then the catalogue references.
  3. Link every setting to its set and carry the usual value across.
  4. Serve the sets in the vocabulary and switch the screen to them.
  5. Add the trigger and prove a bad value is refused from the BFF and from psql.
  6. Remove options and kind from pricing_pair_settings and from the generator, and have the generator check each usual value against its set.
  7. Extend the coverage report: a parameter with no set, a set with no values, a usual value outside its set, a catalogue registered but never used.

The values are curated data with a source, a reference, a curator and a date, the same as every other curated fact. The ORE parsers are read to find what the values are, and the curated file records the parser as the reference; no script copies from the parser into the data.

5. Measurements

Every number this task claims about a tree, with the command that produced it and the commit it was measured at. A number without both is not a measurement, because a clean-up record describes a tree and trunk moves under a branch: a consumer count, a gate's component count and a suite's size all change with no code changing, and the reader of a bare number cannot tell which tree it came from. Where the raw output matters, put it in an evidence/ file beside the task and name that file in the Command column.

What Value Command Commit
       

6. Notes

  • The scalar domain stays as the storage type. referenced_entity on it is unused and is removed; the catalogue reference belongs to the set.
  • Training.BasisFunction is the worked example: one enumerated set named by the setting, shared by every pair and every product type that takes it.
  • A catalogue value is the catalogue's own key, such as a tenor code or a currency pair code, so nothing is denormalised into the parameter tables.
  • The web's fallback by setting name stays, because a configuration may hold a pair the guide does not list; the set is keyed the same way, so the control works there too.

7. Test Scenarios

Manual QA scenarios (scaffolded via compass add test_scenario) that verify this task. Link new ones here as they're created; the scenario doc itself links back via its "Verifies task" field.

Scenario State Notes
     

8. PRs

PR Title
   

9. Review

Comment summary File Decision Notes
       

10. Result

The four tables exist and the database recreates with them. One hundred and eighty nine sets, seventy seven values, two hundred and fifty one setting links and two catalogue rows are seeded. Every parameter of the contract has a set: 57 boolean, 13 enumerated, 44 integer, 62 number and 75 text. The vocabulary reply carries the sets in one map keyed by `(scope, name)`, and the screen reads a control from that map in one lookup. `ore_parameter_settings check` in the shell prints the coverage, the links outside the contract, the empty enumerations and any set whose kind contradicts the contract's domain, then OK or FAIL; on the environment it prints OK with 251 of 251. The contract's own domain now comes from the set, so the two cannot disagree. Playwright confirms the screen: the product editor draws `Training.Sequence` and `Pricing.Sequence` as the five-value sequence combo, `Training.BasisFunction` as the seven curves, and a global flag as true and false.

The type extractor reads the parser that wraps every engineParameter and modelParameter call and follows an enumerated parser to the enum map it builds, so the enums come from the code and not from a person's guess. It types 151 parameters, and the seed gives each a set. The trigger refused a grid typed as a boolean and a period set that carried no rule during provisioning, and both were fixed at the source before the provisioning passed.

The retirement of the per-pair `options` and `kind` is left. The sets now state the type of every setting the code reads, and the guide's choices are sets too, so the columns are redundant; the remaining work is to drop them from the pair setting and have the contract generator take the domain from the set. The screen already prefers the set and uses the columns only as a fallback.

10.1. The code is the authority for a type

The guide states some choices and omits the rest. Where the guide is silent, the ORE code is the authority, and the type it reads is what the set records. Extraction therefore reads the code, not the guide:

  1. For each parameter name, find the call site that reads it and the parser that wraps it: `parseBool`, `parseInteger`, `parseReal`, `parsePeriod`, `parseListOfValues`, or one of the named parsers such as `parsePolynomType`, `parseFdmSchemeDesc`, `parseSequenceType`, `parseSobolBrownianGeneratorOrdering`, `parseRegressorModel` and `parseSalvagingAlgorithmType`.
  2. A boolean parser gives a flag set. A numeric parser gives no set unless the call states bounds. An enumerated parser is followed to the enum type it constructs, and the enum's own values, in its own order, are the set's values, with the enum's names as the labels.
  3. The set records `ore_code` as its source, the parser and the enum definition as its reference, and the curator and the date, like every other curated fact.

The extraction is a build-time step. It produces curation data, which the seed writes into the tables. Nothing at run time reads the code, the parser or the guide: the tables are the model, and the screen and the write path read them.

This reverses the earlier rule against deriving a set from the code. Deriving the enumeration from the enum the code itself declares is not a workaround; it is the only complete statement of the values, and it is recorded as curated data with its reference. The parser remains evidence, and the enum is the definition.

Finished. A parameter's value set lives in four tables: the set, its enumerated values, the catalogue registry and the setting junction. Two hundred and fifty one contract parameters have a set: 57 boolean, 13 enumerated, 44 integer, 62 number and 75 text, over 189 sets. The type comes from the ORE code, one pass over the parser that wraps each call and the enum map it builds, with a reviewed file for the handful the call form states ambiguously. The contract takes its storage domain from the set, so the two cannot disagree.

`ore_parameter_settings check` prints the coverage, the links outside the contract, the empty enumerations and any kind/domain disagreement, then OK: 251 of 251, 0, 0, 0. The trigger refuses a value outside its set from any client, and provisioning passes with it active.

The retirement of the per-pair `kind` and `options` is the only part left; the sets now say everything those columns said.

Emacs 29.3 (Org mode 9.6.15)