mirror of
https://github.com/OPM/ResInsight.git
synced 2026-09-03 20:53:13 -05:00
#14637 Orion Events: Rename report date directive
This commit is contained in:
committed by
Magne Sjaastad
parent
753cd65672
commit
d226e10e89
@@ -25,7 +25,7 @@ It demonstrates the full event coverage of the format:
|
||||
7. A GROUP-level MEMBER event expanded to one GRUPTREE record per member
|
||||
8. SCHEDULE-level keyword events not tied to a well: RPTRST, GRUPTREE, TUNING
|
||||
9. Multiline RAW_TEXT inserted at a chosen position without parsing its contents
|
||||
10. Recurring REPORT dates with explicit and implicit end dates, passed to
|
||||
10. Recurring INSERT_DATE directives with explicit and implicit end dates, passed to
|
||||
generate_schedule_text(additional_dates=...) as summary-report triggers
|
||||
11. Schedule metadata, COMPORD generation and aligned-column output
|
||||
|
||||
@@ -114,10 +114,10 @@ WTRACER
|
||||
/
|
||||
END_RAW_TEXT
|
||||
|
||||
# Recurring report dates become bare DATES keywords. The first series ends at
|
||||
# the last event; the second uses an explicit inclusive end date.
|
||||
REPORT STARTUP EVERY MONTH
|
||||
REPORT 2024-07-01 EVERY 3 MONTHS UNTIL STARTUP + 365
|
||||
# Recurring inserted dates become bare DATES keywords. The first series ends at
|
||||
# the last event; the second uses an explicit inclusive end date.
|
||||
INSERT_DATE STARTUP EVERY MONTH
|
||||
INSERT_DATE 2024-07-01 EVERY 3 MONTHS UNTIL STARTUP + 365
|
||||
"""
|
||||
|
||||
|
||||
@@ -177,7 +177,7 @@ def main():
|
||||
)
|
||||
print(f" Events applied: {report.events_applied}")
|
||||
print(f" Events skipped: {report.events_skipped}")
|
||||
print(f" Report dates: {report.report_dates}")
|
||||
print(f" Inserted dates: {report.report_dates}")
|
||||
for warning in report.warnings:
|
||||
print(f" WARNING: {warning}")
|
||||
for error in report.errors:
|
||||
@@ -210,7 +210,7 @@ def main():
|
||||
if case is None:
|
||||
print(" No Eclipse case loaded - skipping schedule generation.")
|
||||
return
|
||||
# REPORT dates become bare DATES keywords via additional_dates. Aligned output
|
||||
# INSERT_DATE values become bare DATES keywords via additional_dates. Aligned output
|
||||
# adds column-title comments; the schedule header identifies its timestamp and
|
||||
# user, and each generated WELSPECS record has a matching COMPORD INPUT record.
|
||||
schedule_text = timeline.generate_schedule_text(
|
||||
|
||||
@@ -19,10 +19,11 @@ FILTER POROPERM = "PORO > 0.1 AND PERMX > 100.0"
|
||||
|
||||
WELL A1 = "55_33-A-1"
|
||||
|
||||
# Report dates become bare DATES keywords in the generated schedule. Recurring
|
||||
# dates may use an explicit inclusive end date or default to the last event.
|
||||
REPORT A1_STARTUP EVERY MONTH
|
||||
REPORT A2_STARTUP EVERY 30 DAYS UNTIL A2_STARTUP + 90
|
||||
# Inserted dates become bare DATES keywords in the generated schedule. Recurring
|
||||
# dates may use an explicit inclusive end date or default to the last event.
|
||||
SCHEDULE
|
||||
INSERT_DATE A1_STARTUP EVERY MONTH
|
||||
INSERT_DATE A2_STARTUP EVERY 30 DAYS UNTIL A2_STARTUP + 90
|
||||
|
||||
# Raw schedule text is preserved verbatim. This block is inserted immediately
|
||||
# after the date declaration; lower priorities are emitted first when multiple
|
||||
|
||||
@@ -22,11 +22,11 @@ File format grammar, version 2.0 (EBNF-ish)::
|
||||
|
||||
document = header , { statement } ;
|
||||
header = "ORIONEVENTS" , "2.0" ; (* first meaningful line *)
|
||||
statement = unit_directive | declaration | report_line | well_block_open
|
||||
statement = unit_directive | declaration | insert_date_line | well_block_open
|
||||
| group_block_open | schedule_block_open | event_line
|
||||
| raw_text_event ;
|
||||
unit_directive = "UNIT" , ( "METRIC" | "FIELD" | "LAB" ) ;
|
||||
report_line = "REPORT" , date_expr , [ recurrence ] ;
|
||||
insert_date_line = "INSERT_DATE" , date_expr , [ recurrence ] ;
|
||||
recurrence = "EVERY" , [ positive_integer ] , period ,
|
||||
[ "UNTIL" , date_expr ] ;
|
||||
period = "DAY" | "DAYS" | "MONTH" | "MONTHS" | "YEAR" | "YEARS" ;
|
||||
@@ -68,7 +68,7 @@ Notes on the grammar:
|
||||
|
||||
* The format is line-oriented; every non-blank line is dispatched on its first
|
||||
token: ``ORIONEVENTS`` (once), ``UNIT``, ``DATE``, ``DURATION``, ``WELL``,
|
||||
``GROUP``, ``SCHEDULE``, ``REPORT`` or an event date. Anything else is an
|
||||
``GROUP``, ``SCHEDULE``, ``INSERT_DATE`` or an event date. Anything else is an
|
||||
error. Keywords are
|
||||
uppercase and case-sensitive (the ``DAYS`` suffix is also accepted as ``days``).
|
||||
* Comments start with ``#`` (outside of double quotes) and run to end of line.
|
||||
@@ -94,8 +94,8 @@ Notes on the grammar:
|
||||
record per unique member, with the enclosing group as parent. A schedule may
|
||||
contain one attribute-free ``RESTART`` event; it truncates generated schedule
|
||||
output before its timestamp and is not itself emitted as a keyword.
|
||||
* ``REPORT <date_expr>`` (anywhere after the header) names a date that should
|
||||
appear as a bare ``DATES`` keyword in the generated schedule even when no
|
||||
* ``INSERT_DATE <date_expr>`` inside a ``SCHEDULE`` block names a date that
|
||||
should appear as a bare ``DATES`` keyword in the generated schedule even when no
|
||||
events fall on it — in Eclipse/Flow a ``DATES`` entry ensures a summary
|
||||
report at that date. ``EVERY [n] DAYS|MONTHS|YEARS`` makes it recurring and
|
||||
an inclusive ``UNTIL <date_expr>`` sets the end date. When ``UNTIL`` is
|
||||
@@ -105,7 +105,7 @@ Notes on the grammar:
|
||||
surfaced by the applier as sorted ISO strings on
|
||||
:attr:`ApplyReport.report_dates`, ready to pass to
|
||||
``WellEventTimeline.generate_schedule_text(additional_dates=...)``. A
|
||||
``REPORT`` line is not tied to any well and does not close an open block.
|
||||
``INSERT_DATE`` line is not tied to any well.
|
||||
* ``RAW_TEXT`` is valid only inside a ``SCHEDULE`` block. Its body is copied
|
||||
without parsing or formatting through the mandatory standalone
|
||||
``END_RAW_TEXT`` line. ``PLACEMENT`` is ``AFTER_DATE``, ``BEFORE_KEYWORD``,
|
||||
@@ -328,7 +328,7 @@ class GroupBlock:
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class _ReportSpec:
|
||||
"""One REPORT declaration, expanded after all event dates are known."""
|
||||
"""One INSERT_DATE declaration, expanded after all event dates are known."""
|
||||
|
||||
start: Union[datetime.date, datetime.datetime]
|
||||
interval: Optional[int]
|
||||
@@ -407,7 +407,7 @@ _KEYWORDS = (
|
||||
"FILTER",
|
||||
"GROUP",
|
||||
"SCHEDULE",
|
||||
"REPORT",
|
||||
"INSERT_DATE",
|
||||
)
|
||||
|
||||
_IDENT = r"[A-Za-z_]\w*"
|
||||
@@ -423,8 +423,8 @@ _DURATION_DECL_RE = re.compile(
|
||||
r"(?:\s+(?:DAYS|days))?$"
|
||||
)
|
||||
_WELL_DECL_RE = re.compile(rf'^WELL\s+(?P<name>{_IDENT})\s*=\s*"(?P<well>[^"]*)"$')
|
||||
_REPORT_RE = re.compile(
|
||||
rf"^REPORT\s+{_DATE_BASE}{_TERMS}"
|
||||
_INSERT_DATE_RE = re.compile(
|
||||
rf"^INSERT_DATE\s+{_DATE_BASE}{_TERMS}"
|
||||
rf"(?:\s+EVERY\s+(?:(?P<count>\d+)\s+)?"
|
||||
rf"(?P<period>DAY|DAYS|MONTH|MONTHS|YEAR|YEARS)"
|
||||
rf"(?:\s+UNTIL\s+(?P<end_base>{_ISO_DATE}|{_IDENT})"
|
||||
@@ -624,7 +624,7 @@ def _expand_report_specs(
|
||||
|
||||
if spec.interval is None or spec.interval <= 0:
|
||||
issues.append(
|
||||
ParseIssue("REPORT interval must be greater than zero", spec.loc)
|
||||
ParseIssue("INSERT_DATE interval must be greater than zero", spec.loc)
|
||||
)
|
||||
continue
|
||||
|
||||
@@ -632,14 +632,16 @@ def _expand_report_specs(
|
||||
if end is None:
|
||||
issues.append(
|
||||
ParseIssue(
|
||||
"Recurring REPORT without UNTIL requires at least one event",
|
||||
"Recurring INSERT_DATE without UNTIL requires at least one event",
|
||||
spec.loc,
|
||||
)
|
||||
)
|
||||
continue
|
||||
if _as_datetime(end) < _as_datetime(spec.start):
|
||||
issues.append(
|
||||
ParseIssue("REPORT end date must not precede its start date", spec.loc)
|
||||
ParseIssue(
|
||||
"INSERT_DATE end date must not precede its start date", spec.loc
|
||||
)
|
||||
)
|
||||
continue
|
||||
|
||||
@@ -790,12 +792,14 @@ def _parse_line(
|
||||
)
|
||||
return schedule_events
|
||||
|
||||
if first == "REPORT":
|
||||
match = _REPORT_RE.match(line)
|
||||
if first == "INSERT_DATE":
|
||||
if current_events is not schedule_events:
|
||||
raise OrionParseError("INSERT_DATE is only valid in a SCHEDULE block", loc)
|
||||
match = _INSERT_DATE_RE.match(line)
|
||||
if match is None:
|
||||
raise OrionParseError(
|
||||
f"Malformed REPORT line: {line!r} "
|
||||
"(expected REPORT <date-expr> [EVERY [count] "
|
||||
f"Malformed INSERT_DATE line: {line!r} "
|
||||
"(expected INSERT_DATE <date-expr> [EVERY [count] "
|
||||
"DAYS|MONTHS|YEARS [UNTIL <date-expr>]])",
|
||||
loc,
|
||||
)
|
||||
@@ -905,6 +909,13 @@ def _parse_line(
|
||||
wells.append(new_well)
|
||||
return new_well.events
|
||||
|
||||
if first == "REPORT":
|
||||
raise OrionParseError(
|
||||
"REPORT has been renamed to INSERT_DATE and is only valid in a "
|
||||
"SCHEDULE block",
|
||||
loc,
|
||||
)
|
||||
|
||||
if current_events is not None and _EVENT_RE.match(line):
|
||||
current_events.append(_parse_event_line(line, variables, loc))
|
||||
return current_events
|
||||
@@ -1601,7 +1612,7 @@ def apply_orion_document(
|
||||
event types are passed through as generic Eclipse keywords.
|
||||
|
||||
Returns:
|
||||
ApplyReport: counts plus collected warnings/errors. ``REPORT`` dates
|
||||
ApplyReport: counts plus collected warnings/errors. ``INSERT_DATE`` dates
|
||||
from the document are returned as sorted, deduplicated ISO strings
|
||||
on ``report_dates`` — they do not create timeline events; pass them
|
||||
to ``timeline.generate_schedule_text(additional_dates=...)`` to
|
||||
|
||||
@@ -627,8 +627,9 @@ class TestParsing:
|
||||
text = (
|
||||
"ORIONEVENTS 2.0\n"
|
||||
"DATE START = 2024-01-01\n"
|
||||
"REPORT 2024-06-01\n"
|
||||
"REPORT START + 31\n"
|
||||
"SCHEDULE\n"
|
||||
"INSERT_DATE 2024-06-01\n"
|
||||
"INSERT_DATE START + 31\n"
|
||||
)
|
||||
doc = parse_orion_events(text)
|
||||
assert doc.report_dates == [
|
||||
@@ -637,31 +638,31 @@ class TestParsing:
|
||||
]
|
||||
|
||||
def test_report_keeps_duplicates_and_file_order(self):
|
||||
text = "ORIONEVENTS 2.0\nREPORT 2024-06-01\nREPORT 2024-06-01\n"
|
||||
text = (
|
||||
"ORIONEVENTS 2.0\nSCHEDULE\n"
|
||||
"INSERT_DATE 2024-06-01\nINSERT_DATE 2024-06-01\n"
|
||||
)
|
||||
doc = parse_orion_events(text)
|
||||
assert doc.report_dates == [datetime.date(2024, 6, 1)] * 2
|
||||
|
||||
def test_report_inside_block_does_not_close_it(self):
|
||||
text = (
|
||||
'ORIONEVENTS 2.0\nWELL "W"\n'
|
||||
" 2024-01-01 WCONHIST STATUS=OPEN\n"
|
||||
"REPORT 2024-06-01\n"
|
||||
" 2024-02-01 WELTARG CMODE=ORAT VALUE=5000\n"
|
||||
)
|
||||
doc = parse_orion_events(text)
|
||||
assert [len(w.events) for w in doc.wells] == [2]
|
||||
assert doc.report_dates == [datetime.date(2024, 6, 1)]
|
||||
def test_insert_date_is_only_valid_in_schedule_block(self):
|
||||
with pytest.raises(OrionParseError, match="only valid in a SCHEDULE"):
|
||||
parse_orion_events("ORIONEVENTS 2.0\nINSERT_DATE 2024-06-01\n")
|
||||
|
||||
def test_legacy_report_keyword_is_rejected(self):
|
||||
with pytest.raises(OrionParseError, match="renamed to INSERT_DATE"):
|
||||
parse_orion_events("ORIONEVENTS 2.0\nSCHEDULE\nREPORT 2024-06-01\n")
|
||||
|
||||
def test_report_with_undeclared_variable_raises(self):
|
||||
with pytest.raises(OrionParseError, match="NOPE"):
|
||||
parse_orion_events("ORIONEVENTS 2.0\nREPORT NOPE + 1\n")
|
||||
parse_orion_events("ORIONEVENTS 2.0\nSCHEDULE\nINSERT_DATE NOPE + 1\n")
|
||||
|
||||
def test_malformed_report_line_raises(self):
|
||||
with pytest.raises(OrionParseError, match="Malformed REPORT line"):
|
||||
parse_orion_events("ORIONEVENTS 2.0\nREPORT\n")
|
||||
with pytest.raises(OrionParseError, match="Malformed INSERT_DATE line"):
|
||||
parse_orion_events("ORIONEVENTS 2.0\nSCHEDULE\nINSERT_DATE\n")
|
||||
|
||||
def test_report_with_datetime_literal(self):
|
||||
text = "ORIONEVENTS 2.0\nREPORT 2024-06-01T14:45:30.500\n"
|
||||
text = "ORIONEVENTS 2.0\nSCHEDULE\nINSERT_DATE 2024-06-01T14:45:30.500\n"
|
||||
doc = parse_orion_events(text)
|
||||
assert doc.report_dates == [datetime.datetime(2024, 6, 1, 14, 45, 30, 500000)]
|
||||
|
||||
@@ -670,7 +671,8 @@ class TestParsing:
|
||||
"ORIONEVENTS 2.0\n"
|
||||
"DATE START = 2024-01-01\n"
|
||||
"DATE END = 2024-01-05\n"
|
||||
"REPORT START + 1 EVERY 2 DAYS UNTIL END\n"
|
||||
"SCHEDULE\n"
|
||||
"INSERT_DATE START + 1 EVERY 2 DAYS UNTIL END\n"
|
||||
)
|
||||
doc = parse_orion_events(text)
|
||||
assert doc.report_dates == [
|
||||
@@ -679,7 +681,10 @@ class TestParsing:
|
||||
]
|
||||
|
||||
def test_monthly_report_recurrence_is_anchored_to_initial_day(self):
|
||||
text = "ORIONEVENTS 2.0\nREPORT 2024-01-31 EVERY MONTH UNTIL 2024-04-30\n"
|
||||
text = (
|
||||
"ORIONEVENTS 2.0\nSCHEDULE\n"
|
||||
"INSERT_DATE 2024-01-31 EVERY MONTH UNTIL 2024-04-30\n"
|
||||
)
|
||||
doc = parse_orion_events(text)
|
||||
assert doc.report_dates == [
|
||||
datetime.date(2024, 1, 31),
|
||||
@@ -689,7 +694,10 @@ class TestParsing:
|
||||
]
|
||||
|
||||
def test_yearly_report_recurrence_clamps_leap_day(self):
|
||||
text = "ORIONEVENTS 2.0\nREPORT 2024-02-29 EVERY YEAR UNTIL 2028-02-29\n"
|
||||
text = (
|
||||
"ORIONEVENTS 2.0\nSCHEDULE\n"
|
||||
"INSERT_DATE 2024-02-29 EVERY YEAR UNTIL 2028-02-29\n"
|
||||
)
|
||||
doc = parse_orion_events(text)
|
||||
assert doc.report_dates == [
|
||||
datetime.date(2024, 2, 29),
|
||||
@@ -702,7 +710,8 @@ class TestParsing:
|
||||
def test_report_recurrence_without_until_uses_last_event(self):
|
||||
text = (
|
||||
"ORIONEVENTS 2.0\n"
|
||||
"REPORT 2024-01-01 EVERY MONTH\n"
|
||||
"SCHEDULE\n"
|
||||
"INSERT_DATE 2024-01-01 EVERY MONTH\n"
|
||||
'WELL "W"\n'
|
||||
" 2024-03-15 WCONHIST STATUS=OPEN\n"
|
||||
)
|
||||
@@ -716,7 +725,8 @@ class TestParsing:
|
||||
def test_recurring_report_preserves_datetime(self):
|
||||
text = (
|
||||
"ORIONEVENTS 2.0\n"
|
||||
"REPORT 2024-06-01T14:45:30.500 EVERY DAY "
|
||||
"SCHEDULE\n"
|
||||
"INSERT_DATE 2024-06-01T14:45:30.500 EVERY DAY "
|
||||
"UNTIL 2024-06-02T14:45:30.500\n"
|
||||
)
|
||||
doc = parse_orion_events(text)
|
||||
@@ -728,14 +738,17 @@ class TestParsing:
|
||||
@pytest.mark.parametrize(
|
||||
("report_line", "message"),
|
||||
[
|
||||
("REPORT 2024-01-01 EVERY 0 DAYS UNTIL 2024-01-02", "greater than zero"),
|
||||
("REPORT 2024-01-02 EVERY DAY UNTIL 2024-01-01", "must not precede"),
|
||||
("REPORT 2024-01-01 EVERY DAY", "requires at least one event"),
|
||||
(
|
||||
"INSERT_DATE 2024-01-01 EVERY 0 DAYS UNTIL 2024-01-02",
|
||||
"greater than zero",
|
||||
),
|
||||
("INSERT_DATE 2024-01-02 EVERY DAY UNTIL 2024-01-01", "must not precede"),
|
||||
("INSERT_DATE 2024-01-01 EVERY DAY", "requires at least one event"),
|
||||
],
|
||||
)
|
||||
def test_invalid_report_recurrence_rejected(self, report_line, message):
|
||||
with pytest.raises(OrionParseError, match=message):
|
||||
parse_orion_events(f"ORIONEVENTS 2.0\n{report_line}\n")
|
||||
parse_orion_events(f"ORIONEVENTS 2.0\nSCHEDULE\n{report_line}\n")
|
||||
|
||||
def test_event_dates_parse_without_prefix(self):
|
||||
text = (
|
||||
@@ -1325,9 +1338,10 @@ class TestApplying:
|
||||
text = (
|
||||
'ORIONEVENTS 2.0\nWELL "55_33-A-1"\n'
|
||||
" 2018-01-01 WCONHIST STATUS=OPEN\n"
|
||||
"REPORT 2018-07-01\n"
|
||||
"REPORT 2018-03-01\n"
|
||||
"REPORT 2018-07-01\n"
|
||||
"SCHEDULE\n"
|
||||
"INSERT_DATE 2018-07-01\n"
|
||||
"INSERT_DATE 2018-03-01\n"
|
||||
"INSERT_DATE 2018-07-01\n"
|
||||
)
|
||||
timeline, report = self._apply(text)
|
||||
# Sorted, deduplicated ISO strings ready for
|
||||
@@ -2112,9 +2126,9 @@ class TestOrionEventsIntegration:
|
||||
" 2024-01-01 WCONHIST STATUS=OPEN CMODE=ORAT ORAT=100\n"
|
||||
" 2024-02-01 WCONHIST STATUS=OPEN CMODE=ORAT ORAT=200\n"
|
||||
" 2024-03-01 WCONHIST STATUS=OPEN CMODE=ORAT ORAT=300\n"
|
||||
"REPORT 2024-01-15\n"
|
||||
"REPORT 2024-04-01\n"
|
||||
"SCHEDULE\n"
|
||||
"INSERT_DATE 2024-01-15\n"
|
||||
"INSERT_DATE 2024-04-01\n"
|
||||
" 2024-02-01 RESTART\n"
|
||||
)
|
||||
|
||||
@@ -2139,7 +2153,8 @@ class TestOrionEventsIntegration:
|
||||
def test_report_only_document_generates_schedule(self, project_with_case_and_wells):
|
||||
project, case, timeline = project_with_case_and_wells
|
||||
document = parse_orion_events(
|
||||
"ORIONEVENTS 2.0\nREPORT 2024-02-01\nREPORT 2024-06-01\n"
|
||||
"ORIONEVENTS 2.0\nSCHEDULE\n"
|
||||
"INSERT_DATE 2024-02-01\nINSERT_DATE 2024-06-01\n"
|
||||
)
|
||||
|
||||
report = apply_orion_document(document, timeline, project)
|
||||
@@ -2169,7 +2184,8 @@ class TestOrionEventsIntegration:
|
||||
" START PERFORATION MDSTART=2000 MDEND=2200 RADIUS=0.05 SKIN=0.5 COMPLETION_NUMBER=1\n"
|
||||
" START + RAMP WCONHIST STATUS=OPEN CMODE=ORAT VFP=1\n"
|
||||
" START + RAMP WELTARG CMODE=BHP VALUE=50\n"
|
||||
"REPORT 2024-07-01\n"
|
||||
"SCHEDULE\n"
|
||||
"INSERT_DATE 2024-07-01\n"
|
||||
)
|
||||
document = parse_orion_events(text)
|
||||
report = apply_orion_document(document, timeline, project)
|
||||
@@ -2187,7 +2203,7 @@ class TestOrionEventsIntegration:
|
||||
assert abs(perf.end_measured_depth - 2200.0) < 1.0
|
||||
|
||||
# The generated schedule should carry the mapped keywords, and the
|
||||
# REPORT date should appear as a bare DATES entry (issue #14514).
|
||||
# INSERT_DATE date should appear as a bare DATES entry (issue #14514).
|
||||
schedule = timeline.generate_schedule_text(
|
||||
eclipse_case=case,
|
||||
export_msw_for_wells=[],
|
||||
@@ -2197,7 +2213,7 @@ class TestOrionEventsIntegration:
|
||||
assert "WCONHIST" in schedule
|
||||
assert "WELTARG" in schedule
|
||||
assert "1 'JUL' 2024" in schedule, (
|
||||
"REPORT date should be emitted as a DATES entry"
|
||||
"INSERT_DATE date should be emitted as a DATES entry"
|
||||
)
|
||||
|
||||
def test_apply_full_event_coverage_and_schedule(self, project_with_case_and_wells):
|
||||
|
||||
Reference in New Issue
Block a user