invalid_type

This rule checks that the type of a value matches with a defined type or can be coerced into the defined type. There are a number of types in the ODM that this validation rule will need to handle, we’ll go over each of them in the following sections.

integer

An integer is defined as a number without a decimal point. Imagine a column geoLat in the sites table which is defined to be an integer. The following ODM data snippet would fail validation,

# This is a YAML file since in a CSV file all the numbers would be included as
# a string
pprint_yaml_file(asset("integer-invalid-dataset-1.yml"))
[
{
│   │   'siteID': 1,
│   │   'geoLat': 1.23
}
]

whereas the following would pass validation

# This is a YAML file since in a CSV file all the numbers would be included as
# a string
pprint_yaml_file(asset("integer-valid-dataset-1.yml"))
[
{
│   │   'siteID': 1,
│   │   'geoLat': 1
}
]

Values that are not explicitly integers but can be coerced into integers should pass validation. For example, the following should pass validation,

                                               Valid integer dataset                                               
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ siteID                                                  geoLat                                                 ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ 1                                                      │ 1                                                      │
│ 2                                                      │ 1.00                                                   │
└────────────────────────────────────────────────────────┴────────────────────────────────────────────────────────┘

A floating point number can be coerced into an integer if the coercion does not result in a value change. In above example, coercing 1.00 to its integer value of 1 does not change its value.

The snippet below should fail validation since they cannot be coerced to integers.

                                              Invalid integer dataset                                              
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ siteID                                                  geoLat                                                 ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ 1                                                      │ a                                                      │
│ 2                                                      │ 1.01                                                   │
└────────────────────────────────────────────────────────┴────────────────────────────────────────────────────────┘

float

A float is a decimal number. Imagine the same geoLat column but its defined type is now a float. The following dataset snippet would fail validation,

                                               Invalid float dataset                                               
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ siteID                                                  geoLat                                                 ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ 1                                                      │ a                                                      │
└────────────────────────────────────────────────────────┴────────────────────────────────────────────────────────┘

whereas the following would pass validation

[
{
│   │   'siteID': 1,
│   │   'geoLat': 1.23
}
]

Once again, values that can be coerced into floats should pass validation. All integers and certain strings can be coerced into floats. For example, the following snippet below should pass validation,

                                                Valid float dataset                                                
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ siteID                                                  geoLat                                                 ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ 1                                                      │ 1.0                                                    │
└────────────────────────────────────────────────────────┴────────────────────────────────────────────────────────┘

boolean

A column that can have one of two values, representing Yes/No. The category values representing Yes/No are encoded in the dictionary. For example, consider the reportable column in the measures table whose data type is boolean. Also, assume that true and false are the allowed boolean category values. The following dataset snippet would fail validation,

                                               Invalid bool dataset                                                
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ measureID                                             reportable                                               ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ 1                                                    │ Yes                                                      │
└──────────────────────────────────────────────────────┴──────────────────────────────────────────────────────────┘

whereas the following the would pass validation,

                                                Valid bool dataset                                                 
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ measureID                                             reportable                                               ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ 1                                                    │ true                                                     │
└──────────────────────────────────────────────────────┴──────────────────────────────────────────────────────────┘

The category values are case-sensitive i.e. the boolean column value case should match the one in the category values. For example, the following would fail validation,

                                               Invalid bool dataset                                                
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ measureID                                             reportable                                               ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ 1                                                    │ True                                                     │
└──────────────────────────────────────────────────────┴──────────────────────────────────────────────────────────┘

datetime

A type that allows columns to have a string that holds a date and time value. The date component is mandatory whereas the time component is optional. Regardless of what’s included in the column value, the string should be formatted using the ISO 8601 standard. Although the standard specifies a number of ways to represent datetime, for the purposes of the ODM only the following formats will be supported.

  1. date

  2. datetime and

  3. datetime with timezone

For example, consider the reportDate column in the measures table whose type is datetime. The following dataset snippet should pass validation,

pprint_csv_file(asset("datetime-valid-dataset-1.csv"),
                "Valid datetime dataset")
                                              Valid datetime dataset                                               
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ measureID                        reportDate                                                                    ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ 1                               │ 2022-01-01                                                                    │
│ 2                               │ 2022-01-01T06:11:54                                                           │
│ 3                               │ 2022-01-01T06:11:54+13:30                                                     │
└─────────────────────────────────┴───────────────────────────────────────────────────────────────────────────────┘

