ores.cpp.oresmd.projections

Table of Contents

The ORE projection helpers: ore_type and ore_<asset_class>_metric switches generated from the spec's Quote types table (the metric cases grouped by metric, preserving first-appearance order), and the quote_key_* bodies, which are genuinely per-class shapes and sit in asset-class-keyed sections. The ir helpers (index_name_ir, curve_key_ir, default_metric, qualifier_includes_index, ore_vol_model) are static.

The ORE spellings the tables emit live in the static ore_type_spec, ore_metric_spec, and ore_vol_spec constant namespaces (a "constexpr set of constants"); the generated switches reference them by name from the spec's ore_constant column. The static inverse projection (from_ore_key, ORE key string to market_data_identifier) dispatches on the same constants, so a rename stays in sync in both directions.

The per-class function order (fx, equity, credit, commodity, inflation, and commodity's quote_key_ at the end) mirrors the hand-crafted file.

1. Template

{{! GENERATED FILE — tangled from ores.cpp.oresmd.projections.org. Edit the org source. }}
{{{cpp_license}}}
#include "ores.marketdata.core/oresmd/oresmd_projections.hpp"
#include "ores.marketdata.core/oresmd/detail/oresmd_index_family_utils.hpp"
#include "ores.marketdata.core/oresmd/detail/oresmd_string_utils.hpp"
#include <algorithm>
#include <cctype>
#include <format>
#include <magic_enum/magic_enum.hpp>
#include <sstream>
#include <vector>

namespace {

using namespace ores::marketdata::domain;
using ores::marketdata::core::detail::is_overnight;
using ores::marketdata::core::detail::to_lower;
using ores::marketdata::core::detail::to_upper;

/*
 * ORE spelling tables. The first segment of a quote key is the ORE series type, the
 * second is the metric; the swaption family also carries a volatility model. These
 * constants are the single source of truth for those spellings: the forward
 * ore_type()/ore_metric()/ore_vol_model() tables and the inverse dispatcher below both
 * reference them, so a rename stays in sync in both directions.
 */
namespace ore_type_spec {
constexpr std::string_view fx{"FX"};
constexpr std::string_view fxfwd{"FXFWD"};
constexpr std::string_view ir_swap{"IR_SWAP"};
constexpr std::string_view discount{"DISCOUNT"};
constexpr std::string_view mm{"MM"};
constexpr std::string_view fra{"FRA"};
constexpr std::string_view imm_fra{"IMM_FRA"};
constexpr std::string_view basis_swap{"BASIS_SWAP"};
constexpr std::string_view bma_swap{"BMA_SWAP"};
constexpr std::string_view cc_basis_swap{"CC_BASIS_SWAP"};
constexpr std::string_view cc_fix_float_swap{"CC_FIX_FLOAT_SWAP"};
constexpr std::string_view zero{"ZERO"};
constexpr std::string_view mm_future{"MM_FUTURE"};
constexpr std::string_view oi_future{"OI_FUTURE"};
constexpr std::string_view swaption{"SWAPTION"};
constexpr std::string_view equity{"EQUITY"};
constexpr std::string_view equity_fwd{"EQUITY_FWD"};
constexpr std::string_view equity_dividend{"EQUITY_DIVIDEND"};
constexpr std::string_view commodity{"COMMODITY"};
constexpr std::string_view commodity_fwd{"COMMODITY_FWD"};
constexpr std::string_view cpr{"CPR"};
constexpr std::string_view cds{"CDS"};
constexpr std::string_view hazard_rate{"HAZARD_RATE"};
constexpr std::string_view recovery_rate{"RECOVERY_RATE"};
constexpr std::string_view cds_index{"CDS_INDEX"};
constexpr std::string_view index_cds_tranche{"INDEX_CDS_TRANCHE"};
constexpr std::string_view zc_inflation_swap{"ZC_INFLATIONSWAP"};
constexpr std::string_view yy_inflation_swap{"YY_INFLATIONSWAP"};
constexpr std::string_view seasonality{"SEASONALITY"};
constexpr std::string_view correlation{"CORRELATION"};
}  // namespace ore_type_spec

namespace ore_metric_spec {
constexpr std::string_view rate{"RATE"};
constexpr std::string_view price{"PRICE"};
constexpr std::string_view basis_spread{"BASIS_SPREAD"};
constexpr std::string_view ratio{"RATIO"};
constexpr std::string_view yield_spread{"YIELD_SPREAD"};
constexpr std::string_view credit_spread{"CREDIT_SPREAD"};
constexpr std::string_view base_correlation{"BASE_CORRELATION"};
}  // namespace ore_metric_spec

namespace ore_vol_spec {
constexpr std::string_view rate_lnvol{"RATE_LNVOL"};
constexpr std::string_view rate_nvol{"RATE_NVOL"};
constexpr std::string_view rate_slnvol{"RATE_SLNVOL"};
constexpr std::string_view shift{"SHIFT"};
constexpr std::string_view price{"PRICE"};
}  // namespace ore_vol_spec

std::vector<std::string> split_point(const std::string& point) {
    std::vector<std::string> parts;
    std::stringstream ss(point);
    std::string part;
    while (std::getline(ss, part, ','))
        parts.push_back(to_upper(part));
    return parts;
}

std::string curve_id(std::string_view ccy, std::string_view tenor) {
    return std::format("{}{}", ccy, to_upper(tenor));
}

/*
 * Both index name and curve key are produced together, gated on `type=fixing` -- the
 * design doc's own worked-examples table shows a single `type=fixing` URI producing
 * BOTH an index name and a curve key (the underlying curve construct has both facets),
 * and reads "--" for both columns on every `type=quote` row. See
 * id:C3E053CA-0D4B-480B-9119-E11530160EC1, "Worked examples" > "Interest rates".
 */
std::optional<std::string> index_name_ir(const ir_market_data_identifier& id) {
    if (id.type != instrument_type::fixing || !id.index)
        return std::nullopt;
    const auto family = to_upper(std::string(magic_enum::enum_name(*id.index)));
    if (is_overnight(*id.index) || !id.tenor)
        return std::format("{}-{}", id.ccy, family);
    return std::format("{}-{}-{}", id.ccy, family, to_upper(*id.tenor));
}

std::optional<std::string> curve_key_ir(const ir_market_data_identifier& id) {
    if (id.type != instrument_type::fixing || !id.tenor)
        return std::nullopt;
    return std::format("Yield/{}/{}", id.ccy, curve_id(id.ccy, *id.tenor));
}

// Default metric from ir_quote_type when metric is absent (e.g. quote=mm implies metric=rate).
metric default_metric(ir_quote_type qt) {
    switch (qt) {
        case ir_quote_type::ir_swap:
        case ir_quote_type::discount:
        case ir_quote_type::mm:
        case ir_quote_type::fra:
        case ir_quote_type::imm_fra:
        case ir_quote_type::cc_fix_float_swap:
            return metric::rate;
        case ir_quote_type::basis_swap:
        case ir_quote_type::cc_basis_swap:
            return metric::basis_spread;
        case ir_quote_type::bma_swap:
            return metric::ratio;
        case ir_quote_type::zero:
            return metric::rate;
        case ir_quote_type::mm_future:
        case ir_quote_type::oi_future:
            return metric::price;
    }
    return metric::rate;
}

{{#ir_ore}}
// ORE TYPE string for each ir_quote_type.
std::string_view ore_type({{asset_class}}_quote_type qt) {
    switch (qt) {
{{#quote_types}}
        case {{asset_class}}_quote_type::{{enum_name}}:
            return ore_type_spec::{{ore_constant}};
{{/quote_types}}
    }
    return ore_type_spec::{{ore_constant_default}};
}
{{/ir_ore}}

// ORE METRIC string for each metric.
std::string_view ore_metric(metric m) {
    switch (m) {
        case metric::rate:
            return ore_metric_spec::rate;
        case metric::price:
            return ore_metric_spec::price;
        case metric::basis_spread:
            return ore_metric_spec::basis_spread;
        case metric::ratio:
            return ore_metric_spec::ratio;
        case metric::yield_spread:
            return ore_metric_spec::yield_spread;
    }
    return ore_metric_spec::rate;
}

// Whether this ir_quote_type includes the index in the qualifier (as opposed to just
// ccy/tenor for xccy/BMA types).
bool qualifier_includes_index(ir_quote_type qt) {
    switch (qt) {
        case ir_quote_type::cc_basis_swap:
        case ir_quote_type::cc_fix_float_swap:
        case ir_quote_type::bma_swap:
            return false;
        default:
            return true;
    }
}

std::string_view ore_vol_model(volatility_model_subtype m) {
    switch (m) {
        case volatility_model_subtype::rate_lnvol:
            return ore_vol_spec::rate_lnvol;
        case volatility_model_subtype::rate_nvol:
            return ore_vol_spec::rate_nvol;
        case volatility_model_subtype::rate_slnvol:
            return ore_vol_spec::rate_slnvol;
        case volatility_model_subtype::shift:
            return ore_vol_spec::shift;
        case volatility_model_subtype::price:
            return ore_vol_spec::price;
    }
    return ore_vol_spec::rate_lnvol;
}

std::optional<std::string> quote_key_ir(const ir_market_data_identifier& id) {
    if (id.type == instrument_type::vol) {
        // Use typed vol struct if available, else fall back to point composite.
        if (id.vol && id.tenor) {
            const auto& v = *id.vol;
            return std::format("SWAPTION/{}/{}/{}/{}/{}",
                               ore_vol_model(v.model_subtype),
                               id.ccy,
                               to_upper(v.expiry),
                               to_upper(*id.tenor),
                               to_upper(v.strike));
        }
        if (!id.point)
            return std::nullopt;
        const auto parts = split_point(*id.point);
        if (parts.size() != 3)
            return std::nullopt;
        const auto t = id.tenor ? to_upper(*id.tenor) : parts[1];
        return std::format("SWAPTION/RATE_LNVOL/{}/{}/{}/{}", id.ccy, parts[0], t, parts[2]);
    }
    if (id.type != instrument_type::quote || !id.quote_type || !id.point || !id.tenor)
        return std::nullopt;

    const auto qt = *id.quote_type;
    const auto m = id.metric ? *id.metric : default_metric(qt);
    const auto point = to_upper(*id.point);
    const auto t = to_upper(*id.tenor);

    if (qualifier_includes_index(qt)) {
        if (qt == ir_quote_type::ir_swap)
            return std::format("{}/{}/{}/2D/{}/{}", ore_type(qt), ore_metric(m), id.ccy, t, point);
        if (qt == ir_quote_type::discount)
            return std::format("{}/{}/{}/{}/{}",
                               ore_type(qt),
                               ore_metric(m),
                               id.ccy,
                               curve_id(id.ccy, *id.tenor),
                               point);
        if (!id.index)
            return std::nullopt;
        const auto idx = to_upper(std::string(magic_enum::enum_name(*id.index)));
        return std::format("{}/{}/{}/{}/{}/{}", ore_type(qt), ore_metric(m), id.ccy, idx, t, point);
    }
    // No-index types: CC_BASIS_SWAP, CC_FIX_FLOAT_SWAP, BMA_SWAP
    return std::format("{}/{}/{}/{}/{}", ore_type(qt), ore_metric(m), id.ccy, t, point);
}

{{#fx_ore}}
std::string_view ore_type({{asset_class}}_quote_type qt) {
    switch (qt) {
{{#quote_types}}
        case {{asset_class}}_quote_type::{{enum_name}}:
            return ore_type_spec::{{ore_constant}};
{{/quote_types}}
    }
    return ore_type_spec::{{ore_constant_default}};
}

std::string_view ore_{{asset_class}}_metric({{asset_class}}_quote_type qt) {
    switch (qt) {
{{#metric_groups}}
{{#names}}
        case {{asset_class}}_quote_type::{{.}}:
{{/names}}
            return ore_metric_spec::{{constant}};
{{/metric_groups}}
    }
    return ore_metric_spec::{{ore_metric_constant_default}};
}

std::optional<std::string> quote_key_fx(const {{asset_class}}_market_data_identifier& id) {
    if (id.type != instrument_type::quote || id.pair.size() != 6)
        return std::nullopt;
    const auto qt = id.quote_type.value_or(fx_quote_type::spot);
    // spot: TYPE/METRIC/CCY1/CCY2 (scalar).
    if (qt == fx_quote_type::spot)
        return std::format("{}/{}/{}/{}",
                           ore_type(qt),
                           ore_fx_metric(qt),
                           id.pair.substr(0, 3),
                           id.pair.substr(3, 3));
    // fwd: TYPE/METRIC/CCY1/CCY2/TENOR — forward curve, needs point for tenor.
    if (!id.point)
        return std::nullopt;
    return std::format("{}/{}/{}/{}/{}",
                       ore_type(qt),
                       ore_fx_metric(qt),
                       id.pair.substr(0, 3),
                       id.pair.substr(3, 3),
                       to_upper(*id.point));
}

{{/fx_ore}}
{{#equity_ore}}
std::string_view ore_type({{asset_class}}_quote_type qt) {
    switch (qt) {
{{#quote_types}}
        case {{asset_class}}_quote_type::{{enum_name}}:
            return ore_type_spec::{{ore_constant}};
{{/quote_types}}
    }
    return ore_type_spec::{{ore_constant_default}};
}

std::string_view ore_{{asset_class}}_metric({{asset_class}}_quote_type qt) {
    switch (qt) {
{{#metric_groups}}
{{#names}}
        case {{asset_class}}_quote_type::{{.}}:
{{/names}}
            return ore_metric_spec::{{constant}};
{{/metric_groups}}
    }
    return ore_metric_spec::{{ore_metric_constant_default}};
}

std::optional<std::string> quote_key_equity(const {{asset_class}}_market_data_identifier& id) {
    if (id.type != instrument_type::quote)
        return std::nullopt;
    const auto qt = id.quote_type.value_or(equity_quote_type::spot);
    // spot: EQUITY/PRICE/TICKER/CCY (scalar, no tenor).
    if (qt == equity_quote_type::spot)
        return std::format("{}/{}/{}/{}", ore_type(qt), ore_equity_metric(qt), id.ticker, id.ccy);
    // dividend/fwd: TYPE/METRIC/TICKER/CCY/TENOR — curves, need point for the tenor dimension.
    if (!id.point)
        return std::nullopt;
    return std::format("{}/{}/{}/{}/{}",
                       ore_type(qt),
                       ore_equity_metric(qt),
                       id.ticker,
                       id.ccy,
                       to_upper(*id.point));
}

{{/equity_ore}}
{{#credit_ore}}
std::string_view ore_type({{asset_class}}_quote_type qt) {
    switch (qt) {
{{#quote_types}}
        case {{asset_class}}_quote_type::{{enum_name}}:
            return ore_type_spec::{{ore_constant}};
{{/quote_types}}
    }
    return ore_type_spec::{{ore_constant_default}};
}

std::string_view ore_{{asset_class}}_metric({{asset_class}}_quote_type qt) {
    switch (qt) {
{{#metric_groups}}
{{#names}}
        case {{asset_class}}_quote_type::{{.}}:
{{/names}}
            return ore_metric_spec::{{constant}};
{{/metric_groups}}
    }
    return ore_metric_spec::{{ore_metric_constant_default}};
}

std::optional<std::string> quote_key_credit(const {{asset_class}}_market_data_identifier& id) {
    if (id.type != instrument_type::quote)
        return std::nullopt;

    const auto qt = id.quote_type.value_or(credit_quote_type::cds);
    const auto qm = ore_credit_metric(qt);

    if (!id.point)
        return std::nullopt;

    const auto parts = split_point(*id.point);

    // RECOVERY_RATE/RATE/ENTITY/SENIORITY/CCY — point is just the seniority.
    if (qt == credit_quote_type::recovery_rate) {
        if (parts.size() != 1)
            return std::nullopt;
        return std::format(
            "{}/{}/{}/{}/{}", ore_type(qt), qm, id.reference_entity, parts[0], id.ccy);
    }

    // CDS_INDEX/BASE_CORRELATION/INDEX/TENOR/DETACHMENT — no ccy dimension.
    // INDEX_CDS_TRANCHE/BASE_CORRELATION/INDEX/SERIES_TENOR/DETACHMENT — no ccy dimension.
    if (qt == credit_quote_type::cds_index || qt == credit_quote_type::index_cds_tranche) {
        if (parts.size() != 2)
            return std::nullopt;
        return std::format(
            "{}/{}/{}/{}/{}", ore_type(qt), qm, id.reference_entity, parts[0], parts[1]);
    }

    // CDS/CREDIT_SPREAD/ENTITY/SENIORITY/CCY/TENOR — point=seniority,tenor (2 parts).
    // HAZARD_RATE/RATE/ENTITY/SENIORITY/CCY/TENOR — same 6-segment shape.
    if (parts.size() != 2)
        return std::nullopt;
    return std::format(
        "{}/{}/{}/{}/{}/{}", ore_type(qt), qm, id.reference_entity, parts[0], id.ccy, parts[1]);
}

{{/credit_ore}}
{{#commodity_ore}}
std::string_view ore_type({{asset_class}}_quote_type qt) {
    switch (qt) {
        case commodity_quote_type::spot:
            return ore_type_spec::commodity;
        case commodity_quote_type::fwd:
            return ore_type_spec::commodity_fwd;
        // NOTE: Real ORE CPR/RATE quotes are security-level, keyed by ISIN
        // (e.g. CPR/RATE/ISIN:XS0983610930, a scalar inside <Security> blocks in
        // curveconfig.xml), not commodity/ccy/tenor. Modelling CPR under
        // commodity_market_data_identifier is a deliberate simplification since
        // ORE Studio has no security-level identifier yet; the key emitted here
        // (CPR/RATE/CODE/CCY/TENOR) is ORE-Studio-internal shaped, not ORE-native.
        case commodity_quote_type::cpr:
            return ore_type_spec::cpr;
    }
    return ore_type_spec::commodity;
}

std::string_view ore_commodity_metric(commodity_quote_type qt) {
    switch (qt) {
        case commodity_quote_type::spot:
        case commodity_quote_type::fwd:
            return ore_metric_spec::price;
        case commodity_quote_type::cpr:
            return ore_metric_spec::rate;
    }
    return ore_metric_spec::price;
}

{{/commodity_ore}}
{{#inflation_ore}}
std::string_view ore_type({{asset_class}}_quote_type qt) {
    switch (qt) {
{{#quote_types}}
        case {{asset_class}}_quote_type::{{enum_name}}:
            return ore_type_spec::{{ore_constant}};
{{/quote_types}}
    }
    return ore_type_spec::{{ore_constant_default}};
}

std::string_view ore_{{asset_class}}_metric({{asset_class}}_quote_type qt) {
    switch (qt) {
{{#metric_groups}}
{{#names}}
        case {{asset_class}}_quote_type::{{.}}:
{{/names}}
            return ore_metric_spec::{{constant}};
{{/metric_groups}}
    }
    return ore_metric_spec::{{ore_metric_constant_default}};
}

std::optional<std::string> quote_key_inflation(const {{asset_class}}_market_data_identifier& id) {
    if (id.type != instrument_type::quote || !id.quote_type || !id.point)
        return std::nullopt;
    const auto qt = *id.quote_type;
    // SEASONALITY/RATE/MULT/<INDEX>/<POINT> — 5-segment key with literal MULT.
    if (qt == inflation_quote_type::seasonality)
        return std::format("{}/{}/MULT/{}/{}",
                           ore_type(qt),
                           ore_inflation_metric(qt),
                           id.index_code,
                           to_upper(*id.point));
    return std::format(
        "{}/{}/{}/{}", ore_type(qt), ore_inflation_metric(qt), id.index_code, to_upper(*id.point));
}

{{/inflation_ore}}
{{#correlation_ore}}
std::optional<std::string> quote_key_correlation(const {{asset_class}}_market_data_identifier& id) {
    if (id.type != instrument_type::quote)
        return std::nullopt;
    return std::format("CORRELATION/RATE/{}", id.factor_pair);
}

{{/correlation_ore}}
{{#commodity_ore}}
std::optional<std::string> quote_key_commodity(const commodity_market_data_identifier& id) {
    if (id.type != instrument_type::quote)
        return std::nullopt;
    const auto qt = id.quote_type.value_or(commodity_quote_type::spot);
    // spot: COMMODITY/PRICE/CODE/CCY (scalar).
    if (qt == commodity_quote_type::spot)
        return std::format(
            "{}/{}/{}/{}", ore_type(qt), ore_commodity_metric(qt), id.commodity_code, id.ccy);
    // fwd/cpr: TYPE/METRIC/CODE/CCY/TENOR — curves, need point for the tenor.
    if (!id.point)
        return std::nullopt;
    return std::format("{}/{}/{}/{}/{}",
                       ore_type(qt),
                       ore_commodity_metric(qt),
                       id.commodity_code,
                       id.ccy,
                       to_upper(*id.point));
}

{{/commodity_ore}}

/*
 * ─── Inverse projection: ORE quote key string → oresmd identifier ─────────────
 *
 * Follows the quote-key forward projections above in reverse. Seeded by the
 * series_key_registry's decomposition table: the segment boundaries mirror that
 * table's per-type rows (qualifier vs point), and every type the table knows is
 * either mapped here or has no oresmd identifier at all (BOND, the option and
 * capfloor families) — those, plus unknown types and malformed keys, yield
 * nullopt. Segment spelling normalisation mirrors the parser: codes/ccy upper,
 * tenor/point/index lower.
 */

std::optional<std::vector<std::string>> split_key(const std::string& key) {
    std::vector<std::string> parts;
    std::stringstream ss(key);
    std::string tok;
    while (std::getline(ss, tok, '/'))
        parts.push_back(tok);
    if (parts.size() < 3)
        return std::nullopt;
    if (std::ranges::any_of(parts, [](const std::string& p) { return p.empty(); }))
        return std::nullopt;
    return parts;
}

template <typename Enum>
std::optional<Enum> parse_enum_lower(std::string_view value) {
    return magic_enum::enum_cast<Enum>(to_lower(value));
}

std::optional<metric> parse_metric(std::string_view value) {
    return parse_enum_lower<metric>(value);
}

std::optional<index_family> parse_index(std::string_view value) {
    return parse_enum_lower<index_family>(value);
}

std::optional<volatility_model_subtype> parse_vol_model(std::string_view value) {
    return parse_enum_lower<volatility_model_subtype>(value);
}

// The forward projections emit one fixed METRIC segment per type for every
// non-IR asset class; a key whose metric differs could not have come from a
// forward projection and is rejected.
bool metric_is(std::string_view metric_segment, std::string_view expected) {
    return to_upper(metric_segment) == expected;
}

// The forward projections emit exactly three alphabetic characters per
// currency segment; a key with anything else could not have come from a
// forward projection.
bool is_currency_code(const std::string& x) {
    return x.size() == 3 &&
           std::ranges::all_of(x, [](unsigned char c) { return std::isalpha(c); });
}

std::optional<market_data_identifier>
from_fx_spot(const std::vector<std::string>& parts,
             const ores::ore::market::fx_quote_convention_checker* checker) {
    // FX/RATE/CCY1/CCY2 — scalar, no point.
    if (parts.size() != 4 || !metric_is(parts[1], ore_metric_spec::rate))
        return std::nullopt;
    if (!is_currency_code(parts[2]) || !is_currency_code(parts[3]))
        return std::nullopt;
    auto base = to_upper(parts[2]);
    auto quote = to_upper(parts[3]);
    if (checker) {
        const auto result = checker->check(base, quote);
        base = result.base_currency;
        quote = result.quote_currency;
    }
    fx_market_data_identifier id;
    id.pair = base + quote;
    id.type = instrument_type::quote;
    id.quote_type = fx_quote_type::spot;
    return id;
}

std::optional<market_data_identifier> from_fx_fwd(const std::vector<std::string>& parts) {
    // FXFWD/RATE/CCY1/CCY2/TENOR.
    if (parts.size() != 5 || !metric_is(parts[1], ore_metric_spec::rate))
        return std::nullopt;
    if (!is_currency_code(parts[2]) || !is_currency_code(parts[3]))
        return std::nullopt;
    fx_market_data_identifier id;
    id.pair = to_upper(parts[2]) + to_upper(parts[3]);
    id.type = instrument_type::quote;
    id.quote_type = fx_quote_type::fwd;
    id.point = to_lower(parts[4]);
    return id;
}

std::optional<market_data_identifier> from_ir_swap(const std::vector<std::string>& parts) {
    // IR_SWAP/METRIC/CCY/SETTLE/TENOR/POINT. The forward hardcodes the settlement
    // ("2D") into the fourth segment; the identifier has no settle field, so the
    // segment is accepted verbatim and dropped.
    if (parts.size() != 6)
        return std::nullopt;
    const auto m = parse_metric(parts[1]);
    if (!m)
        return std::nullopt;
    ir_market_data_identifier id;
    id.ccy = to_upper(parts[2]);
    id.type = instrument_type::quote;
    id.quote_type = ir_quote_type::ir_swap;
    id.metric = *m;
    id.tenor = to_lower(parts[4]);
    id.point = to_lower(parts[5]);
    return id;
}

std::optional<market_data_identifier> from_ir_discount(const std::vector<std::string>& parts) {
    // DISCOUNT/METRIC/CCY/CURVE_ID/POINT, where CURVE_ID = CCY + TENOR.
    if (parts.size() != 5)
        return std::nullopt;
    const auto m = parse_metric(parts[1]);
    if (!m)
        return std::nullopt;
    const auto ccy = to_upper(parts[2]);
    const auto curve_id = to_upper(parts[3]);
    if (curve_id.size() <= ccy.size() || curve_id.substr(0, ccy.size()) != ccy)
        return std::nullopt;
    ir_market_data_identifier id;
    id.ccy = ccy;
    id.type = instrument_type::quote;
    id.quote_type = ir_quote_type::discount;
    id.metric = *m;
    id.tenor = to_lower(curve_id.substr(ccy.size()));
    id.point = to_lower(parts[4]);
    return id;
}

std::optional<market_data_identifier>
from_ir_indexed(ir_quote_type qt, const std::vector<std::string>& parts) {
    // TYPE/METRIC/CCY/INDEX/TENOR/POINT — the families whose qualifier includes
    // the index (MM, FRA, IMM_FRA, BASIS_SWAP, ZERO, MM_FUTURE, OI_FUTURE).
    if (parts.size() != 6)
        return std::nullopt;
    const auto m = parse_metric(parts[1]);
    const auto idx = parse_index(parts[3]);
    if (!m || !idx)
        return std::nullopt;
    ir_market_data_identifier id;
    id.ccy = to_upper(parts[2]);
    id.type = instrument_type::quote;
    id.quote_type = qt;
    id.metric = *m;
    id.index = *idx;
    id.tenor = to_lower(parts[4]);
    id.point = to_lower(parts[5]);
    return id;
}

std::optional<market_data_identifier>
from_ir_no_index(ir_quote_type qt, const std::vector<std::string>& parts) {
    // TYPE/METRIC/CCY/TENOR/POINT — the xccy/BMA families whose qualifier is just
    // ccy/tenor (CC_BASIS_SWAP, CC_FIX_FLOAT_SWAP, BMA_SWAP).
    if (parts.size() != 5)
        return std::nullopt;
    const auto m = parse_metric(parts[1]);
    if (!m)
        return std::nullopt;
    ir_market_data_identifier id;
    id.ccy = to_upper(parts[2]);
    id.type = instrument_type::quote;
    id.quote_type = qt;
    id.metric = *m;
    id.tenor = to_lower(parts[3]);
    id.point = to_lower(parts[4]);
    return id;
}

std::optional<market_data_identifier> from_ir_swaption(const std::vector<std::string>& parts) {
    // SWAPTION/MODEL/CCY/EXPIRY/TENOR/STRIKE. The forward emits either branch (typed
    // vol struct or point composite) in this same six-segment shape; both collapse
    // into the vol struct plus the serialised point, matching what the parser builds
    // for a type=vol URI.
    if (parts.size() != 6)
        return std::nullopt;
    const auto model = parse_vol_model(parts[1]);
    if (!model)
        return std::nullopt;
    ir_market_data_identifier id;
    id.ccy = to_upper(parts[2]);
    id.type = instrument_type::vol;
    id.tenor = to_lower(parts[4]);
    id.point = to_lower(parts[3]) + "," + to_lower(parts[4]) + "," + to_lower(parts[5]);
    volatility_surface_point v;
    v.expiry = to_upper(parts[3]);
    v.strike = to_upper(parts[5]);
    v.model_subtype = *model;
    id.vol = std::move(v);
    return id;
}

std::optional<market_data_identifier> from_equity_spot(const std::vector<std::string>& parts) {
    // EQUITY/PRICE/TICKER/CCY — scalar, no point.
    if (parts.size() != 4 || !metric_is(parts[1], ore_metric_spec::price))
        return std::nullopt;
    equity_market_data_identifier id;
    id.ticker = to_upper(parts[2]);
    id.ccy = to_upper(parts[3]);
    id.type = instrument_type::quote;
    id.quote_type = equity_quote_type::spot;
    return id;
}

std::optional<market_data_identifier>
from_equity_curve(equity_quote_type qt, const std::vector<std::string>& parts) {
    // EQUITY_FWD/PRICE/TICKER/CCY/TENOR, EQUITY_DIVIDEND/RATE/TICKER/CCY/TENOR.
    const auto expected =
        (qt == equity_quote_type::dividend) ? ore_metric_spec::rate : ore_metric_spec::price;
    if (parts.size() != 5 || !metric_is(parts[1], expected))
        return std::nullopt;
    equity_market_data_identifier id;
    id.ticker = to_upper(parts[2]);
    id.ccy = to_upper(parts[3]);
    id.type = instrument_type::quote;
    id.quote_type = qt;
    id.point = to_lower(parts[4]);
    return id;
}

std::optional<market_data_identifier> from_commodity_spot(const std::vector<std::string>& parts) {
    // COMMODITY/PRICE/CODE/CCY — scalar, no point.
    if (parts.size() != 4 || !metric_is(parts[1], ore_metric_spec::price))
        return std::nullopt;
    commodity_market_data_identifier id;
    id.commodity_code = to_upper(parts[2]);
    id.ccy = to_upper(parts[3]);
    id.type = instrument_type::quote;
    id.quote_type = commodity_quote_type::spot;
    return id;
}

std::optional<market_data_identifier>
from_commodity_curve(commodity_quote_type qt, const std::vector<std::string>& parts) {
    // COMMODITY_FWD/PRICE/CODE/CCY/TENOR, CPR/RATE/CODE/CCY/TENOR.
    const auto expected =
        (qt == commodity_quote_type::cpr) ? ore_metric_spec::rate : ore_metric_spec::price;
    if (parts.size() != 5 || !metric_is(parts[1], expected))
        return std::nullopt;
    commodity_market_data_identifier id;
    id.commodity_code = to_upper(parts[2]);
    id.ccy = to_upper(parts[3]);
    id.type = instrument_type::quote;
    id.quote_type = qt;
    id.point = to_lower(parts[4]);
    return id;
}

std::optional<market_data_identifier>
from_credit_curve(credit_quote_type qt, const std::vector<std::string>& parts) {
    // CDS/CREDIT_SPREAD/ENTITY/SENIORITY/CCY/TENOR,
    // HAZARD_RATE/RATE/ENTITY/SENIORITY/CCY/TENOR — point = seniority,tenor.
    const auto expected = (qt == credit_quote_type::cds) ? ore_metric_spec::credit_spread
                                                         : ore_metric_spec::rate;
    if (parts.size() != 6 || !metric_is(parts[1], expected))
        return std::nullopt;
    credit_market_data_identifier id;
    id.reference_entity = to_upper(parts[2]);
    id.ccy = to_upper(parts[4]);
    id.type = instrument_type::quote;
    id.quote_type = qt;
    id.point = to_lower(parts[3]) + "," + to_lower(parts[5]);
    return id;
}

std::optional<market_data_identifier> from_credit_recovery(const std::vector<std::string>& parts) {
    // RECOVERY_RATE/RATE/ENTITY/SENIORITY/CCY — scalar; point is just the seniority.
    if (parts.size() != 5 || !metric_is(parts[1], ore_metric_spec::rate))
        return std::nullopt;
    credit_market_data_identifier id;
    id.reference_entity = to_upper(parts[2]);
    id.ccy = to_upper(parts[4]);
    id.type = instrument_type::quote;
    id.quote_type = credit_quote_type::recovery_rate;
    id.point = to_lower(parts[3]);
    return id;
}

std::optional<market_data_identifier>
from_credit_index(credit_quote_type qt, const std::vector<std::string>& parts) {
    // CDS_INDEX/BASE_CORRELATION/INDEX/TENOR/DETACHMENT,
    // INDEX_CDS_TRANCHE/BASE_CORRELATION/INDEX/SERIES_TENOR/DETACHMENT — no ccy
    // dimension; point = tenor,detachment.
    if (parts.size() != 5 || !metric_is(parts[1], ore_metric_spec::base_correlation))
        return std::nullopt;
    credit_market_data_identifier id;
    id.reference_entity = to_upper(parts[2]);
    id.type = instrument_type::quote;
    id.quote_type = qt;
    id.point = to_lower(parts[3]) + "," + to_lower(parts[4]);
    return id;
}

std::optional<market_data_identifier>
from_inflation_swap(inflation_quote_type qt, const std::vector<std::string>& parts) {
    // ZC_INFLATIONSWAP/RATE/INDEX/POINT, YY_INFLATIONSWAP/RATE/INDEX/POINT.
    if (parts.size() != 4 || !metric_is(parts[1], ore_metric_spec::rate))
        return std::nullopt;
    inflation_market_data_identifier id;
    id.index_code = to_upper(parts[2]);
    id.type = instrument_type::quote;
    id.quote_type = qt;
    id.point = to_lower(parts[3]);
    return id;
}

std::optional<market_data_identifier> from_inflation_seasonality(const std::vector<std::string>& parts) {
    // SEASONALITY/RATE/MULT/INDEX/POINT — the forward emits the literal MULT in the
    // third segment; it is accepted verbatim and dropped.
    if (parts.size() != 5 || !metric_is(parts[1], ore_metric_spec::rate))
        return std::nullopt;
    inflation_market_data_identifier id;
    id.index_code = to_upper(parts[3]);
    id.type = instrument_type::quote;
    id.quote_type = inflation_quote_type::seasonality;
    id.point = to_lower(parts[4]);
    return id;
}

std::optional<market_data_identifier> from_correlation(const std::vector<std::string>& parts) {
    // CORRELATION/RATE/FACTOR_PAIR — scalar, no point.
    if (parts.size() != 3 || !metric_is(parts[1], ore_metric_spec::rate))
        return std::nullopt;
    correlation_market_data_identifier id;
    id.factor_pair = to_upper(parts[2]);
    id.type = instrument_type::quote;
    id.quote_type = correlation_quote_type::pairwise;
    return id;
}

std::optional<market_data_identifier>
inverse_projection(const std::vector<std::string>& parts,
                   const ores::ore::market::fx_quote_convention_checker* checker) {
    const auto type = to_upper(parts[0]);
    if (type == ore_type_spec::fx)
        return from_fx_spot(parts, checker);
    if (type == ore_type_spec::fxfwd)
        return from_fx_fwd(parts);
    if (type == ore_type_spec::ir_swap)
        return from_ir_swap(parts);
    if (type == ore_type_spec::discount)
        return from_ir_discount(parts);
    if (type == ore_type_spec::swaption)
        return from_ir_swaption(parts);
    if (type == ore_type_spec::mm || type == ore_type_spec::fra ||
        type == ore_type_spec::imm_fra || type == ore_type_spec::basis_swap ||
        type == ore_type_spec::zero || type == ore_type_spec::mm_future ||
        type == ore_type_spec::oi_future) {
        const auto qt = parse_enum_lower<ir_quote_type>(type);
        return qt ? from_ir_indexed(*qt, parts) : std::nullopt;
    }
    if (type == ore_type_spec::cc_basis_swap || type == ore_type_spec::cc_fix_float_swap ||
        type == ore_type_spec::bma_swap) {
        const auto qt = parse_enum_lower<ir_quote_type>(type);
        return qt ? from_ir_no_index(*qt, parts) : std::nullopt;
    }
    if (type == ore_type_spec::equity)
        return from_equity_spot(parts);
    // The ORE spellings differ from the enum names for these types ("EQUITY_FWD" vs
    // the enumerator "fwd"), so they are dispatched explicitly rather than by
    // magic_enum name lookup -- mirroring the forward ore_type(quote_type) tables.
    if (type == ore_type_spec::equity_fwd)
        return from_equity_curve(equity_quote_type::fwd, parts);
    if (type == ore_type_spec::equity_dividend)
        return from_equity_curve(equity_quote_type::dividend, parts);
    if (type == ore_type_spec::commodity)
        return from_commodity_spot(parts);
    if (type == ore_type_spec::commodity_fwd)
        return from_commodity_curve(commodity_quote_type::fwd, parts);
    if (type == ore_type_spec::cpr)
        return from_commodity_curve(commodity_quote_type::cpr, parts);
    if (type == ore_type_spec::cds || type == ore_type_spec::hazard_rate) {
        const auto qt = parse_enum_lower<credit_quote_type>(type);
        return qt ? from_credit_curve(*qt, parts) : std::nullopt;
    }
    if (type == ore_type_spec::recovery_rate)
        return from_credit_recovery(parts);
    if (type == ore_type_spec::cds_index || type == ore_type_spec::index_cds_tranche) {
        const auto qt = parse_enum_lower<credit_quote_type>(type);
        return qt ? from_credit_index(*qt, parts) : std::nullopt;
    }
    if (type == ore_type_spec::zc_inflation_swap)
        return from_inflation_swap(inflation_quote_type::zc_swap, parts);
    if (type == ore_type_spec::yy_inflation_swap)
        return from_inflation_swap(inflation_quote_type::yy_swap, parts);
    if (type == ore_type_spec::seasonality)
        return from_inflation_seasonality(parts);
    if (type == ore_type_spec::correlation)
        return from_correlation(parts);
    // No oresmd mapping: BOND, FX_OPTION, CAPFLOOR, INDEX_CDS_OPTION,
    // EQUITY_OPTION, COMMODITY_OPTION, ZC_INFLATIONCAPFLOOR, YY_INFLATIONCAPFLOOR,
    // and any type the registry does not know.
    return std::nullopt;
}

}

namespace ores::marketdata::core {

std::optional<std::string>
oresmd_projections::to_index_name(const domain::market_data_identifier& identifier) {
    if (const auto* ir = std::get_if<ir_market_data_identifier>(&identifier))
        return index_name_ir(*ir);
    return std::nullopt;
}

std::optional<std::string>
oresmd_projections::to_curve_key(const domain::market_data_identifier& identifier) {
    if (const auto* ir = std::get_if<ir_market_data_identifier>(&identifier))
        return curve_key_ir(*ir);
    return std::nullopt;
}

std::optional<std::string>
oresmd_projections::to_quote_key(const domain::market_data_identifier& identifier) {
    return std::visit(
        [](const auto& id) -> std::optional<std::string> {
            using T = std::decay_t<decltype(id)>;
{{#oresmd_variant_specs}}
{{#variant_first}}            if constexpr{{/variant_first}}{{^variant_first}}            else if constexpr{{/variant_first}} (std::is_same_v<T, {{asset_class}}_market_data_identifier>)
                return quote_key_{{asset_class}}(id);
{{/oresmd_variant_specs}}
        },
        identifier);
}

std::optional<market_series_key>
oresmd_projections::split_market_series_key(const std::string& key) {
    std::vector<std::string> parts;
    std::stringstream ss(key);
    std::string tok;
    while (std::getline(ss, tok, '/'))
        parts.push_back(tok);
    if (parts.size() < 3)
        return std::nullopt;

    market_series_key result;
    result.series_type = parts[0];
    result.metric = parts[1];
    result.qualifier = parts[2];
    for (std::size_t i = 3; i < parts.size(); ++i)
        result.qualifier += "/" + parts[i];
    return result;
}

std::optional<domain::market_data_identifier>
oresmd_projections::from_ore_key(const std::string& key) {
    const auto parts = split_key(key);
    if (!parts)
        return std::nullopt;
    return inverse_projection(*parts, nullptr);
}

std::optional<domain::market_data_identifier> oresmd_projections::from_ore_key(
    const std::string& key, const ores::ore::market::fx_quote_convention_checker& checker) {
    const auto parts = split_key(key);
    if (!parts)
        return std::nullopt;
    return inverse_projection(*parts, &checker);
}

}

Emacs 29.3 (Org mode 9.6.15)