missing_values_found

This rule checks if a mandatory column has been left empty. A value is considered missing if it is an empty string, or an empty string once trimmed.

Consider a mandatory column named geoLat in a sites table. The following ODM dataset snippet would fail validation due to the first row having an empty string for the geoLat column value.

                                                  Invalid Dataset                                                  
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ siteID                                                 ┃ geoLat                                                 ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ 1                                                      │                                                        │
│ 2                                                      │ 1                                                      │
└────────────────────────────────────────────────────────┴────────────────────────────────────────────────────────┘

In addition, column values that are an empty string when trimmed should also fail this validation rule. For example, the following ODM data snippet would also fail validation,

                                                  Invalid Dataset                                                  
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ siteID                                                 ┃ geoLat                                                 ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ 1                                                      │                                                        │
│ 2                                                      │ 1                                                      │
└────────────────────────────────────────────────────────┴────────────────────────────────────────────────────────┘

A missingness value states why a value is absent, which is a valid way of populating a mandatory column. It therefore does not trigger this rule. The following dataset snippet would pass validation, since NA is a missingness value that geoLat is allowed to take on. Which missingness values a column can take on is decided by the missingness rule, which is also the rule that forbids them in an identifier (primary key) column.

                                                   Valid Dataset                                                   
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ siteID                                                 ┃ geoLat                                                 ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ 1                                                      │ 2                                                      │
│ 2                                                      │ NA                                                     │
└────────────────────────────────────────────────────────┴────────────────────────────────────────────────────────┘

Warning report

A value that is invalid due to this rule should generate a warning and not an error. The warning report should have the following fields

  • warningType: missing_values_found

  • tableName: The name of the table containing the missing values

  • columnName: The name of the mandatory column containing the missing values

  • invalidValue: The invalid value

  • rowNumber: The index of the table row with the error

  • row: The dictionary containing the row

  • validationRuleFields: The ODM data dictionary rows used to generate this rule

  • message: Mandatory column <column_name> in table <table_name> has a missing value in row <row_index>

The warning report object for the example ODM datasets in the previous section are shown below.

Example 1 warning report,

{
│   'warnings': [
│   │   {
│   │   │   'warningType': 'missing_values_found',
│   │   │   'tableName': 'sites',
│   │   │   'columnName': 'geoLat',
│   │   │   'rowNumber': 1,
│   │   │   'row': {
│   │   │   │   'siteID': '1',
│   │   │   │   'geoLat': ''
│   │   │   },
│   │   │   'invalidValue': '',
│   │   │   'validationRuleFields': [
│   │   │   │   {
│   │   │   │   │   'partID': 'geoLat',
│   │   │   │   │   'sites': 'header',
│   │   │   │   │   'sitesRequired': 'mandatory'
│   │   │   │   }
│   │   │   ],
│   │   │   'message': 'missing_values_found rule triggered in table sites, column geoLat, row(s) 1: Empty string found'
│   │   }
│   ],
│   'errors': []
}

Example 2 warning report,

{
│   'warnings': [
│   │   {
│   │   │   'warningType': 'missing_values_found',
│   │   │   'tableName': 'sites',
│   │   │   'columnName': 'geoLat',
│   │   │   'invalidValue': '  ',
│   │   │   'rowNumber': 1,
│   │   │   'row': {
│   │   │   │   'siteID': '1',
│   │   │   │   'geoLat': '  '
│   │   │   },
│   │   │   'validationRuleFields': [
│   │   │   │   {
│   │   │   │   │   'partID': 'geoLat',
│   │   │   │   │   'sites': 'header',
│   │   │   │   │   'sitesRequired': 'mandatory'
│   │   │   │   }
│   │   │   ],
│   │   │   'message': 'missing_values_found rule triggered in table sites, column geoLat, row(s) 1: Missing value "  "'
│   │   }
│   ],
│   'errors': []
}

Rule metadata

All the metadata for this rule is contained in the parts sheet in the data dictionary. The steps involved are:

  1. Get the table names from the dictionary

  2. Get all mandatory columns for each table

  3. Add this rule for each mandatory column

For example the ODM snippet below will be used to generate the validation schema for the examples above,

                                                     Parts v2                                                      
┏━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┓
┃ partID   ┃ partType     ┃ status  ┃ sites   ┃ sitesRequired  ┃ missingnessSet     ┃ firstReleased ┃ lastUpdated ┃
┡━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━┩
│ NA       │ missingness  │ active  │ NA      │ NA             │ NA                 │ 2.0.0         │ 2.0.0       │
│ sites    │ tables       │ active  │ NA      │ NA             │ NA                 │ 1.0.0         │ 2.0.0       │
│ geoLat   │ attributes   │ active  │ header  │ mandatory      │ genMissingnessSet  │ 1.0.0         │ 2.0.0       │
│ geoLong  │ attributes   │ active  │ header  │ optional       │ genMissingnessSet  │ 1.0.0         │ 2.0.0       │
└──────────┴──────────────┴─────────┴─────────┴────────────────┴────────────────────┴───────────────┴─────────────┘

