# The Event lifecycle

An Event does not appear all at once. It goes **Pending** when its Rule's Conditions start holding, becomes **Confirmed** once they have held for the required duration, and **Ends** later. A Pending Event whose Conditions stop holding too early is **Discarded** and never counts.

## Four callbacks

The Detector tells its `EventListener` about each stage. All four methods are optional, so implement only the ones you need:

- `onPending`: the Conditions started holding; the required duration is not reached yet.
- `onConfirmed`: the Conditions held long enough. Only Confirmed Events count as detected.
- `onEnded`: a Confirmed Event is over. It now has its end, and its Aggregates cover the whole Event.
- `onDiscarded`: a Pending Event stopped holding before it was Confirmed.

## Pending, then Confirmed or Discarded

`Overheat` requires `temp` to stay above 80 for a duration, two seconds by default.

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

- Duration (`duration`): 0 to 6 s in steps of 1, default 2

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

Java source at the default parameters:

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

Channel temp = Channel.named("temp");

Rule overheat = Rule.named("Overheat")
    .when(temp.above(80)).forDuration(Duration.ofSeconds(2));
Detector detector = Detector.builder()
    .rules(overheat)
    .listener(new EventListener() {
      @Override public void onPending(Event e) {
        System.out.println("pending at " + e.start());
      }
      @Override public void onConfirmed(Event e) {
        System.out.println("confirmed at " + e.confirmed().getAsLong());
      }
      @Override public void onEnded(Event e) {
        System.out.println("ended at " + e.end().getAsLong());
      }
      @Override public void onDiscarded(Event e) {
        System.out.println("discarded");
      }
    })
    .build();

double[] values = {70, 72, 75, 85, 88, 91, 89, 86, 84, 76, 72, 74, 84, 87, 83, 75, 72, 90, 74, 71, 70};
for (int i = 0; i < values.length; i++) {
  detector.push(i * 1_000L, Map.of(temp, values[i]));   // one Sample per second
}
detector.close();
```

At the default duration of two seconds:

- The first Event goes Pending at 3 s, is Confirmed at 5 s and Ends at 9 s, when `temp` falls to 76.
- The second goes Pending at 12 s and is Confirmed at 14 s, just in time: at 15 s `temp` is back at 75.
- The spike at 17 s lasts a single Sample. At 18 s the Conditions no longer hold, so the Pending Event is Discarded. It never counts as detected: it is not in the Event table, only among the Discarded Pending Events.

### Things to try

- Raise the duration to 3 s: the second Event is Discarded as well. Its three seconds would be reached at 15 s, but by then `temp` has dropped.
- Raise it to 6 s: not even the first Event is Confirmed.
- Set it to 0 s: every Event is Confirmed the moment it starts, and the spike counts, too.

## The start lies before the confirmation

An Event's `start()` is where its Conditions started holding, not where it was Confirmed: the first Event starts at 3 s, although it is only known at 5 s. `confirmed()` returns the moment of confirmation, which the Event table shows in a column of its own.

A Detector only sees what its Samples show. The Discarded moment on this page is the Sample at which the Conditions were found no longer holding. The `Event` passed to `onDiscarded` has no such timestamp, only its start.

## Durations: forDuration and forSamples

`forDuration` measures from the Event's start, so the required duration is reached at `start()` plus the duration, and the Event is Confirmed at the first Sample or [advance](https://romanhaendler.github.io/rbed/silent-sensors/) from then on. `forSamples(n)` instead requires at least `n` consecutive Samples; with both, both must be met. Each applies to what comes right before it: `when(...)`, or `endWhen(...)` for an [End Condition](https://romanhaendler.github.io/rbed/end-conditions/#a-duration-for-the-end-condition). Without either, an Event is Pending and Confirmed at the same moment.

## One Event at a time

Each Rule has at most one Event in progress. After an Event, the next one only starts once the Rule has seen `when` not holding. When an Event Ends because `when` stopped holding, or a Pending Event is Discarded, that Sample already counts, so the next Sample can start a new Event. While a Confirmed Event runs on its End Condition, however, `when` is not evaluated, and after the Event has Ended, `when` must be seen not holding at a later Sample. After a [data gap](https://romanhaendler.github.io/rbed/data-gaps/) longer than `maxGap`, the next Sample can start an Event at once, with a Truncated start. So a Rule with an End Condition does not flicker: see [End Conditions](https://romanhaendler.github.io/rbed/end-conditions/#when-does-the-next-event-start).

## What an Event carries

An `Event` is an immutable record, and the listener receives a fresh snapshot at each stage. It carries:

- `name()`: the name of the Rule that produced it, for example to store the Event.
- `is(rule)`: whether `rule` produced it.
- `start()`: the timestamp (ms) of the first moment at which `when` held.
- `confirmed()`: an `OptionalLong`, when it was Confirmed; empty while Pending or if Discarded.
- `end()`: an `OptionalLong`, when it Ended; empty until then.
- `truncatedStart()`: it may have begun earlier than `start()`.
- `truncatedEnd()`: observation stopped while it ran, by `close()` or a data gap.
- `aggregates(x)`: the [Aggregates](https://romanhaendler.github.io/rbed/aggregates/) of Channel `x` over the Event so far.

The duration of an Ended Event is `e.end().getAsLong() - e.start()`.

## Truncated Events

Two flags mark Events whose true extent is not known. The Event table shows both in its Truncated column.

- `truncatedEnd()`: observation stopped while the Event was running. `close()` Ends a Confirmed Event this way (and Discards a Pending one), and so does a data gap.
- `truncatedStart()`: the Event may have begun before its `start()`, because its Rule had not yet seen its Conditions *not* holding: the very first Sample already satisfies them, a moving average is still warming up, or a data gap has just ended.

The [first Rule](https://romanhaendler.github.io/rbed/#a-first-rule) on the front page shows a Truncated start: lower its threshold to 70, and its Event starts at the very first Sample.

## Reference

- [The Event lifecycle](https://github.com/romanhaendler/rbed#the-event-lifecycle), [Events](https://github.com/romanhaendler/rbed#events) and [Durations: forDuration and forSamples](https://github.com/romanhaendler/rbed#durations-forduration-and-forsamples) in the README, the canonical text
- [EventListener](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/EventListener.html) and [Event](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Event.html) in the Javadoc

Methods used: [`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), [`Rule.forDuration`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Rule.html#forDuration%28java.time.Duration%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.listener`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Detector.Builder.html#listener%28io.github.romanhaendler.rbed.EventListener%29), [`EventListener.onPending`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/EventListener.html#onPending%28io.github.romanhaendler.rbed.Event%29), [`Event.start`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Event.html#start%28%29), [`EventListener.onConfirmed`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/EventListener.html#onConfirmed%28io.github.romanhaendler.rbed.Event%29), [`Event.confirmed`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Event.html#confirmed%28%29), [`EventListener.onEnded`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/EventListener.html#onEnded%28io.github.romanhaendler.rbed.Event%29), [`Event.end`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/Event.html#end%28%29), [`EventListener.onDiscarded`](https://romanhaendler.github.io/rbed/api/io.github.romanhaendler.rbed/io/github/romanhaendler/rbed/EventListener.html#onDiscarded%28io.github.romanhaendler.rbed.Event%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).