whereas the following dataset should fail validation,

pprint_csv_file(asset("datetime-invalid-dataset-1.csv"),
                "Invalid datetime dataset")
                                             Invalid datetime dataset                                              
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ measureID                                             reportDate                                               ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ 1                                                    │ a                                                        │
│ 2                                                    │ 2022                                                     │
└──────────────────────────────────────────────────────┴──────────────────────────────────────────────────────────┘

categorical

This type is handled by the invalid_category rule

varchar

No validation is currently needed for this type since all the previously mentioned types can be coerced into a varchar.

see measure

Can be ignored for now.

Error report

The error report should have the following fields,

  • errorType: invalid_type

  • tableName: The name of the table with the invalid value

  • columnName: The name of the column with the invalid value

  • rowNumber: The 1-based index of the row with the invalid value

  • row: The dictionary containing the invalid row

  • invalidValue: The invalid value

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

  • message: Value <invalid_value> in row <row_number> in column <column_name> in table <table_name> has type <invalid_value_type> but should be of type <valid_type> or coercable into a <valid_type>.

For a boolean data type, the message is shown below,

  • message: Row <row_number> in column <column_name> in table <table_name> is a boolean but has value <invalid_value>. Allowed values are <boolean_categories>.

For a datetime data type, the message is shown belowm,

  • message: Row <row_number> in column <column_name> in table <table_name> is a datetime with value <invalid_value> that has an unsupported datetime format. Allowed values are ISO 8601 standard full dates, full dates and times, or full dates and times with timezone.

The error report objects for the invalid examples above can be seen below,

integer

{
'errors': [
│   │   {
│   │   │   'errorType': 'invalid_type',
│   │   │   'tableName': 'sites',
│   │   │   'columnName': 'geoLat',
│   │   │   'rowNumber': 1,
│   │   │   'row': {
│   │   │   │   'siteID': 1,
│   │   │   │   'geoLat': 1.23
│   │   │   },
│   │   │   'invalidValue': 1.23,
│   │   │   'validationRuleFields': [
│   │   │   │   {
│   │   │   │   │   'partID': 'geoLat',
│   │   │   │   │   'dataType': 'integer',
│   │   │   │   │   'sites': 'header'
│   │   │   │   }
│   │   │   ],
│   │   │   'message': 'invalid_type rule violated in table sites, column geoLat, row(s) 1: Value "1.23" has type float which is incompatible with integer'
│   │   }
],
'warnings': []
}

integer

{
'errors': [
│   │   {
│   │   │   'errorType': 'invalid_type',
│   │   │   'tableName': 'sites',
│   │   │   'columnName': 'geoLat',
│   │   │   'rowNumber': 1,
│   │   │   'row': {
│   │   │   │   'siteID': '1',
│   │   │   │   'geoLat': 'a'
│   │   │   },
│   │   │   'invalidValue': 'a',
│   │   │   'validationRuleFields': [
│   │   │   │   {
│   │   │   │   │   'partID': 'geoLat',
│   │   │   │   │   'dataType': 'integer',
│   │   │   │   │   'sites': 'header'
│   │   │   │   }
│   │   │   ],
│   │   │   'message': 'invalid_type rule violated in table sites, column geoLat, row(s) 1: Value "a" has type string which is incompatible with integer'
│   │   },
│   │   {
│   │   │   'errorType': 'invalid_type',
│   │   │   'tableName': 'sites',
│   │   │   'columnName': 'geoLat',
│   │   │   'rowNumber': 2,
│   │   │   'row': {
│   │   │   │   'siteID': '2',
│   │   │   │   'geoLat': '1.01'
│   │   │   },
│   │   │   'invalidValue': '1.01',
│   │   │   'validationRuleFields': [
│   │   │   │   {
│   │   │   │   │   'partID': 'geoLat',
│   │   │   │   │   'dataType': 'integer',
│   │   │   │   │   'sites': 'header'
│   │   │   │   }
│   │   │   ],
│   │   │   'message': 'invalid_type rule violated in table sites, column geoLat, row(s) 2: Value "1.01" has type string which is incompatible with integer'
│   │   }
],
'warnings': []
}

float

