Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
309ebd1
Refactor validation criteria and validation items
dc-almeida Jul 20, 2026
d875b79
Add `MetaValidationItem`
dc-almeida Jul 21, 2026
b1db510
Add validation arguments properties to MetaCode
dc-almeida Jul 21, 2026
64e3cae
Add DataValidationValue
dc-almeida Jul 21, 2026
f0c4922
Add `MetaValidationValue` and `MetaValidator.from_codelist`
dc-almeida Jul 21, 2026
20febb6
Extract abstract Validator base class
dc-almeida Jul 21, 2026
3d076f7
Fix aliases in `validation_args`
dc-almeida Jul 21, 2026
660908d
Add `MetaFilter`, and MetaValidator methods
dc-almeida Jul 22, 2026
5b8ab96
Add validate_meta to `validate_project`
dc-almeida Jul 22, 2026
1dbc6d0
Add `_validate_meta` function; extract `apply` to Validator
dc-almeida Jul 23, 2026
814e3e0
Update field validator; add test config file
dc-almeida Jul 27, 2026
162c11e
Update tests
dc-almeida Jul 27, 2026
8d45e1f
Update GitHub Actions
dc-almeida Jul 27, 2026
aed870b
Make Ruff
dc-almeida Jul 27, 2026
83e5002
Fix validation; add tests for warnings, errors, multiple warning levels
dc-almeida Jul 28, 2026
3d65e35
Add test
dc-almeida Jul 28, 2026
bff2d7a
Allow int in range validation
dc-almeida Jul 28, 2026
3777e8f
Refactor ValidationValue to allow rtol/atol in MetaValidator
dc-almeida Jul 28, 2026
e23b50a
Add test for multi-column meta indicator validation
dc-almeida Jul 28, 2026
24f1890
Add documentation
dc-almeida Jul 28, 2026
4d2d925
Change wording and formatting
dc-almeida Jul 28, 2026
d8eb17a
Apply suggestions
dc-almeida Aug 3, 2026
ec92fa8
Add empty column names to log warning
dc-almeida Aug 3, 2026
47bca76
Set filter to ignore non existent columns
dc-almeida Aug 4, 2026
48aa63c
Keep all values in failed validation rows by filtering by index; test…
dc-almeida Aug 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/nightly.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ jobs:
name: py${{ matrix.python-version }}

steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
uses: actions/setup-python@v7
with:
python-version: ${{ matrix.python-version }}
#----------------------------------------------
Expand All @@ -44,7 +44,7 @@ jobs:
#----------------------------------------------
- name: Load cached venv
id: cached-poetry-dependencies
uses: actions/cache@v4
uses: actions/cache@v6
with:
path: .venv
key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/pytest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ jobs:
name: ${{ matrix.os }} py${{ matrix.python-version }}

steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v7

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
uses: actions/setup-python@v7
with:
python-version: ${{ matrix.python-version }}

Expand All @@ -46,7 +46,7 @@ jobs:
#----------------------------------------------
- name: Load cached venv
id: cached-pip-wheels
uses: actions/cache@v4
uses: actions/cache@v6
with:
path: ~/.cache
key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/ruff.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ jobs:
ruff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: astral-sh/ruff-action@v3
- uses: actions/checkout@v7
- uses: astral-sh/ruff-action@v4.1.0
1 change: 1 addition & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ API documentation
api/codelist
api/regionprocessor
api/datavalidator
api/metavalidator
api/requireddatavalidator
api/countries
api/nuts
Expand Down
56 changes: 56 additions & 0 deletions docs/api/metavalidator.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
.. _meta-validation:

.. currentmodule:: nomenclature

**MetaValidator**
=================

**Meta validation** checks if meta indicators follow allowed values and ranges.

Consider the example below:

.. code:: yaml

- name: Sustainability Concern|Exceeding Prudent Limit For Geological Carbon Storage|World
meta: Emissions Diagnostics|Cumulative CCS [2020-2100, Gt CO2]
validation:
- warning_level: high
upper_bound: 1490
- warning_level: medium
upper_bound: 1290
- meta: Project
values: [Project Name 1, Project Name 2]


Each criteria item contains **meta indicator filter arguments** and **validation arguments**.

The meta indicator filter argument ``meta`` (also allowed as ``meta_columns_to_validate``)
refers to the column(s) in the meta indicator table that will undergo validation.
For the first criteria item, validation will check the values of column
*"Emissions Diagnostics|Cumulative CCS [2020-2100, Gt CO2]"*.
For the second criteria item, validation will check column *"Project"*.
If multiple columns are specified, the validation fails if *any* value for a given
row fails (e.g.: if Column A fails validation and Column B doesn't, the row
is flagged as failed).
The ``name`` field specifies the meta indicator column that will be added post-validation
with the validation results (``ok``, ``low``, ``medium``, ``high``, ``error``).

The ``validation`` arguments follow the same rules as :class:`DataValidator`
(see :ref:`data-validation`), but apply exclusively to meta indicator columns.
In addition, the ``values`` field supports membership checks. In the example
above, the "Project" meta indicator will be checked for its values being either
*"Project Name 1"* or *"Project Name 2"*.

