library(epwshiftr)

workflow_root <- file.path(tempdir(), "epwshiftr-esg-dictionaries")
if (dir.exists(workflow_root)) {
    unlink(workflow_root, recursive = TRUE)
}
dir.create(workflow_root, recursive = TRUE, showWarnings = FALSE)

options(
    epwshiftr.dir_cache = file.path(workflow_root, "cache")
)

show_rows <- function(x, n = 8L) {
    utils::head(data.table::as.data.table(x), n)
}

ESG projects describe model output through controlled vocabularies. Some projects also publish request-table metadata that describes valid relationships between fields such as activities, experiments, variables, tables, and frequencies.

EsgDict is epwshiftr’s project-aware dictionary object. The main future EPW workflow can run without manual dictionary code, but dictionaries are the best first check when you want to validate request values before sending a live ESGF query.

Build a CMIP6 Dictionary

EsgDict$new(project = "CMIP6") creates an empty dictionary object. $build() downloads upstream controlled-vocabulary and request-table sources, parses them, and builds normalized indices used by options() and check().

This article uses a fresh article-local source directory so the output comes from a live upstream build.

cmip6_source_dir <- file.path(workflow_root, "sources", "CMIP6")

dict <- EsgDict$new(project = "CMIP6")
dict$status()
#> [1] "empty"

dict$build(
    source_dir = cmip6_source_dir,
    use_cache = FALSE
)

dict$status()
#> [1] "built"
dict$has_data()
#> [1] TRUE

Inspect Source Metadata

After a build, the dictionary records where its source data came from, which versions were used, source timestamps where available, and when the local dictionary was built.

dict$project()
#> [1] "CMIP6"
dict$profile()
#> [1] "cmip6"
dict$sources()
#> $vocab
#> $vocab$repo
#> [1] "WCRP-CMIP/CMIP6_CVs"
#>
#> $vocab$tag
#> [1] "6.2.60.0"
#>
#> $vocab$commit
#> [1] "78a465a7dabb01e357928ea223bc5da871a6e6dc"
#>
#> $vocab$source_dir
#> [1] "/private/var/folders/8f/t8sk2pps6135xbp47cs8qb2r0000gn/T/Rtmp3Hed7X/epwshiftr-esg-dictionaries/sources/CMIP6/vocab/6.2.60.0"
#>
#>
#> $request
#> $request$repo
#> [1] "PCMDI/cmip6-cmor-tables"
#>
#> $request$tag
#> [1] "6.9.33"
#>
#> $request$commit
#> [1] "c8810526268e8cb13f2b37aba6af377e15644561"
#>
#> $request$source_dir
#> [1] "/private/var/folders/8f/t8sk2pps6135xbp47cs8qb2r0000gn/T/Rtmp3Hed7X/epwshiftr-esg-dictionaries/sources/CMIP6/request/6.9.33"
dict$version()
#> $vocab
#> [1] '6.2.60.0'
#>
#> $request
#> [1] '1.0.33'
dict$timestamp()
#> $vocab
#> [1] "2025-09-04 23:01:22 UTC"
#>
#> $drs
#> [1] "2025-09-02 22:42:23 UTC"
#>
#> $activity_id
#> [1] "2018-03-06 00:39:09 UTC"
#>
#> $experiment_id
#> [1] "2020-12-15 20:25:59 UTC"
#>
#> $frequency
#> [1] "2021-05-24 14:48:15 UTC"
#>
#> $grid_label
#> [1] "2017-09-09 01:12:00 UTC"
#>
#> $institution_id
#> [1] "2022-08-16 04:41:00 UTC"
#>
#> $nominal_resolution
#> [1] "2016-11-15 23:04:00 UTC"
#>
#> $realm
#> [1] "2017-04-18 19:03:00 UTC"
#>
#> $required_global_attributes
#> [1] "2019-12-19 23:32:17 UTC"
#>
#> $source_id
#> [1] "2025-04-18 23:28:35 UTC"
#>
#> $source_type
#> [1] "2017-09-09 00:57:00 UTC"
#>
#> $sub_experiment_id
#> [1] "2019-06-17 18:01:51 UTC"
#>
#> $table_id
#> [1] "2017-01-13 16:27:00 UTC"
dict$built_time()
#> [1] "2026-06-25 02:14:54 CST"

