Files
ResInsight/docs/simulatorEvents.md
T

463 lines
25 KiB
Markdown
Raw Normal View History

# SIMEVENTS File Format — Version 1.1
## Overview
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 implementation has two independent layers:
| Layer | Entry points | Needs ResInsight? |
|---|---|---|
| A — parser | `rips.simulator_events.parse_simulator_events(text)` / `parse_simulator_events_file(path)` | No |
| B — applier | `rips.simulator_events.apply_simulator_events_document(document, timeline, project)` / `apply_simulator_events_file(path, timeline, project)` | Yes |
A standalone validator runs Layer A only:
```
python3 -m rips.simulator_events myfile.events
```
Runnable examples: `rips/PythonExamples/experimental/import_simulator_events.py` (reads the shipped sample `rips/example_input_files/simulator_events.events`) and `well_event_schedule_simulator_events.py` (full event coverage, generates schedule text).
## Quick example
```
SIMEVENTS 1.1
UNIT METRIC
# Typed declarations
DATE STARTUP = 2024-01-01
DATE PHASE2 = 2024-03-01 + 9d
DURATION RAMP = 31d
FILTER POROPERM = "PORO > 0.4 AND PERMX > 100.0"
WELL A1 = "55_33-A-1" # alias declaration (has '=')
WELL A1 # opens an event block via the alias
@STARTUP WELSPECS GROUP="OP" PRESSURE_COMPONENTS=HFA
@STARTUP SEGMENT MDSTART=0 MDEND=2500 INNER_DIAMETER=0.15 ROUGHNESS=1.0e-5
@STARTUP + RAMP PERFORATION MDSTART=2000 MDEND=2200 RADIUS=0.05 SKIN=0.5 COMPLETION_NUMBER=1 FILTER=POROPERM
@2024-05-15T14:45:30 PERFORATION MDSTART=2300 MDEND=2350 RADIUS=0.05
@2024-03-01 VALVE MD=2100 TYPE=ICV STATE=OPEN CV=0.7 AREA=0.0001
@STARTUP + RAMP WCONHIST STATUS=OPEN CMODE=ORAT VFP=1
@2024-05-01 WELTARG CMODE=ORAT VALUE=5000.0
@2024-06-01 WRFTPLT OUTPUT_RFT=YES OUTPUT_PLT=NO OUTPUT_SEGMENT=NO
WELL "55_33-A-2" # quoted form: literal well name
@PHASE2 - 1d PERFORATION MDSTART=1692.79 MDEND=1706 RADIUS=0.12065 SKIN=5
SCHEDULE # schedule-level keywords, not tied to a well
@STARTUP RPTRST BASIC=2 FREQ=1
@STARTUP GRUPTREE CHILD=OP PARENT=FIELD
@STARTUP TUNING TSINIT=1 TSMAXZ=30 NEWTMX=12
```
## Grammar (EBNF-ish)
```
document = header , { statement } ;
header = "SIMEVENTS" , "1.1" ; (* first meaningful line *)
statement = unit_directive | declaration | report_line | well_block_open
| group_block_open | schedule_block_open | event_line ;
unit_directive = "UNIT" , ( "METRIC" | "FIELD" | "LAB" ) ;
report_line = "REPORT" , date_expr ;
declaration = date_decl | duration_decl | well_decl | filter_decl ;
date_decl = "DATE" , ident , "=" , date_expr ; (* DATE X = 2018-03-01 + 9d *)
duration_decl = "DURATION" , ident , "=" , duration_expr ; (* DURATION RAMP = 5d12h *)
well_decl = "WELL" , ident , "=" , quoted_string ; (* WELL A1 = "55_33-A-1" *)
filter_decl = "FILTER" , ident , "=" , '"' , filter_expr , '"' ;
(* FILTER F = "PORO > 0.4 AND PERMX > 100.0" *)
filter_expr = filter_term , { ( "AND" | "OR" ) , filter_term } ;
(* one combine mode; mixing AND and OR is an error *)
filter_term = [ result_type , "." ] , ident , comp_op , number ;
comp_op = ">" | ">=" | "<" | "<=" ;
well_block_open = "WELL" , ( quoted_string | ident ) ; (* no "=" present *)
group_block_open = "GROUP" , quoted_string ; (* group keyword events *)
schedule_block_open = "SCHEDULE" ; (* well-less keyword events *)
event_line = "@" , date_expr , event_type , { attribute } ;
event_type = completion_event_type | eclipse_keyword ;
completion_event_type = "PERFORATION" | "SEGMENT" | "VALVE" | "STATE" | "WELSPECS" ;
segment_attribute = "MDSTART" | "MDEND" | "INNER_DIAMETER" | "ROUGHNESS"
| "FIXED_SEGMENT_LENGTH" | "MIN_SEGMENT_LENGTH"
| "MAX_SEGMENT_LENGTH" | "COMMENT" ;
(* accepted attribute names when event_type is "SEGMENT" *)
wellspec_attribute = "GROUP" | "CROSSFLOW" | "REFDEPTH" | "PHASE"
| "PRESSURE_COMPONENTS" | "COMMENT" ;
(* accepted attribute names when event_type is "WELSPECS" *)
date_expr = ( iso_datetime | date_ident ) , { sign , term } ;
duration_expr = term , { sign , term } ;
term = duration_lit | dur_ident ; (* e.g. 2d, -12h30m, RAMP *)
sign = "+" | "-" ;
duration_lit = [ sign ] , component , { component } ;
(* units strictly descending, each at most once *)
component = number , ( "mon" | "d" | "h" | "m" | "s" ) ;
number = digits | digits , "." , digits ;
(* a fraction only on the last component,
never on "mon" or "s" *)
iso_datetime = 4digit , "-" , 2digit , "-" , 2digit ,
[ "T" , 2digit , ":" , 2digit , ":" , 2digit , [ "." , digits ] ] ;
(* 2024-05-15 or 2024-05-15T14:45:30 *)
ident = letter_or_underscore , { word_char } ;
attribute = ident , "=" , ( quoted_string | bareword ) ;
comment = "#" , rest-of-line ; (* line or trailing *)
```
## Line types
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.
| First token | Meaning |
|---|---|
| `SIMEVENTS` | Header with version; must be the first meaningful line, exactly once |
| `UNIT` | Unit system: `METRIC`, `FIELD` or `LAB` (default `METRIC`) |
| `DATE` | Declare a typed date variable |
| `DURATION` | Declare a typed duration variable (Go-style literal, e.g. `5d`, `12h30m`) |
| `FILTER` | Declare a typed cell-filter expression |
| `REPORT` | Add a date that must appear in generated schedule output |
| `WELL` | With `=`: declare a well-name alias. Without `=`: open a well event block |
| `GROUP` | Open a block of group-level keyword events |
| `SCHEDULE` | Open a block of schedule-level keyword events (bare keyword, no arguments) |
| `@` | An event line, appended to the enclosing WELL, GROUP or SCHEDULE block |
| `#` | Comment (also allowed trailing on any line, outside double quotes) |
## Typed declarations
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).
```
DATE STARTUP = 2024-01-01
DURATION RAMP = 31d # Go-style duration literal
DATE PHASE2 = STARTUP + RAMP # date arithmetic with variables
WELL A1 = "55_33-A-1"
FILTER POROPERM = "PORO > 0.4 AND PERMX > 100.0"
```
Using a variable of the wrong type is an error citing both the use and the declaration site:
```
Line 18: Variable 'RAMP' is a DURATION (declared line 10) but a DATE is required here
```
Redeclaring a name with the same type warns and the last value wins; redeclaring with a different type is an error.
## Date expressions
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:
```
@2024-01-01
@STARTUP + 5d
@STARTUP + RAMP - 2d
@STARTUP + 12h30m
@2024-05-15T14:45:30 # time-of-day, second precision
@2024-01-31 + 1mon # calendar month: 2024-02-29
```
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 blocks
`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 types
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.
### Built-in completion events
**PERFORATION** → `add_perf_event` (state is always OPEN)
| Attribute | Required | Maps to |
|---|---|---|
| `MDSTART` | yes | `start_md` |
| `MDEND` | yes | `end_md` |
| `RADIUS` | no | `diameter` = 2 × radius |
| `SKIN` | no | `skin_factor` |
| `COMPLETION_NUMBER` | no | `completion_number` (COMPLUMP grouping) |
| `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) |
2026-10-16 14:33:20 +02:00
**SEGMENT** → `add_segment_event` and `add_segment_interval`
2026-10-16 14:33:20 +02:00
Each event adds the underlying segment timeline event and a corresponding **Segment Interval** with the same measured-depth range, diameter, and roughness.
| Attribute | Required | Maps to |
|---|---|---|
2026-10-16 14:33:20 +02:00
| `MDSTART` | yes | segment event and segment interval `start_md` |
| `MDEND` | yes | segment event and segment interval `end_md` |
| `INNER_DIAMETER` | no | segment event `inner_diameter` [m] |
| `ROUGHNESS` | no | segment event `roughness` [m] |
| `FIXED_SEGMENT_LENGTH` | no | segment event `fixed_segment_length` and segment interval fixed segment length [m] |
| `MIN_SEGMENT_LENGTH` | no | segment event `min_segment_length` and segment interval min segment length [m] |
| `MAX_SEGMENT_LENGTH` | no | segment event `max_segment_length` and segment interval max segment length [m] |
The segment lengths control the MSW segmentation of the interval. Lengths must be positive. `FIXED_SEGMENT_LENGTH` cannot be combined with `MIN_SEGMENT_LENGTH` or `MAX_SEGMENT_LENGTH`, and `MIN_SEGMENT_LENGTH` cannot exceed `MAX_SEGMENT_LENGTH`. Violations are reported as parse errors.
Applying a SEGMENT event also sets the well's segment *Reference MD Type* to *User Defined*, with *User Defined Reference MD* set to the start MD of the first (shallowest) segment interval. This value is written as `TOP_LENGTH` in `WELSEGS`.
**VALVE** → `add_valve_event` (requires an existing perforation at the MD)
| Attribute | Required | Maps to |
|---|---|---|
| `MD` | yes | `measured_depth` |
| `TYPE` | yes | `valve_type`: `ICV`, `ICD` or `AICD` |
| `STATE` | no | `state`: `OPEN` or `SHUT` |
| `CV` | no | `flow_coefficient` |
| `AREA` | no | `area` [m²] |
| `AICD_STRENGTH`, `AICD_DENSITY_CALIB_FLUID`, `AICD_VISCOSITY_CALIB_FLUID`, `AICD_VOL_FLOW_EXP`, `AICD_VISC_FUNC_EXP` | no | the corresponding AICD parameters |
**STATE** → `add_state_event`
| Attribute | Required | Maps to |
|---|---|---|
| `STATE` | yes | `well_state`: `OPEN`, `SHUT` or `STOP` |
**WELSPECS** → `add_wellspec_event`
Well-level settings. At least one setting attribute is required; omitted values inherit the previous WELSPECS event for the well, initially the well's completion settings. Only one WELSPECS event per well and timestamp is allowed. `timeline.set_timestamp()` writes the values in effect back to the completion settings.
| Attribute | Required | Maps to |
|---|---|---|
| `GROUP` | no | `group_name` |
| `CROSSFLOW` | no | `allow_cross_flow`: `True` or `False` |
| `REFDEPTH` | no | `reference_depth` [m] |
| `PHASE` | no | `well_type`: `OIL`, `GAS`, `WATER` or `LIQUID` |
| `PRESSURE_COMPONENTS` | no | `pressure_drop`: `H--`, `HF-` or `HFA`. Written to the WELSEGS header of the MSW keywords exported at or after the event date |
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.
### Well keyword events
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:
| Event type | Translation |
|---|---|
| `WCONHIST` | `VFP` → `VFP_TABLE` |
| `WELTARG` | `VALUE` → `NEW_VALUE` |
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:
```
SCHEDULE
@STARTUP RPTRST BASIC=2 FREQ=1
@STARTUP GRUPTREE CHILD=OP PARENT=FIELD
@STARTUP TUNING TSINIT=1 TSMAXZ=30 NEWTMX=12
```
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.
## Attributes and quoting
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:
```
FILTER="SOIL > 0.8 AND PERMX > 200"
```
## Filter expressions
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.
## Diagnostics
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:
```
Line 3: SET is not supported; declare a typed variable instead, e.g. DATE NAME = 2018-01-01
Line 5: Malformed WELL line: 'WELL 55_33-A-2' (well names containing special characters must be double-quoted, e.g. WELL "55_33-A-1")
Line 6: Unknown variable 'A1_STRTUP'; did you mean 'A1_STARTUP'?
Line 7: Variable 'RAMP' is a DURATION (declared line 3) but a DATE is required here
```
The validator CLI prints these and exits non-zero:
```
$ python3 -m rips.simulator_events simulator_events.events
simulator_events.events: OK (SIMEVENTS 1.1, units METRIC)
4 variable(s), 2 well block(s), 8 well event(s), 0 schedule event(s)
```
## Applying to a project
```python
import rips
resinsight = rips.Instance.find()
project = resinsight.project
well_path_coll = project.descendants(rips.WellPathCollection)[0]
timeline = well_path_coll.event_timeline()
report = rips.simulator_events.apply_simulator_events_file(
"simulator_events.events", timeline, project, case=case, on_unknown_well="warn"
)
print(report.events_applied, report.events_skipped, report.warnings, report.errors)
timeline.set_timestamp(timestamp="2024-12-24") # materialize completions up to a date
schedule_text = timeline.generate_schedule_text(
eclipse_case=case, export_msw_for_wells=timeline.wells_with_msw_events()
)
```
`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).
They also accept two policies, each `"warn"` (default), `"error"` or `"skip"`:
| Policy | Governs |
|---|---|
| `on_unknown_well` | A well name with no matching well path in the project |
| `on_unknown_event` | An event type that looks like a misspelled built-in |
The returned `ApplyReport` carries `events_applied`, `events_skipped`, `warnings` and `errors`.
### Querying events
Both the parsed document and the timeline can answer common questions directly, so clients do not
need to walk wells, groups and schedule events themselves. The filters are the same in both layers:
| Filter | Meaning |
|---|---|
| `start_date`, `end_date` | Inclusive date range. `None` leaves the bound open. A date without time of day is midnight. |
| `wells` | Only events for these wells. When given, schedule-level and `INSERT_DATE` events are excluded. |
| `event_types` | Event types or keyword names (`WCONHIST`, `RPTRST`, ...), case-insensitive. |
An empty list or `None` disables a filter. The filters are combined with AND.
On a `SimulatorEventsDocument` (pure Python, no ResInsight needed), wells and groups are given by name and
event types use the SIMEVENTS names (`SEGMENT`, `PERFORATION`, `INSERT_DATE`, ...). `groups` is an additional
owner filter. When both `wells` and `groups` are supplied, matching WELL and GROUP events are combined
(union), then filtered by date and event type:
```python
document = rips.simulator_events.parse_simulator_events_file("simulator_events.events")
document.events(wells=["OP_1"], event_types=["WCONHIST"]) # List[SimulatorEvent], sorted by date
document.event_dates(start_date=datetime.datetime(2024, 1, 1))
document.latest_event_date() # or earliest_event_date(); None when nothing matches
document.wells_with_events(event_types=["PERFORATION"]) # well names
document.wells_with_msw_events() # wells with SEGMENT or VALVE events
document.groups_with_events()
```
On a `WellEventTimeline`, wells are `WellPath` objects. Event types use the timeline names `SEGMENT`, `PERF`,
`VALVE`, `WSTATE`, `WTYPE`, `WELLSPEC`, `WCONTROL`, `KEYWORD`, `SCHEDULE_KEYWORD`, `RAW_TEXT` and `INSERT_DATE`.
Any other name is matched against keyword names. The queries always use the full timeline. `set_timestamp()` does
not limit them:
```python
timeline.event_dates(wells=[well_path]) # List[datetime]
timeline.set_timestamp(timestamp=timeline.latest_event_date().isoformat())
timeline.wells_with_events(start_date="2025-01-01") # List[WellPath]
schedule_text = timeline.generate_schedule_text(
eclipse_case=case, export_msw_for_wells=timeline.wells_with_msw_events()
)
```
`wells_with_msw_events()` includes wells with `SEGMENT` or `VALVE` events. It does not inspect
segment configuration on well paths: add MSW wells with only `PERFORATION` (`PERF` on the timeline)
events explicitly to `export_msw_for_wells`.
Timeline well queries resolve names within the well path collection. Matching names must be unique;
`wells_with_events()` and `wells_with_msw_events()` raise `ValueError` rather than choose between
well paths sharing a name.
### Generating a schedule without a grid
`eclipse_case` is optional in `generate_schedule_text()`. It accepts any Eclipse-type case, including Roff and
corner point cases. Without a case, the keywords that need grid geometry are skipped: `WELSPECS`, `COMPORD`,
`COMPDAT`, `COMPLUMP`, `WELSEGS`, `COMPSEGS`, `WSEGVALV` and `WSEGAICD`. `DATES`, well controls (`WCONHIST`,
`WCONINJH`, `WELTARG`, ...), schedule keywords and raw text are still emitted. This supports setups driven only by
production data, or by a simple event file that controls reporting and production constraints. A well that has
skipped keywords gets one `-- WARNING:` comment line in the schedule header, for example:
```text
-- WARNING: No grid case: COMPDAT/COMPLUMP skipped for well 'OP_1'
```
```python
schedule_text = timeline.generate_schedule_text() # no grid case
```
## Exporting a schedule from the GUI
The schedule generated from the well event timeline can be written to file without scripting.
Enable *Preferences -> System -> Experimental Features -> Simulator Events* (the same flag that
enables the import), then
use **Export Schedule from Events** from either:
- the context menu of the *Well Event Timeline* item (shown under *Wells* once the timeline has
events),
- the context menu of the *Wells* (well path collection) item, or
- *File -> Export*.
The only thing asked for is the file name (default `schedule.SCH` in the last used export folder).
Everything else uses fixed defaults:
| Setting | Value |
|---|---|
| Eclipse case | the case of the active 3D view, else the first Eclipse case of the project, else none (grid-dependent keywords are skipped, see above) |
| Multi-segment-well keywords | exported for every well with events |
| First date | written as a `-- Date: ...` comment instead of a `DATES` keyword |
| Formatting | aligned columns with a `--`-prefixed column header per keyword |
As in `generate_schedule_text()`, a `set_timestamp()`/*Apply events up to date* timestamp limits the
export to the events up to that date, while inserted dates (`INSERT_DATE`) are always emitted.
The written file name and the event count are logged to the message panel. An export without events
reports the reason in a dialog.
## Version history
- **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.