{
'errors': [
│   │   {
│   │   │   'errorType': 'invalid_type',
│   │   │   'tableName': 'sites',
│   │   │   'columnName': 'geoLat',
│   │   │   'rowNumber': 1,
│   │   │   'row': {
│   │   │   │   'siteID': '1',
│   │   │   │   'geoLat': 'a'
│   │   │   },
│   │   │   'invalidValue': 'a',
│   │   │   'validationRuleFields': [
│   │   │   │   {
│   │   │   │   │   'partID': 'geoLat',
│   │   │   │   │   'dataType': 'float',
│   │   │   │   │   'sites': 'header'
│   │   │   │   }
│   │   │   ],
│   │   │   'message': 'invalid_type rule violated in table sites, column geoLat, row(s) 1: Value "a" has type string which is incompatible with float'
│   │   }
],
'warnings': []
}

boolean 1

{
'errors': [
│   │   {
│   │   │   'errorType': 'invalid_type',
│   │   │   'tableName': 'measures',
│   │   │   'columnName': 'reportable',
│   │   │   'rowNumber': 1,
│   │   │   'row': {
│   │   │   │   'measureID': '1',
│   │   │   │   'reportable': 'Yes'
│   │   │   },
│   │   │   'invalidValue': 'Yes',
│   │   │   'validationRuleFields': [
│   │   │   │   {
│   │   │   │   │   'partID': 'reportable',
│   │   │   │   │   'dataType': 'boolean',
│   │   │   │   │   'measures': 'header'
│   │   │   │   },
│   │   │   │   {
│   │   │   │   │   'partID': 'false',
│   │   │   │   │   'setID': 'booleanSet'
│   │   │   │   },
│   │   │   │   {
│   │   │   │   │   'partID': 'true',
│   │   │   │   │   'setID': 'booleanSet'
│   │   │   │   }
│   │   │   ],
│   │   │   'message': 'invalid_type rule violated in table measures, column reportable, row(s) 1: Column reportable is a boolean but has value "Yes". Allowed values are false/true.'
│   │   }
],
'warnings': []
}

boolean 2

{
'errors': [
│   │   {
│   │   │   'errorType': 'invalid_type',
│   │   │   'tableName': 'measures',
│   │   │   'columnName': 'reportable',
│   │   │   'rowNumber': 1,
│   │   │   'row': {
│   │   │   │   'measureID': '1',
│   │   │   │   'reportable': 'True'
│   │   │   },
│   │   │   'invalidValue': 'True',
│   │   │   'validationRuleFields': [
│   │   │   │   {
│   │   │   │   │   'partID': 'reportable',
│   │   │   │   │   'dataType': 'boolean',
│   │   │   │   │   'measures': 'header'
│   │   │   │   },
│   │   │   │   {
│   │   │   │   │   'partID': 'false',
│   │   │   │   │   'setID': 'booleanSet'
│   │   │   │   },
│   │   │   │   {
│   │   │   │   │   'partID': 'true',
│   │   │   │   │   'setID': 'booleanSet'
│   │   │   │   }
│   │   │   ],
│   │   │   'message': 'invalid_type rule violated in table measures, column reportable, row(s) 1: Column reportable is a boolean but has value "True". Allowed values are false/true.'
│   │   }
],
'warnings': []
}

datetime

pprint_json_file(asset("datetime-error-report-1.json"))
{
'errors': [
│   │   {
│   │   │   'errorType': 'invalid_type',
│   │   │   'tableName': 'measures',
│   │   │   'columnName': 'reportDate',
│   │   │   'row': {
│   │   │   │   'measureID': '1',
│   │   │   │   'reportDate': 'a'
│   │   │   },
│   │   │   'rowNumber': 1,
│   │   │   'invalidValue': 'a',
│   │   │   'validationRuleFields': [
│   │   │   │   {
│   │   │   │   │   'partID': 'reportDate',
│   │   │   │   │   'dataType': 'datetime',
│   │   │   │   │   'measures': 'header'
│   │   │   │   }
│   │   │   ],
│   │   │   'message': 'invalid_type rule violated in table measures, column reportDate, row(s) 1: Column reportDate is a datetime but has value "a". Allowed values are ISO 8601 standard full dates, full dates and times, or full dates and times with timezone.'
│   │   },
│   │   {
│   │   │   'errorType': 'invalid_type',
│   │   │   'tableName': 'measures',
│   │   │   'columnName': 'reportDate',
│   │   │   'row': {
│   │   │   │   'measureID': '2',
│   │   │   │   'reportDate': '2022'
│   │   │   },
│   │   │   'rowNumber': 2,
│   │   │   'invalidValue': '2022',
│   │   │   'validationRuleFields': [
│   │   │   │   {
│   │   │   │   │   'partID': 'reportDate',
│   │   │   │   │   'dataType': 'datetime',
│   │   │   │   │   'measures': 'header'
│   │   │   │   }
│   │   │   ],
│   │   │   'message': 'invalid_type rule violated in table measures, column reportDate, row(s) 2: Column reportDate is a datetime but has value "2022". Allowed values are ISO 8601 standard full dates, full dates and times, or full dates and times with timezone.'
│   │   }
],
'warnings': []
}