capabilities() is the fastest way to see whether a project dictionary has vocabulary data, request metadata, and relation indices.

dict$capabilities()
#> $vocab
#> [1] TRUE
#>
#> $request
#> [1] TRUE
#>
#> $relations
#> [1] "variable"            "activity_experiment" "activity_source"
dict$fields()
#>  [1] "activity_id"        "experiment_id"      "frequency"
#>  [4] "grid_label"         "institution_id"     "mip_era"
#>  [7] "nominal_resolution" "project"            "realm"
#> [10] "source_id"          "source_type"        "sub_experiment_id"
#> [13] "table_id"           "variable_id"        "variant_label"
dict$relation_fields()
#> $variable
#> [1] "variable_id" "table_id"    "frequency"   "realm"
#>
#> $activity_experiment
#> [1] "activity_id"       "experiment_id"     "sub_experiment_id"
#>
#> $activity_source
#> [1] "activity_id"    "source_id"      "institution_id"

CMIP6 includes request-table metadata, so it can answer both value questions and many cross-field legality questions.

Work with Dictionary Indices

The raw vocabulary payload is available through $get(), but most workflows should use normalized fields and indices.

index_names <- names(dict$indices())
index_names
#> [1] "values"              "variable"            "activity_experiment"
#> [4] "activity_source"

show_rows(dict$indices("values"), 12L)
#>             field      value
#>            <char>     <char>
#>  1:       project      CMIP6
#>  2:       mip_era      CMIP6
#>  3: variant_label       <NA>
#>  4:   activity_id AerChemMIP
#>  5:   activity_id      C4MIP
#>  6:   activity_id     CDRMIP
#>  7:   activity_id      CFMIP
#>  8:   activity_id       CMIP
#>  9:   activity_id     CORDEX
#> 10:   activity_id      DAMIP
#> 11:   activity_id       DCPP
#> 12:   activity_id  DynVarMIP
#>                                                                                                     description
#>                                                                                                          <char>
#>  1:                                                                                               CMIP6 project
#>  2:                                                                                               CMIP6 project
#>  3:                                               CMIP6 realization-initialization-physics-forcing member label
#>  4:                                                        Aerosols and Chemistry Model Intercomparison Project
#>  5:                                                  Coupled Climate Carbon Cycle Model Intercomparison Project
#>  6:                                                        Carbon Dioxide Removal Model Intercomparison Project
#>  7:                                                                Cloud Feedback Model Intercomparison Project
#>  8: CMIP DECK: 1pctCO2, abrupt4xCO2, amip, esm-piControl, esm-historical, historical, and piControl experiments
#>  9:                                                         Coordinated Regional Climate Downscaling Experiment
#> 10:                                                     Detection and Attribution Model Intercomparison Project
#> 11:                                                                          Decadal Climate Prediction Project
#> 12:                                                      Dynamics and Variability Model Intercomparison Project
#>                    pattern   source
#>                     <char>   <char>
#>  1:                   <NA> constant
#>  2:                   <NA> constant
#>  3: ^r\\d+i\\d+p\\d+f\\d+$  pattern
#>  4:                   <NA>    vocab
#>  5:                   <NA>    vocab
#>  6:                   <NA>    vocab
#>  7:                   <NA>    vocab
#>  8:                   <NA>    vocab
#>  9:                   <NA>    vocab
#> 10:                   <NA>    vocab
#> 11:                   <NA>    vocab
#> 12:                   <NA>    vocab