Standard usage
--------------

.. code-block:: python

from nomenclature import MetaValidator

# ...setting directory/file paths and loading dataset

MetaValidator.from_file(yaml_file_containing_meta_validation_criteria).apply(df)

.. autoclass:: MetaValidator
:members: from_file, from_codelist, apply, validate_with_definition
13 changes: 11 additions & 2 deletions nomenclature/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ def validate_project(
validate_data: Annotated[
str | None, typer.Option(help="Name of data validation folder")
] = None,
validate_meta: Annotated[
str | None, typer.Option(help="Name of meta validation folder")
] = None,
dimensions: Annotated[
list[str] | None,
typer.Option("--dimension", help="Dimensions to check (defaults to all)"),
Expand All @@ -88,15 +91,21 @@ def validate_project(
- YAML syntax validation for all files
- Parsing of codelists in the definitions folder
- Validation of model mappings against region codelists
- Consistency checks for required-data and data-validation criteria
- Consistency checks for required-data, data-validation, and meta criteria

\b
Example:
$ nomenclature validate-project . --definitions def --mappings map
"""
assert_valid_yaml(path)
assert_valid_structure(
path, definitions, mappings, required_data, validate_data, dimensions
path,
definitions,
mappings,
required_data,
validate_data,
validate_meta,
dimensions,
)


Expand Down
18 changes: 16 additions & 2 deletions nomenclature/code.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

from nomenclature.countries import countries

# This must be kept in sync with the subtypes of `DataValidationCriteria`
# This must be kept in sync with the subtypes of `ValidationCriteria`
VALIDATION_ARGS = [
"upper_bound",
"lower_bound",
Expand Down Expand Up @@ -431,4 +431,18 @@ class MetaCode(Code):

"""

allowed_values: list[Any] | None = None
value: list[Any] | None = Field(default=None, alias="allowed_values")
model_config = ConfigDict(validate_by_name=True, validate_by_alias=True)

@property
def has_validation_args(self) -> bool:
return True if self.validation_args else False

@property
def validation_args(self) -> dict:
alias_map = {"allowed_values": "value"} # Map alias to canonical name
return {
alias_map.get(key, key): value
for key, value in self.extra_attributes.items()
if alias_map.get(key, key) in VALIDATION_ARGS and value is not None
}
2 changes: 1 addition & 1 deletion nomenclature/codelist.py
Original file line number Diff line number Diff line change
Expand Up @@ -1030,7 +1030,7 @@ def _parse_region_code_dir(


class MetaCodeList(CodeList):
"""A subclass of CodeList specified for MetaCodes
"""A subclass of CodeList specified for meta indicators

Attributes
----------
Expand Down
12 changes: 8 additions & 4 deletions nomenclature/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,21 +328,25 @@ def check_datetime_format(self, df: IamDataFrame) -> None:
"The following datetime values are invalid:", errors
)

def validate_datetime(self, df: IamDataFrame, dimensions: list[str] | None = None) -> None:
def validate_datetime(
self, df: IamDataFrame, dimensions: list[str] | None = None
) -> None:
"""Validate datetime coordinates against allowed format and/or timezone.

Parameters
----------
df : IamDataFrame
The dataframe to validate
dimensions : list of str, optional
List of allowed dimensions for validation
"""
if "subannual" in df.data.columns and (dimensions is None or "subannual" not in dimensions):
if "subannual" in df.data.columns and (
dimensions is None or "subannual" not in dimensions
):
raise TimeDomainError(
"Invalid time domain - `subannual` found, but not allowed."
)

if df.time_domain == "year":
if not self.year_allowed:
raise TimeDomainError(
Expand Down
14 changes: 14 additions & 0 deletions nomenclature/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,20 @@ def __init__(
super().__init__(message)


class MetaValidationError(NoTracebackException):
def __init__(
self,
fail_list: list[str],
file: Path,
) -> None:
failed_criteria_info = "\n".join(fail_list)
message = (
f"Meta indicator validation failed with error(s) (file: {get_relative_path(file)}):\n"
f"{failed_criteria_info}"
)
super().__init__(message)


class RequiredDataMissingError(ValueError):
def __init__(self, missing_data_info: str, file: Path) -> None:
message = (
Expand Down
4 changes: 3 additions & 1 deletion nomenclature/processor/__init__.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
from nomenclature.processor.processor import Processor # noqa
from nomenclature.processor.validator import Validator # noqa
from nomenclature.processor.region import ( # noqa
RegionAggregationMapping,
RegionProcessor,
)
from nomenclature.processor.nuts import NutsProcessor # noqa
from nomenclature.processor.required_data import RequiredDataValidator # noqa
from nomenclature.processor.data_validator import DataValidator # noqa
from nomenclature.processor.data import DataValidator # noqa
from nomenclature.processor.meta import MetaValidator # noqa
from nomenclature.processor.aggregator import Aggregator # noqa
Loading