STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
naming.py
Go to the documentation of this file.
1"""Classify exported component names into family/field/unit/kind.
2
3A flattened Elodin export names each file `<Entity>.<field>` where Entity is like
4`PT_Cal.Ox_Upstream`, `TC1.CH2`, `ACT4.CH5`, `CONTROLLER.diagnostics`,
5`BOARD.HB_52`. The last dotted segment is the field (`pressure_psi`, `raw_adc`,
6`to_state`, ...). A handful of components are genuinely unnamed and arrive as a
7bare numeric hash — those are grouped as "Other".
8"""
9
10from __future__ import annotations
11
12import re
13from dataclasses import dataclass, asdict
14
15# Fields that are step/discrete (state enums, on/off, status) → step-draw, no
16# min/max decimation, forward-fill when aligning to a shared time grid.
17_DISCRETE_FIELDS = {
18 "to_state",
19 "from_state",
20 "engine_state",
21 "board_state",
22 "current_state",
23 "debug_mode",
24 "actuator_state",
25 "actuator_state_commanded",
26 "fire_active",
27 "cutoff_active",
28 "safety_filtered",
29 "valid",
30 "cal_status",
31 "reason",
32 "channel_id",
33}
34
35# Plumbing fields hidden from the picker by default (still exportable via advanced).
36_SECONDARY_FIELDS = {
37 "cal_status",
38 "channel_id",
39 "timestamp_ns",
40 "packet_ts_ms",
41 "reason",
42 "raw_adc",
43 "raw_uv",
44}
45
46# Explicit, hand-maintained unit per field. The axis grouping keys on unit
47# (falling back to the field name when a field is unit-less), so getting these
48# right is what puts e.g. pressures and ADC counts on separate y-axes. Any field
49# not listed here is treated as unit-less. Keep this in sync with the fields the
50# DAQ actually publishes (see naming test / `elodin-db info`).
51_UNIT_BY_FIELD = {
52 # pressures (raw PT + controller measurement/diagnostics)
53 "pressure_psi": "psi",
54 "p_ch": "psi", "p_ch_mp1": "psi", "p_ch_mp2": "psi",
55 "p_copv": "psi", "p_reg": "psi",
56 "p_u_ox": "psi", "p_u_fuel": "psi", "p_d_ox": "psi", "p_d_fuel": "psi",
57 # temperature (TC / RTD calibrated)
58 "temperature_c": "°C", "temp_c": "°C",
59 # resistance (RTD raw)
60 "raw_resistance": "Ω", "raw_resistance_counts": "counts",
61 # force (load cells + thrust estimate/reference)
62 "force_n": "N", "force_kg": "kg", "force_kg_tared": "kg",
63 "f_ref": "N", "f_estimated": "N",
64 # current (actuator sense)
65 "current_a": "A",
66 # ball valve position (BV<n>_Cal), 0 closed / 100 open
67 "position_pct": "%",
68 # raw ADC counts
69 "raw_adc": "counts", "raw_adc_counts": "counts",
70 # LabJack T7: raw input volts, and the µV code calibration works in (T7<n>_Cal.*.raw_uv)
71 "volts": "V", "raw_uv": "µV",
72 # encoder
73 "raw_angle": "deg",
74 # controller duty cycles
75 "duty_f": "0-1", "duty_o": "0-1",
76 # timestamps
77 "timestamp_ns": "ns",
78 "sample_ts_ms": "ms", "packet_ts_ms": "ms",
79 # left unit-less on purpose (grouped by field name): channel_id, sensor_id,
80 # board_id, board_type, status, result, cal_status, valid, *_state,
81 # fire_active, debug_mode, allowed_bitmask, reason, cutoff_active,
82 # safety_filtered, u_f_on, u_o_on, solver_iters, cost, mr_ref, mr_estimated.
83}
84
85_HASH_RE = re.compile(r"^\d{6,}$")
86
87
88def _family(entity: str) -> str:
89 """Coarse family for grouping in the UI, derived from the entity prefix."""
90 head = entity.split(".", 1)[0]
91 if _HASH_RE.match(head):
92 return "Other"
93 # LabJack T7: T71 / T71_Cal. The generic rule below would read "T71" as "T" + slot 71.
94 t7 = re.match(r"^T7\d+(_Cal)?$", head)
95 if t7:
96 return "T7" + (t7.group(1) or "")
97 # Strip a trailing board/index number: PT1 -> PT, PT_Cal -> PT_Cal, ACT4 -> ACT.
98 m = re.match(r"^([A-Za-z_]+?)\d*$", head)
99 base = m.group(1).rstrip("_") if m else head
100 return base or head
101
102
103def _unit(field: str) -> str:
104 return _UNIT_BY_FIELD.get(field.lower(), "")
105
106
107@dataclass
109 name: str # full "<Entity>.<field>", == parquet file stem
110 entity: str # "<Entity>" (may itself contain dots, e.g. PT_Cal.Ox_Upstream)
111 field: str # last dotted segment
112 family: str # coarse group for the UI
113 unit: str
114 discrete: bool # step-draw / forward-fill
115 primary: bool # surfaced in the picker by default (vs plumbing)
116
117 def as_dict(self) -> dict:
118 return asdict(self)
119
120
121def classify(name: str) -> Component:
122 """Split a component name into (entity, field) and classify it."""
123 if "." in name:
124 entity, field = name.rsplit(".", 1)
125 else:
126 entity, field = name, ""
127 fam = _family(entity)
128 return Component(
129 name=name,
130 entity=entity,
131 field=field,
132 family=fam,
133 unit=_unit(field),
134 discrete=field.lower() in _DISCRETE_FIELDS,
135 primary=fam != "Other" and field.lower() not in _SECONDARY_FIELDS,
136 )
str _family(str entity)
Definition naming.py:88
str _unit(str field)
Definition naming.py:103
Component classify(str name)
Definition naming.py:121