if ("variable" %in% index_names) {
    show_rows(dict$indices("variable"), 12L)
}
#>     variable_id table_id frequency  realm
#>          <char>   <char>    <char> <char>
#>  1:         clt      3hr       3hr  atmos
#>  2:        hfls      3hr       3hr  atmos
#>  3:        hfss      3hr       3hr  atmos
#>  4:        huss      3hr     3hrPt  atmos
#>  5:        mrro      3hr       3hr   land
#>  6:       mrsos      3hr     3hrPt   land
#>  7:          pr      3hr       3hr  atmos
#>  8:         prc      3hr       3hr  atmos
#>  9:        prsn      3hr       3hr  atmos
#> 10:          ps      3hr     3hrPt  atmos
#> 11:        rlds      3hr       3hr  atmos
#> 12:      rldscs      3hr       3hr  atmos
#>                                            long_name      units
#>                                               <char>     <char>
#>  1:                     Total Cloud Cover Percentage          %
#>  2:                  Surface Upward Latent Heat Flux      W m-2
#>  3:                Surface Upward Sensible Heat Flux      W m-2
#>  4:                   Near-Surface Specific Humidity          1
#>  5:                                     Total Runoff kg m-2 s-1
#>  6:         Moisture in Upper Portion of Soil Column     kg m-2
#>  7:                                    Precipitation kg m-2 s-1
#>  8:                         Convective Precipitation kg m-2 s-1
#>  9:                                    Snowfall Flux kg m-2 s-1
#> 10:                             Surface Air Pressure         Pa
#> 11:           Surface Downwelling Longwave Radiation      W m-2
#> 12: Surface Downwelling Clear-Sky Longwave Radiation      W m-2

$get() is useful when you need the source-level payload for a specific field or request table.

show_rows(dict$get("experiment_id"), 8L)
#>          experiment_id
#>                 <char>
#> 1:             1pctCO2
#> 2:       1pctCO2-4xext
#> 3:         1pctCO2-bgc
#> 4:         1pctCO2-cdr
#> 5:         1pctCO2-rad
#> 6:         1pctCO2Ndep
#> 7:     1pctCO2Ndep-bgc
#> 8: 1pctCO2to4x-withism
#>                                                                                                               experiment
#>                                                                                                                   <char>
#> 1:                                                                                    1 percent per year increase in CO2
#> 2:                                                                         extension from year 140 of 1pctCO2 with 4xCO2
#> 3:                                      biogeochemically-coupled version of 1 percent per year increasing CO2 experiment
#> 4:                                                                         1 percent per year decrease in CO2 from 4xCO2
#> 5:                                           radiatively-coupled version of 1 percent per year increasing CO2 experiment
#> 6:                                             1 percent per year increasing CO2 experiment with increasing N-deposition
#> 7:         biogeochemically-coupled version of 1 percent per year increasing CO2 experiment with increasing N-deposition
#> 8: simulation with interactive ice sheet forced by 1 percent per year increase in CO2 to 4xCO2 (subsequently held fixed)
#>                                                                                                                                                                                        description
#>                                                                                                                                                                                             <char>
#> 1:                                                                                                                                                                                   DECK: 1pctCO2
#> 2:                                                                                                 branched from 1pctCO2 run at year 140 and run with CO2 fixed at 4x pre-industrial concentration
#> 3:                                                                   Biogeochemically-coupled specified concentration simulation in which CO2 increases at a rate of 1% per year until quadrupling
#> 4:          1 percent per year decrease in CO2 (immediately after reaching 4xCO2 in the 1pctCO2 simulation); then held constant at pre-industrial level (part of the CDR-reversibility experiment)
#> 5:                                                                        Radiatively-coupled specified concentration simulation in which CO2 increases at a rate of 1% per year until quadrupling
#> 6:            Fully-coupled specified concentration simulation in which CO2 increases at a rate of 1% per year until quadrupling, plus an additional scenario of anthropogenic nitrogen deposition
#> 7: Biogeochemically-coupled specified concentration simulation in which CO2 increases at a rate of 1% per year until quadrupling, plus an additional scenario of anthropogenic nitrogen deposition
#> 8:                                   Idealized 1%/yr CO2 increase to 4xC02 over 140yrs and kept constant at 4xCO2 for an additional 200 to 400 yrs simulation that includes interactive ice sheets
#>     tier start_year end_year min_number_yrs_per_sim required_model_components
#>    <int>      <int>    <int>                  <int>                    <list>
#> 1:     1         NA       NA                    150                     AOGCM
#> 2:     1         NA       NA                    210                     AOGCM
#> 3:     1         NA       NA                    150                 AOGCM,BGC
#> 4:     1         NA       NA                    200                 AOGCM,BGC
#> 5:     2         NA       NA                    150                 AOGCM,BGC
#> 6:     2         NA       NA                    150                 AOGCM,BGC
#> 7:     2         NA       NA                    150                 AOGCM,BGC
#> 8:     1         NA       NA                    350                 AOGCM,ISM
#>    parent_experiment_id sub_experiment_id activity_id parent_activity_id
#>                  <list>            <list>      <list>             <list>
#> 1:            piControl              none        CMIP               CMIP
#> 2:              1pctCO2              none      ISMIP6               CMIP
#> 3:            piControl              none       C4MIP               CMIP
#> 4:              1pctCO2              none      CDRMIP               CMIP
#> 5:            piControl              none       C4MIP               CMIP
#> 6:            piControl              none       C4MIP               CMIP
#> 7:            piControl              none       C4MIP               CMIP
#> 8:    piControl-withism              none      ISMIP6             ISMIP6
#>    additional_allowed_model_components
#>                                 <list>
#> 1:                        AER,CHEM,BGC
#> 2:                        AER,CHEM,BGC
#> 3:                            AER,CHEM
#> 4:                            AER,CHEM
#> 5:                            AER,CHEM
#> 6:                            AER,CHEM
#> 7:                            AER,CHEM
#> 8:                        AER,CHEM,BGC