we would add this rule only to the geoLat column in the sites table. This rule would not be added to the geoLong column in the same table.

Cerberus Schema

Cerberus does not have a rule that fits this one. We need to extend the cerberus validator with a custom rule called emptyTrimmed for empty values, which also handles trimming. Although the cerberus library has an empty rule, that rule allows values that are “empty” once trimmed.

The cerberus schema for the ODM dictionary snippet above is shown below,

{
│   'schemaVersion': '2.0.0',
│   'schema': {
│   │   'sites': {
│   │   │   'type': 'list',
│   │   │   'schema': {
│   │   │   │   'type': 'dict',
│   │   │   │   'schema': {
│   │   │   │   │   'geoLat': {
│   │   │   │   │   │   'emptyTrimmed': False,
│   │   │   │   │   │   'meta': [
│   │   │   │   │   │   │   {
│   │   │   │   │   │   │   │   'ruleID': 'missing_values_found',
│   │   │   │   │   │   │   │   'meta': [
│   │   │   │   │   │   │   │   │   {
│   │   │   │   │   │   │   │   │   │   'partID': 'geoLat',
│   │   │   │   │   │   │   │   │   │   'sites': 'header',
│   │   │   │   │   │   │   │   │   │   'sitesRequired': 'mandatory'
│   │   │   │   │   │   │   │   │   }
│   │   │   │   │   │   │   │   ]
│   │   │   │   │   │   │   }
│   │   │   │   │   │   ]
│   │   │   │   │   }
│   │   │   │   },
│   │   │   │   'meta': [
│   │   │   │   │   {
│   │   │   │   │   │   'partID': 'sites',
│   │   │   │   │   │   'partType': 'tables'
│   │   │   │   │   }
│   │   │   │   ]
│   │   │   }
│   │   }
│   }
}

The meta field for this rule should have one entry: the row from the parts sheet for the mandatory column. The entry should include the partID and the <table_name>Required fields.

ODM Version 1

For version 1 validation schemas, we add this rule to only those version 2 columns which have a version 1 equivalent part.

For the ODM snippet below,

                                                     Parts v1                                                      
┏━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━┓
┃ partID  ┃ partType ┃ status ┃ sites  ┃ sitesRe… ┃ missing… ┃ version… ┃ versio… ┃ version… ┃ firstR… ┃ lastUpd… ┃
┡━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━┩
│ NA      │ missing… │ active │ NA     │ NA       │ NA       │ NA       │ NA      │ NA       │ 2.0.0   │ 2.0.0    │
│ sites   │ tables   │ active │ NA     │ NA       │ NA       │ tables   │ Site    │ NA       │ 1.0.0   │ 2.0.0    │
│ geoLat  │ attribu… │ active │ header │ mandato… │ genMiss… │ variabl… │ Site    │ latitude │ 1.0.0   │ 2.0.0    │
│ geoLong │ attribu… │ active │ header │ optional │ genMiss… │ variabl… │ Site    │ longitu… │ 1.0.0   │ 2.0.0    │
└─────────┴──────────┴────────┴────────┴──────────┴──────────┴──────────┴─────────┴──────────┴─────────┴──────────┘

The corresponding cerberus schema would be,

{
│   'schemaVersion': '1.0.0',
│   'schema': {
│   │   'Site': {
│   │   │   'type': 'list',
│   │   │   'schema': {
│   │   │   │   'type': 'dict',
│   │   │   │   'schema': {
│   │   │   │   │   'latitude': {
│   │   │   │   │   │   'emptyTrimmed': False,
│   │   │   │   │   │   'meta': [
│   │   │   │   │   │   │   {
│   │   │   │   │   │   │   │   'ruleID': 'missing_values_found',
│   │   │   │   │   │   │   │   'meta': [
│   │   │   │   │   │   │   │   │   {
│   │   │   │   │   │   │   │   │   │   'partID': 'geoLat',
│   │   │   │   │   │   │   │   │   │   'sites': 'header',
│   │   │   │   │   │   │   │   │   │   'sitesRequired': 'mandatory',
│   │   │   │   │   │   │   │   │   │   'version1Location': 'variables',
│   │   │   │   │   │   │   │   │   │   'version1Table': 'Site',
│   │   │   │   │   │   │   │   │   │   'version1Variable': 'latitude'
│   │   │   │   │   │   │   │   │   }
│   │   │   │   │   │   │   │   ]
│   │   │   │   │   │   │   }
│   │   │   │   │   │   ]
│   │   │   │   │   }
│   │   │   │   },
│   │   │   │   'meta': [
│   │   │   │   │   {
│   │   │   │   │   │   'partID': 'sites',
│   │   │   │   │   │   'partType': 'tables',
│   │   │   │   │   │   'version1Location': 'tables',
│   │   │   │   │   │   'version1Table': 'Site'
│   │   │   │   │   }
│   │   │   │   ]
│   │   │   }
│   │   }
│   }
}

The meta field for version 1 includes everything in version 2 but should also include the following columns for the entry for the mandatory column,

  • version1Location

  • version1Table

  • version1Variable