SIMEVENTS is a compact, human-authored text format describing dated well events (completions and Eclipse schedule keywords) over time. ResInsight reads it through the pure-Python parser in `GrpcInterface/Python/rips/simulator_events.py` and applies the events to a project via the `WellEventTimeline` API, from which Eclipse schedule text (COMPDAT, WELSEGS, WCONHIST, ...) can be generated.
The format is line-oriented. Every non-blank line is dispatched on its first token; anything else is an error. Keywords are uppercase and case-sensitive; duration units (`mon`, `d`, `h`, `m`, `s`) are lowercase.
Variables are typed — `DATE`, `DURATION`, `WELL` (well-name alias) and `FILTER` (cell filter expression) — and share one namespace. They must be declared before use (single-pass, no forward references).
A date expression is an ISO datetime or a `DATE` variable, followed by a chain of signed terms. Each term is a [duration literal](#duration-literals) or a `DURATION` variable:
Every date is a datetime with **second** resolution. A date-only literal (`2024-01-01`) means midnight; a time-of-day is written with the `T` separator (`YYYY-MM-DDTHH:MM:SS`). Fractional seconds are accepted and rounded to the nearest second. An operand may carry its own sign (`START + -2d` equals `START - 2d`); whitespace around `+`/`-` is optional but conventional. A non-midnight time-of-day is preserved through the applier and emitted as the optional TIME field of the generated DATES keyword.
## Duration literals
A duration literal is written Go-style: one or more `<number><unit>` components with no spaces, using the units below in strictly descending order, each at most once.
| Unit | Meaning |
|---|---|
| `mon` | calendar month |
| `d` | day (24 h) |
| `h` | hour |
| `m` | minute |
| `s` | second |
```
DURATION RAMP = 5d
DURATION SHIFT = 12h30m
DURATION QUARTER = 3mon
DURATION BACK = -3d
DURATION HALF = 1.5h # fraction allowed on the last component only
DURATION TOTAL = RAMP + SHIFT - 6h # duration arithmetic with variables
```
Rules:
* A unit is mandatory: `5` and the pre-1.1 `5 DAYS` are errors (write `5d`).
* Units must be in the order `mon`, `d`, `h`, `m`, `s` and may not repeat: `12h30m` is valid, `30m12h` and `1h1h` are not.
* A fraction is allowed only on the **last** component, and never on `mon` or `s`: `1.5h` and `1d1.5h` are valid, `1.5h30m`, `1.5s` and `1.5mon` are not. The result is rounded to whole seconds.
*`mon` is calendar-relative and is applied before the fixed part of a duration. Adding months clamps to the last day of a shorter month: `2024-01-31 + 1mon` is `2024-02-29` and `2024-01-31 + 1mon1d` is `2024-03-01`.
* A leading `-` negates the whole literal (`-1mon12h` is minus one month and minus twelve hours).
In Python a `DURATION` value is a `rips.simulator_events.Duration` with `months` and `delta` (`datetime.timedelta`) fields; `str()` renders the Go-style form, e.g. `1mon5d12h30m`.
`WELL <ident>` opens an event block for a declared `WELL` alias; `WELL "<name>"` opens a block for the literal well name and never consults variables — so there is no shadowing ambiguity. Well names containing special characters must use the quoted form. Empty blocks are legal. When applied, the well name must match a well path in the project (see the `on_unknown_well` policy below).
Event lines are `@<date_expr> <EVENT_TYPE> KEY=VALUE ...`. Inside a WELL block, the event type is either one of the five built-in completion events or any Eclipse well keyword.
| `FILTER` | no | case-level combined data filter attached to the perforation: a declared `FILTER` variable (`FILTER=POROPERM`) or an inline quoted expression (`FILTER="PORO > 0.4"`) — see [Filter expressions](#filter-expressions) |
Each event adds the underlying tubing timeline event and a corresponding **Segment Interval** with the same measured-depth range, diameter, and roughness.
Unknown attributes on a completion event are an error and skip the event. On completion events other than `PERFORATION`, `FILTER` is ignored with a warning.
Any other event type in a WELL block is passed through as an Eclipse well keyword via `add_well_keyword_event`, with the well name injected as the `WELL` item and all attributes forwarded (type-inferred as int, float or string). Two keywords have convenience attribute translations:
All other keywords (`WRFTPLT`, `WCONPROD`, `WELOPEN`, ...) forward attributes unchanged, so attribute keys must match the Eclipse item names ResInsight uses for that keyword. `FILTER` is ignored with a warning.
An event type that closely resembles a misspelled built-in (e.g. `PERFORATIN`) is **not** passed through as a keyword; it is handled by the `on_unknown_event` policy with a "did you mean" hint.
### SCHEDULE blocks
A bare `SCHEDULE` line opens a block of schedule-level keyword events applied through `add_keyword_event` — no well association, no `WELL` item injected:
Completion event types (`PERFORATION`, `SEGMENT`, `VALVE`, `STATE`, `WELSPECS`) are rejected in a SCHEDULE block. A later `WELL` line switches back to well events; blocks can be interleaved freely.
Every attribute is `KEY=VALUE`; bare positional tokens are rejected. Values are type-inferred (int, then float, otherwise string). Double quotes are used everywhere — well names, filter expressions and attribute values — and allow spaces and operators inside a value:
A filter expression — the quoted value of a `FILTER` declaration or of an inline `FILTER="..."` attribute — is a list of comparison terms joined by `AND` or `OR` (uppercase, one mode per expression; mixing them is an error, since a combined filter has a single combine mode):
```
FILTER POROPERM = "PORO > 0.4 AND PERMX > 100.0"
FILTER LOWSAT = "dynamic.SOIL < 0.2 OR PRESSURE < 150"
```
Each term is `[TYPE.]NAME <op> NUMBER`:
* Operators are `>`, `>=`, `<` and `<=`. A term sets only one bound of the generated property filter; the other bound keeps the result's minimum/maximum. Bounds are inclusive, so `>` behaves as `>=` (and `<` as `<=`).
* The result name is case-insensitive (uppercased on parse). An unqualified name is searched in the case's `STATIC_NATIVE`, then `DYNAMIC_NATIVE`, then `GENERATED` results; the first type containing it wins.
* A `TYPE.` qualifier restricts the search to one result type: `STATIC`/`STATIC_NATIVE`, `DYNAMIC`/`DYNAMIC_NATIVE` or `GENERATED`, case-insensitive (`DYNAMIC_NATIVE.MY_PROPERTY` and `dynamic.my_property` are equivalent).
* A result that does not exist in the case raises an error. All filter terms are checked up front, before any event is applied.
When applied, each used filter becomes a case-level **combined data filter** (under the case's *Data Filters*) holding one property filter per term. A declared filter is created once, named after its declaration, and shared between every perforation that references it; an inline filter gets an auto-derived name from its content (identical inline expressions share one filter). The filter is attached to the perforation event and carried onto the perforation interval when completions are materialized with `set_timestamp`. Declared-but-unreferenced filters create nothing.
The parser recovers per line and reports **all** errors in one pass. The raised `SimulatorEventsParseError` carries one `ParseIssue` (message + source line) per problem, and unknown names get difflib-based "did you mean" suggestions:
`apply_simulator_events_document` / `apply_simulator_events_file` accept an optional `case` — the `rips.Case` used to resolve filter result names and to own the created combined filters. It is only needed when the document uses `FILTER` and defaults to the project's first case; a `RipsError` is raised if a filter is used and no case is available, or if a filter references a result missing from the case (checked before any event is applied).
- **1.1** — current: all dates are datetimes with second resolution (`T` separator required for a time-of-day; fractional seconds rounded); `DURATION` values and offset terms are Go-style literals (`5d`, `12h30m`, `1.5h`, `3mon`, `-3d`) with mandatory units, strict descending unit order, and calendar-aware months. Unitless numbers and the `DAYS` suffix are rejected; `1.0` files are rejected with a migration hint.
- **1.0** — typed `DATE`/`DURATION`/`WELL`/`FILTER` declarations, `WELL`/`SCHEDULE` blocks, signed day-offset chains, ISO datetimes, built-in completion events (including `SEGMENT` custom intervals and pressure components) plus generic keyword pass-through, perforation data filters, multi-error diagnostics, validator CLI.