request_payload <- dict$get("request")
show_rows(request_payload[, c(
    "variable", "table_id", "frequency", "long_name", "units"
), with = FALSE], 8L)
#>    variable table_id frequency                                long_name
#>      <char>   <char>    <char>                                   <char>
#> 1:      clt      3hr       3hr             Total Cloud Cover Percentage
#> 2:     hfls      3hr       3hr          Surface Upward Latent Heat Flux
#> 3:     hfss      3hr       3hr        Surface Upward Sensible Heat Flux
#> 4:     huss      3hr     3hrPt           Near-Surface Specific Humidity
#> 5:     mrro      3hr       3hr                             Total Runoff
#> 6:    mrsos      3hr     3hrPt Moisture in Upper Portion of Soil Column
#> 7:       pr      3hr       3hr                            Precipitation
#> 8:      prc      3hr       3hr                 Convective Precipitation
#>         units
#>        <char>
#> 1:          %
#> 2:      W m-2
#> 3:      W m-2
#> 4:          1
#> 5: kg m-2 s-1
#> 6:     kg m-2
#> 7: kg m-2 s-1
#> 8: kg m-2 s-1
names(request_payload)
#>  [1] "variable"        "table_id"        "modeling_realm"  "standard_name"
#>  [5] "long_name"       "frequency"       "units"           "cell_methods"
#>  [9] "cell_measures"   "comment"         "dimensions"      "out_name"
#> [13] "type"            "positive"        "valid_min"       "valid_max"
#> [17] "ok_min_mean_abs" "ok_max_mean_abs"

$options() lists valid values for a field. Constraints narrow the result when a relation index is available.

