During another Event
A Rule can refer to the Events of another Rule by passing that Rule itself. during(a) holds while an Event of Rule a is Confirmed and has not Ended. It turns an Event into a state that other Rules can use.
A Rule may only refer to Rules added before it with rules(...), so it sees their state at the same Sample, and dependencies can never be circular. Since the reference is the Rule object, not a name, a misspelled reference does not compile, and build() rejects a reference to a Rule that was not added or is added later.
Restricting a Rule to a phase
Many criteria only make sense in a certain operating state. Vibration of a stopped machine is irrelevant, and a Rule for it would produce an Event for every truck driving past. Define the phase once as a Rule and refer to it:
Rule running = Rule.named("Running").when(rpm.above(100)).forDuration(Duration.ofSeconds(10));
Rule roughRunning = Rule.named("RoughRunning").when(during(running).and(vibration.above(4)));
RoughRunning Ends at the latest when Running Ends. The phase Rule brings its own timing, here ten seconds of start-up during which vibration is ignored, which a plain rpm.above(100) could not express.
Combining Events with their own timing
Each part keeps its own duration, End Condition and hysteresis, and the combination holds while both are Confirmed. A patient's heart rate hr is above 100 from 40 s to 200 s, and once more from 230 s to 250 s. Their oxygen saturation spo2 dips below 90 for one Sample at 20 s, from 100 s to 160 s, and from 230 s to 260 s. Tachycardia needs 30 seconds, Desaturation 10, and HypoxicTachycardia holds while both are Confirmed.
The live chart and its controls need JavaScript. The tables show the Events at the default parameters.
The site cannot run the default parameters: (JavaScript) TypeError: Cannot read properties of undefined (reading 'length')
import static io.github.romanhaendler.rbed.Conditions.*;
import io.github.romanhaendler.rbed.*;
import java.time.Duration;
import java.util.Map;
Channel hr = Channel.named("hr");
Channel spo2 = Channel.named("spo2");
Rule tachycardia = Rule.named("Tachycardia")
.when(hr.above(100)).forDuration(Duration.ofSeconds(30));
Rule desaturation = Rule.named("Desaturation")
.when(spo2.below(90)).forDuration(Duration.ofSeconds(10));
Rule hypoxicTachycardia = Rule.named("HypoxicTachycardia")
.when(during(tachycardia).and(during(desaturation)));
Detector detector = Detector.builder()
.rules(tachycardia, desaturation, hypoxicTachycardia)
.build();
for (int s = 0; s <= 300; s += 5) { // one Sample every 5 s
detector.push(s * 1_000L, Map.of(
hr, s >= 40 && s < 200 ? 115.0 : s >= 230 && s < 250 ? 110.0 : 75.0,
spo2, s == 20 ? 86.0 : s >= 100 && s < 160 ? 87.0 : s >= 230 && s < 260 ? 86.0 : 97.0));
}
detector.close();
At the default durations:
Tachycardiagoes Pending at 40 s and is Confirmed at 70 s.Desaturationgoes Pending at 100 s and is Confirmed at 110 s, and so isHypoxicTachycardia: a Pending Event does not count forduring.HypoxicTachycardiaEnds at 160 s withDesaturation, althoughTachycardiaruns until 200 s. A combination Ends at the latest when one of its parts Ends.- The dip at 20 s is too short for
Desaturationand is Discarded. - At 230 s both go Pending again.
Desaturationis Confirmed at 240 s, but the heart rate falls at 250 s, before its 30 seconds are reached:Tachycardiais Discarded, and there is noHypoxicTachycardia.
Things to try
- Shorten
Tachycardia's duration to 15 s: it is Confirmed at 245 s, soHypoxicTachycardiaholds from 245 s untilTachycardiaEnds at 250 s. - Set
Desaturation's duration to 0 s:HypoxicTachycardiastarts at 100 s, as soon as the saturation drops, and even the dip at 20 s is aDesaturationof its own.
The Healthcare example uses these Rules on a longer recording.
Reference
- during: while another Event runs and Rule order in the README, the canonical text
- Conditions in the Javadoc
Methods used: Channel.named, Rule.named, Rule.when, Expression.above, Rule.forDuration, Expression.below, Conditions.during, Condition.and, Detector.builder, Detector.Builder.rules, Detector.Builder.build, Detector.push, Detector.close.