Task: Consolidate the ORE element maps
Table of Contents
- 1. Goal
- 2. Status
- 3. Acceptance
- 4. Discovery
- 5. The map is keyed by the leaf's address
- 6. Design
- 7. What slice 1a found
- 8. The addresses the map leaves unconstrained
- 9. The map states the rule from the ORE parser
- 10. The extractor learns the list forms
- 11. A set says how many values it governs
- 12. The ORE schema is the second authority
- 13. A list states what one item is
- 14. The schema states the values it enumerates
- 15. No leaf is left unread
- 16. The schema states a pattern and a range too
- 17. Plan
- 18. Measurements
- 19. Notes
- 20. Test Scenarios
- 21. PRs
- 22. Review
- 23. Result
This page documents a task in the Normalise every ORE parameter representation story. It captures the goal, current status, acceptance, and any notes or results.
1. Goal
One table states which ORE element a leaf column is and which value set states its values, with the projection columns attached to it, and one link shape joins a parameter or a leaf to its set. Nothing else in the story has to guess which of three maps wins.
2. Status
| Field | Value |
|---|---|
| State | DONE |
| Parent story | Normalise every ORE parameter representation |
| Now | Nothing. |
| Waiting on | Nothing. |
| Next | Nothing. |
| Last touched | 2026-10-31 |
3. Acceptance
- One table keys a leaf by its address,
(schema_name, xpath), and it is the only statement of which value set governs a leaf. - Every projection column that carries a leaf is attached to that map, so both guards read the map and the parameter contract is no longer a prerequisite of enforcement. Done: both guards join the child and the map.
- Every row of the three tables is represented in the new map, and a test counts
the leaves and the columns before and after. Done:
reporting_ore_element_map_test.sqlstates the 480 leaves, the 482 columns, and that neither side holds a row the other does not. - Both guards' pgTAP suites pass unchanged.
- The leaves the maps name and the contract does not describe are listed by the
map, each with a decision: a value set, or
text. Done: every one of the 480 names the authority that decided it, and none is left unaccounted.
4. Discovery
Measured from the seed scripts rather than from a database, so the numbers do not
depend on which schema a checkout's database holds:
evidence/leaf_maps_overlap.py, with its output in leaf_maps_overlap.txt
beside it.
| Source | Rows | Leaves | Schema |
|---|---|---|---|
| curve configuration columns | 212 | 123 | curveconfig.xsd |
| convention kind terms | 270 | 176 | conventions.xsd |
| vocabularies | 4 | 4 | curveconfig.xsd |
| the parameter contract | 2339 | 293 | both, and ore.xsd |
| setting links to a value set | 760 |
4.1. The three maps do not overlap
The curve columns name only curveconfig.xsd leaves and the convention terms
only conventions.xsd ones, so their intersection is empty. The vocabularies
table names four curveconfig.xsd leaves, and for each of the four the contract
path resolves the same set to the same code:
| Leaf | Set, both ways |
|---|---|
BusinessDayConvention |
curve.business_day_convention |
Currency |
curve.currency |
DayCounter |
curve.day_counter |
YieldCurveDayCounter |
curve.day_counter |
So the tree holds two column maps, one per schema, and a four-row duplicate of what the contract already states. It does not hold three statements of one leaf.
4.2. The real gap is coverage
Of the 299 distinct leaves the two guards check, 245 resolve to a value set and
54 resolve to none, because the contract holds no row for them at all. A leaf the
map names and the contract does not is silently unguarded: the guard's join finds
nothing and admits every value. The 54 are mostly lists (AtmTenors,
CapStrikes, DetachmentPoints, Index1, OptionTenors, SwapTenors, and
others) plus a few leaves whose spelling differs from the contract's
(Accuracy, Guess, Qualifier).
Both findings are in leaf_maps_overlap.txt, leaf by leaf.
4.3. What that changes
The consolidation is not the de-duplication of three equal statements. It is one map keyed by the ORE element that absorbs the two column maps, absorbs or removes the four-row duplicate, and shows the 54 leaves that no set governs. Enforcement then reads the map, so a tenant that has not published the parameter contract still meets the guard; today it meets nothing.
5. The map is keyed by the leaf's address
The first cut keyed the map by (schema_name, element_name). That key is wrong,
and a test found it: SpotDays is xs:integer in two conventions and xs:string
that ORE reads as a period in a third, so a rule keyed by the name gave the
commodity forward's integer leaf the CMS spread option's period rule, and a count
of days was refused.
The key is the leaf's address in its schema. element_name stays beside it, so a
person can read and search the map, and a vocabulary that holds for every leaf of
a name (Currency, DayCounter, BusinessDayConvention) is stated once in
ore_parameter_vocabularies.json and applied to every address that declares it.
That is a rule, not a fallback: the declaration says the name is a vocabulary, and
the map says which addresses declare it.
Measured with that key: 480 addresses over 482 columns and 288 element names, of
which 181 addresses take text, and 31 carry a curated vocabulary.
6. Design
The map is one table keyed by the ORE element, with the projection columns as a child. Both guards read it directly, so enforcement stops depending on the parameter contract, and the seeding stops depending on a column being noticed.
6.1. The two tables
ores_reporting_ore_parameter_vocabularies_tbl already keys a leaf by
(schema_name, element_name) and carries value_set_code, name, source,
source_ref and reason. It becomes the map: no rename in this task, and a
rename to ore_element_map is its own small task, because the name is carried by
generated C++, TypeScript, shell recipes and their tests.
| Table | Keys | States |
|---|---|---|
ores_reporting_ore_parameter_vocabularies_tbl (the map) |
(schema_name, element_name) |
the value set that governs the element, and where its provenance came from |
ores_reporting_ore_element_map_columns_tbl (new child) |
(element_map_code, entity_name, column_name) |
the projection column that carries the element, its table, and the xpath the column's leaf sits at |
The child is what the two existing column maps become: the curve configuration
columns (212 rows) and the convention kind terms (270). The xpath stays on the
child because it is the leaf's address in the document, which is what the import
and the convention screens show.
6.2. What the guards join
Today a guard walks three tables: its column map, then the contract on
(schema_name, xpath), then the setting link on (scope, name). After this task
it walks two: its child rows for the entity the trigger names, then the map, then
the value set. The contract keeps its own job, which is documentation for the
screens, and stops being a prerequisite of enforcement; a tenant that has not
published the contract still meets the guard.
6.3. Why every column with a leaf is mapped
The seeding decides what is guarded. generate_curve_column_guards.py already
knows every column of every curve family table and the ORE leaf it carries (it
writes the xpath today), so it also writes the child rows and the map rows,
with text where no set exists. The convention terms are generated the same way.
A column whose leaf is known but has no set therefore lands in the map as a
decision to be made, instead of vanishing as the 54 do today.
6.4. The slices, each one verifiable on its own
- The map gains the child table, and the seed writes both from the three sources. Both guards read the map; their pgTAP suites pass unchanged, which is the proof that the same column still resolves the same set.
- The convention CRUD and the ORE import read the child instead of
ores_refdata_convention_kind_terms_tbl, then the two old column maps and their populate scripts go. - The 54 leaves the contract does not describe get their decision: a set where
one exists,
textwhere the document takes a list. The generator emits them, so the count of unmapped leaves goes to zero and stays there. value_set_codegains a foreign key to the value sets, which closes the misspelt-code gap the knowledge cluster records.
Slices 1 and 2 are this task. Slice 3 is value-set curation and belongs to the story's canonical-form tasks. Slice 4 rides slice 1, because it is the same migration.
6.5. Out of scope
The canonical rendering of a value (next task), the screen that shows a leaf, and any change to what a value set states. This task moves where a leaf's set is found, not what the set says.
7. What slice 1a found
The map and its child are seeded, and the switches are built, but two things had to be settled before the guards could read them.
7.1. The curve column map on the trunk was stale
Regenerating it added six business_day_convention columns that the projection
had gained and the committed seed had not. They were unguarded on the trunk, and
generate_curve_column_guards.py --check failed there before this task touched
it. The regeneration is in this slice, so the trunk's check goes green.
7.2. An element-keyed map guards leaves that were silent, and two of them fail
The old guards matched a column's xpath against the contract, so a column whose
path the contract did not hold was never checked. The map is keyed by the element,
so it guards every column that carries a leaf, including those. That is the point
of the change, and it exposed two refusals on paths that today pass.
The first was the currency catalogue, and it was a fixture, not a defect. The
catalogue read is fine: ores_reporting_value_in_set_fn reads
ores_refdata_currency_catalogue_v for the caller's tenant, which is the tenant
the publish writes as. The catalogue was empty because nothing had published
iso.currencies for that tenant. The curve column suite already publishes them
for the same reason; the publish test now does too, and its seven assertions pass
with the switch in place.
The second is a wrong rule the map inherited. With the currencies present, the
conventions publish refuses 2 for commodity_forward_convention.spot_days:
SpotDays resolves to convention.period, whose pattern requires a unit, so a
count of days is refused. The old guard never applied that rule to this leaf
because the contract held no row at that path. The element map applies it to every
SpotDays column, which is what an element key means.
So the blocker for slice 1b is not the catalogue. It is that the rules the map
inherits are stated per parameter and were never exercised on the leaves the map
now applies them to. That is slice 3, the curation of the map's rules, and it has
to come before the guards read the map: SpotDays is a whole number of days
(convention.integer), and the link that says otherwise is the thing to fix.
The switch itself is written and was exercised. Under it the curve column suite
passed unchanged and the convention suite failed only on its expected error text,
which the map's key changes from Swap.FixedFrequency to
swap_convention.fixed_frequency. Both are recorded here rather than lost.
8. The addresses the map leaves unconstrained
181 of the 480 addresses take the shared text set, which admits every value.
They are not one problem. evidence/text_addresses.py splits them, and its
output is beside it:
| Group | Addresses | What it needs |
|---|---|---|
the contract states text, and no other address of that element has a rule |
76 | nothing: a person decided the leaf is free text |
the contract states text, but the same element has a rule elsewhere |
26 | a review: the statement is probably wrong |
| the contract holds no row, and a sibling address has a rule | 12 | a proposal to accept or reject |
| the contract holds no row, and no sibling has a rule | 67 | a decision from the schema type and the ORE parser |
The 26 and the 12 are the cheap end, because a rule already exists for the same element somewhere. The 67 are the real curation: mostly lists of tenors, strikes, dates and indices, where the honest answer is often a stated decision that the leaf is a list rather than a single value.
Two examples of the 26, and why they matter: SettlementDays, FixingDays and
SpotDays state text at some addresses while another address of the same name
takes a rule, and DayCounter and BusinessDayConvention state text at two
addresses while the map elsewhere states a catalogue. Each is either a wrong
statement or a name that is two leaves, which is the SpotDays problem again.
Five of the 26 are now corrected. The extractor states no parser for those
members, so it typed them text; the ORE schema states xs:integer for every
declaration of SettlementDays and FixingDays and for the commodity forward's
SpotDays, and convention_value_sets.py now states that type with the reason.
The unconstrained count falls from 181 to 176.
Twenty-one are left, and they are not one job either. The Index, Conventions
and Calendar addresses state text where a sibling states curve.text, which
is the same rule under another code, so the question there is whether two text
sets should exist, not whether the leaf is text. The DayCounter and
BusinessDayConvention addresses at IborIndex and OvernightIndex state text
where a catalogue exists.
Those three are now stated as the catalogue, and the ORE source is why. The
extractor typed them text and it was faithful: IborIndexConvention::fromXML
reads DayCounter and BusinessDayConvention with getChildValue into
std::string and never parses them
(OREData/ored/configuration/conventions.cpp:428 and :430), and
OvernightIndexConvention reads its day counter the same way at :470. So the
engine accepts any string there and a typo in one changes nothing, silently.
The field means the same vocabulary the other twenty conventions state, so the rule is the catalogue and the engine's permissiveness is not a licence. A value the catalogue does not hold is refused with a sentence that names the set, which is a better answer than a typo the engine ignores. The unconstrained count falls to 173.
What was left of the 26 was eight addresses of Index, Conventions and
Calendar that state text where a sibling states curve.text. Those two are
one rule under two codes: both are the text kind, both are named Text, and
neither constrains anything. They are now one set, text, and curve.text is
gone.
The collapse corrects a number this page carried. Counting only addresses that
name text understated the gap, because curve.text was counted as a rule when
it constrains nothing. The honest statement of the map is: 274 of the 480
addresses are free text, and 206 state a real rule. 195 of the 274 are a
decision the contract makes; 79 have no statement at all.
So the 26 contradictions are closed, and the remaining work is the 79 addresses no contract row describes, plus a review of the 195 that say text where the schema declares something narrower.
9. The map states the rule from the ORE parser
The 79 were not a curation backlog. They were the signature of a wrong source.
A leaf's set was resolved through the parameter contract: the leaf's xpath
joined a contract row, and the row's (scope, name) joined the setting link. The
contract holds a row only for a leaf a person wrote text for, so a leaf ORE reads
and the guide does not describe had no rule and took text by omission. Measured
with the extractor as it stood, 79 of 480.
The rule now comes from the parser the ORE source reads the leaf with, through the
curation the value set seed is written from. A leaf's rule and the rule of the
setting that shares its name are one fact, so the map reads the resolved curation
(generate_value_sets.resolved()) instead of deriving the answer a second way,
and the contract keeps only its own job, which is documentation for the screens.
That changes what a row says. source is ore_code, source_ref is the ORE
source line that reads the leaf, and reason is generated from the parser and the
set's kind, so a reviewer checks the rule instead of trusting it. The 232 leaves
that stay text state why they stay text: ORE never parses the value, or it does
and this tree holds no catalogue of the names it accepts.
Three numbers moved, and only one of them by curation:
| Before | After | |
|---|---|---|
| leaves that state a rule | 206 | 220 |
| text, and the ORE source says why | 195 | 232 |
| text, because no ORE source read names the leaf | 79 | 28 |
The 14 new rules are leaves the extractor already read and the contract did not
describe: BMAPaymentConvention, BMAPaymentLag, OvernightLockouDays,
Accuracy, Guess, MaxEvaluations, the two NthWeekday/Weekday leaves,
BalanceOfTheMonth, OffPeakHours, Deposit/SettlementDays, LongTermAtmType,
LongTermDeltaType and UseLastFixingDate. The 51 that fell out of the
"no contract row" group did so because they now have a term: 37 of them state text
with a reason, and 14 state a rule.
The 28 that remain are the extractor's gap, not the model's: extract_curve_terms.py
reads getChildValue, getChildNode, getNodeValue and their typed forms, and
these leaves are read with getChildrenValuesAsStrings, getChildrenValuesAsDoublesCompact
or a name built from a variable. UNREAD_ALLOWED in the generator holds the number
so it can fall and not grow.
9.1. A rule from the parser is enforced, and a test says so
The 14 leaves that gained a rule are enforced by the existing guards, which read
the map, so the proof is a refusal. refdata_convention_terms_validate_test.sql
now writes a BMA basis swap with bma_payment_convention = 'Follwing' and expects
the refusal to name convention.business_day_convention. Before this slice the
leaf took text, so the same typo was written and nothing refused it: the test
fails on the previous commit and passes on this one.
9.2. The collapse left a red suite
Collapsing curve.text into text in the previous slice changed a link's set code
from curve.text to text, and one assertion read the code's prefix:
reporting_ore_parameter_curve_value_sets_test.sql required every set a curve leaf
uses to start with curve., so it failed on the next run. The assertion was a
proxy for "a curve leaf does not take a convention or pricing vocabulary", and the
shared free-text set is not a family vocabulary: it is the one set that says no
vocabulary governs the value. The assertion now reads the set's kind and states
that rule, so it can still catch a curve leaf pointing at another family's
catalogue.
10. The extractor learns the list forms
The 28 leaves the map could not rule were mostly lists, and the reason was the
extractor. `READ` knew `getChildValue`, `getChildNode`, `getNodeValue` and their
typed forms; ORE reads a list with `getChildrenValuesAsStrings`,
`getChildrenValuesAsDoublesCompact`, `getChildrenValuesAsPeriods` and
`getChildValueAsPeriod`, and none of them matched. A leaf read that way produced
no term, so no parser stated a rule and the leaf took text.
Two changes, both in extract_curve_terms.py:
- the four accessors are read, and each records the type of one item: a template
that names its item (
parseListOfValues<Real>) states a list of numbers, a template that names none states a list of strings, and the compact forms state what their name says; - a leaf read with
getNodeValuetakes the type of the call that wraps the read, soparseListOfValues<Real>(getNodeValue(node))is a list of numbers andparseBool(getNodeValue(node))is a flag.
The second change is why the count moved so far. A report grid's leaves were all
typed text because the read is a getNodeValue, and the parse sits around it;
reading the wrapper types the grid's flags as flags and its expiry and strike
lists as lists. The parser reuses one variable for every child, so a read is
matched to the child that precedes it, bounded by the next read into the same
variable: without the bound the first child's parser typed every later child and
a grid's strikes became the flag beside them.
| Before | After | |
|---|---|---|
| leaves that state a rule | 220 | 253 |
| text, and the ORE source says why | 232 | 227 |
| text, because no ORE source read names the leaf | 28 | 7 |
| leaves the ORE source reads as terms | 483 | 508 |
The 7 that remain: SwapTenors, BondTenors and SmileSwapTenors are named by
a string the code builds (underlyingLabel_ + "Tenors"), so no read spells them;
UseEffeciveVolatility is spelled that way in ORE and read elsewhere;
DefaultCurve/Type, IndexInterpolated and Qualifier the extractor still does
not reach.
11. A set says how many values it governs
A value set stated a kind and nothing else, so it could say what one period is and could not say whether the document writes one period or a list of them. ORE writes a list into a single element, and a numeric set already split on commas to admit one, with a comment saying why. That was a rule in the wrong place: it applied to every numeric parameter, whether or not its element holds a list.
ores_reporting_ore_parameter_value_sets_tbl gains cardinality, one or
many. The kind states what one value may be; the cardinality states how many.
ores_reporting_value_in_set_fn splits on the comma when the set says many and
holds every item to the kind, and refuses an empty item, which is a trailing or
doubled comma. A set of one refuses the comma, because a comma there is a
document the engine cannot parse. The numeric split left the function with the
comment, because curve.number_list now says the same rule as data.
Keeping the two facts apart keeps the kinds at nine. The alternative is a kind per
combination, period_list beside period and so on, and then a screen reads a
name to know whether its control repeats; with a cardinality it reads the kind for
the control and the cardinality for whether it repeats.
11.1. The link cannot hold two leaves of one name
The map resolves a leaf's rule by the leaf. The setting link still keys a rule by
(scope, name), and two leaves of one section can share a name while ORE reads
them differently: the equity volatility curve's Expiries is a list of strings,
and the report grid's beside it is a list of periods. The link holds one, so a
name whose drawn leaves disagree is not linked, and curve_value_sets.py returns
it in conflicted and generate_value_sets.py prints it. Today no drawn leaf
disagrees: the link follows the leaf the projection draws, and only that leaf.
This is the same lesson as the contract one level down, and it is the next slice: a setting keyed by scope and name cannot state two rules, so either the link gains a subtype or the curve screens read the map by address.
11.2. A list is checked item by item, and a test says so
reporting_ore_parameter_list_value_set_test.sql states both directions: a list of
numbers is in curve.number_list, an item that is not a number is not, an empty
item is not, a list of periods is in curve.period_list, a value that is not a
period is not, and curve.period, which is one value, refuses a comma.
12. The ORE schema is the second authority
The parser says what the engine accepts; it is not the only authority. Where the
parser reads a leaf as a plain string, the schema still declares a type, and a
leaf declared xs:integer is not free text: a document that writes two there is
one the engine cannot parse, even though the parser hands the string on.
ore_schema_types.py walks the schema from the document's root element to the
leaf, following a named complex type and an inline one, and states the leaf's type
when that type is narrower than xs:string. A type the schema enumerates is left
alone for now, because the values are the schema's and the set needs labels of its
own.
The schema speaks in two places. A leaf the parser reads and states no more of takes the schema's type, after the parser's own answer. A leaf the extractor cannot reach at all takes the schema's type too, because nothing else speaks, and the row says so. The parser stays first in both, so a leaf the parser reads as a number keeps the number rule even where the schema is looser.
evidence/schema_types.py prints the two authorities side by side. Before this
slice 16 leaves stated text where the schema declared a narrower type: six
xs:integer, four xs:nonNegativeInteger, five bool and one xs:double. Now
none does.
| Before | After | |
|---|---|---|
| leaves that state a rule | 253 | 273 |
| text, and the ORE source says why | 227 | 207 |
| text, because no ORE source read names the leaf | 7 | 5 |
| leaves where the schema is narrower than the map | 16 | 0 |
| leaves whose schema type the reader resolves | 462 | 467 |
The 5 that remain are the three tenor leaves a variable names, DefaultCurve/Type
and Qualifier. The schema types none of the five, so text is the honest answer
rather than a gap. The 13 leaves whose schema type the reader does not resolve are
listed by the evidence report; they all state a rule from another authority.
13. A list states what one item is
A set of cardinality many stated how many values a leaf holds and not what one
may be, so 16 leaves said "a list of anything". The ORE user guide states the
item for most of them in the sentence it describes the leaf with: Tenors and
AtmTenors are "a comma separated list of valid tenor strings", Terms is "a comma
separated list of tenors", SmileOptionTenors are "option expiries, in period
form", CapStrikes and FloorStrikes are the strikes, DetachmentPoints are "any
positive number less than one", and SmileSpreads are strike spreads.
curve_value_sets.py now states that per name, and each entry carries the reason
and the guide or code line it came from, so the map's row for a tenor list reads
ore_guide and cites the line. Twelve leaves gained an item rule: eight take
curve.period_list and four curve.number_list.
Four keep curve.list, and their reason says why rather than leaving the gap
unstated. A delta is written 25D, which is neither a number nor a period; an
expiry may be a tenor, a date or a wildcard *, which the guide states; and a
pillar date is a date, for which this tree has no kind yet. A date set is the
missing piece, and it is the next one.
| Before | After | |
|---|---|---|
| leaves that take a period list | 0 | 8 |
| leaves that take a number list | 5 | 9 |
| leaves that take a list with no item rule | 16 | 4 |
The seeded corpus holds real tenor and strike lists, and it publishes under the new rules unchanged, which is what proves the item rule is not too narrow.
14. The schema states the values it enumerates
An enumerated simple type in the schema names its values, so the rule for a leaf
of that type is a set of them and not a shape one value must have. The schema
states 142 such types, and 33 mapped leaves had text where one of them applied:
Dimension with 4 values, VolatilityType with 3, Extrapolation with 4, the
commodity future weekday with 7, the default curve's Type with 8, and the
segment types of a yield curve.
ore_schema_types.py now reads them, and curve_value_sets.py and
convention_value_sets.py generate one enumerated set per type, named
curve.<type> or convention.<type>, with the values in the schema's order and
the file and line the schema states them at.
Two rules keep the sets honest. A set the schema states is generated only for a leaf a column carries, because a set nothing names and no row carries is dead data; that is what the curve value-set test's orphan assertion catches. And the schema lists the empty string where a leaf may be left unset, which the guard treats as absent rather than as a choice, so the set states the choices and the unset case stays an empty field.
| Before | After | |
|---|---|---|
| leaves that state a rule | 273 | 306 |
| text, and the ORE source says why | 207 | 174 |
| text, because no ORE source read names the leaf | 5 | 4 |
| sets | 229 | 245 |
The scheme also closed one of the five untyped leaves: DefaultCurve/Type is
declared defaultCurveType in the schema, which enumerates eight values, so the
map states them without any ORE source reading the leaf.
15. No leaf is left unread
Four leaves were still unread, and all four were the same shape: ORE names the
element from a label the class passes to its base constructor, so no read spells
the name. The swaption class passes ("Swap", "SwaptionVolatility", "SWAPTION",
"") and the yield class ("Bond", "YieldVolatility", "BOND_OPTION",
"Qualifier"), and GenericYieldVolatilityCurveConfig::fromXML reads
underlyingLabel_ + "Tenors" and qualifierLabel_.
extract_curve_terms.py now reads those two forms and expands them once the
class's labels are known: one term per label, named as the document spells it.
Swaptions gain SwapTenors and SmileSwapTenors, yield curves gain BondTenors,
SmileBondTenors and Qualifier, and the empty qualifier label states no leaf,
because the swaption class never reads one.
The three tenor leaves take curve.period_list, citing the guide where it states
the leaf in period form and the class constructor where it does not. Qualifier
states text with the reason the parser gives: the engine reads it and never
parses it.
| Before | After | |
|---|---|---|
| leaves that state a rule | 306 | 309 |
| text, and the authority says why | 174 | 171 |
| leaves that name no authority at all | 4 | 0 |
| leaves the ORE schema types alone | 2 | 3 |
| leaves the ORE source reads as terms | 508 | 513 |
UNREAD_ALLOWED is now zero, so the check fails the moment a projection column
carries a leaf no authority can state a rule for. Coverage is complete in the
sense the task set out to prove: every leaf the map carries names the authority
that decided it.
Three leaves take their rule from the ORE schema because no ORE source read names
them at all: UseEffeciveVolatility, which ORE spells that way and reads
elsewhere, DefaultCurve/Type, whose projection path the schema types but no read
matches, and IndexInterpolated. Their rows say the schema is the only authority
that speaks, so the difference between "the parser read it" and "the schema typed
it" is read rather than assumed.
16. The schema states a pattern and a range too
A schema simple type states more than an enumeration. It restricts its base to a pattern, or to a range of values, and both are rules the model can hold:
datestates the pattern ORE's date parser accepts, and its own comment says it "is not super strict, e.g. 2016-17-39 would pass, but it catches most errors";calendarstates a pattern naming every calendar ORE resolves, including the joint formUS,MWKthat ORE writes by repeating the group;dayOfMonthrestrictsxs:integerto 1 through 31.
So ore_schema_types.py now reads the restriction facets: base, pattern,
minInclusive and maxInclusive. A built-in type keeps the family's static set,
because a flag is a flag and the static set accepts the Y and N that ORE also
writes; a named type the family has no static set for, or one that narrows a kind
with bounds, generates a set of its own.
Sixteen leaves gained a rule: the thirteen calendars the projection carries, the
base correlation's StartDate, and the two commodity future day-of-month leaves.
PillarDates gained an item rule too, curve.date_list, so a list of dates is
stated rather than left as a list of anything.
| Before | After | |
|---|---|---|
| leaves that state a rule | 309 | 325 |
| text, and the authority says why | 171 | 155 |
| leaves that take a list of dates | 0 | 1 |
| sets | 245 | 249 |
16.1. A pattern is anchored
The guard matches a pattern anywhere in the value, so a seeded pattern is wrapped
in ^(...)$. Without the anchor date would admit x2024-01-31y, and a pattern
that means "the whole value" would be read as "somewhere in the value". The test
states both directions: a date the pattern allows is in, one it refuses is out.
The schema's date pattern admits month 13, and that is the schema's own statement about its strictness rather than a gap in the model. The test says so, so a reader does not mistake the rule for calendar validation.
17. Plan
17.1. Read the three maps
ores_refdata_curve_configuration_columns_tbl (212 rows) maps a projection
column to an entity, a table and an xpath.
ores_refdata_convention_kind_terms_tbl (270) maps a convention term to a
schema and an xpath. ores_reporting_ore_parameter_vocabularies_tbl (4) maps
a schema and an element to a value set. The first two carry the element
implicitly in the xpath, the third names it, and only the third names a set.
The Discovery section above holds what each one actually names.
Only the first two are read at runtime. Both guards resolve a set by joining
the map to the parameter contract on (schema_name, xpath) and the contract to
the setting links on (scope, name)
(projects/ores.sql/create/refdata/refdata_curve_configuration_columns_validate_create.sql:43,
refdata_convention_terms_validate_create.sql:51). Nothing reads the
vocabularies table: grep -rln "ore_parameter_vocabularies" --include=*.cpp
--include=*.hpp --include=*.ts --include=*.tsx projects/ apps/ returns nothing.
So the third is not a rival authority, it is a statement with no enforcer. The
consolidation either makes it the map the guards read, or removes it; leaving
it as a row nobody consults is the one outcome that does not finish the task.
17.2. Write the one map
Key it by the ORE element: (schema, element). Attach the projection columns
that carry it (entity, table, column), the value set, and the provenance the
vocabulary rows already state. One row per element, the columns in a child
table or an array, and the seeds read from the three sources already in the
tree.
17.3. Collapse the links
ores_reporting_ore_parameter_setting_value_sets_tbl keys a parameter by
(scope, name); the element map keys a leaf by (schema, element). One shape
with a kind column, or two tables with one reader, is a decision for the task's
first commit; what must not survive is a caller that knows which table to ask.
17.4. Carry the data and prove it
Every row of the three tables moves, and a test counts them before and after. The curve column guard's pgTAP suite must pass unchanged, which is the proof that the same column resolves the same set through the new map.
18. 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 |
|---|---|---|---|
| leaves in the map | 480 over 482 columns | generate_ore_element_map.py --check |
337d74dbfc plus this branch |
| leaves that state a rule | 220 | the same, first line of its report | 337d74dbfc plus this branch |
| leaves that state text, with the ORE source's reason | 232 | the same | 337d74dbfc plus this branch |
| leaves no ORE source read names | 28 | the same, and evidence/text_addresses.txt |
337d74dbfc plus this branch |
| setting links to a value set | 972 | generate_value_sets.py --check |
337d74dbfc plus this branch |
| value sets | 226 | the same | 337d74dbfc plus this branch |
| leaves that state a rule | 253 | the same, first line of its report | 337d74dbfc plus this branch |
| leaves no ORE source read names | 7 | the same, and evidence/text_addresses.txt |
337d74dbfc plus this branch |
| setting links to a value set | 991 | generate_value_sets.py --check |
337d74dbfc plus this branch |
| pgTAP suites | 72 files, 669 tests, one failure that also fails on main | projects/ores.sql/test/run_tests.sh |
337d74dbfc plus this branch |
19. Notes
20. 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 |
|---|---|---|
21. PRs
| PR | Title |
|---|---|
| #4167 | Measure the leaf maps and design the one that replaces them |
| #4170 | Seed the element map and the columns that carry it |
| #4172 | Publish the currencies a fresh database's publish test reads |
| #4175 | Key the element map by the leaf's address, and read it from both guards |
| #4186 | Census the leaf rules the map inherits |
| #4188 | State the type three convention modifiers are read as |
| #4190 | State the catalogue on the index conventions' vocabulary fields |
| #4194 | Collapse the two free-text sets into one |
| #4199 | Take a leaf's rule from the ORE parser, not the contract |
| #4207 | State how many values a set governs, and read ORE's list forms |
| #4208 | Let the ORE schema state the rule where the parser states none |
| #4213 | State what one item of a list may be |
| #4217 | Take the values the ORE schema enumerates |
| #4219 | Read the leaves ORE names after a class label |
| #4225 | Take the pattern and the range the ORE schema states |
| #4226 | State the map's shape as a test |
22. Review
| Comment summary | File | Decision | Notes |
|---|---|---|---|
23. Result
The map is one table keyed by a leaf's address, its child names the projection column that carries the leaf, and both leaf guards join the pair, so the parameter contract is no longer a prerequisite of enforcement.
Every one of the 480 leaves states a rule or the reason it has none, and each row
names the authority that decided it: the ORE parser for 207 rules and 171 text
decisions, the ORE user guide for 14, and the ORE schema for 88. No leaf is left
unaccounted, and UNREAD_ALLOWED is zero so one cannot arrive quietly.
The value set states what one value may be by its kind, how many the document writes by its cardinality, and the values, pattern, bounds or catalogue it draws on. A set is generated from the parser's map or the schema's restriction wherever a source states it, and curated with its reason where a person does.
What the work found that the plan did not expect: the contract was the wrong source for a leaf's rule, a set could not say how many values it governed, the extractor could not read a list or a name a class builds, and the schema was an authority nobody consulted. Each is a case of a rule that lived in prose, in a name, or in another table rather than in the model.
What is left belongs to the next task. A setting link is keyed by (scope, name)
and so cannot state two rules for two leaves of one name: the equity volatility
curve's Expiries is a list of dates and the report grid beside it is a list of
periods, and only the drawn leaf is linked. No drawn leaf disagrees today, so no
data is wrong; the key is what has to change before a screen can draw both.