show_rows(dict$options("experiment_id", activity_id = "ScenarioMIP"), 12L)
#>             field       value
#>            <char>      <char>
#>  1: experiment_id rcp26-cmip5
#>  2: experiment_id rcp45-cmip5
#>  3: experiment_id rcp60-cmip5
#>  4: experiment_id rcp85-cmip5
#>  5: experiment_id      ssp119
#>  6: experiment_id      ssp126
#>  7: experiment_id      ssp245
#>  8: experiment_id      ssp370
#>  9: experiment_id      ssp434
#> 10: experiment_id      ssp460
#> 11: experiment_id ssp534-over
#> 12: experiment_id      ssp585
#>                                                                                                                                                                                                                                                description
#>                                                                                                                                                                                                                                                     <char>
#>  1:                                                                                        future scenario with low radiative forcing by the end of century. Following RCP2.6 global forcing pathway. Concentration-driven (CMIP5-era [2006-2100] forcing)
#>  2:                                                                                 future scenario with low-medium radiative forcing by the end of century. Following RCP4.5 global forcing pathway. Concentration-driven (CMIP5-era [2006-2100] forcing)
#>  3:                                                                                     future scenario with medium radiative forcing by the end of century. Following RCP6.0 global forcing pathway. Concentration-driven (CMIP5-era [2006-2100] forcing)
#>  4:                                                                                       future scenario with high radiative forcing by the end of century. Following RCP8.5 global forcing pathway. Concentration-driven (CMIP5-era [2006-2100] forcing)
#>  5:                                                                                                                              Future scenario with low radiative forcing throughout reaching about 1.9 W/m2 in 2100 based on SSP1. Concentration-driven
#>  6:  Future scenario with low radiative forcing by the end of century. Following approximately RCP2.6 global forcing pathway but with new forcing based on SSP1. Concentration-driven. As a tier 2 option, this simulation should be extended to year 2300
#>  7:                                                                    Future scenario with medium radiative forcing by the end of century. Following approximately RCP4.5 global forcing pathway but with new forcing based on SSP2. Concentration-driven
#>  8:                                                                    Future scenario with high radiative forcing by the end of century. Reaches about 7.0 W/m2 by 2100; fills gap in RCP forcing pathways between 6.0 and 8.5 W/m2. Concentration-driven
#>  9:                                                                     Future scenario with low radiative forcing by the end of century. Reaches about 3.4 W/m2 by 2100; fills gap in RCP forcing pathways between 4.5 and 2.6 W/m2. Concentration-driven
#> 10:                                                                    Future scenario with medium radiative forcing by the end of century. Following approximately RCP6.0 global forcing pathway but with new forcing based on SSP4. Concentration-driven
#> 11:                                          21st century overshoot scenario relative to SSP5_34. Branches from SSP5_85 at 2040 with emissions reduced to zero by 2070 and negative thereafter. This simulation should optionally be extended to year 2300
#> 12: Future scenario with high radiative forcing by the end of century. Following approximately RCP8.5 global forcing pathway but with new forcing based on SSP5. Concentration-driven. As a tier 2 option, this simulation should be extended to year 2300
#>     pattern source
#>      <char> <char>
#>  1:    <NA>  vocab
#>  2:    <NA>  vocab
#>  3:    <NA>  vocab
#>  4:    <NA>  vocab
#>  5:    <NA>  vocab
#>  6:    <NA>  vocab
#>  7:    <NA>  vocab
#>  8:    <NA>  vocab
#>  9:    <NA>  vocab
#> 10:    <NA>  vocab
#> 11:    <NA>  vocab
#> 12:    <NA>  vocab
show_rows(dict$options("variable_id", table_id = "Amon"), 12L)
#>           field  value description pattern  source
#>          <char> <char>      <char>  <char>  <char>
#>  1: variable_id    clt        <NA>    <NA> request
#>  2: variable_id   hfls        <NA>    <NA> request
#>  3: variable_id   hfss        <NA>    <NA> request
#>  4: variable_id   huss        <NA>    <NA> request
#>  5: variable_id     pr        <NA>    <NA> request
#>  6: variable_id    prc        <NA>    <NA> request
#>  7: variable_id   prsn        <NA>    <NA> request
#>  8: variable_id     ps        <NA>    <NA> request
#>  9: variable_id   rlds        <NA>    <NA> request
#> 10: variable_id rldscs        <NA>    <NA> request
#> 11: variable_id   rlus        <NA>    <NA> request
#> 12: variable_id   rsds        <NA>    <NA> request
show_rows(dict$options("table_id", variable_id = "tas"), 12L)
#>        field     value description pattern source
#>       <char>    <char>      <char>  <char> <char>
#>  1: table_id       3hr        <NA>    <NA>  vocab
#>  2: table_id   6hrPlev        <NA>    <NA>  vocab
#>  3: table_id 6hrPlevPt        <NA>    <NA>  vocab
#>  4: table_id     AERhr        <NA>    <NA>  vocab
#>  5: table_id      Amon        <NA>    <NA>  vocab
#>  6: table_id   CFsubhr        <NA>    <NA>  vocab
#>  7: table_id    Esubhr        <NA>    <NA>  vocab
#>  8: table_id   ImonAnt        <NA>    <NA>  vocab
#>  9: table_id   ImonGre        <NA>    <NA>  vocab
#> 10: table_id       day        <NA>    <NA>  vocab

The top-level helper uses a supplied dictionary or the package-level default dictionary. Supplying dict = makes the example explicit.

