sift-stack-py 0.3.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- google/__init__.py +1 -0
- google/api/__init__.py +0 -0
- google/api/annotations_pb2.py +27 -0
- google/api/annotations_pb2.pyi +29 -0
- google/api/annotations_pb2_grpc.py +4 -0
- google/api/annotations_pb2_grpc.pyi +30 -0
- google/api/field_behavior_pb2.py +30 -0
- google/api/field_behavior_pb2.pyi +175 -0
- google/api/field_behavior_pb2_grpc.py +4 -0
- google/api/field_behavior_pb2_grpc.pyi +30 -0
- google/api/http_pb2.py +31 -0
- google/api/http_pb2.pyi +433 -0
- google/api/http_pb2_grpc.py +4 -0
- google/api/http_pb2_grpc.pyi +30 -0
- protoc_gen_openapiv2/__init__.py +0 -0
- protoc_gen_openapiv2/options/__init__.py +0 -0
- protoc_gen_openapiv2/options/annotations_pb2.py +27 -0
- protoc_gen_openapiv2/options/annotations_pb2.pyi +48 -0
- protoc_gen_openapiv2/options/annotations_pb2_grpc.py +4 -0
- protoc_gen_openapiv2/options/annotations_pb2_grpc.pyi +17 -0
- protoc_gen_openapiv2/options/openapiv2_pb2.py +132 -0
- protoc_gen_openapiv2/options/openapiv2_pb2.pyi +1533 -0
- protoc_gen_openapiv2/options/openapiv2_pb2_grpc.py +4 -0
- protoc_gen_openapiv2/options/openapiv2_pb2_grpc.pyi +17 -0
- sift/__init__.py +0 -0
- sift/annotation_logs/__init__.py +0 -0
- sift/annotation_logs/v1/__init__.py +0 -0
- sift/annotation_logs/v1/annotation_logs_pb2.py +115 -0
- sift/annotation_logs/v1/annotation_logs_pb2.pyi +370 -0
- sift/annotation_logs/v1/annotation_logs_pb2_grpc.py +135 -0
- sift/annotation_logs/v1/annotation_logs_pb2_grpc.pyi +84 -0
- sift/annotations/__init__.py +0 -0
- sift/annotations/v1/__init__.py +0 -0
- sift/annotations/v1/annotations_pb2.py +180 -0
- sift/annotations/v1/annotations_pb2.pyi +539 -0
- sift/annotations/v1/annotations_pb2_grpc.py +237 -0
- sift/annotations/v1/annotations_pb2_grpc.pyi +144 -0
- sift/assets/__init__.py +0 -0
- sift/assets/v1/__init__.py +0 -0
- sift/assets/v1/assets_pb2.py +90 -0
- sift/assets/v1/assets_pb2.pyi +235 -0
- sift/assets/v1/assets_pb2_grpc.py +168 -0
- sift/assets/v1/assets_pb2_grpc.pyi +101 -0
- sift/calculated_channels/__init__.py +0 -0
- sift/calculated_channels/v1/__init__.py +0 -0
- sift/calculated_channels/v1/calculated_channels_pb2.py +99 -0
- sift/calculated_channels/v1/calculated_channels_pb2.pyi +280 -0
- sift/calculated_channels/v1/calculated_channels_pb2_grpc.py +101 -0
- sift/calculated_channels/v1/calculated_channels_pb2_grpc.pyi +64 -0
- sift/campaigns/__init__.py +0 -0
- sift/campaigns/v1/__init__.py +0 -0
- sift/campaigns/v1/campaigns_pb2.py +144 -0
- sift/campaigns/v1/campaigns_pb2.pyi +383 -0
- sift/campaigns/v1/campaigns_pb2_grpc.py +169 -0
- sift/campaigns/v1/campaigns_pb2_grpc.pyi +104 -0
- sift/channel_schemas/__init__.py +0 -0
- sift/channel_schemas/v1/__init__.py +0 -0
- sift/channel_schemas/v1/channel_schemas_pb2.py +69 -0
- sift/channel_schemas/v1/channel_schemas_pb2.pyi +117 -0
- sift/channel_schemas/v1/channel_schemas_pb2_grpc.py +101 -0
- sift/channel_schemas/v1/channel_schemas_pb2_grpc.pyi +64 -0
- sift/channels/__init__.py +0 -0
- sift/channels/v2/__init__.py +0 -0
- sift/channels/v2/channels_pb2.py +88 -0
- sift/channels/v2/channels_pb2.pyi +183 -0
- sift/channels/v2/channels_pb2_grpc.py +101 -0
- sift/channels/v2/channels_pb2_grpc.pyi +64 -0
- sift/common/__init__.py +0 -0
- sift/common/type/__init__.py +0 -0
- sift/common/type/v1/__init__.py +0 -0
- sift/common/type/v1/channel_bit_field_element_pb2.py +34 -0
- sift/common/type/v1/channel_bit_field_element_pb2.pyi +33 -0
- sift/common/type/v1/channel_bit_field_element_pb2_grpc.py +4 -0
- sift/common/type/v1/channel_bit_field_element_pb2_grpc.pyi +17 -0
- sift/common/type/v1/channel_data_type_pb2.py +29 -0
- sift/common/type/v1/channel_data_type_pb2.pyi +50 -0
- sift/common/type/v1/channel_data_type_pb2_grpc.py +4 -0
- sift/common/type/v1/channel_data_type_pb2_grpc.pyi +17 -0
- sift/common/type/v1/channel_enum_type_pb2.py +32 -0
- sift/common/type/v1/channel_enum_type_pb2.pyi +29 -0
- sift/common/type/v1/channel_enum_type_pb2_grpc.py +4 -0
- sift/common/type/v1/channel_enum_type_pb2_grpc.pyi +17 -0
- sift/common/type/v1/organization_pb2.py +27 -0
- sift/common/type/v1/organization_pb2.pyi +29 -0
- sift/common/type/v1/organization_pb2_grpc.py +4 -0
- sift/common/type/v1/organization_pb2_grpc.pyi +17 -0
- sift/common/type/v1/resource_identifier_pb2.py +46 -0
- sift/common/type/v1/resource_identifier_pb2.pyi +145 -0
- sift/common/type/v1/resource_identifier_pb2_grpc.py +4 -0
- sift/common/type/v1/resource_identifier_pb2_grpc.pyi +17 -0
- sift/common/type/v1/user_pb2.py +33 -0
- sift/common/type/v1/user_pb2.pyi +36 -0
- sift/common/type/v1/user_pb2_grpc.py +4 -0
- sift/common/type/v1/user_pb2_grpc.pyi +17 -0
- sift/data/__init__.py +0 -0
- sift/data/v1/__init__.py +0 -0
- sift/data/v1/data_pb2.py +212 -0
- sift/data/v1/data_pb2.pyi +745 -0
- sift/data/v1/data_pb2_grpc.py +67 -0
- sift/data/v1/data_pb2_grpc.pyi +44 -0
- sift/ingest/__init__.py +0 -0
- sift/ingest/v1/__init__.py +0 -0
- sift/ingest/v1/ingest_pb2.py +35 -0
- sift/ingest/v1/ingest_pb2.pyi +118 -0
- sift/ingest/v1/ingest_pb2_grpc.py +66 -0
- sift/ingest/v1/ingest_pb2_grpc.pyi +41 -0
- sift/ingestion_configs/__init__.py +0 -0
- sift/ingestion_configs/v1/__init__.py +0 -0
- sift/ingestion_configs/v1/ingestion_configs_pb2.py +115 -0
- sift/ingestion_configs/v1/ingestion_configs_pb2.pyi +332 -0
- sift/ingestion_configs/v1/ingestion_configs_pb2_grpc.py +203 -0
- sift/ingestion_configs/v1/ingestion_configs_pb2_grpc.pyi +124 -0
- sift/notifications/__init__.py +0 -0
- sift/notifications/v1/__init__.py +0 -0
- sift/notifications/v1/notifications_pb2.py +64 -0
- sift/notifications/v1/notifications_pb2.pyi +225 -0
- sift/notifications/v1/notifications_pb2_grpc.py +101 -0
- sift/notifications/v1/notifications_pb2_grpc.pyi +64 -0
- sift/ping/__init__.py +0 -0
- sift/ping/v1/__init__.py +0 -0
- sift/ping/v1/ping_pb2.py +38 -0
- sift/ping/v1/ping_pb2.pyi +36 -0
- sift/ping/v1/ping_pb2_grpc.py +66 -0
- sift/ping/v1/ping_pb2_grpc.pyi +41 -0
- sift/remote_files/__init__.py +0 -0
- sift/remote_files/v1/__init__.py +0 -0
- sift/remote_files/v1/remote_files_pb2.py +174 -0
- sift/remote_files/v1/remote_files_pb2.pyi +472 -0
- sift/remote_files/v1/remote_files_pb2_grpc.py +271 -0
- sift/remote_files/v1/remote_files_pb2_grpc.pyi +164 -0
- sift/report_templates/__init__.py +0 -0
- sift/report_templates/v1/__init__.py +0 -0
- sift/report_templates/v1/report_templates_pb2.py +146 -0
- sift/report_templates/v1/report_templates_pb2.pyi +381 -0
- sift/report_templates/v1/report_templates_pb2_grpc.py +169 -0
- sift/report_templates/v1/report_templates_pb2_grpc.pyi +104 -0
- sift/reports/__init__.py +0 -0
- sift/reports/v1/__init__.py +0 -0
- sift/reports/v1/reports_pb2.py +193 -0
- sift/reports/v1/reports_pb2.pyi +562 -0
- sift/reports/v1/reports_pb2_grpc.py +205 -0
- sift/reports/v1/reports_pb2_grpc.pyi +136 -0
- sift/rule_evaluation/__init__.py +0 -0
- sift/rule_evaluation/v1/__init__.py +0 -0
- sift/rule_evaluation/v1/rule_evaluation_pb2.py +89 -0
- sift/rule_evaluation/v1/rule_evaluation_pb2.pyi +263 -0
- sift/rule_evaluation/v1/rule_evaluation_pb2_grpc.py +101 -0
- sift/rule_evaluation/v1/rule_evaluation_pb2_grpc.pyi +64 -0
- sift/rules/__init__.py +0 -0
- sift/rules/v1/__init__.py +0 -0
- sift/rules/v1/rules_pb2.py +420 -0
- sift/rules/v1/rules_pb2.pyi +1355 -0
- sift/rules/v1/rules_pb2_grpc.py +577 -0
- sift/rules/v1/rules_pb2_grpc.pyi +351 -0
- sift/runs/__init__.py +0 -0
- sift/runs/v2/__init__.py +0 -0
- sift/runs/v2/runs_pb2.py +150 -0
- sift/runs/v2/runs_pb2.pyi +413 -0
- sift/runs/v2/runs_pb2_grpc.py +271 -0
- sift/runs/v2/runs_pb2_grpc.pyi +164 -0
- sift/saved_searches/__init__.py +0 -0
- sift/saved_searches/v1/__init__.py +0 -0
- sift/saved_searches/v1/saved_searches_pb2.py +144 -0
- sift/saved_searches/v1/saved_searches_pb2.pyi +385 -0
- sift/saved_searches/v1/saved_searches_pb2_grpc.py +237 -0
- sift/saved_searches/v1/saved_searches_pb2_grpc.pyi +144 -0
- sift/tags/__init__.py +0 -0
- sift/tags/v1/__init__.py +0 -0
- sift/tags/v1/tags_pb2.py +49 -0
- sift/tags/v1/tags_pb2.pyi +71 -0
- sift/tags/v1/tags_pb2_grpc.py +4 -0
- sift/tags/v1/tags_pb2_grpc.pyi +17 -0
- sift/users/__init__.py +0 -0
- sift/users/v2/__init__.py +0 -0
- sift/users/v2/users_pb2.py +61 -0
- sift/users/v2/users_pb2.pyi +142 -0
- sift/users/v2/users_pb2_grpc.py +135 -0
- sift/users/v2/users_pb2_grpc.pyi +84 -0
- sift/views/__init__.py +0 -0
- sift/views/v1/__init__.py +0 -0
- sift/views/v1/views_pb2.py +130 -0
- sift/views/v1/views_pb2.pyi +466 -0
- sift/views/v1/views_pb2_grpc.py +305 -0
- sift/views/v1/views_pb2_grpc.pyi +184 -0
- sift_grafana/py.typed +0 -0
- sift_grafana/sift_query_model.py +64 -0
- sift_py/__init__.py +923 -0
- sift_py/_internal/__init__.py +5 -0
- sift_py/_internal/cel.py +18 -0
- sift_py/_internal/channel.py +42 -0
- sift_py/_internal/convert/__init__.py +3 -0
- sift_py/_internal/convert/json.py +24 -0
- sift_py/_internal/convert/protobuf.py +34 -0
- sift_py/_internal/convert/timestamp.py +9 -0
- sift_py/_internal/test_util/__init__.py +0 -0
- sift_py/_internal/test_util/channel.py +136 -0
- sift_py/_internal/test_util/fn.py +14 -0
- sift_py/_internal/test_util/server_interceptor.py +62 -0
- sift_py/_internal/time.py +48 -0
- sift_py/_internal/user.py +39 -0
- sift_py/data/__init__.py +171 -0
- sift_py/data/_channel.py +38 -0
- sift_py/data/_deserialize.py +208 -0
- sift_py/data/_deserialize_test.py +134 -0
- sift_py/data/_service_test.py +276 -0
- sift_py/data/_validate.py +10 -0
- sift_py/data/error.py +5 -0
- sift_py/data/query.py +299 -0
- sift_py/data/service.py +497 -0
- sift_py/data_import/__init__.py +130 -0
- sift_py/data_import/_config.py +167 -0
- sift_py/data_import/_config_test.py +166 -0
- sift_py/data_import/_csv_test.py +395 -0
- sift_py/data_import/_status_test.py +176 -0
- sift_py/data_import/_tdms_test.py +238 -0
- sift_py/data_import/ch10.py +157 -0
- sift_py/data_import/config.py +19 -0
- sift_py/data_import/csv.py +259 -0
- sift_py/data_import/status.py +113 -0
- sift_py/data_import/tdms.py +206 -0
- sift_py/data_import/tempfile.py +30 -0
- sift_py/data_import/time_format.py +39 -0
- sift_py/error.py +11 -0
- sift_py/file_attachment/__init__.py +88 -0
- sift_py/file_attachment/_internal/__init__.py +0 -0
- sift_py/file_attachment/_internal/download.py +13 -0
- sift_py/file_attachment/_internal/upload.py +100 -0
- sift_py/file_attachment/_service_test.py +161 -0
- sift_py/file_attachment/entity.py +30 -0
- sift_py/file_attachment/metadata.py +107 -0
- sift_py/file_attachment/service.py +142 -0
- sift_py/grpc/__init__.py +15 -0
- sift_py/grpc/_async_interceptors/__init__.py +0 -0
- sift_py/grpc/_async_interceptors/base.py +72 -0
- sift_py/grpc/_async_interceptors/metadata.py +36 -0
- sift_py/grpc/_interceptors/__init__.py +0 -0
- sift_py/grpc/_interceptors/base.py +61 -0
- sift_py/grpc/_interceptors/context.py +25 -0
- sift_py/grpc/_interceptors/metadata.py +33 -0
- sift_py/grpc/_retry.py +70 -0
- sift_py/grpc/keepalive.py +34 -0
- sift_py/grpc/transport.py +250 -0
- sift_py/grpc/transport_test.py +170 -0
- sift_py/ingestion/__init__.py +6 -0
- sift_py/ingestion/_internal/__init__.py +6 -0
- sift_py/ingestion/_internal/channel.py +12 -0
- sift_py/ingestion/_internal/error.py +10 -0
- sift_py/ingestion/_internal/ingest.py +350 -0
- sift_py/ingestion/_internal/ingest_test.py +357 -0
- sift_py/ingestion/_internal/ingestion_config.py +130 -0
- sift_py/ingestion/_internal/run.py +46 -0
- sift_py/ingestion/_service_test.py +478 -0
- sift_py/ingestion/buffer.py +189 -0
- sift_py/ingestion/channel.py +422 -0
- sift_py/ingestion/config/__init__.py +3 -0
- sift_py/ingestion/config/telemetry.py +281 -0
- sift_py/ingestion/config/telemetry_test.py +405 -0
- sift_py/ingestion/config/yaml/__init__.py +0 -0
- sift_py/ingestion/config/yaml/error.py +44 -0
- sift_py/ingestion/config/yaml/load.py +126 -0
- sift_py/ingestion/config/yaml/spec.py +58 -0
- sift_py/ingestion/config/yaml/test_load.py +25 -0
- sift_py/ingestion/flow.py +73 -0
- sift_py/ingestion/manager.py +99 -0
- sift_py/ingestion/rule/__init__.py +4 -0
- sift_py/ingestion/rule/config.py +11 -0
- sift_py/ingestion/service.py +237 -0
- sift_py/py.typed +0 -0
- sift_py/report_templates/__init__.py +0 -0
- sift_py/report_templates/_config_test.py +34 -0
- sift_py/report_templates/_service_test.py +94 -0
- sift_py/report_templates/config.py +36 -0
- sift_py/report_templates/service.py +171 -0
- sift_py/rest.py +29 -0
- sift_py/rule/__init__.py +0 -0
- sift_py/rule/_config_test.py +109 -0
- sift_py/rule/_service_test.py +168 -0
- sift_py/rule/config.py +229 -0
- sift_py/rule/service.py +484 -0
- sift_py/yaml/__init__.py +0 -0
- sift_py/yaml/_channel_test.py +169 -0
- sift_py/yaml/_rule_test.py +207 -0
- sift_py/yaml/channel.py +224 -0
- sift_py/yaml/report_templates.py +73 -0
- sift_py/yaml/rule.py +321 -0
- sift_py/yaml/utils.py +15 -0
- sift_stack_py-0.3.2.dist-info/LICENSE +7 -0
- sift_stack_py-0.3.2.dist-info/METADATA +109 -0
- sift_stack_py-0.3.2.dist-info/RECORD +291 -0
- sift_stack_py-0.3.2.dist-info/WHEEL +5 -0
- sift_stack_py-0.3.2.dist-info/top_level.txt +5 -0
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Any, Dict, List, Optional, cast
|
|
5
|
+
|
|
6
|
+
from typing_extensions import Self
|
|
7
|
+
|
|
8
|
+
from sift_py._internal.channel import channel_fqn
|
|
9
|
+
from sift_py.ingestion.channel import (
|
|
10
|
+
ChannelBitFieldElement,
|
|
11
|
+
ChannelConfig,
|
|
12
|
+
ChannelDataType,
|
|
13
|
+
ChannelEnumType,
|
|
14
|
+
)
|
|
15
|
+
from sift_py.ingestion.config.yaml.load import (
|
|
16
|
+
load_named_expression_modules,
|
|
17
|
+
read_and_validate,
|
|
18
|
+
)
|
|
19
|
+
from sift_py.ingestion.config.yaml.spec import TelemetryConfigYamlSpec
|
|
20
|
+
from sift_py.ingestion.flow import FlowConfig
|
|
21
|
+
from sift_py.rule.config import (
|
|
22
|
+
ExpressionChannelReference,
|
|
23
|
+
ExpressionChannelReferenceChannelConfig,
|
|
24
|
+
RuleAction,
|
|
25
|
+
RuleActionAnnotationKind,
|
|
26
|
+
RuleActionCreateDataReviewAnnotation,
|
|
27
|
+
RuleActionCreatePhaseAnnotation,
|
|
28
|
+
RuleConfig,
|
|
29
|
+
)
|
|
30
|
+
from sift_py.yaml.rule import RuleYamlSpec, load_rule_modules
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class TelemetryConfig:
|
|
34
|
+
"""
|
|
35
|
+
Configurations necessary to start ingestion.
|
|
36
|
+
- `asset_name`: The name of the asset that you wish to telemeter data for.
|
|
37
|
+
- `ingestion_client_key`: An arbitrary string chosen by the user to uniquely identify this ingestion configuration.
|
|
38
|
+
- `flows`: A single flow can specify a single channel value or a set of channel values that are ingested together.
|
|
39
|
+
- `organization_id`: ID of your organization in Sift. This field is only required if your user belongs to multiple organizations.
|
|
40
|
+
- `rules`: Rules to evaluate during ingestion.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
asset_name: str
|
|
44
|
+
ingestion_client_key: str
|
|
45
|
+
organization_id: Optional[str]
|
|
46
|
+
flows: List[FlowConfig]
|
|
47
|
+
rules: List[RuleConfig]
|
|
48
|
+
|
|
49
|
+
def __init__(
|
|
50
|
+
self,
|
|
51
|
+
asset_name: str,
|
|
52
|
+
ingestion_client_key: str,
|
|
53
|
+
organization_id: Optional[str] = None,
|
|
54
|
+
flows: List[FlowConfig] = [],
|
|
55
|
+
rules: List[RuleConfig] = [],
|
|
56
|
+
):
|
|
57
|
+
"""
|
|
58
|
+
Will raise a `TelemetryConfigValidationError` under the following conditions:
|
|
59
|
+
- Multiple flows with the same name
|
|
60
|
+
- Multiple rules with the same name
|
|
61
|
+
- Identical channels in the same flow
|
|
62
|
+
"""
|
|
63
|
+
self.__class__.validate_flows(flows)
|
|
64
|
+
self.__class__.validate_rules(rules)
|
|
65
|
+
|
|
66
|
+
self.asset_name = asset_name
|
|
67
|
+
self.ingestion_client_key = ingestion_client_key
|
|
68
|
+
self.organization_id = organization_id
|
|
69
|
+
self.flows = flows
|
|
70
|
+
self.rules = rules
|
|
71
|
+
|
|
72
|
+
@staticmethod
|
|
73
|
+
def validate_rules(rules: List[RuleConfig]):
|
|
74
|
+
"""
|
|
75
|
+
Ensure that there are no rules with identical names
|
|
76
|
+
"""
|
|
77
|
+
seen_rule_names = set()
|
|
78
|
+
|
|
79
|
+
for rule in rules:
|
|
80
|
+
if rule.name in seen_rule_names:
|
|
81
|
+
raise TelemetryConfigValidationError(
|
|
82
|
+
f"Can't have two rules with identical names, '{rule.name}'."
|
|
83
|
+
)
|
|
84
|
+
seen_rule_names.add(rule.name)
|
|
85
|
+
|
|
86
|
+
@staticmethod
|
|
87
|
+
def validate_flows(flows: List[FlowConfig]):
|
|
88
|
+
"""
|
|
89
|
+
Ensures no duplicate channels and flows with the same name, otherwise raises a `TelemetryConfigValidationError` exception.
|
|
90
|
+
"""
|
|
91
|
+
flow_names = set()
|
|
92
|
+
|
|
93
|
+
for flow in flows:
|
|
94
|
+
seen_channels = set()
|
|
95
|
+
|
|
96
|
+
if flow.name in flow_names:
|
|
97
|
+
raise TelemetryConfigValidationError(
|
|
98
|
+
f"Can't have two flows with the same name, '{flow.name}'."
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
flow_names.add(flow.name)
|
|
102
|
+
|
|
103
|
+
for channel in flow.channels:
|
|
104
|
+
fqn = channel.fqn()
|
|
105
|
+
|
|
106
|
+
if fqn in seen_channels:
|
|
107
|
+
raise TelemetryConfigValidationError(
|
|
108
|
+
f"Can't have two identical channels, '{fqn}', in flow '{flow.name}'."
|
|
109
|
+
)
|
|
110
|
+
else:
|
|
111
|
+
seen_channels.add(fqn)
|
|
112
|
+
|
|
113
|
+
@classmethod
|
|
114
|
+
def try_from_yaml(
|
|
115
|
+
cls,
|
|
116
|
+
path: Path,
|
|
117
|
+
named_expression_modules: Optional[List[Path]] = None,
|
|
118
|
+
named_rule_modules: Optional[List[Path]] = None,
|
|
119
|
+
) -> Self:
|
|
120
|
+
"""
|
|
121
|
+
Initializes a telemetry config from a YAML file found at the provided `path` as well as optional
|
|
122
|
+
paths to named expression modules if named expressions are leveraged.
|
|
123
|
+
"""
|
|
124
|
+
|
|
125
|
+
config_as_yaml = read_and_validate(path)
|
|
126
|
+
|
|
127
|
+
named_expressions = {}
|
|
128
|
+
rule_modules = []
|
|
129
|
+
if named_expression_modules is not None:
|
|
130
|
+
named_expressions = load_named_expression_modules(named_expression_modules)
|
|
131
|
+
if named_rule_modules is not None:
|
|
132
|
+
rule_modules = load_rule_modules(named_rule_modules)
|
|
133
|
+
|
|
134
|
+
return cls._from_yaml(config_as_yaml, named_expressions, rule_modules)
|
|
135
|
+
|
|
136
|
+
@classmethod
|
|
137
|
+
def _from_yaml(
|
|
138
|
+
cls,
|
|
139
|
+
config_as_yaml: TelemetryConfigYamlSpec,
|
|
140
|
+
named_expressions: Dict[str, str] = {},
|
|
141
|
+
rule_modules: List[RuleYamlSpec] = [],
|
|
142
|
+
) -> Self:
|
|
143
|
+
rules = []
|
|
144
|
+
flows = []
|
|
145
|
+
|
|
146
|
+
for flow in config_as_yaml.get("flows", []):
|
|
147
|
+
channels = []
|
|
148
|
+
|
|
149
|
+
for channel in flow["channels"]:
|
|
150
|
+
data_type = cast(ChannelDataType, ChannelDataType.from_str(channel["data_type"]))
|
|
151
|
+
|
|
152
|
+
bit_field_elements = []
|
|
153
|
+
for bit_field_element in channel.get("bit_field_elements", []):
|
|
154
|
+
bit_field_elements.append(
|
|
155
|
+
ChannelBitFieldElement(
|
|
156
|
+
name=bit_field_element["name"],
|
|
157
|
+
index=bit_field_element["index"],
|
|
158
|
+
bit_count=bit_field_element["bit_count"],
|
|
159
|
+
)
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
enum_types = []
|
|
163
|
+
for enum_type in channel.get("enum_types", []):
|
|
164
|
+
enum_types.append(
|
|
165
|
+
ChannelEnumType(
|
|
166
|
+
name=enum_type["name"],
|
|
167
|
+
key=enum_type["key"],
|
|
168
|
+
)
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
channels.append(
|
|
172
|
+
ChannelConfig(
|
|
173
|
+
name=channel["name"],
|
|
174
|
+
data_type=data_type,
|
|
175
|
+
description=channel.get("description"),
|
|
176
|
+
unit=channel.get("unit"),
|
|
177
|
+
component=channel.get("component"),
|
|
178
|
+
bit_field_elements=bit_field_elements,
|
|
179
|
+
enum_types=enum_types,
|
|
180
|
+
)
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
flows.append(
|
|
184
|
+
FlowConfig(
|
|
185
|
+
name=flow["name"],
|
|
186
|
+
channels=channels,
|
|
187
|
+
)
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
yaml_rules = config_as_yaml.get("rules", []) + rule_modules
|
|
191
|
+
|
|
192
|
+
for rule in yaml_rules:
|
|
193
|
+
action: Optional[RuleAction] = None
|
|
194
|
+
description: str = ""
|
|
195
|
+
annotation_type = RuleActionAnnotationKind.from_str(rule["type"])
|
|
196
|
+
tags = rule.get("tags")
|
|
197
|
+
description = rule.get("description", "")
|
|
198
|
+
|
|
199
|
+
action = RuleActionCreatePhaseAnnotation(tags)
|
|
200
|
+
if annotation_type == RuleActionAnnotationKind.REVIEW:
|
|
201
|
+
action = RuleActionCreateDataReviewAnnotation(
|
|
202
|
+
assignee=rule.get("assignee"),
|
|
203
|
+
tags=tags,
|
|
204
|
+
)
|
|
205
|
+
|
|
206
|
+
channel_references: List[
|
|
207
|
+
ExpressionChannelReference | ExpressionChannelReferenceChannelConfig
|
|
208
|
+
] = []
|
|
209
|
+
|
|
210
|
+
for channel_reference in rule.get("channel_references", []):
|
|
211
|
+
for ref, val in channel_reference.items():
|
|
212
|
+
name = val["name"]
|
|
213
|
+
component = val.get("component")
|
|
214
|
+
|
|
215
|
+
channel_references.append(
|
|
216
|
+
{
|
|
217
|
+
"channel_reference": ref,
|
|
218
|
+
"channel_identifier": channel_fqn(name, component),
|
|
219
|
+
}
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
expression = rule.get("expression", "")
|
|
223
|
+
rule_client_key = rule.get("rule_client_key", "")
|
|
224
|
+
if isinstance(expression, str):
|
|
225
|
+
rules.append(
|
|
226
|
+
RuleConfig(
|
|
227
|
+
name=rule["name"],
|
|
228
|
+
description=description,
|
|
229
|
+
expression=expression,
|
|
230
|
+
action=action,
|
|
231
|
+
rule_client_key=rule_client_key,
|
|
232
|
+
channel_references=channel_references,
|
|
233
|
+
)
|
|
234
|
+
)
|
|
235
|
+
else:
|
|
236
|
+
expression_name = cast(str, expression.get("name"))
|
|
237
|
+
|
|
238
|
+
expr = named_expressions.get(expression_name)
|
|
239
|
+
|
|
240
|
+
if expr is None:
|
|
241
|
+
raise TelemetryConfigValidationError(
|
|
242
|
+
f"Named expression '{expression_name}' could not be found. Make sure it was loaded in."
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
sub_expressions = rule.get("sub_expressions", [])
|
|
246
|
+
|
|
247
|
+
sub_exprs: Dict[str, Any] = {}
|
|
248
|
+
for sub_expression in sub_expressions:
|
|
249
|
+
for iden, value in sub_expression.items():
|
|
250
|
+
sub_exprs[iden] = value
|
|
251
|
+
|
|
252
|
+
rules.append(
|
|
253
|
+
RuleConfig(
|
|
254
|
+
name=rule["name"],
|
|
255
|
+
description=description,
|
|
256
|
+
expression=expr,
|
|
257
|
+
action=action,
|
|
258
|
+
rule_client_key=rule_client_key,
|
|
259
|
+
channel_references=channel_references,
|
|
260
|
+
sub_expressions=sub_exprs,
|
|
261
|
+
)
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
return cls(
|
|
265
|
+
asset_name=config_as_yaml["asset_name"],
|
|
266
|
+
ingestion_client_key=config_as_yaml["ingestion_client_key"],
|
|
267
|
+
organization_id=config_as_yaml.get("organization_id"),
|
|
268
|
+
rules=rules,
|
|
269
|
+
flows=flows,
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
class TelemetryConfigValidationError(Exception):
|
|
274
|
+
"""
|
|
275
|
+
When the telemetry config has invalid properties
|
|
276
|
+
"""
|
|
277
|
+
|
|
278
|
+
message: str
|
|
279
|
+
|
|
280
|
+
def __init__(self, message: str):
|
|
281
|
+
super().__init__(message)
|
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import Any, Dict, cast
|
|
3
|
+
|
|
4
|
+
import pytest
|
|
5
|
+
import yaml
|
|
6
|
+
from pytest_mock import MockerFixture, MockFixture
|
|
7
|
+
|
|
8
|
+
import sift_py.ingestion.config.telemetry
|
|
9
|
+
import sift_py.ingestion.config.yaml.load
|
|
10
|
+
from sift_py._internal.test_util.fn import _mock_path as _mock_path_imp
|
|
11
|
+
from sift_py.ingestion.channel import ChannelConfig, ChannelDataType
|
|
12
|
+
from sift_py.ingestion.config.telemetry import TelemetryConfig, TelemetryConfigValidationError
|
|
13
|
+
from sift_py.ingestion.config.yaml.load import (
|
|
14
|
+
_validate_yaml,
|
|
15
|
+
load_named_expression_modules,
|
|
16
|
+
read_and_validate,
|
|
17
|
+
)
|
|
18
|
+
from sift_py.ingestion.flow import FlowConfig
|
|
19
|
+
from sift_py.ingestion.rule.config import (
|
|
20
|
+
RuleActionCreateDataReviewAnnotation,
|
|
21
|
+
RuleActionCreatePhaseAnnotation,
|
|
22
|
+
RuleActionKind,
|
|
23
|
+
RuleConfig,
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
_mock_path = _mock_path_imp(sift_py.ingestion.config.telemetry)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_telemetry_config_load_from_yaml(mocker: MockFixture):
|
|
30
|
+
raw_yaml_config = cast(Dict[Any, Any], yaml.safe_load(TEST_YAML_CONFIG_STR))
|
|
31
|
+
yaml_config = _validate_yaml(raw_yaml_config)
|
|
32
|
+
|
|
33
|
+
mock_read_and_validate = mocker.patch(_mock_path(read_and_validate))
|
|
34
|
+
mock_read_and_validate.return_value = yaml_config
|
|
35
|
+
|
|
36
|
+
mock_load_named_expression_modules = mocker.patch(_mock_path(load_named_expression_modules))
|
|
37
|
+
mock_load_named_expression_modules.return_value = {
|
|
38
|
+
"log_substring_contains": "contains($1, $substr)",
|
|
39
|
+
"kinetic_energy_gt": "0.5 * $mass * $1 * $1 > $threshold",
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
dummy_yaml_path = Path()
|
|
43
|
+
dummy_named_expr_mod_path = Path()
|
|
44
|
+
|
|
45
|
+
telemetry_config = TelemetryConfig.try_from_yaml(dummy_yaml_path, [dummy_named_expr_mod_path])
|
|
46
|
+
|
|
47
|
+
assert telemetry_config.asset_name == "LunarVehicle426"
|
|
48
|
+
assert telemetry_config.ingestion_client_key == "lunar_vehicle_426"
|
|
49
|
+
assert len(telemetry_config.flows) == 3
|
|
50
|
+
|
|
51
|
+
flow_configs = telemetry_config.flows
|
|
52
|
+
assert flow_configs[0].name == "readings"
|
|
53
|
+
assert flow_configs[1].name == "partial_readings"
|
|
54
|
+
assert flow_configs[2].name == "logs"
|
|
55
|
+
|
|
56
|
+
readings_flow, partial_readings_flow, logs_flow = flow_configs
|
|
57
|
+
assert len(readings_flow.channels) == 4
|
|
58
|
+
assert len(partial_readings_flow.channels) == 2
|
|
59
|
+
assert len(logs_flow.channels) == 1
|
|
60
|
+
|
|
61
|
+
log_channel = logs_flow.channels[0]
|
|
62
|
+
assert log_channel.name == "log"
|
|
63
|
+
assert log_channel.description == "asset logs"
|
|
64
|
+
assert log_channel.data_type == ChannelDataType.STRING
|
|
65
|
+
|
|
66
|
+
velocity_channel, voltage_channel, vehicle_state_channel, gpio_channel = readings_flow.channels
|
|
67
|
+
assert velocity_channel.name == "velocity"
|
|
68
|
+
assert velocity_channel.data_type == ChannelDataType.DOUBLE
|
|
69
|
+
assert velocity_channel.unit == "Miles Per Hour"
|
|
70
|
+
assert velocity_channel.component == "mainmotor"
|
|
71
|
+
assert velocity_channel.description == "speed"
|
|
72
|
+
|
|
73
|
+
assert voltage_channel.name == "voltage"
|
|
74
|
+
assert voltage_channel.data_type == ChannelDataType.INT_32
|
|
75
|
+
assert voltage_channel.unit == "Volts"
|
|
76
|
+
assert voltage_channel.description == "voltage at the source"
|
|
77
|
+
|
|
78
|
+
assert vehicle_state_channel.name == "vehicle_state"
|
|
79
|
+
assert vehicle_state_channel.data_type == ChannelDataType.ENUM
|
|
80
|
+
assert vehicle_state_channel.unit == "vehicle state"
|
|
81
|
+
assert vehicle_state_channel.description == "vehicle state"
|
|
82
|
+
assert len(vehicle_state_channel.enum_types) == 3
|
|
83
|
+
assert vehicle_state_channel.enum_types[0].name == "Accelerating"
|
|
84
|
+
assert vehicle_state_channel.enum_types[0].key == 0
|
|
85
|
+
assert vehicle_state_channel.enum_types[1].name == "Decelerating"
|
|
86
|
+
assert vehicle_state_channel.enum_types[1].key == 1
|
|
87
|
+
assert vehicle_state_channel.enum_types[2].name == "Stopped"
|
|
88
|
+
assert vehicle_state_channel.enum_types[2].key == 2
|
|
89
|
+
|
|
90
|
+
assert gpio_channel.name == "gpio"
|
|
91
|
+
assert gpio_channel.data_type == ChannelDataType.BIT_FIELD
|
|
92
|
+
assert gpio_channel.unit is None
|
|
93
|
+
assert gpio_channel.description == "on/off values for pins on gpio"
|
|
94
|
+
assert len(gpio_channel.bit_field_elements) == 4
|
|
95
|
+
assert gpio_channel.bit_field_elements[0].name == "12v"
|
|
96
|
+
assert gpio_channel.bit_field_elements[0].index == 0
|
|
97
|
+
assert gpio_channel.bit_field_elements[0].bit_count == 1
|
|
98
|
+
assert gpio_channel.bit_field_elements[1].name == "charge"
|
|
99
|
+
assert gpio_channel.bit_field_elements[1].index == 1
|
|
100
|
+
assert gpio_channel.bit_field_elements[1].bit_count == 2
|
|
101
|
+
assert gpio_channel.bit_field_elements[2].name == "led"
|
|
102
|
+
assert gpio_channel.bit_field_elements[2].index == 3
|
|
103
|
+
assert gpio_channel.bit_field_elements[2].bit_count == 4
|
|
104
|
+
assert gpio_channel.bit_field_elements[3].name == "heater"
|
|
105
|
+
assert gpio_channel.bit_field_elements[3].index == 7
|
|
106
|
+
assert gpio_channel.bit_field_elements[3].bit_count == 1
|
|
107
|
+
|
|
108
|
+
assert len(telemetry_config.rules) == 4
|
|
109
|
+
|
|
110
|
+
(
|
|
111
|
+
overheating_rule,
|
|
112
|
+
speeding_rule,
|
|
113
|
+
failures_rule,
|
|
114
|
+
kinetic_energy_rule,
|
|
115
|
+
) = telemetry_config.rules
|
|
116
|
+
|
|
117
|
+
assert overheating_rule.name == "overheating"
|
|
118
|
+
assert overheating_rule.description == "Checks for vehicle overheating"
|
|
119
|
+
assert overheating_rule.expression == '$1 == "Accelerating" && $2 > 80'
|
|
120
|
+
assert overheating_rule.action.kind() == RuleActionKind.ANNOTATION # type: ignore
|
|
121
|
+
assert isinstance(overheating_rule.action, RuleActionCreateDataReviewAnnotation)
|
|
122
|
+
|
|
123
|
+
assert speeding_rule.name == "speeding"
|
|
124
|
+
assert speeding_rule.description == "Checks high vehicle speed"
|
|
125
|
+
assert speeding_rule.expression == "$1 > 20"
|
|
126
|
+
assert overheating_rule.action.kind() == RuleActionKind.ANNOTATION
|
|
127
|
+
assert isinstance(speeding_rule.action, RuleActionCreatePhaseAnnotation)
|
|
128
|
+
|
|
129
|
+
assert failures_rule.name == "failures"
|
|
130
|
+
assert failures_rule.description == "Checks for failure logs"
|
|
131
|
+
assert failures_rule.expression == 'contains($1, "ERROR")'
|
|
132
|
+
assert overheating_rule.action.kind() == RuleActionKind.ANNOTATION
|
|
133
|
+
assert isinstance(failures_rule.action, RuleActionCreateDataReviewAnnotation)
|
|
134
|
+
|
|
135
|
+
assert kinetic_energy_rule.name == "kinetic_energy"
|
|
136
|
+
assert kinetic_energy_rule.description == "Tracks high energy output while in motion"
|
|
137
|
+
assert kinetic_energy_rule.expression == "0.5 * 10 * $1 * $1 > 470"
|
|
138
|
+
assert overheating_rule.action.kind() == RuleActionKind.ANNOTATION
|
|
139
|
+
assert isinstance(kinetic_energy_rule.action, RuleActionCreateDataReviewAnnotation)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def test_telemetry_config_err_if_duplicate_channels_in_flow(mocker: MockerFixture):
|
|
143
|
+
"""
|
|
144
|
+
Raise an error if there are duplicate channels in a flow.
|
|
145
|
+
"""
|
|
146
|
+
raw_yaml_config = cast(
|
|
147
|
+
Dict[Any, Any], yaml.safe_load(DUPLICATE_CHANNEL_IN_FLOW_TELEMETRY_CONFIG)
|
|
148
|
+
)
|
|
149
|
+
yaml_config = _validate_yaml(raw_yaml_config)
|
|
150
|
+
|
|
151
|
+
mock_read_and_validate = mocker.patch(_mock_path(read_and_validate))
|
|
152
|
+
mock_read_and_validate.return_value = yaml_config
|
|
153
|
+
|
|
154
|
+
mock_load_named_expression_modules = mocker.patch(_mock_path(load_named_expression_modules))
|
|
155
|
+
mock_load_named_expression_modules.return_value = {
|
|
156
|
+
"log_substring_contains": "contains($1, $substr)",
|
|
157
|
+
"kinetic_energy_gt": "0.5 * $mass * $1 * $1 > $threshold",
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
dummy_yaml_path = Path()
|
|
161
|
+
dummy_named_expr_mod_path = Path()
|
|
162
|
+
|
|
163
|
+
with pytest.raises(TelemetryConfigValidationError, match="Can't have two identical channels"):
|
|
164
|
+
_ = TelemetryConfig.try_from_yaml(dummy_yaml_path, [dummy_named_expr_mod_path])
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def test_telemetry_config_named_expression_interpolation():
|
|
168
|
+
pass
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def test_telemetry_config_validations_duplicate_rules():
|
|
172
|
+
channel = ChannelConfig(
|
|
173
|
+
name="my_channel",
|
|
174
|
+
data_type=ChannelDataType.DOUBLE,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
rule_on_my_channel_a = RuleConfig(
|
|
178
|
+
name="rule_a",
|
|
179
|
+
description="",
|
|
180
|
+
expression="$1 > 10",
|
|
181
|
+
channel_references=[
|
|
182
|
+
{"channel_reference": "$1", "channel_identifier": channel.fqn()},
|
|
183
|
+
],
|
|
184
|
+
action=RuleActionCreateDataReviewAnnotation(
|
|
185
|
+
assignee="bob@example.com",
|
|
186
|
+
tags=["barometer"],
|
|
187
|
+
),
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
another_rule_on_my_channel_a = RuleConfig(
|
|
191
|
+
name="rule_a", # same name
|
|
192
|
+
description="",
|
|
193
|
+
expression="$1 > 11",
|
|
194
|
+
channel_references=[
|
|
195
|
+
{"channel_reference": "$1", "channel_identifier": channel.fqn()},
|
|
196
|
+
],
|
|
197
|
+
action=RuleActionCreateDataReviewAnnotation(
|
|
198
|
+
assignee="bob@example.com",
|
|
199
|
+
tags=["barometer"],
|
|
200
|
+
),
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
with pytest.raises(TelemetryConfigValidationError, match="Can't have two rules"):
|
|
204
|
+
TelemetryConfig(
|
|
205
|
+
asset_name="my_asset",
|
|
206
|
+
ingestion_client_key="my_asset_key",
|
|
207
|
+
organization_id="my_organization_id",
|
|
208
|
+
flows=[
|
|
209
|
+
FlowConfig(
|
|
210
|
+
name="my_flow",
|
|
211
|
+
channels=[channel],
|
|
212
|
+
)
|
|
213
|
+
],
|
|
214
|
+
rules=[rule_on_my_channel_a, another_rule_on_my_channel_a],
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def test_telemetry_config_validations_duplicate_channels():
|
|
219
|
+
channel = ChannelConfig(
|
|
220
|
+
name="my_channel",
|
|
221
|
+
data_type=ChannelDataType.DOUBLE,
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
with pytest.raises(TelemetryConfigValidationError, match="Can't have two identical channels"):
|
|
225
|
+
TelemetryConfig(
|
|
226
|
+
asset_name="my_asset",
|
|
227
|
+
ingestion_client_key="my_asset_key",
|
|
228
|
+
organization_id="my_organization_id",
|
|
229
|
+
flows=[
|
|
230
|
+
FlowConfig(
|
|
231
|
+
name="my_flow",
|
|
232
|
+
channels=[
|
|
233
|
+
channel,
|
|
234
|
+
channel,
|
|
235
|
+
],
|
|
236
|
+
)
|
|
237
|
+
],
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def test_telemetry_config_validations_flows_with_same_name():
|
|
242
|
+
channel = ChannelConfig(
|
|
243
|
+
name="my_channel",
|
|
244
|
+
data_type=ChannelDataType.DOUBLE,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
channel_b = ChannelConfig(
|
|
248
|
+
name="my_other_channel",
|
|
249
|
+
data_type=ChannelDataType.DOUBLE,
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
with pytest.raises(TelemetryConfigValidationError, match="Can't have two flows"):
|
|
253
|
+
TelemetryConfig(
|
|
254
|
+
asset_name="my_asset",
|
|
255
|
+
ingestion_client_key="my_asset_key",
|
|
256
|
+
organization_id="my_organization_id",
|
|
257
|
+
flows=[
|
|
258
|
+
FlowConfig(
|
|
259
|
+
name="my_flow",
|
|
260
|
+
channels=[channel],
|
|
261
|
+
),
|
|
262
|
+
FlowConfig(
|
|
263
|
+
name="my_flow",
|
|
264
|
+
channels=[channel_b],
|
|
265
|
+
),
|
|
266
|
+
],
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
TEST_YAML_CONFIG_STR = """
|
|
271
|
+
asset_name: LunarVehicle426
|
|
272
|
+
ingestion_client_key: lunar_vehicle_426
|
|
273
|
+
|
|
274
|
+
channels:
|
|
275
|
+
log_channel: &log_channel
|
|
276
|
+
name: log
|
|
277
|
+
data_type: string
|
|
278
|
+
description: asset logs
|
|
279
|
+
|
|
280
|
+
velocity_channel: &velocity_channel
|
|
281
|
+
name: velocity
|
|
282
|
+
data_type: double
|
|
283
|
+
description: speed
|
|
284
|
+
unit: Miles Per Hour
|
|
285
|
+
component: mainmotor
|
|
286
|
+
|
|
287
|
+
voltage_channel: &voltage_channel
|
|
288
|
+
name: voltage
|
|
289
|
+
data_type: int32
|
|
290
|
+
description: voltage at the source
|
|
291
|
+
unit: Volts
|
|
292
|
+
|
|
293
|
+
vehicle_state_channel: &vehicle_state_channel
|
|
294
|
+
name: vehicle_state
|
|
295
|
+
data_type: enum
|
|
296
|
+
description: vehicle state
|
|
297
|
+
unit: vehicle state
|
|
298
|
+
enum_types:
|
|
299
|
+
- name: Accelerating
|
|
300
|
+
key: 0
|
|
301
|
+
- name: Decelerating
|
|
302
|
+
key: 1
|
|
303
|
+
- name: Stopped
|
|
304
|
+
key: 2
|
|
305
|
+
|
|
306
|
+
gpio_channel: &gpio_channel
|
|
307
|
+
name: gpio
|
|
308
|
+
data_type: bit_field
|
|
309
|
+
description: on/off values for pins on gpio
|
|
310
|
+
bit_field_elements:
|
|
311
|
+
- name: 12v
|
|
312
|
+
index: 0
|
|
313
|
+
bit_count: 1
|
|
314
|
+
- name: charge
|
|
315
|
+
index: 1
|
|
316
|
+
bit_count: 2
|
|
317
|
+
- name: led
|
|
318
|
+
index: 3
|
|
319
|
+
bit_count: 4
|
|
320
|
+
- name: heater
|
|
321
|
+
index: 7
|
|
322
|
+
bit_count: 1
|
|
323
|
+
|
|
324
|
+
rules:
|
|
325
|
+
- name: overheating
|
|
326
|
+
description: Checks for vehicle overheating
|
|
327
|
+
expression: $1 == "Accelerating" && $2 > 80
|
|
328
|
+
channel_references:
|
|
329
|
+
- $1: *vehicle_state_channel
|
|
330
|
+
- $2: *voltage_channel
|
|
331
|
+
type: review
|
|
332
|
+
|
|
333
|
+
- name: speeding
|
|
334
|
+
description: Checks high vehicle speed
|
|
335
|
+
type: phase
|
|
336
|
+
expression: $1 > 20
|
|
337
|
+
channel_references:
|
|
338
|
+
- $1: *velocity_channel
|
|
339
|
+
|
|
340
|
+
- name: failures
|
|
341
|
+
description: Checks for failure logs
|
|
342
|
+
type: review
|
|
343
|
+
assignee: homer@example.com
|
|
344
|
+
expression:
|
|
345
|
+
name: log_substring_contains
|
|
346
|
+
channel_references:
|
|
347
|
+
- $1: *log_channel
|
|
348
|
+
sub_expressions:
|
|
349
|
+
- $substr: ERROR
|
|
350
|
+
tags:
|
|
351
|
+
- foo
|
|
352
|
+
- bar
|
|
353
|
+
- baz
|
|
354
|
+
|
|
355
|
+
- name: kinetic_energy
|
|
356
|
+
description: Tracks high energy output while in motion
|
|
357
|
+
type: review
|
|
358
|
+
assignee: homer@example.com
|
|
359
|
+
expression:
|
|
360
|
+
name: kinetic_energy_gt
|
|
361
|
+
channel_references:
|
|
362
|
+
- $1: *velocity_channel
|
|
363
|
+
sub_expressions:
|
|
364
|
+
- $mass: 10
|
|
365
|
+
- $threshold: 470
|
|
366
|
+
tags:
|
|
367
|
+
- nostromo
|
|
368
|
+
|
|
369
|
+
flows:
|
|
370
|
+
- name: readings
|
|
371
|
+
channels:
|
|
372
|
+
- <<: *velocity_channel
|
|
373
|
+
- <<: *voltage_channel
|
|
374
|
+
- <<: *vehicle_state_channel
|
|
375
|
+
- <<: *gpio_channel
|
|
376
|
+
|
|
377
|
+
- name: partial_readings
|
|
378
|
+
channels:
|
|
379
|
+
- <<: *velocity_channel
|
|
380
|
+
- <<: *voltage_channel
|
|
381
|
+
|
|
382
|
+
- name: logs
|
|
383
|
+
channels:
|
|
384
|
+
- <<: *log_channel
|
|
385
|
+
|
|
386
|
+
"""
|
|
387
|
+
|
|
388
|
+
DUPLICATE_CHANNEL_IN_FLOW_TELEMETRY_CONFIG = """
|
|
389
|
+
asset_name: LunarVehicle426
|
|
390
|
+
ingestion_client_key: lunar_vehicle_426
|
|
391
|
+
|
|
392
|
+
channels:
|
|
393
|
+
velocity_channel: &velocity_channel
|
|
394
|
+
name: velocity
|
|
395
|
+
data_type: double
|
|
396
|
+
description: speed
|
|
397
|
+
unit: Miles Per Hour
|
|
398
|
+
component: mainmotor
|
|
399
|
+
|
|
400
|
+
flows:
|
|
401
|
+
- name: readings
|
|
402
|
+
channels:
|
|
403
|
+
- <<: *velocity_channel
|
|
404
|
+
- <<: *velocity_channel
|
|
405
|
+
"""
|
|
File without changes
|