Task: Migrate market_series qualifier/point_id generation onto oresmd
Table of Contents
This page documents a task in the Migrate to oresmd, delete market_series qualifier and ore_key story. It captures the goal, current status, acceptance, and any notes or results.
Goal
Generate market_series.qualifier=/=market_observation.point_id from
oresmd at every write path instead of free text, then delete any
remaining legacy free-text generation path.
Status
| Field | Value |
|---|---|
| State | DONE |
| Parent story | Migrate to oresmd, delete market_series qualifier and ore_key |
| Now | Nothing. |
| Waiting on | Plan sign-off. |
| Next | Nothing. |
| Last touched | 2026-07-30 |
Acceptance
[X]The duplicated "split a projected TYPE/METRIC/QUALIFIER… key string into parts" logic (currently reimplemented ad-hoc in three places) is consolidated into one shared, tested oresmd utility function, used by all three call sites. Done:oresmd_projections::split_market_series_key(), with 4 new unit tests;ores.synthetic.service/feed_controller.hpp's two copies andores.marketdata.service/app/feed_ingest_loop.cpp's copy all now call it.[X]Confirmed and documented: FX's and IR'squalifiergeneration already derives from an oresmd-projected key (via =ore_key=/the IR curve's own qualifier, both migrated in the two prior tasks on this story) – no separate free-text generation path remains for either.[X]Confirmed and documented:point_idvalues (tenor codes like "3M"/"1Y", or "SPOT" for scalars) already match oresmd's ownpointgrammar shape exactly (a plain string, e.g. "5y") – no transformation needed.[X]Confirmed:market_serieshas no hand-entry Qt UI at all (rows are only ever auto-created by ingest loops/import_service), so there is no free-text manual-entry path to migrate or delete.[X]ores.ore.core's real ORE market-data-file import (import_service.cpp,market_data_parser.cpp,series_key_registry.cpp) is confirmed out of scope – it parses externally-authored ORE key strings directly (the reverse direction from oresmd URI generation), and belongs to the separate, already-scoped Build ORE curve-config and market-data XML round-trip via oresmd task.[X]Fullcompass buildclean;ctestfull suite green, except one pre-existing, unrelated failure – see* Result.
Plan
(Implementation strategy. Written when work starts; key decisions
are distilled into the parent story's * Decisions at close, but the
plan itself stays — it is the historical record of what we did.)
Landscape survey (as found)
Grepped every real write site of .qualifier =/=point_id =
across =ores.marketdata, ores.synthetic, ores.ore, ores.qt:
- FX:
ores.synthetic.service/feed_controller.hppbuildsore_keyviaoresmd_projections::to_quote_key(migrated in theore_keytask), then a localparse_ore_key-style helper (two near-identical copies in this one file, lines ~268 and ~485) splits that already-oresmd string on'/'intoseries_type=/=metric=/=qualifierformarket_serieslookups/creation.ores.marketdata.service/app/feed_ingest_loop.cpphas a third, separately hand-written copy of the exact same split-on-'/'logic, applied tofeed_binding.ore_key(also oresmd-derived). - IR:
ores.synthetic.service/ir_curve_feed.cpp'squalifier_iscurrency_code + "/" + index_display_suffix(cfg), itself built fromindex_family=/=tenor(migrated in their_curve_generation_configtask) – already oresmd-derived, no splitting needed since IR publishes qualifier directly rather than a combined key string.ores.marketdata.service/app/curve_feed_ingest_loop.cppis a pure pass-through: it copiestick->qualifier=/=tick->point_idverbatim, no generation logic of its own. point_idfor IR ticks comes fromir_curve_resolved_entry.point_id(a tenor code, e.g. "3M"), sourced fromir_curve_template_resolver.cpp. Checked oresmd's ownpointfield (market_data_identifier.hpp): it is a plainstd::optional<std::string>with worked examples like "5y" – a bare tenor code already is exactly the oresmdpointshape. No transformation needed; this was already correct before this task started.- No hand-entry UI:
ores.marketdata.market_series.orghas:ores.cpp.qt.enabled: false– confirmed no Detail Dialog exists for this entity at all.market_seriesrows are only ever auto-created byfeed_ingest_loop.cpp=/=curve_feed_ingest_loop.cpp=/=import_service.cpp, never hand-typed. - Out of scope:
ores.ore.core/src/market/market_data_parser.cppandseries_key_registry.cppparse real, externally-authored ORE market-data files/keys (e.g. a vendoredmarket.txt) into type/metric/qualifier/point_id – the reverse direction from this story's oresmd URI generation work, andimport_service.cpp's ownfind_or_create_series=/observation-write logic is downstream of that parser, not a free-text-vs-oresmd generation choice. This is explicitly the separate [[id:A202712C-9CFD-48DC-9014-40BA5A9408C6][Build ORE curve-config and market-data XML round-trip via oresmd]] task's domain, per this story's own =* Decisions; not touched here.
Step 1 – Consolidate the duplicated key-splitting helper
Add a shared utility (likely
ores::marketdata::core::oresmd_projections::split_projected_key or a
small free function alongside it in ores.marketdata.core/oresmd/)
that splits a TYPE/METRIC/QUALIFIER... string (the shape
to_quote_key=/=to_curve_key etc. already produce) into
{series_type, metric, qualifier}, with the same "qualifier absorbs
every remaining slash-segment" behaviour the three existing ad-hoc
copies share. Replace:
ores.synthetic.service/feed_controller.hpp' two copies,ores.marketdata.service/app/feed_ingest_loop.cpp's copy,
with calls to the shared function. Add unit tests for the shared function itself (trivial, pure, easily testable unlike the NATS-dependent call sites).
Step 2 – Document the already-satisfied criteria
Update this task's * Acceptance checkboxes with findings rather than
code changes for FX qualifier generation, IR qualifier/point_id
generation, and the no-hand-entry-UI confirmation – these were
already correct going in, a direct consequence of the two prior tasks
on this story having done their job properly.
Step 3 – Verification
Full compass build + ctest; add/extend a small unit test suite for
the new shared split-key function (pure, no NATS dependency, same
"extract for testability" pattern used for
ir_curve_feeds_conflict=/=select_vintage_anchor_entry).
Notes
Test Scenarios
Manual QA scenarios (scaffolded via compass add test_scenario, run
through the QA Validation Runner panel) 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 |
|---|---|---|
PRs
| PR | Title |
|---|---|
| #1776 | [marketdata] Consolidate market_series key split parsers into oresmd_projections |
Review
| # | Comment summary | File | Decision | Notes |
|---|---|---|---|---|
| 5134091482/5134093024 | Unused #include <sstream> left over from removed inline parsers |
feed_controller.hpp |
Accepted | Fixed in d2616720a |
Result
Landscape survey found the task's title overstated its scope: FX's and
IR's market_series.qualifier=/=point_id generation already went
through oresmd as a direct consequence of the two prior tasks on this
story (ore_key and ir_curve_generation_config), and there is no
hand-entry UI for market_series to migrate at all. The one real gap
was three independent, hand-written copies of the same "split a
projected TYPE/METRIC/QUALIFIER... key string back into its columns"
logic. Consolidated all three into
oresmd_projections::split_market_series_key(), the documented reverse
of to_quote_key()=/=to_curve_key(), with 4 new unit tests covering the
simple/absorbing/round-trip/rejection cases.
Verification: compass build --preset linux-clang-debug-make clean;
ctest --preset linux-clang-debug-make 73/74 test binaries passed.
The one failure, ores.refdata.core.tests (calendar-rule insert soft-FK
trigger checks a nonexistent id column on ores_refdata_calendars_tbl
instead of its real PK column code), is pre-existing on main,
unrelated to this task's diff, and reported to the environment
(clever_dijkstra) already working the calendar-materialisation story
for a fix.