About the FACTS-module-registry entry (module YAML file)¶
Here, you will find a description of the different sections of a module YAML file and the information required in each.
Skeleton module YAML file¶
# MY MODULE CONFIGURATION
# Input data archive:
# https://zenodo.org/records/<record-id>/files/<archive>.tgz
module_name: "my-module"
container_image: "ghcr.io/fact-sealevel/my-module:0.1.0"
command: "main"
uses_climate_file: false
climate_file_required: false
arguments:
top_level: [...] # experiment-wide values, from metadata.*
options: [...] # module-specific knobs, from module_inputs.options.*
fingerprint_params: [...] # sea-level fingerprint dirs/files
inputs: [...] # files the module reads
outputs:
files: [...] # files the module writes
other: [...] # non-file outputs (directories)
volumes: {...} # named host→container mounts
¶
# MY MODULE CONFIGURATION
# Input data archive:
# https://zenodo.org/records/<record-id>/files/<archive>.tgz
module_name: "my-module"
container_image: "ghcr.io/fact-sealevel/my-module:0.1.0"
command: "main"
uses_climate_file: false
climate_file_required: false
arguments:
top_level: [...] # experiment-wide values, from metadata.*
options: [...] # module-specific knobs, from module_inputs.options.*
fingerprint_params: [...] # sea-level fingerprint dirs/files
inputs: [...] # files the module reads
outputs:
files: [...] # files the module writes
other: [...] # non-file outputs (directories)
volumes: {...} # named host→container mounts
Top-level keys¶
| Key | Type | Required | Notes |
|---|---|---|---|
module_name |
str | yes | Kebab-case; must equal the directory name |
container_image |
str | yes | Full image URI with tag or digest |
arguments |
map | yes | See §6 |
volumes |
map | yes | See §7 |
command |
str | usually | Subcommand passed before the flags. Use "main" for single-command CLIs |
uses_climate_file |
bool | usually | true if the module consumes climate-step output |
climate_file_required |
bool | usually | true if that climate file is mandatory |
depends_on |
list | no | Compose service dependencies |
input_dir_name |
str | no | See below |
skip_fingerprint_params |
bool | no | true if the module has no fingerprint arguments |
per_workflow |
bool | no | true instantiates one service per workflow rather than per module (used by facts-total) |
output_types |
list | no | Defaults to [global, local] |
Pinning the image. Either a semver tag (my-module:0.1.0) or a digest
(my-module@sha256:281e89..., as emulandice2-* does). A digest is exactly reproducible;
a tag is easier to read. Do not use :latest.
command and multi-command images. If your container's entrypoint is a Click group
with subcommands, each subcommand gets its own registry entry pointing at the same image.
ipccar5 does this: ipccar5-glaciers sets command: "glaciers" and ipccar5-icesheets
sets command: "icesheets", both on ghcr.io/fact-sealevel/ipccar5:0.1.2.
input_dir_name. By default FEB expects a module's input data in a subdirectory named
after the module (module_specific_input_data/my-module/). Sibling entries sharing one
image usually share one input directory too, so both ipccar5-* entries set
input_dir_name: "ipccar5", and all three emulandice2-* entries set
input_dir_name: "emulandice2".
The arguments section¶
Five subsections. The split matters because it determines where FEB looks for the
value — that is the source dot-path — and which fields are legal.
Every entry, in every subsection, needs at least:
- name: "chunksize" # becomes --chunksize
type: "int" # str | int | float | bool | file
source: "module_inputs.options.chunksize" # where FEB reads the value
name must match the CLI flag exactly, minus the leading --. FEB emits
--{name}={value}.
Which fields each subsection allows¶
| Field | top_level |
options |
fingerprint_params |
inputs |
outputs.files |
outputs.other |
|---|---|---|---|---|---|---|
name, type, source |
✅ req | ✅ req | ✅ req | ✅ req | ✅ req | ✅ req |
help |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
optional |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
mount |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
alternatives |
✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
transform |
✅ | — | ✅ | — | — | — |
default_value |
— | ✅ | ✅ | ✅ | — | — |
filename |
— | — | ✅ | ✅ (str or list) | ✅ | — |
filename_map |
— | — | — | ✅ | ✅ | — |
multiple |
— | ✅ | — | ✅ | — | — |
envvar |
— | ✅ | — | ✅ | — | — |
allowed_values |
— | ✅ | — | — | — | — |
external_volume |
— | — | — | ✅ | — | — |
climate_step_output |
— | — | — | ✅ | — | — |
output_type |
— | — | — | — | ✅ req | — |
pass_to_total |
— | — | — | — | ✅ (default true) |
— |
Note optional: true means FEB will not error if no value is supplied — it is about
FEB's config handling, not about whether your CLI marks the flag required.
Arguments: top_level¶
Values that apply to the whole experiment, sourced from metadata.*. FEB collects these
across all selected modules and prompts the user for them once.
top_level:
- name: "pipeline-id"
type: "str"
source: "metadata.pipeline-id"
help: "Unique identifier for the pipeline running this code"
- name: "nsamps"
type: "int"
source: "metadata.nsamps"
help: "Number of samples to generate"
- name: "baseyear"
type: "int"
source: "metadata.baseyear"
help: "Year from which projections are zeroed"
- name: "pyear-start"
type: "int"
source: "metadata.pyear_start"
- name: "pyear-end"
type: "int"
source: "metadata.pyear_end"
- name: "pyear-step"
type: "int"
source: "metadata.pyear_step"
- name: "location-file"
type: "str"
source: "metadata.location-file"
optional: true
transform: "filename"
help: "File containing name, id, lat, and lon of points for localization"
mount:
volume: "shared_input"
container_path: "/mnt/shared_in"
Conventional sources — reuse these spellings so values are shared rather than duplicated:
| Source | Meaning |
|---|---|
metadata.pipeline-id |
Run identifier |
metadata.scenario |
Emissions scenario |
metadata.nsamps |
Sample count |
metadata.baseyear |
Reference/zeroing year |
metadata.pyear_start / _end / _step |
Projection year range |
metadata.location-file |
Localization site list |
Your CLI's flag name does not have to match the source. ipccar5-glaciers maps
metadata.baseyear onto its --start-year flag:
- name: "start-year"
type: "int"
source: "metadata.baseyear"
optional: true
help: "Year from which to start integrating temperature"
Arguments: options¶
Module-specific settings, sourced from module_inputs.options.*. These become the tunable
block in the user's experiment-config.yaml. Give every option a help string and a
default_value where one is sensible — this is the main documentation users see.
options:
- name: "chunksize"
type: "int"
source: "module_inputs.options.chunksize"
optional: true
default_value: 1000
help: "Number of locations to process in each chunk"
- name: "rng-seed"
type: "int"
source: "module_inputs.options.seed"
optional: false
default_value: 1234
help: "Seed for the random number generator."
- name: "region"
type: "str"
source: "module_inputs.options.region"
default_value: "ALL"
allowed_values: ["ALL", "EAIS", "WAIS", "PEN"]
help: "Region within this ice source to use."
Note the convention that a --rng-seed flag reads from module_inputs.options.seed, so
seeds are consistent across modules.
Arguments: fingerprint_params¶
Sea-level fingerprint files and directories that translate global to local sea level,
sourced from module_inputs.fingerprint_params.*. These live in the shared input
directory, since several modules use the same fingerprint data.
fingerprint_params:
- name: "fingerprint-dir"
type: "str"
source: "module_inputs.fingerprint_params.fingerprint_dir"
default_value: FPRINT
help: "Directory containing sea-level fingerprint files"
mount:
volume: "shared_input"
container_path: "/mnt/shared_in"
If your module needs none, omit the subsection and set skip_fingerprint_params: true
at the top level.
Arguments: inputs¶
Files the module reads, sourced from module_inputs.inputs.*. Declare filename for each
one — that string is what check-data looks for on disk, so it is how users learn what to
download.
inputs:
- name: "gia-stats-input-file"
type: "file"
source: "module_inputs.inputs.gia_stats_input_fname"
filename: "GIA_stats.nc"
help: "Path to GIA statistics netCDF file"
mount:
volume: "module_specific_input"
container_path: "/mnt/module_specific_in"
Consuming climate output (cross-module input). If your module reads a file produced by
the climate step, set external_volume: true, mount from the output volume, and name the
argument climate-data-file (or input-data-file). For those two names FEB requires
climate_step_output, which names the output argument in the climate module's YAML that
you want:
- name: "climate-data-file"
type: "file"
source: "module_inputs.inputs.climate_data_file"
help: "NetCDF file containing surface temperature data"
climate_step_output: "output-climate-file" # or "output-gsat-file"
external_volume: true
mount:
volume: "output"
container_path: "/mnt/out"
transform: "filename"
Valid climate_step_output values are output names declared by the climate modules —
fair-temperature and fair2-climate both expose output-climate-file,
output-gsat-file, output-oceantemp-file, and output-ohc-file. Pair this with
uses_climate_file: true (and climate_file_required as appropriate) at the top level.
Repeatable flags. multiple: true lets FEB emit the flag once per value:
- name: "item"
type: "file"
source: "module_inputs.inputs.item"
multiple: true
mount:
volume: "input"
container_path: "/mnt/total_in"
Env-var inputs. Some values must reach the container as environment variables rather
than flags (the emulandice R harness needs this). Use envvar:
- name: "forcing-head-path"
type: "file"
source: "module_inputs.inputs.forcing_head_path"
envvar: "EMULANDICE_FORCING_HEAD_PATH"
Arguments: outputs¶
Split into files (things with a filename and an output type) and other (directories and
other non-file outputs). Sourced from module_inputs.outputs.*.
outputs:
files:
- name: "output-lslr-file"
type: "file"
source: "module_inputs.outputs.output_lslr_file"
filename: "lslr.nc"
output_type: "local"
pass_to_total: true
help: "Output local sea-level rise projections file"
mount:
volume: "output"
container_path: "/mnt/out"
transform: "filename"
- name: "output-quantiles-file"
type: "file"
source: "module_inputs.outputs.output_quantiles_file"
filename: "quantiles.nc"
output_type: "local"
pass_to_total: false
help: "Output quantiles file"
mount:
volume: "output"
container_path: "/mnt/out"
transform: "filename"
other:
- name: "output-glacier-dir"
type: "str"
source: "module_inputs.outputs.output_glacier_dir"
optional: true
help: "Directory into which per-glacier GSLR files are written"
mount:
volume: "output"
container_path: "/mnt/out"
output_type is required on every files entry:
| Value | Meaning |
|---|---|
global |
Global mean sea-level contribution |
local |
Localized sea level at the requested sites |
total |
Aggregated total across components |
esl |
Extreme sea level |
pass_to_total (default true) controls whether the file is fed into facts-total.
Set it to false for diagnostics like quantile files, which would otherwise be
double-counted.
Output mounts almost always carry transform: "filename", so the CLI receives a bare
filename and the container writes it into the mounted output directory.
The volumes section¶
Declares the named bind mounts your arguments reference. Every mount.volume value used
anywhere in arguments must appear as a key here.
volumes:
module_specific_input:
host_path: "module_inputs.input_paths.module_specific_input_dir"
container_path: "/mnt/module_specific_in"
help: "Input data specific to this module."
shared_input:
host_path: "module_inputs.input_paths.shared_input_dir"
container_path: "/mnt/shared_in"
help: "Input data shared across modules (fingerprints, location file)."
output:
host_path: "module_inputs.output_paths.output_dir"
container_path: "/mnt/out"
help: "Directory for this module's output files."
| Field | Notes |
|---|---|
host_path |
Dot-path FEB resolves to a real host directory |
container_path |
Absolute mount point inside the container |
help |
Free text |
Use those three volumes and those three host_path values unless you have a specific
reason not to. Two conventions are load-bearing:
- Name the output volume exactly
output. FEB special-cases this key when building mounts and when deciding how to rewrite output paths. - The output volume's
host_pathmust containoutput_paths. FEB identifies the output volume by searching for that substring, which is how it detects cross-module inputs.container_pathis declared in two places — on the volume and on eachmount. Keep them consistent; a mismatch is the most common cause of a module that starts but cannot find its files.
Transforms¶
transform applies a named function to a value before it reaches the CLI. Only three names
are implemented in FEB today:
| Transform | Effect |
|---|---|
filename |
Passes only the basename, not the full host path |
scenario_name |
Extracts the scenario string from FEB's scenario object |
scenario_name_ssp_landwaterstorage |
Applies the ssp-landwaterstorage scenario mapping |
Adding a new module-specific mapping is not purely a registry change: FEB dispatches on
the literal transform name in module_service_spec.py, so a new
scenario_name_<module> transform needs a matching branch there plus a function in
core/transforms.py. Open an issue if you need one, and the maintainers can help.
The mapping file itself is a flat key-value YAML: