From 85a8de4ca058490980966cc9300bab7ae81ee0d7 Mon Sep 17 00:00:00 2001 From: "Corey N. Runkel" Date: Mon, 29 Jun 2026 14:24:27 -0400 Subject: [PATCH 1/4] Add data field schemas --- schemas/com.mbta.railds.measurement.json | 107 ++++++ schemas/com.mbta.railds.switch-throw.json | 395 ++++++++++++++++++++++ 2 files changed, 502 insertions(+) create mode 100644 schemas/com.mbta.railds.measurement.json create mode 100644 schemas/com.mbta.railds.switch-throw.json diff --git a/schemas/com.mbta.railds.measurement.json b/schemas/com.mbta.railds.measurement.json new file mode 100644 index 0000000..667e2dc --- /dev/null +++ b/schemas/com.mbta.railds.measurement.json @@ -0,0 +1,107 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/mbta/schemas/blob/main/schemas/com.mbta.railds.measurement.v1.json", + "title": "Mikrolok Measurement Data", + "description": "Raw measurement data structure from Mikrolok devices before CloudEvents wrapping", + "type": "object", + "properties": { + "asset_id": { + "type": "string", + "description": "Identifier for the monitored asset" + }, + "payload": { + "type": "object", + "description": "Payload containing events", + "properties": { + "events": { + "type": "array", + "description": "Array of measurement events", + "items": { + "type": "object", + "properties": { + "mlk_timestamp_utc": { + "type": "string", + "format": "date-time", + "description": "UTC timestamp from the MicroLok device" + }, + "values": { + "type": "array", + "description": "Array of measurement values", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "Name of the measurement point" + }, + "value": { + "type": "integer", + "description": "Measured value" + }, + "value_type": { + "type": "string", + "description": "Type of the value", + "enum": [ + "boolean" + ] + }, + "index": { + "type": "integer", + "description": "Index of the measurement point", + "minimum": 1 + }, + "old": { + "type": [ + "integer", + "null" + ], + "description": "Previous value before change" + }, + "operation": { + "type": "string", + "description": "Operation performed on the value", + "enum": [ + "clear", + "set" + ] + }, + "change_id": { + "type": "string", + "description": "Unique identifier for this change" + }, + "updated_utc": { + "type": "string", + "format": "date-time", + "description": "UTC timestamp when value was updated" + } + }, + "required": [ + "name", + "value", + "value_type", + "index", + "old", + "operation", + "change_id", + "updated_utc" + ] + } + } + }, + "required": [ + "mlk_timestamp_utc", + "values" + ] + } + } + }, + "required": [ + "events" + ] + } + }, + "required": [ + "asset_id", + "payload" + ] +} \ No newline at end of file diff --git a/schemas/com.mbta.railds.switch-throw.json b/schemas/com.mbta.railds.switch-throw.json new file mode 100644 index 0000000..f29b5ed --- /dev/null +++ b/schemas/com.mbta.railds.switch-throw.json @@ -0,0 +1,395 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/mbta/schemas/blob/main/schemas/com.mbta.railds.switch-throw.v1.json", + "title": "Switch Throw Event Data", + "description": "Raw event data structure from Moxa event detector before CloudEvents wrapping", + "type": "object", + "properties": { + "event_label": { + "type": "string", + "description": "Human-readable label for the event" + }, + "event_class": { + "type": "string", + "description": "Classification of the event", + "examples": [ + "Current Sensor" + ] + }, + "event_type": { + "type": "string", + "description": "Type of event detection", + "enum": [ + "analog_threshold" + ] + }, + "detector_mode": { + "type": "string", + "description": "Detection mode for the event", + "enum": [ + "momentary" + ] + }, + "sample_mode": { + "type": "string", + "description": "Sampling mode for the event", + "enum": [ + "all" + ] + }, + "status": { + "type": "string", + "description": "Current status of the event", + "enum": [ + "completed" + ] + }, + "severity": { + "type": "string", + "description": "Severity level of the event", + "enum": [ + "event" + ] + }, + "start": { + "type": "object", + "description": "Event start information", + "properties": { + "timestamp": { + "type": "number", + "description": "Unix timestamp (seconds since epoch) when event started" + }, + "iso": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 formatted timestamp" + }, + "value": { + "type": "number", + "description": "Measured value at event start" + }, + "raw_value": { + "type": "number", + "description": "Raw sensor value at event start" + } + }, + "required": [ + "timestamp", + "iso", + "value", + "raw_value" + ] + }, + "end": { + "type": "object", + "description": "Event end information", + "properties": { + "timestamp": { + "type": "number", + "description": "Unix timestamp (seconds since epoch) when event ended" + }, + "iso": { + "type": "string", + "format": "date-time", + "description": "ISO 8601 formatted timestamp" + }, + "value": { + "type": "number", + "description": "Measured value at event end" + }, + "raw_value": { + "type": "number", + "description": "Raw sensor value at event end" + }, + "reason": { + "type": "string", + "description": "Reason the event ended", + "enum": [ + "normal_end" + ] + } + }, + "required": [ + "timestamp", + "iso", + "value", + "raw_value", + "reason" + ] + }, + "duration_sec": { + "type": "number", + "description": "Duration of the event in seconds", + "minimum": 0 + }, + "source": { + "type": "object", + "description": "Information about the event source", + "properties": { + "slot_key": { + "type": "string", + "description": "Identifier for the device slot", + "pattern": "^slot_[0-9]+$" + }, + "port": { + "type": "integer", + "description": "Port number on the device", + "minimum": 0 + }, + "point_key": { + "type": "string", + "description": "Unique identifier combining slot and port" + }, + "model_names": { + "type": "array", + "description": "Device model names", + "items": { + "type": "string" + } + }, + "custom_target": { + "type": "string", + "description": "Custom monitoring target name" + }, + "monitoring_target": { + "type": "string", + "description": "Type of monitoring target" + }, + "equipment_type": { + "type": "string", + "description": "Type of equipment being monitored" + }, + "sensor": { + "type": "string", + "description": "Sensor type or model" + }, + "unit": { + "type": "string", + "description": "Unit of measurement", + "examples": [ + "Amps" + ] + }, + "units_seen": { + "type": "array", + "description": "All units of measurement seen for this source", + "items": { + "type": "string" + } + } + }, + "required": [ + "slot_key", + "port", + "point_key" + ] + }, + "rule": { + "type": "object", + "description": "Rule configuration that triggered the event", + "properties": { + "rule_id": { + "type": [ + "string", + "null" + ], + "description": "Unique identifier for the rule" + }, + "rule_name": { + "type": "string", + "description": "Name of the rule" + }, + "enabled": { + "type": "boolean", + "description": "Whether the rule is currently enabled" + }, + "condition": { + "type": "object", + "description": "Threshold conditions for the rule", + "properties": { + "start_threshold": { + "type": "number", + "description": "Value above which event starts" + }, + "stop_threshold": { + "type": "number", + "description": "Value below which event ends" + }, + "pre_capture_sec": { + "type": "number", + "description": "Seconds of data to capture before event start", + "minimum": 0 + }, + "event_end_delay_sec": { + "type": "number", + "description": "Delay in seconds before ending event after threshold crossed", + "minimum": 0 + }, + "max_event_time_sec": { + "type": "number", + "description": "Maximum duration of event in seconds", + "minimum": 0 + }, + "event_sample_interval_sec": { + "type": "number", + "description": "Interval between samples during event in seconds", + "minimum": 0 + }, + "event_change_deadband": { + "type": [ + "number", + "null" + ], + "description": "Minimum change required to record a new sample" + } + } + } + }, + "required": [ + "rule_name", + "enabled" + ] + }, + "metrics": { + "type": "object", + "description": "Statistical metrics for the event", + "properties": { + "sample_count": { + "type": "integer", + "description": "Number of samples collected during event", + "minimum": 0 + }, + "peak_value": { + "type": "number", + "description": "Maximum measured value during event" + }, + "peak_raw_value": { + "type": "number", + "description": "Maximum raw sensor value during event" + }, + "min_value": { + "type": "number", + "description": "Minimum measured value during event" + }, + "min_raw_value": { + "type": "number", + "description": "Minimum raw sensor value during event" + }, + "avg_value": { + "type": "number", + "description": "Average measured value during event" + }, + "avg_raw_value": { + "type": "number", + "description": "Average raw sensor value during event" + }, + "time_in_event_sec": { + "type": "number", + "description": "Total time in event state in seconds", + "minimum": 0 + } + }, + "required": [ + "sample_count" + ] + }, + "sample_storage": { + "type": "object", + "description": "Configuration for how samples are stored", + "properties": { + "included": { + "type": "boolean", + "description": "Whether samples are included in this event" + }, + "mode": { + "type": "string", + "description": "Storage mode for samples", + "enum": [ + "inline_arrays" + ] + }, + "compressed": { + "type": "boolean", + "description": "Whether samples are compressed" + } + }, + "required": [ + "included", + "mode", + "compressed" + ] + }, + "samples": { + "type": "object", + "description": "Sample data collected during the event", + "properties": { + "timestamps": { + "type": "array", + "description": "Array of Unix timestamps for each sample", + "items": { + "type": "number" + } + }, + "values": { + "type": "array", + "description": "Array of measured values for each sample", + "items": { + "type": "number" + } + }, + "raw_values": { + "type": "array", + "description": "Array of raw sensor values for each sample", + "items": { + "type": "number" + } + } + }, + "required": [ + "timestamps", + "values", + "raw_values" + ] + }, + "metadata": { + "type": "object", + "description": "Event metadata", + "properties": { + "created_utc": { + "type": "string", + "format": "date-time", + "description": "UTC timestamp when event was created" + }, + "app": { + "type": "string", + "description": "Application that generated the event", + "examples": [ + "moxa_event_detector" + ] + } + }, + "required": [ + "created_utc", + "app" + ] + } + }, + "required": [ + "schema_version", + "event_id", + "event_label", + "event_class", + "event_type", + "detector_mode", + "sample_mode", + "status", + "severity", + "start", + "duration_sec", + "source", + "rule", + "metrics", + "sample_storage", + "metadata" + ] +} \ No newline at end of file From 48fdcc3f1f5ee5c5a7e5e036c0a62cb41797e550 Mon Sep 17 00:00:00 2001 From: "Corey N. Runkel" Date: Mon, 29 Jun 2026 14:25:41 -0400 Subject: [PATCH 2/4] Add CloudEvent spec for RailDS --- schemas/com.mbta.railds.json | 76 ++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100644 schemas/com.mbta.railds.json diff --git a/schemas/com.mbta.railds.json b/schemas/com.mbta.railds.json new file mode 100644 index 0000000..0244939 --- /dev/null +++ b/schemas/com.mbta.railds.json @@ -0,0 +1,76 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://github.com/mbta/schemas/blob/main/schemas/com.mbta.railds.json", + "title": "RailDS Moxa Events", + "description": "RailDS events emitted by Moxa devices.", + "$ref": "cloudevents.json", + "type": "object", + "properties": { + "type": { + "type": "string", + "description": "name of the event", + "$comment": "enforced to be a valid event name by the oneOf, below" + }, + "specversion": { + "const": "1.0" + }, + "source": { + "$ref": "cloudevents.json#/properties/source", + "description": "The Id of the device that emitted the event.", + "examples": [ + "/edge-collector/MOXA-101" + ] + }, + "id": { + "type": "string", + "format": "uuid", + "minLength": 1, + "description": "A unique identifier for the event." + }, + "time": { + "$ref": "#/$defs/timestamp" + }, + "data": { + "type": "object", + "$comment": "data object is validated by each event's schema in the oneOf below", + "oneOf": [ + { + "properties": { + "type": { + "const": "com.mbta.railds.switch-throw" + } + }, + "$ref": "com.mbta.railds.switch-throw.json#/" + }, + { + "properties": { + "type": { + "const": "com.mbta.railds.measurement" + } + }, + "$ref": "com.mbta.railds.measurement.json#/" + } + ] + } + }, + "required": [ + "type", + "specversion", + "source", + "id", + "time", + "data" + ], + "$defs": { + "timestamp": { + "type": "string", + "minLength": 20, + "$comment": "RFC3339 timestamp", + "format": "date-time", + "pattern": "^[0-9]{4}-[01][0-9]-[0-3][0-9]T[012][0-9]:[0-5][0-9]:[0-6][0-9](.[0-9]*)?(Z|[+-][012][0-9]:[0-5][0-9])$", + "examples": [ + "2023-01-20T09:30:00-05:00" + ] + } + } +} \ No newline at end of file From 47ba9b7e06fb906fd16739b9e3f937eafbc1f799 Mon Sep 17 00:00:00 2001 From: "Corey N. Runkel" Date: Mon, 29 Jun 2026 15:20:33 -0400 Subject: [PATCH 3/4] Conform filename to id & format --- schemas/com.mbta.railds.json | 19 +--- ...on => com.mbta.railds.measurement.v1.json} | 30 ++---- ...n => com.mbta.railds.switch-throw.v1.json} | 99 ++++--------------- 3 files changed, 32 insertions(+), 116 deletions(-) rename schemas/{com.mbta.railds.measurement.json => com.mbta.railds.measurement.v1.json} (84%) rename schemas/{com.mbta.railds.switch-throw.json => com.mbta.railds.switch-throw.v1.json} (87%) diff --git a/schemas/com.mbta.railds.json b/schemas/com.mbta.railds.json index 0244939..eeb5e68 100644 --- a/schemas/com.mbta.railds.json +++ b/schemas/com.mbta.railds.json @@ -17,9 +17,7 @@ "source": { "$ref": "cloudevents.json#/properties/source", "description": "The Id of the device that emitted the event.", - "examples": [ - "/edge-collector/MOXA-101" - ] + "examples": ["/edge-collector/MOXA-101"] }, "id": { "type": "string", @@ -53,14 +51,7 @@ ] } }, - "required": [ - "type", - "specversion", - "source", - "id", - "time", - "data" - ], + "required": ["type", "specversion", "source", "id", "time", "data"], "$defs": { "timestamp": { "type": "string", @@ -68,9 +59,7 @@ "$comment": "RFC3339 timestamp", "format": "date-time", "pattern": "^[0-9]{4}-[01][0-9]-[0-3][0-9]T[012][0-9]:[0-5][0-9]:[0-6][0-9](.[0-9]*)?(Z|[+-][012][0-9]:[0-5][0-9])$", - "examples": [ - "2023-01-20T09:30:00-05:00" - ] + "examples": ["2023-01-20T09:30:00-05:00"] } } -} \ No newline at end of file +} diff --git a/schemas/com.mbta.railds.measurement.json b/schemas/com.mbta.railds.measurement.v1.json similarity index 84% rename from schemas/com.mbta.railds.measurement.json rename to schemas/com.mbta.railds.measurement.v1.json index 667e2dc..411db4c 100644 --- a/schemas/com.mbta.railds.measurement.json +++ b/schemas/com.mbta.railds.measurement.v1.json @@ -41,9 +41,7 @@ "value_type": { "type": "string", "description": "Type of the value", - "enum": [ - "boolean" - ] + "enum": ["boolean"] }, "index": { "type": "integer", @@ -51,19 +49,13 @@ "minimum": 1 }, "old": { - "type": [ - "integer", - "null" - ], + "type": ["integer", "null"], "description": "Previous value before change" }, "operation": { "type": "string", "description": "Operation performed on the value", - "enum": [ - "clear", - "set" - ] + "enum": ["clear", "set"] }, "change_id": { "type": "string", @@ -88,20 +80,12 @@ } } }, - "required": [ - "mlk_timestamp_utc", - "values" - ] + "required": ["mlk_timestamp_utc", "values"] } } }, - "required": [ - "events" - ] + "required": ["events"] } }, - "required": [ - "asset_id", - "payload" - ] -} \ No newline at end of file + "required": ["asset_id", "payload"] +} diff --git a/schemas/com.mbta.railds.switch-throw.json b/schemas/com.mbta.railds.switch-throw.v1.json similarity index 87% rename from schemas/com.mbta.railds.switch-throw.json rename to schemas/com.mbta.railds.switch-throw.v1.json index f29b5ed..42736a3 100644 --- a/schemas/com.mbta.railds.switch-throw.json +++ b/schemas/com.mbta.railds.switch-throw.v1.json @@ -12,44 +12,32 @@ "event_class": { "type": "string", "description": "Classification of the event", - "examples": [ - "Current Sensor" - ] + "examples": ["Current Sensor"] }, "event_type": { "type": "string", "description": "Type of event detection", - "enum": [ - "analog_threshold" - ] + "enum": ["analog_threshold"] }, "detector_mode": { "type": "string", "description": "Detection mode for the event", - "enum": [ - "momentary" - ] + "enum": ["momentary"] }, "sample_mode": { "type": "string", "description": "Sampling mode for the event", - "enum": [ - "all" - ] + "enum": ["all"] }, "status": { "type": "string", "description": "Current status of the event", - "enum": [ - "completed" - ] + "enum": ["completed"] }, "severity": { "type": "string", "description": "Severity level of the event", - "enum": [ - "event" - ] + "enum": ["event"] }, "start": { "type": "object", @@ -73,12 +61,7 @@ "description": "Raw sensor value at event start" } }, - "required": [ - "timestamp", - "iso", - "value", - "raw_value" - ] + "required": ["timestamp", "iso", "value", "raw_value"] }, "end": { "type": "object", @@ -104,18 +87,10 @@ "reason": { "type": "string", "description": "Reason the event ended", - "enum": [ - "normal_end" - ] + "enum": ["normal_end"] } }, - "required": [ - "timestamp", - "iso", - "value", - "raw_value", - "reason" - ] + "required": ["timestamp", "iso", "value", "raw_value", "reason"] }, "duration_sec": { "type": "number", @@ -166,9 +141,7 @@ "unit": { "type": "string", "description": "Unit of measurement", - "examples": [ - "Amps" - ] + "examples": ["Amps"] }, "units_seen": { "type": "array", @@ -178,21 +151,14 @@ } } }, - "required": [ - "slot_key", - "port", - "point_key" - ] + "required": ["slot_key", "port", "point_key"] }, "rule": { "type": "object", "description": "Rule configuration that triggered the event", "properties": { "rule_id": { - "type": [ - "string", - "null" - ], + "type": ["string", "null"], "description": "Unique identifier for the rule" }, "rule_name": { @@ -236,19 +202,13 @@ "minimum": 0 }, "event_change_deadband": { - "type": [ - "number", - "null" - ], + "type": ["number", "null"], "description": "Minimum change required to record a new sample" } } } }, - "required": [ - "rule_name", - "enabled" - ] + "required": ["rule_name", "enabled"] }, "metrics": { "type": "object", @@ -289,9 +249,7 @@ "minimum": 0 } }, - "required": [ - "sample_count" - ] + "required": ["sample_count"] }, "sample_storage": { "type": "object", @@ -304,20 +262,14 @@ "mode": { "type": "string", "description": "Storage mode for samples", - "enum": [ - "inline_arrays" - ] + "enum": ["inline_arrays"] }, "compressed": { "type": "boolean", "description": "Whether samples are compressed" } }, - "required": [ - "included", - "mode", - "compressed" - ] + "required": ["included", "mode", "compressed"] }, "samples": { "type": "object", @@ -345,11 +297,7 @@ } } }, - "required": [ - "timestamps", - "values", - "raw_values" - ] + "required": ["timestamps", "values", "raw_values"] }, "metadata": { "type": "object", @@ -363,15 +311,10 @@ "app": { "type": "string", "description": "Application that generated the event", - "examples": [ - "moxa_event_detector" - ] + "examples": ["moxa_event_detector"] } }, - "required": [ - "created_utc", - "app" - ] + "required": ["created_utc", "app"] } }, "required": [ @@ -392,4 +335,4 @@ "sample_storage", "metadata" ] -} \ No newline at end of file +} From 13f85af8eb73fd0ef198fa002b6ef1d883d126f5 Mon Sep 17 00:00:00 2001 From: "Corey N. Runkel" Date: Mon, 29 Jun 2026 15:41:25 -0400 Subject: [PATCH 4/4] ci --- schemas/com.mbta.railds.json | 4 ++-- schemas/com.mbta.railds.switch-throw.v1.json | 22 +------------------- 2 files changed, 3 insertions(+), 23 deletions(-) diff --git a/schemas/com.mbta.railds.json b/schemas/com.mbta.railds.json index eeb5e68..4dc22e3 100644 --- a/schemas/com.mbta.railds.json +++ b/schemas/com.mbta.railds.json @@ -38,7 +38,7 @@ "const": "com.mbta.railds.switch-throw" } }, - "$ref": "com.mbta.railds.switch-throw.json#/" + "$ref": "com.mbta.railds.switch-throw.v1.json#/" }, { "properties": { @@ -46,7 +46,7 @@ "const": "com.mbta.railds.measurement" } }, - "$ref": "com.mbta.railds.measurement.json#/" + "$ref": "com.mbta.railds.measurement.v1.json#/" } ] } diff --git a/schemas/com.mbta.railds.switch-throw.v1.json b/schemas/com.mbta.railds.switch-throw.v1.json index 42736a3..f5da437 100644 --- a/schemas/com.mbta.railds.switch-throw.v1.json +++ b/schemas/com.mbta.railds.switch-throw.v1.json @@ -298,28 +298,9 @@ } }, "required": ["timestamps", "values", "raw_values"] - }, - "metadata": { - "type": "object", - "description": "Event metadata", - "properties": { - "created_utc": { - "type": "string", - "format": "date-time", - "description": "UTC timestamp when event was created" - }, - "app": { - "type": "string", - "description": "Application that generated the event", - "examples": ["moxa_event_detector"] - } - }, - "required": ["created_utc", "app"] } }, "required": [ - "schema_version", - "event_id", "event_label", "event_class", "event_type", @@ -332,7 +313,6 @@ "source", "rule", "metrics", - "sample_storage", - "metadata" + "sample_storage" ] }