show_rows(
    esgdict_option("experiment_id", activity_id = "ScenarioMIP", dict = dict),
    12L
)
#>             field       value
#>            <char>      <char>
#>  1: experiment_id rcp26-cmip5
#>  2: experiment_id rcp45-cmip5
#>  3: experiment_id rcp60-cmip5
#>  4: experiment_id rcp85-cmip5
#>  5: experiment_id      ssp119
#>  6: experiment_id      ssp126
#>  7: experiment_id      ssp245
#>  8: experiment_id      ssp370
#>  9: experiment_id      ssp434
#> 10: experiment_id      ssp460
#> 11: experiment_id ssp534-over
#> 12: experiment_id      ssp585
#>                                                                                                                                                                                                                                                description
#>                                                                                                                                                                                                                                                     <char>
#>  1:                                                                                        future scenario with low radiative forcing by the end of century. Following RCP2.6 global forcing pathway. Concentration-driven (CMIP5-era [2006-2100] forcing)
#>  2:                                                                                 future scenario with low-medium radiative forcing by the end of century. Following RCP4.5 global forcing pathway. Concentration-driven (CMIP5-era [2006-2100] forcing)
#>  3:                                                                                     future scenario with medium radiative forcing by the end of century. Following RCP6.0 global forcing pathway. Concentration-driven (CMIP5-era [2006-2100] forcing)
#>  4:                                                                                       future scenario with high radiative forcing by the end of century. Following RCP8.5 global forcing pathway. Concentration-driven (CMIP5-era [2006-2100] forcing)
#>  5:                                                                                                                              Future scenario with low radiative forcing throughout reaching about 1.9 W/m2 in 2100 based on SSP1. Concentration-driven
#>  6:  Future scenario with low radiative forcing by the end of century. Following approximately RCP2.6 global forcing pathway but with new forcing based on SSP1. Concentration-driven. As a tier 2 option, this simulation should be extended to year 2300
#>  7:                                                                    Future scenario with medium radiative forcing by the end of century. Following approximately RCP4.5 global forcing pathway but with new forcing based on SSP2. Concentration-driven
#>  8:                                                                    Future scenario with high radiative forcing by the end of century. Reaches about 7.0 W/m2 by 2100; fills gap in RCP forcing pathways between 6.0 and 8.5 W/m2. Concentration-driven
#>  9:                                                                     Future scenario with low radiative forcing by the end of century. Reaches about 3.4 W/m2 by 2100; fills gap in RCP forcing pathways between 4.5 and 2.6 W/m2. Concentration-driven
#> 10:                                                                    Future scenario with medium radiative forcing by the end of century. Following approximately RCP6.0 global forcing pathway but with new forcing based on SSP4. Concentration-driven
#> 11:                                          21st century overshoot scenario relative to SSP5_34. Branches from SSP5_85 at 2040 with emissions reduced to zero by 2070 and negative thereafter. This simulation should optionally be extended to year 2300
#> 12: Future scenario with high radiative forcing by the end of century. Following approximately RCP8.5 global forcing pathway but with new forcing based on SSP5. Concentration-driven. As a tier 2 option, this simulation should be extended to year 2300
#>     pattern source
#>      <char> <char>
#>  1:    <NA>  vocab
#>  2:    <NA>  vocab
#>  3:    <NA>  vocab
#>  4:    <NA>  vocab
#>  5:    <NA>  vocab
#>  6:    <NA>  vocab
#>  7:    <NA>  vocab
#>  8:    <NA>  vocab
#>  9:    <NA>  vocab
#> 10:    <NA>  vocab
#> 11:    <NA>  vocab
#> 12:    <NA>  vocab

Check a Request

$check() validates known values and known relationships before the request goes to ESGF.

request_check <- dict$check(
    activity_id = "ScenarioMIP",
    experiment_id = "ssp585",
    source_id = "MPI-ESM1-2-LR",
    variant_label = "r1i1p1f1",
    table_id = "Amon",
    variable_id = epw_morph_variables("recommended")
)