Rule metadata

All the metadata for this rule is contained in the parts sheet in the ODM dictionary. The steps to retreive the metadata are:

  1. Get the table names in the dictionary

  2. Get all the columns for each table

  3. Get the defined data type for each column. If the data type is a boolean then retreive the category values by using the sets sheet: 1. Filter out all rows except for the ones whose setID is booleanSet 2. The partID columns in the filtered rows contains the category values

  4. Add this validation rule if the data type is supported by this validation rule. Currently, only the blob is not supported and we don’t need to add a rule for the varchar column.

For example, the parts sheet snippets for the examples above can be seen below,

For an integer geoLat column in a sites table

                                                 Integer parts v2                                                  
┏━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓
┃ partID      partType          sites       dataType       status      firstReleased        lastUpdated     ┃
┡━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩
│ sites      │ tables           │ NA         │ NA            │ active     │ 1.0.0               │ 2.0.0           │
│ geoLat     │ attributes       │ header     │ integer       │ active     │ 1.0.0               │ 2.0.0           │
└────────────┴──────────────────┴────────────┴───────────────┴────────────┴─────────────────────┴─────────────────┘

For a float geoLat column in a sites table

                                                  Float parts v2                                                   
┏━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓
┃ partID      partType          sites       dataType       status      firstReleased        lastUpdated     ┃
┡━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩
│ sites      │ tables           │ NA         │ NA            │ active     │ 1.0.0               │ 2.0.0           │
│ geoLat     │ attributes       │ header     │ float         │ active     │ 1.0.0               │ 2.0.0           │
└────────────┴──────────────────┴────────────┴───────────────┴────────────┴─────────────────────┴─────────────────┘

For the boolean reportable column in the measures table,

the part sheet is below,

                                                   Bool parts v2                                                   
┏━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ partID        partType      measures    dataType    mmaSet        status   firstReleased    lastUpdated  ┃
┡━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ measures     │ tables       │ NA         │ NA         │ NA           │ active  │ 1.0.0           │ 2.0.0        │
│ reportable   │ attributes   │ header     │ boolean    │ booleanSet   │ active  │ 1.0.0           │ 2.0.0        │
│ true         │ categories   │ NA         │ varchar    │ NA           │ active  │ 1.0.0           │ 2.0.0        │
│ false        │ categories   │ NA         │ varchar    │ NA           │ active  │ 1.0.0           │ 2.0.0        │
└──────────────┴──────────────┴────────────┴────────────┴──────────────┴─────────┴─────────────────┴──────────────┘

and the sets sheet is,

                                                    Bool set v2                                                    
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ setID                       partID             firstReleased                     lastUpdated                 ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ booleanSet                 │ true              │ 1.0.0                            │ 2.0.0                       │
│ booleanSet                 │ false             │ 1.0.0                            │ 2.0.0                       │
└────────────────────────────┴───────────────────┴──────────────────────────────────┴─────────────────────────────┘

For the datetime reportDate column in the measures table,

pprint_csv_file(asset("datetime-parts.csv"), "Datetime parts v2",
                ignore_prefix="version1")
                                                 Datetime parts v2                                                 
┏━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓
┃ partID           partType        measures      dataType      status     firstReleased      lastUpdated    ┃
┡━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━┩
│ measures        │ tables         │ NA           │ NA           │ active    │ 1.0.0             │ 2.0.0          │
│ reportDate      │ attributes     │ header       │ datetime     │ active    │ 1.0.0             │ 2.0.0          │
└─────────────────┴────────────────┴──────────────┴──────────────┴───────────┴───────────────────┴────────────────┘

