///|
/// The canonical observation schema.
pub fn observation_schema() -> DatasetSchema {
{
name: "observation",
required: [Timestamp, Distance, Elapsed],
optional: [ActualRemaining, Weight],
version: "1",
}
}
///|
/// The schema used for labeled evaluation datasets.
pub fn labeled_schema() -> DatasetSchema {
{
name: "labeled-observation",
required: [Timestamp, Distance, Elapsed, ActualRemaining],
optional: [Weight],
version: "1",
}
}
///|
/// Convert a schema field into its wire name.
pub fn schema_field_name(field : SchemaField) -> String {
match field {
Timestamp => "timestamp"
Distance => "distance"
Elapsed => "elapsed"
ActualRemaining => "actual_remaining"
Weight => "weight"
}
}
///|
/// Parse a schema field name.
pub fn schema_field_from_name(name : String) -> SchemaField? {
let normalized = name.trim().to_owned().to_lower()
if normalized == "timestamp" {
Some(Timestamp)
} else if normalized == "distance" {
Some(Distance)
} else if normalized == "elapsed" {
Some(Elapsed)
} else if normalized == "actual_remaining" {
Some(ActualRemaining)
} else if normalized == "weight" {
Some(Weight)
} else {
None
}
}
///|
/// Return all supported field names in canonical order.
pub fn all_schema_field_names() -> Array[String] {
["timestamp", "distance", "elapsed", "actual_remaining", "weight"]
}
///|
/// Check a header against a schema.
pub fn validate_schema_header(
schema : DatasetSchema,
headers : Array[String],
) -> SchemaReport {
let fields : Array[SchemaField] = []
let unknown : Array[String] = []
for header in headers {
match schema_field_from_name(header) {
Some(field) => fields.push(field)
None => unknown.push(header)
}
}
let missing : Array[SchemaField] = []
for required in schema.required {
if !fields.contains(required) {
missing.push(required)
}
}
let valid = missing.length() == 0 && unknown.length() == 0
let message = if valid {
"schema accepted"
} else if missing.length() > 0 {
"required fields are missing"
} else {
"unknown fields are present"
}
{ valid, missing, unknown, message }
}
///|
/// Return whether a field is accepted by the schema.
pub fn DatasetSchema::accepts(
self : DatasetSchema,
field : SchemaField,
) -> Bool {
self.required.contains(field) || self.optional.contains(field)
}
///|
/// Return the number of fields in a schema.
pub fn DatasetSchema::field_count(self : DatasetSchema) -> Int {
self.required.length() + self.optional.length()
}
///|
/// Render a stable schema description.
pub fn format_schema(schema : DatasetSchema) -> String {
let required = schema.required.map(schema_field_name).join(",")
let optional = schema.optional.map(schema_field_name).join(",")
"\{schema.name}@\{schema.version} required=[\{required}] optional=[\{optional}]"
}
///|
/// Return canonical CSV headers for a schema.
pub fn schema_headers(
schema : DatasetSchema,
include_optional? : Bool = false,
) -> Array[String] {
let headers : Array[String] = []
for field in schema.required {
headers.push(schema_field_name(field))
}
if include_optional {
for field in schema.optional {
headers.push(schema_field_name(field))
}
}
headers
}
///|
/// Check whether a field list has duplicates.
pub fn has_duplicate_fields(fields : Array[String]) -> Bool {
for index, field in fields {
for other in (index + 1).. Bool {
!schema.name.trim().is_empty() &&
!schema.version.trim().is_empty() &&
schema.required.length() > 0 &&
!has_duplicate_fields(schema_headers(schema, include_optional=true))
}