Task: Store the inflation swap and return its index
Table of Contents
This page documents a task in the Carry the rates family's fields across the ORE boundary story. It captures the goal, current status, acceptance, and any notes or results.
1. Goal
An inflation swap imports, stores and exports with the index, base level and lag the document states.
2. Status
| Field | Value |
|---|---|
| State | DONE |
| Parent story | Carry the rates family's fields across the ORE boundary |
| Now | Nothing. |
| Waiting on | Nothing. |
| Next | Nothing. |
| Last touched | 2026-10-18 |
3. Acceptance
- The import reads the CPI leg's index, base level and observation lag into the product's fact row.
- The export writes them back into the CPI leg, which currently leaves the leg with no payload at all.
- A test states the document's own values as its expectations and proves both directions.
4. Plan
- Write the failing case first, against the document's own values:
Inflation_CPI_Swap.xmlstatesUKRPI,210and2Mon its CPI leg, so the case asserts those three and nothing the mapper invents. - Read them on import from the CPI leg, which is where the document states them. A swap states one such leg; a second would be another product.
- Write them back on export onto the same leg, because the shared leg reverse states a cashflow, fixed or floating payload and nothing for a CPI leg.
- Record the money limit rather than paper over it:
BaseCPIis anxs:floatin the schema and the generated binding has no lexical form.
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 |
|---|---|---|---|
| Product fields the CPI leg states | 3 | Inflation_CPI_Swap.xml lines 65, 69, 71 |
1dbadf5fba |
| Sites the import writes | 3 | swap_instrument_mapper.cpp, the CPI loop in forward_inflation_swap |
1dbadf5fba |
| Sites the export writes | 3 | swap_instrument_mapper.cpp, the CPI loop in reverse_inflation_swap |
1dbadf5fba |
| Assertions in the new case | 14, of which 3 name the document's values and 3 name values it never states | the case body | 1dbadf5fba |
6. Notes
- The import and the export now key the CPI leg the same way, on
LegType =CPI=, and both take the first such leg. Before that the import keyed on "the leg carries a CPILegData" and the export on the leg's type, so a leg typed otherwise lost its index on the way out; and with two CPI legs the export wrote the first leg's values onto both. - Open, recorded rather than fixed: an
InflationSwapstated with aYYleg hits the same refusal, because neither direction has a YY branch. Captured asan-inflation-swap-on-a-yy-leg-cannot-be-stored. - Open, recorded: the store's
check (base_cpi > 0)refuses aBaseCPIof zero or below, which the schema'sxs:floatadmits. Kept, because an index level of zero is not an economic value and no document in the corpus states one; a finding needs evidence, and this is a conflict to decide rather than a defect to fix here. - An unstated observation lag is stored as an empty string in a nullable column, so "not stated" and "stated empty" are one value in the store. That is the component's nullable-string pattern, not this unit's.
- No test exercises the insert. The store's two constraints were confirmed by
querying
pg_constrainton a recreated database, and the mapper case proves the value is now supplied, but nothing asserts the row reaches Postgres. A pgTAP case pinning the check would close that gap and is not in this unit. - The case's two halves are independent: the export half is fed values the document never stated, so a reverse that wrote a constant fails there rather than being carried by the import's own output.
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 |
|---|---|
| #3546 | [ore] Store the inflation swap and return its index |
9. Review
| # | Comment summary | File | Decision | Notes |
|---|---|---|---|---|
| 1 | The import keyed on the payload and the export on the leg type | swap_instrument_mapper.cpp:1397 |
Fixed | Both key on LegType = CPI= now. |
| 2 | With two CPI legs the export wrote the first leg's values onto both | swap_instrument_mapper.cpp:2357 |
Fixed | The export stops at the first, as the import does. |
| 3 | An inflation swap on a YY leg is still unwritable | swap_instrument_mapper.cpp:1396 |
Captured | Out of this unit's scope; the class is not closed. |
| 4 | A BaseCPI of zero or below is refused by the store, though the schema admits it |
trading_inflation_swap_instruments_create.sql:59 |
Recorded | A conflict to decide, with no corpus case. |
| 5 | An unstated lag is stored as an empty string in a nullable column | create.sql:43 |
Recorded | The component's nullable-string pattern. |
| 6 | The case's halves shared one instrument, and no test exercises the insert | xml_ir_mapper_roundtrip_tests.cpp:486 |
Fixed, in part | The halves are independent now; the insert is still untested and is recorded as an open gap. |
| 7 | "Re-running the import is the remedy" was wrong, and the row's shape was understated | this task | Fixed | Delete the half-written trades first; no legs were written either. |
| 8 | The money framing counted the export cast as a finding-7 site | this task | Fixed | It is a conversion on a leaf that cannot carry a lexical form. |
10. Result
An inflation swap is stored with the index, base level and lag its document states, and exports them back. The import reads them from the CPI leg; the export writes them onto the same leg, which the shared leg reverse leaves with no payload at all.
Proved red first: against the merge base the case fails on an empty
inflation_index_code and an absent base_cpi; after the change it passes
fourteen assertions, three of which name UKRPI, 210 and 2M and three of
which name values the document never states, so the two halves cannot carry each
other.
Money: BaseCPI is an xs:float in the schema, and the generated binding is a
plain float with no lexical form, so the digits are the schema's own limit and
a value from the document round-trips float to double to float unchanged. Two
consequences worth naming. The leaf re-spells a document's 114.4 as
114.400002, because the serialiser prints a float at nine significant digits.
And the export adds one double-to-float conversion, which is not one of
finding 7's sites: those are casts that discard a lexical form the leaf could
have carried, and this leaf cannot. Giving it one would be a codegen change of
the moneyFloat kind the FX rework made, and it is not this unit's.
A trade imported before this unit was left half-written: its trade row and its
rate_instruments header exist, and it has neither a fact nor any legs, because
the instrument save is non-fatal and the leg writes come after it. Nothing needs
migrating, but re-running the import is not the remedy: the planner mints a
fresh trade id per trade, so a second run adds a duplicate and leaves the broken
header in place. Delete the rate_instruments header first, which cascades the
family, and then the trade: a trade delete does not reach the header, and the
import's own cleanup closes bookings only.