Cerberus schema

We can use the type rule in cerberus to perform this validation. Cerberus by default does not coerce the data before validating, we will need to explicitly tell it to do so by using the coerce field.

For a boolean type we will need to use the allowed rule in cerberus.

For a datetime type we will need to extend cerberus and add a new datetime type.

The generated cerberus objects for the examples above are shown below,

integer

{
'schemaVersion': '2.0.0',
'schema': {
│   │   'sites': {
│   │   │   'type': 'list',
│   │   │   'schema': {
│   │   │   │   'type': 'dict',
│   │   │   │   'schema': {
│   │   │   │   │   'geoLat': {
│   │   │   │   │   │   'type': 'integer',
│   │   │   │   │   │   'coerce': 'integer',
│   │   │   │   │   │   'meta': [
│   │   │   │   │   │   │   {
│   │   │   │   │   │   │   │   'ruleID': 'invalid_type',
│   │   │   │   │   │   │   │   'meta': [
│   │   │   │   │   │   │   │   │   {
│   │   │   │   │   │   │   │   │   │   'partID': 'geoLat',
│   │   │   │   │   │   │   │   │   │   'dataType': 'integer',
│   │   │   │   │   │   │   │   │   │   'sites': 'header'
│   │   │   │   │   │   │   │   │   }
│   │   │   │   │   │   │   │   ]
│   │   │   │   │   │   │   }
│   │   │   │   │   │   ]
│   │   │   │   │   }
│   │   │   │   },
│   │   │   │   'meta': [
│   │   │   │   │   {
│   │   │   │   │   │   'partID': 'sites',
│   │   │   │   │   │   'partType': 'tables'
│   │   │   │   │   }
│   │   │   │   ]
│   │   │   }
│   │   }
}
}

float

{
'schemaVersion': '2.0.0',
'schema': {
│   │   'sites': {
│   │   │   'type': 'list',
│   │   │   'schema': {
│   │   │   │   'type': 'dict',
│   │   │   │   'schema': {
│   │   │   │   │   'geoLat': {
│   │   │   │   │   │   'type': 'float',
│   │   │   │   │   │   'coerce': 'float',
│   │   │   │   │   │   'meta': [
│   │   │   │   │   │   │   {
│   │   │   │   │   │   │   │   'ruleID': 'invalid_type',
│   │   │   │   │   │   │   │   'meta': [
│   │   │   │   │   │   │   │   │   {
│   │   │   │   │   │   │   │   │   │   'partID': 'geoLat',
│   │   │   │   │   │   │   │   │   │   'dataType': 'float',
│   │   │   │   │   │   │   │   │   │   'sites': 'header'
│   │   │   │   │   │   │   │   │   }
│   │   │   │   │   │   │   │   ]
│   │   │   │   │   │   │   }
│   │   │   │   │   │   ]
│   │   │   │   │   }
│   │   │   │   },
│   │   │   │   'meta': [
│   │   │   │   │   {
│   │   │   │   │   │   'partID': 'sites',
│   │   │   │   │   │   'partType': 'tables'
│   │   │   │   │   }
│   │   │   │   ]
│   │   │   }
│   │   }
}
}

boolean

