# Pulse Channels

Some Channels report one-off occurrences rather than a state: a deployment, a button press, a trade. An ordinary Channel keeps its last value until the next one arrives, so a `deploy` value of 1 would stay 1 forever. A Pulse Channel's value exists only in the Sample that carries it.

## Declaring one

Create the Channel with `Channel.pulse(name)` instead of `Channel.named(name)`. Everything else stays the same: push its value in the Samples that carry an occurrence, and leave it out of the others.

## Deployments and errors

A service reports its errors once a minute, and the Pulse Channel `deploy` reports its deployments, at minutes 10 and 60. `Deployment` is an Event while a deployment is reported, which is exactly one Sample: at the next Sample `deploy` is absent, and the Event Ends. `Regression` is an error spike within a window after a Deployment, written with `after(deployment).within(...)`.

Parameters (in the page address as ?window=…):

- Window after a Deployment (`window`): 5 to 60 min in steps of 5, default 30
- Error limit (`limit`): 0 to 15 errors/min in steps of 1, default 5

The site cannot run the default parameters: (JavaScript) TypeError: Cannot read properties of undefined (reading 'length')

Java source at the default parameters:

```java
import static io.github.romanhaendler.rbed.Conditions.*;

import io.github.romanhaendler.rbed.*;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

Channel deploy = Channel.pulse("deploy");
Channel errors = Channel.named("errors");

Rule deployment = Rule.named("Deployment").when(deploy.above(0.5));
Rule regression = Rule.named("Regression")
    .when(after(deployment).within(Duration.ofMinutes(30)).and(errors.above(5)));
Detector detector = Detector.builder()
    .rules(deployment, regression)
    .build();

for (int m = 0; m <= 120; m++) {   // one Sample a minute
  Map<Channel, Double> values = new HashMap<>();
  values.put(errors, m >= 25 && m < 35 ? 12.0 : m >= 100 && m < 105 ? 9.0 : 3.0);
  if (m == 10 || m == 60) values.put(deploy, 1.0);   // only the Samples with a deployment carry deploy
  detector.push(m * 60_000L, values);
}
detector.close();
```

At the defaults, the spike at minute 25 starts 14 minutes after the first Deployment Ended (minute 11) and is a `Regression`. The second spike, at minute 100, starts 39 minutes after the second Deployment Ended (minute 61), outside the 30-minute window.

### Things to try

- Widen the window to 45 minutes: the second spike is a Regression too.
- Set it to 40 minutes: the second spike counts only until minute 101, the last minute within 40 minutes of the Deployment's end, so its `Regression` runs from 100 to 102.

## Absent is not undefined

In every Sample that does not carry it, and at every time advance, a Pulse Channel is *absent*. Absent is not undefined, because the absence of a pulse is known:

- Comparisons, `rising` and `falling` on an absent pulse are **false**, so `not()` of them is **true**: `deploy.above(0.5).not()` means "no deployment now".
- Arithmetic on an absent pulse is undefined.
- `age()` is the time since the last pulse: a `heartbeat.age()` above a minute is a heartbeat that has gone quiet.

## Reference

- [Pulse Channels](https://github.com/romanhaendler/rbed#pulse-channels) in the README, the canonical text
- [Channel](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Channel.html) in the Javadoc

Methods used: [`Channel.pulse`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Channel.html#pulse%28java.lang.String%29), [`Channel.named`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Channel.html#named%28java.lang.String%29), [`Rule.named`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Rule.html#named%28java.lang.String%29), [`Rule.when`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Rule.html#when%28io.github.romanhaendler.rbed.Condition%29), [`Expression.above`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Expression.html#above%28double%29), [`Conditions.after`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Conditions.html#after%28io.github.romanhaendler.rbed.Rule%29), [`After.within`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/After.html#within%28java.time.Duration%29), [`Condition.and`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Condition.html#and%28io.github.romanhaendler.rbed.Condition%29), [`Detector.builder`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Detector.html#builder%28%29), [`Detector.Builder.rules`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Detector.Builder.html#rules%28io.github.romanhaendler.rbed.Rule...%29), [`Detector.Builder.build`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Detector.Builder.html#build%28%29), [`Detector.push`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Detector.html#push%28long,java.util.Map%29), [`Detector.close`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Detector.html#close%28%29).
