Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Asset agnostic rule configs #100

Merged
merged 33 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
9325f6a
placeholder
Sep 10, 2024
b1f3388
lint & format
Sep 10, 2024
b06ac42
placeholder while I have internet
niliayu Sep 17, 2024
f13b3f2
placeholder while I have internet
niliayu Sep 18, 2024
a6359df
fill in examples a bit and add logic to telemetry
niliayu Sep 19, 2024
1061b0a
chore: fmt
niliayu Sep 19, 2024
bf15663
fixed example
niliayu Sep 19, 2024
8a57f75
chore: cleanups
niliayu Sep 19, 2024
f7b1d7e
chore: cleanups
niliayu Sep 27, 2024
e20b58e
chore: RuleModuleYamlSpec placeholder
niliayu Sep 27, 2024
84079e0
chore: lint/fmt
niliayu Sep 27, 2024
9d249d9
chore: Add vscode files to gitignore
niliayu Sep 30, 2024
8901789
chore: Cleanup TODOs & add docs
niliayu Oct 1, 2024
840382b
chore: More docs
niliayu Oct 1, 2024
9061bcb
chore: Handle nested directories
niliayu Oct 2, 2024
e8a0947
fix: Fix channel references for namespaced rules and syntax issue
niliayu Oct 3, 2024
b647159
chore: formatting fixes
niliayu Oct 3, 2024
79e3329
test: Add rule namespace to nominal telemetry_test
niliayu Oct 10, 2024
e36637a
chore: placeholders for other tests
niliayu Oct 10, 2024
230f2b0
chore: format
niliayu Oct 10, 2024
ef3721b
feat: Add RuleConfig interpolation of rules
niliayu Oct 10, 2024
1f3a1bd
chore: Clean up optional handling
niliayu Oct 10, 2024
cd9fb8a
chore: fmt
niliayu Oct 10, 2024
430e7f0
chore: Use RuleConfig logic for assembling namespace rules from yaml
niliayu Oct 11, 2024
4c73637
chore: cleanups
niliayu Oct 11, 2024
ff124b5
chore: cleanup
niliayu Oct 11, 2024
05196de
fix: Bug in rule namespace interpolation and test cleanup
niliayu Oct 11, 2024
be9d095
test: Add config tests for namespace interpolation
niliayu Oct 11, 2024
8fc01b9
test: Load test for misconfigured namespace rule with additional fields
niliayu Oct 11, 2024
2cc161d
chore: format
niliayu Oct 11, 2024
98631a5
chore: Clean up optionals in RuleConfig
niliayu Oct 14, 2024
275366c
chore: type fixes
niliayu Oct 14, 2024
08462a4
chore: clarify example rule wording
niliayu Oct 14, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,5 @@ python/protos/**/*
python/build
python/docs/sift_py
*.egg-info/