{
'schemaVersion': '2.0.0',
'schema': {
│   │   'measures': {
│   │   │   'type': 'list',
│   │   │   'schema': {
│   │   │   │   'type': 'dict',
│   │   │   │   'schema': {
│   │   │   │   │   'reportable': {
│   │   │   │   │   │   'type': 'string',
│   │   │   │   │   │   'allowed': [
│   │   │   │   │   │   │   'false',
│   │   │   │   │   │   │   'true'
│   │   │   │   │   │   ],
│   │   │   │   │   │   'meta': [
│   │   │   │   │   │   │   {
│   │   │   │   │   │   │   │   'ruleID': 'invalid_type',
│   │   │   │   │   │   │   │   'meta': [
│   │   │   │   │   │   │   │   │   {
│   │   │   │   │   │   │   │   │   │   'partID': 'reportable',
│   │   │   │   │   │   │   │   │   │   'dataType': 'boolean',
│   │   │   │   │   │   │   │   │   │   'measures': 'header'
│   │   │   │   │   │   │   │   │   },
│   │   │   │   │   │   │   │   │   {
│   │   │   │   │   │   │   │   │   │   'partID': 'false',
│   │   │   │   │   │   │   │   │   │   'setID': 'booleanSet'
│   │   │   │   │   │   │   │   │   },
│   │   │   │   │   │   │   │   │   {
│   │   │   │   │   │   │   │   │   │   'partID': 'true',
│   │   │   │   │   │   │   │   │   │   'setID': 'booleanSet'
│   │   │   │   │   │   │   │   │   }
│   │   │   │   │   │   │   │   ]
│   │   │   │   │   │   │   }
│   │   │   │   │   │   ]
│   │   │   │   │   }
│   │   │   │   },
│   │   │   │   'meta': [
│   │   │   │   │   {
│   │   │   │   │   │   'partID': 'measures',
│   │   │   │   │   │   'partType': 'tables'
│   │   │   │   │   }
│   │   │   │   ]
│   │   │   }
│   │   }
}
}

datetime

pprint_yaml_file(asset("datetime-schema-v2.yml"))
{
'schemaVersion': '2.0.0',
'schema': {
│   │   'measures': {
│   │   │   'type': 'list',
│   │   │   'schema': {
│   │   │   │   'type': 'dict',
│   │   │   │   'schema': {
│   │   │   │   │   'reportDate': {
│   │   │   │   │   │   'type': 'datetime',
│   │   │   │   │   │   'coerce': 'datetime',
│   │   │   │   │   │   'meta': [
│   │   │   │   │   │   │   {
│   │   │   │   │   │   │   │   'ruleID': 'invalid_type',
│   │   │   │   │   │   │   │   'meta': [
│   │   │   │   │   │   │   │   │   {
│   │   │   │   │   │   │   │   │   │   'partID': 'reportDate',
│   │   │   │   │   │   │   │   │   │   'dataType': 'datetime',
│   │   │   │   │   │   │   │   │   │   'measures': 'header'
│   │   │   │   │   │   │   │   │   }
│   │   │   │   │   │   │   │   ]
│   │   │   │   │   │   │   }
│   │   │   │   │   │   ]
│   │   │   │   │   }
│   │   │   │   },
│   │   │   │   'meta': [
│   │   │   │   │   {
│   │   │   │   │   │   'partID': 'measures',
│   │   │   │   │   │   'partType': 'tables'
│   │   │   │   │   }
│   │   │   │   ]
│   │   │   }
│   │   }
}
}

The meta for this rule should include the partID, <table_name>, and dataType.

For a boolean data type, it should also include the rows that contain the category values. The partID and setID from each row should be included in the category value rows.

ODM Version 1

When generating the schema for version 1, we check whether the column has an equivalent part in version 1. If it does, then we add this validaton rule to it. For example, the v1 parts snippets for each of examples above are shown below,

integer

                                                 Integer parts v1                                                  
┏━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓
┃ partID      partType          sites       dataType       status      firstReleased        lastUpdated     ┃
┡━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩
│ sites      │ tables           │ NA         │ NA            │ active     │ 1.0.0               │ 2.0.0           │
│ geoLat     │ attributes       │ header     │ integer       │ active     │ 1.0.0               │ 2.0.0           │
└────────────┴──────────────────┴────────────┴───────────────┴────────────┴─────────────────────┴─────────────────┘

float

                                                  Float parts v1                                                   
┏━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━┓
┃ partID      partType          sites       dataType       status      firstReleased        lastUpdated     ┃
┡━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━┩
│ sites      │ tables           │ NA         │ NA            │ active     │ 1.0.0               │ 2.0.0           │
│ geoLat     │ attributes       │ header     │ float         │ active     │ 1.0.0               │ 2.0.0           │
└────────────┴──────────────────┴────────────┴───────────────┴────────────┴─────────────────────┴─────────────────┘

boolean

                                                   Bool parts v1                                                   