show_rows(request_check, 20L)
#>             field         value  valid   type        rule  source
#>            <char>        <char> <lgcl> <char>      <char>  <char>
#>  1:   activity_id   ScenarioMIP   TRUE  value field_value   vocab
#>  2: experiment_id        ssp585   TRUE  value field_value   vocab
#>  3:     source_id MPI-ESM1-2-LR   TRUE  value field_value   vocab
#>  4: variant_label      r1i1p1f1   TRUE  value field_value pattern
#>  5:      table_id          Amon   TRUE  value field_value   vocab
#>  6:   variable_id           tas   TRUE  value field_value request
#>  7:   variable_id          hurs   TRUE  value field_value request
#>  8:   variable_id           psl   TRUE  value field_value request
#>  9:   variable_id          rlds   TRUE  value field_value request
#> 10:   variable_id          rsds   TRUE  value field_value request
#> 11:   variable_id       sfcWind   TRUE  value field_value request
#> 12:   variable_id           clt   TRUE  value field_value request
#>     constraint_fields message suggestions compatible_values
#>                <list>  <char>      <list>            <list>
#>  1:                      <NA>
#>  2:                      <NA>
#>  3:                      <NA>
#>  4:                      <NA>
#>  5:                      <NA>
#>  6:                      <NA>
#>  7:                      <NA>
#>  8:                      <NA>
#>  9:                      <NA>
#> 10:                      <NA>
#> 11:                      <NA>
#> 12:                      <NA>

Suggestions are useful for typographical errors or aliases that do not match the project vocabulary.

show_rows(
    dict$check(
        experiment_id = "ssp58",
        table_id = "Amon",
        variable_id = "tas",
        suggest = TRUE,
        error = FALSE
    ),
    12L
)
#>            field  value  valid   type        rule  source constraint_fields
#>           <char> <char> <lgcl> <char>      <char>  <char>            <list>
#> 1: experiment_id  ssp58  FALSE  value field_value   vocab
#> 2:      table_id   Amon   TRUE  value field_value   vocab
#> 3:   variable_id    tas   TRUE  value field_value request
#>                                    message                        suggestions
#>                                     <char>                             <list>
#> 1: `ssp58` is not a valid `experiment_id`. ssp585,ssp119,ssp126,ssp245,ssp370
#> 2:                                    <NA>
#> 3:                                    <NA>
#>    compatible_values
#>               <list>
#> 1:
#> 2:
#> 3:

The same check is available through esgdict_check().

show_rows(
    esgdict_check(
        activity_id = "ScenarioMIP",
        experiment_id = "ssp585",
        variable_id = "tas",
        table_id = "Amon",
        dict = dict
    )
)
#>            field       value  valid   type        rule  source
#>           <char>      <char> <lgcl> <char>      <char>  <char>
#> 1:   activity_id ScenarioMIP   TRUE  value field_value   vocab
#> 2: experiment_id      ssp585   TRUE  value field_value   vocab
#> 3:   variable_id         tas   TRUE  value field_value request
#> 4:      table_id        Amon   TRUE  value field_value   vocab
#>    constraint_fields message suggestions compatible_values
#>               <list>  <char>      <list>            <list>
#> 1:                      <NA>
#> 2:                      <NA>
#> 3:                      <NA>
#> 4:                      <NA>

Save and Reload

Saved dictionaries are schema-validated JSON files. Use this when you want a known dictionary snapshot beside a project store or a reviewable analysis artifact.

dict_path <- file.path(workflow_root, "cmip6-dict-live.json")
dict$save(dict_path)
#> [1] "/var/folders/8f/t8sk2pps6135xbp47cs8qb2r0000gn/T//Rtmp3Hed7X/epwshiftr-esg-dictionaries/cmip6-dict-live.json"

restored <- EsgDict$new(project = "CMIP6")
restored$load(dict_path)

restored$status()
#> [1] "loaded"
restored$version()
#> $vocab
#> [1] '6.2.60.0'
#>
#> $request
#> [1] '1.0.33'
identical(restored$fields(), dict$fields())
#> [1] TRUE

Build a CV-Only Project

The dictionary object is project-aware. Projects without registered request metadata still use the same object shape, but their capabilities and relation checks reflect what is available upstream.

cmip6plus_source_dir <- file.path(workflow_root, "sources", "CMIP6PLUS")