.vscode/*
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace: velocity

rules:
- name: vehicle_stuck
description: Triggers if the vehicle velocity is not 0 for 5s after entering accelerating state
expression: $1 == "Accelerating" && persistence($2 == 0, 5)
type: review

- name: vehicle_not_stopped
description: Triggers if the vehicle velocity does not remain 0 while stopped
expression: $1 == "Stopped" && $2 > 0
type: review
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace: voltage

rules:
- name: overvoltage
description: Checks for overvoltage while accelerating
expression: $1 == "Accelerating" && $2 > 80
type: review

- name: undervoltage
description: Checks for undervoltage while accelerating
expression: $1 == "Accelerating" && $2 < 40
type: review
73 changes: 72 additions & 1 deletion python/examples/ingestion_with_python_config/telemetry_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,14 @@
ChannelEnumType,
)
from sift_py.ingestion.config.telemetry import FlowConfig, TelemetryConfig
from sift_py.ingestion.config.yaml.load import load_named_expression_modules
from sift_py.ingestion.config.yaml.load import load_named_expression_modules, load_rule_namespaces
from sift_py.ingestion.rule.config import (
RuleActionCreateDataReviewAnnotation,
RuleConfig,
)

EXPRESSION_MODULES_DIR = Path().joinpath("expression_modules")
RULE_NAMESPACES_DIR = Path().joinpath("rule_modules")


def nostromos_lv_426() -> TelemetryConfig:
Expand All @@ -24,6 +25,12 @@ def nostromos_lv_426() -> TelemetryConfig:
]
)

rule_namespaces = load_rule_namespaces(
[
RULE_NAMESPACES_DIR,
]
)

log_channel = ChannelConfig(
name="log",
data_type=ChannelDataType.STRING,
Expand Down Expand Up @@ -124,6 +131,70 @@ def nostromos_lv_426() -> TelemetryConfig:
tags=["nostromo", "failure"],
),
),
RuleConfig(
name="overvoltage",
namespace="voltage",
namespace_rules=rule_namespaces,
channel_references=[
# INFO: Can use either "channel_identifier" or "channel_config"
{
"channel_reference": "$1",
"channel_identifier": vehicle_state_channel.fqn(),
},
{
"channel_reference": "$2",
"channel_config": voltage_channel,
},
],
),
RuleConfig(
name="undervoltage",
namespace="voltage",
namespace_rules=rule_namespaces,
channel_references=[
# INFO: Can use either "channel_identifier" or "channel_config"
{
"channel_reference": "$1",
"channel_identifier": vehicle_state_channel.fqn(),
},
{
"channel_reference": "$2",
"channel_config": voltage_channel,
},
],
),
RuleConfig(
name="vehicle_stuck",
namespace="velocity",
namespace_rules=rule_namespaces,
channel_references=[
# INFO: Can use either "channel_identifier" or "channel_config"
{
"channel_reference": "$1",
"channel_identifier": vehicle_state_channel.fqn(),
},
{
"channel_reference": "$2",
"channel_config": velocity_channel,
},
],
),
RuleConfig(
name="vehicle_not_stopped",
namespace="velocity",
namespace_rules=rule_namespaces,
channel_references=[
# INFO: Can use either "channel_identifier" or "channel_config"
{
"channel_reference": "$1",
"channel_identifier": vehicle_state_channel.fqn(),
},
{
"channel_reference": "$2",
"channel_config": velocity_channel,
},
],
),
],
flows=[
FlowConfig(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace: velocity

rules:
- name: vehicle_stuck
description: Triggers if the vehicle velocity is not 0 for 5s after entering accelerating state
expression: $1 == "Accelerating" && persistence($2 == 0, 5)
type: review

- name: vehicle_not_stopped
description: Triggers if the vehicle velocity does not remain 0 while stopped
expression: $1 == "Stopped" && $2 > 0
type: review
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace: voltage

rules:
- name: overvoltage
description: Checks for overvoltage while accelerating
expression: $1 == "Accelerating" && $2 > 80
type: review

- name: undervoltage
description: Checks for undervoltage while accelerating
expression: $1 == "Accelerating" && $2 < 40
type: review
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

TELEMETRY_CONFIGS_DIR = Path().joinpath("telemetry_configs")
EXPRESSION_MODULES_DIR = Path().joinpath("expression_modules")
RULE_MODULES_DIR = Path().joinpath("rule_modules")


def nostromos_lv_426() -> TelemetryConfig:
Expand All @@ -15,11 +16,12 @@ def nostromos_lv_426() -> TelemetryConfig:

telemetry_config_path = TELEMETRY_CONFIGS_DIR.joinpath(telemetry_config_name)

# Load your telemetry config with your reusable expressions modules
# Load your telemetry config with your reusable expressions modules and rule modules
return TelemetryConfig.try_from_yaml(
telemetry_config_path,
[
EXPRESSION_MODULES_DIR.joinpath("kinematics.yml"),
EXPRESSION_MODULES_DIR.joinpath("string.yml"),
],
[RULE_MODULES_DIR],
)
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,30 @@ rules:
- failure
- nostromo

- namespace: voltage
niliayu marked this conversation as resolved.
Show resolved Hide resolved
name: overvoltage
channel_references:
- $1: *vehicle_state_channel
- $2: *voltage_channel

- namespace: voltage
name: undervoltage
channel_references:
- $1: *vehicle_state_channel
- $2: *voltage_channel

- namespace: velocity
name: vehicle_stuck
channel_references:
- $1: *vehicle_state_channel
- $2: *velocity_channel

- namespace: velocity
name: vehicle_not_stopped
channel_references:
- $1: *vehicle_state_channel
- $2: *velocity_channel

flows:
- name: readings
channels:
Expand All @@ -103,7 +127,7 @@ flows:
- name: gpio_channel
channels:
- <<: *gpio_channel

- name: logs
channels:
- <<: *log_channel
Expand Down
52 changes: 35 additions & 17 deletions python/lib/sift_py/ingestion/config/telemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@
ChannelEnumType,
_channel_fqn,
)
from sift_py.ingestion.config.yaml.load import load_named_expression_modules, read_and_validate
from sift_py.ingestion.config.yaml.load import (
load_named_expression_modules,
load_rule_namespaces,
read_and_validate,
)
from sift_py.ingestion.config.yaml.spec import TelemetryConfigYamlSpec
from sift_py.ingestion.flow import FlowConfig
from sift_py.ingestion.rule.config import (
Expand Down Expand Up @@ -111,6 +115,7 @@ def try_from_yaml(
cls,
path: Path,
named_expression_modules: Optional[List[Path]] = None,
named_rule_modules: Optional[List[Path]] = None,
) -> Self:
"""
Initializes a telemetry config from a YAML file found at the provided `path` as well as optional
Expand All @@ -119,17 +124,21 @@ def try_from_yaml(

config_as_yaml = read_and_validate(path)

named_expressions = {}
rule_namespaces = {}
if named_expression_modules is not None:
named_expressions = load_named_expression_modules(named_expression_modules)
return cls._from_yaml(config_as_yaml, named_expressions)
else:
return cls._from_yaml(config_as_yaml)
if named_rule_modules is not None:
rule_namespaces = load_rule_namespaces(named_rule_modules)

return cls._from_yaml(config_as_yaml, named_expressions, rule_namespaces)

@classmethod
def _from_yaml(
cls,
config_as_yaml: TelemetryConfigYamlSpec,
named_expressions: Dict[str, str] = {},
rule_namespaces: Dict[str, List] = {},
) -> Self:
rules = []
flows = []
Expand Down Expand Up @@ -179,16 +188,21 @@ def _from_yaml(
)

for rule in config_as_yaml.get("rules", []):
annotation_type = RuleActionAnnotationKind.from_str(rule["type"])

tags = rule.get("tags")

action: RuleAction = RuleActionCreatePhaseAnnotation(tags)
if annotation_type == RuleActionAnnotationKind.REVIEW:
action = RuleActionCreateDataReviewAnnotation(
assignee=rule.get("assignee"),
tags=tags,
)
namespace = rule.get("namespace", "")

action: Optional[RuleAction] = None
description: str = ""
if not namespace:
annotation_type = RuleActionAnnotationKind.from_str(rule["type"])
tags = rule.get("tags")
description = rule.get("description", "")

action = RuleActionCreatePhaseAnnotation(tags)
if annotation_type == RuleActionAnnotationKind.REVIEW:
action = RuleActionCreateDataReviewAnnotation(
assignee=rule.get("assignee"),
tags=tags,
)

channel_references: List[
ExpressionChannelReference | ExpressionChannelReferenceChannelConfig
Expand All @@ -206,15 +220,17 @@ def _from_yaml(
}
)

expression = rule["expression"]
expression = rule.get("expression", "")
if isinstance(expression, str):
rules.append(
RuleConfig(
name=rule["name"],
description=rule.get("description", ""),
description=description,
expression=expression,
action=action,
channel_references=channel_references,
namespace=namespace,
namespace_rules=rule_namespaces,
)
)
else:
Expand All @@ -237,11 +253,13 @@ def _from_yaml(
rules.append(
RuleConfig(
name=rule["name"],
description=rule.get("description", ""),
description=description,
expression=expr,
action=action,
channel_references=channel_references,
sub_expressions=sub_exprs,
namespace=namespace,
namespace_rules=rule_namespaces,
)
)

Expand Down
Loading
Loading