┏━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┓
┃ partID        partType      measures    dataType    mmaSet        status   firstReleased    lastUpdated  ┃
┡━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━┩
│ measures     │ tables       │ NA         │ NA         │ NA           │ active  │ 1.0.0           │ 2.0.0        │
│ reportable   │ attributes   │ header     │ boolean    │ booleanSet   │ active  │ 1.0.0           │ 2.0.0        │
│ true         │ categories   │ NA         │ varchar    │ NA           │ active  │ 1.0.0           │ 2.0.0        │
│ false        │ categories   │ NA         │ varchar    │ NA           │ active  │ 1.0.0           │ 2.0.0        │
└──────────────┴──────────────┴────────────┴────────────┴──────────────┴─────────┴─────────────────┴──────────────┘

datetime

pprint_csv_file(asset("datetime-parts.csv"), "Datetime parts v1",
                ignore_prefix="version1")
                                                 Datetime parts v1                                                 
┏━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━┓
┃ partID           partType        measures      dataType      status     firstReleased      lastUpdated    ┃
┡━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━┩
│ measures        │ tables         │ NA           │ NA           │ active    │ 1.0.0             │ 2.0.0          │
│ reportDate      │ attributes     │ header       │ datetime     │ active    │ 1.0.0             │ 2.0.0          │
└─────────────────┴────────────────┴──────────────┴──────────────┴───────────┴───────────────────┴────────────────┘

The corresponding v1 cerberus schemas are shown below,

integer

{
'schemaVersion': '1.0.0',
'schema': {
│   │   'Site': {
│   │   │   'type': 'list',
│   │   │   'schema': {
│   │   │   │   'type': 'dict',
│   │   │   │   'schema': {
│   │   │   │   │   'Latitude': {
│   │   │   │   │   │   'type': 'integer',
│   │   │   │   │   │   'coerce': 'integer',
│   │   │   │   │   │   'meta': [
│   │   │   │   │   │   │   {
│   │   │   │   │   │   │   │   'ruleID': 'invalid_type',
│   │   │   │   │   │   │   │   'meta': [
│   │   │   │   │   │   │   │   │   {
│   │   │   │   │   │   │   │   │   │   'partID': 'geoLat',
│   │   │   │   │   │   │   │   │   │   'dataType': 'integer',
│   │   │   │   │   │   │   │   │   │   'sites': 'header',
│   │   │   │   │   │   │   │   │   │   'version1Location': 'variables',
│   │   │   │   │   │   │   │   │   │   'version1Table': 'Site',
│   │   │   │   │   │   │   │   │   │   'version1Variable': 'Latitude'
│   │   │   │   │   │   │   │   │   }
│   │   │   │   │   │   │   │   ]
│   │   │   │   │   │   │   }
│   │   │   │   │   │   ]
│   │   │   │   │   }
│   │   │   │   },
│   │   │   │   'meta': [
│   │   │   │   │   {
│   │   │   │   │   │   'partID': 'sites',
│   │   │   │   │   │   'partType': 'tables',
│   │   │   │   │   │   'version1Location': 'tables',
│   │   │   │   │   │   'version1Table': 'Site'
│   │   │   │   │   }
│   │   │   │   ]
│   │   │   }
│   │   }
}
}

float

{
'schemaVersion': '1.0.0',
'schema': {
│   │   'Site': {
│   │   │   'type': 'list',
│   │   │   'schema': {
│   │   │   │   'type': 'dict',
│   │   │   │   'schema': {
│   │   │   │   │   'Latitude': {
│   │   │   │   │   │   'type': 'float',
│   │   │   │   │   │   'coerce': 'float',
│   │   │   │   │   │   'meta': [
│   │   │   │   │   │   │   {
│   │   │   │   │   │   │   │   'ruleID': 'invalid_type',
│   │   │   │   │   │   │   │   'meta': [
│   │   │   │   │   │   │   │   │   {
│   │   │   │   │   │   │   │   │   │   'partID': 'geoLat',
│   │   │   │   │   │   │   │   │   │   'dataType': 'float',
│   │   │   │   │   │   │   │   │   │   'sites': 'header',
│   │   │   │   │   │   │   │   │   │   'version1Location': 'variables',
│   │   │   │   │   │   │   │   │   │   'version1Table': 'Site',
│   │   │   │   │   │   │   │   │   │   'version1Variable': 'Latitude'
│   │   │   │   │   │   │   │   │   }
│   │   │   │   │   │   │   │   ]
│   │   │   │   │   │   │   }
│   │   │   │   │   │   ]
│   │   │   │   │   }
│   │   │   │   },
│   │   │   │   'meta': [
│   │   │   │   │   {
│   │   │   │   │   │   'partID': 'sites',
│   │   │   │   │   │   'partType': 'tables',
│   │   │   │   │   │   'version1Location': 'tables',
│   │   │   │   │   │   'version1Table': 'Site'
│   │   │   │   │   }
│   │   │   │   ]
│   │   │   }
│   │   }
}
}