cmip6plus <- EsgDict$new(project = "CMIP6PLUS")
cmip6plus$build(
    source_dir = cmip6plus_source_dir,
    use_cache = FALSE
)

cmip6plus$project()
#> [1] "CMIP6PLUS"
cmip6plus$sources()
#> $vocab
#> $vocab$repo
#> [1] "WCRP-CMIP/CMIP6Plus_CVs"
#>
#> $vocab$tag
#> [1] "esgvoc"
#>
#> $vocab$commit
#> [1] NA
#>
#> $vocab$source_dir
#> [1] "/private/var/folders/8f/t8sk2pps6135xbp47cs8qb2r0000gn/T/Rtmp3Hed7X/epwshiftr-esg-dictionaries/sources/CMIP6PLUS/vocab/esgvoc"
#>
#>
#> $request
#> NULL
cmip6plus$capabilities()
#> $vocab
#> [1] TRUE
#>
#> $request
#> [1] FALSE
#>
#> $relations
#> character(0)
cmip6plus$relation_fields()
#> $activity_experiment
#> [1] "activity_id"       "experiment_id"     "sub_experiment_id"
#>
#> $activity_source
#> [1] "activity_id"    "source_id"      "institution_id"

CV-only projects can still list fields and validate individual values. Relationship checks degrade according to the available indices instead of pretending that CMIP6 request metadata applies to every project.

utils::head(cmip6plus$fields(), 20L)
#>  [1] "_archive"             "activity_id"          "calendar"
#>  [4] "citation_url"         "conventions"          "creation_date"
#>  [7] "cv"                   "data_specs_version"   "experiment_id"
#> [10] "forcing_index"        "frequency"            "further_info_url"
#> [13] "grid_label"           "initialization_index" "institution_id"
#> [16] "license"              "member_id"            "mip_era"
#> [19] "nominal_resolution"   "physics_index"

if ("activity_id" %in% cmip6plus$fields()) {
    show_rows(cmip6plus$options("activity_id"), 12L)
}
#>          field       value description pattern source
#>         <char>      <char>      <char>  <char> <char>
#> 1: activity_id    ceresmip        <NA>    <NA>  vocab
#> 2: activity_id        cmip        <NA>    <NA>  vocab
#> 3: activity_id        dcpp        <NA>    <NA>  vocab
#> 4: activity_id     lesfmip        <NA>    <NA>  vocab
#> 5: activity_id  methanemip        <NA>    <NA>  vocab
#> 6: activity_id       ramip        <NA>    <NA>  vocab
#> 7: activity_id scenariomip        <NA>    <NA>  vocab
#> 8: activity_id      tbimip        <NA>    <NA>  vocab
#> 9: activity_id      tipmip        <NA>    <NA>  vocab

if ("experiment_id" %in% cmip6plus$fields()) {
    show_rows(cmip6plus$options("experiment_id"), 12L)
}
#>             field        value description pattern source
#>            <char>       <char>      <char>  <char> <char>
#>  1: experiment_id      1pctco2        <NA>    <NA>  vocab
#>  2: experiment_id abrupt-4xco2        <NA>    <NA>  vocab
#>  3: experiment_id     amip-aer        <NA>    <NA>  vocab
#>  4: experiment_id     amip-ghg        <NA>    <NA>  vocab
#>  5: experiment_id     amip-nat        <NA>    <NA>  vocab
#>  6: experiment_id   amip-noaer        <NA>    <NA>  vocab
#>  7: experiment_id   amip-noghg        <NA>    <NA>  vocab
#>  8: experiment_id   amip-nudge        <NA>    <NA>  vocab
#>  9: experiment_id    amip-slcf        <NA>    <NA>  vocab
#> 10: experiment_id     amip-sst        <NA>    <NA>  vocab
#> 11: experiment_id         amip        <NA>    <NA>  vocab
#> 12: experiment_id  dcppa-assim        <NA>    <NA>  vocab

Where Dictionaries Fit

  • In the main workflow, dictionary checks protect request construction before shift_datasets() contacts ESGF.
  • In ESGF query results, dictionary checks complement live index-node metadata such as facets, fields, shards, and values.
  • In ESGF troubleshooting, dictionaries are the first local check for invalid values or incompatible request fields.