boolean

{
'schemaVersion': '1.1.0',
'schema': {
│   │   'Measure': {
│   │   │   'type': 'list',
│   │   │   'schema': {
│   │   │   │   'type': 'dict',
│   │   │   │   'schema': {
│   │   │   │   │   'reported': {
│   │   │   │   │   │   'type': 'string',
│   │   │   │   │   │   'allowed': [
│   │   │   │   │   │   │   'FALSE',
│   │   │   │   │   │   │   'TRUE'
│   │   │   │   │   │   ],
│   │   │   │   │   │   'meta': [
│   │   │   │   │   │   │   {
│   │   │   │   │   │   │   │   'ruleID': 'invalid_type',
│   │   │   │   │   │   │   │   'meta': [
│   │   │   │   │   │   │   │   │   {
│   │   │   │   │   │   │   │   │   │   'partID': 'reportable',
│   │   │   │   │   │   │   │   │   │   'dataType': 'boolean',
│   │   │   │   │   │   │   │   │   │   'measures': 'header',
│   │   │   │   │   │   │   │   │   │   'version1Location': 'variables',
│   │   │   │   │   │   │   │   │   │   'version1Table': 'Measure',
│   │   │   │   │   │   │   │   │   │   'version1Variable': 'reported'
│   │   │   │   │   │   │   │   │   }
│   │   │   │   │   │   │   │   ]
│   │   │   │   │   │   │   }
│   │   │   │   │   │   ]
│   │   │   │   │   }
│   │   │   │   },
│   │   │   │   'meta': [
│   │   │   │   │   {
│   │   │   │   │   │   'partID': 'measures',
│   │   │   │   │   │   'partType': 'tables',
│   │   │   │   │   │   'version1Location': 'tables',
│   │   │   │   │   │   'version1Table': 'Measure'
│   │   │   │   │   }
│   │   │   │   ]
│   │   │   }
│   │   }
}
}

datetime

pprint_yaml_file(asset("datetime-schema-v1.yml"))
{
'schemaVersion': '1.0.0',
'schema': {
│   │   'Measure': {
│   │   │   'type': 'list',
│   │   │   'schema': {
│   │   │   │   'type': 'dict',
│   │   │   │   'schema': {
│   │   │   │   │   'DateReport': {
│   │   │   │   │   │   'type': 'datetime',
│   │   │   │   │   │   'coerce': 'datetime',
│   │   │   │   │   │   'meta': [
│   │   │   │   │   │   │   {
│   │   │   │   │   │   │   │   'ruleID': 'invalid_type',
│   │   │   │   │   │   │   │   'meta': [
│   │   │   │   │   │   │   │   │   {
│   │   │   │   │   │   │   │   │   │   'partID': 'reportDate',
│   │   │   │   │   │   │   │   │   │   'dataType': 'datetime',
│   │   │   │   │   │   │   │   │   │   'measures': 'header',
│   │   │   │   │   │   │   │   │   │   'version1Location': 'variables',
│   │   │   │   │   │   │   │   │   │   'version1Table': 'Measure',
│   │   │   │   │   │   │   │   │   │   'version1Variable': 'DateReport'
│   │   │   │   │   │   │   │   │   }
│   │   │   │   │   │   │   │   ]
│   │   │   │   │   │   │   }
│   │   │   │   │   │   ]
│   │   │   │   │   }
│   │   │   │   },
│   │   │   │   'meta': [
│   │   │   │   │   {
│   │   │   │   │   │   'partID': 'measures',
│   │   │   │   │   │   'partType': 'tables',
│   │   │   │   │   │   'version1Location': 'tables',
│   │   │   │   │   │   'version1Table': 'Measure'
│   │   │   │   │   }
│   │   │   │   ]
│   │   │   }
│   │   }
}
}

The metadata should include the meta columns for version 2 as well as the version1Location, version1Table, and the version1Variable columns.

For a boolean data type, only the version1Category column should be added on for the boolean category values.