mirror of
https://github.com/OPM/ResInsight.git
synced 2026-09-03 20:53:13 -05:00
#14615 Orion Events: Preserve historical keyword values
This commit is contained in:
@@ -17,8 +17,9 @@ It demonstrates the full event coverage of the format:
|
||||
materialized as a case-level combined data filter
|
||||
4. COMMENT attributes preserved on timeline events and emitted before their
|
||||
generated schedule keywords
|
||||
5. Same-owner/type/date WCONHIST lines merged into one event, while same-date
|
||||
perforations remain separate
|
||||
5. Same-owner/type/date WCONHIST lines merged with conflict diagnostics, and
|
||||
historical keyword values carried forward to later partial events, while
|
||||
same-date perforations remain separate
|
||||
6. Well keyword events: WCONHIST and WELTARG (with attribute translation) and
|
||||
WRFTPLT (generic Eclipse well keyword pass-through)
|
||||
7. A GROUP-level MEMBER event expanded to one GRUPTREE record per member
|
||||
@@ -80,11 +81,16 @@ WELL W1
|
||||
@2024-03-01 VALVE MD=2100 TYPE=ICV STATE=OPEN CV=0.7 AREA=0.0001
|
||||
@2024-02-15 STATE STATE=OPEN
|
||||
|
||||
# Matching owner/type/date lines merge. The second line extends the first;
|
||||
# repeated attributes would use the value from the later line.
|
||||
@2024-01-15 WCONHIST STATUS=OPEN CMODE=RESV COMMENT="Start production history controls"
|
||||
# Matching owner/type/date lines merge. The second line extends the first.
|
||||
# Conflicting GRAT values produce a warning, and the later value wins.
|
||||
@2024-01-15 WCONHIST STATUS=OPEN CMODE=RESV GRAT=4756545.5 COMMENT="Start production history controls"
|
||||
@2024-01-15 WCONHIST ORAT=3999.99 WRAT=0.01 GRAT=550678.44 VFP=1
|
||||
|
||||
# Later partial keyword events inherit historical values for the same well
|
||||
# and keyword. This event overrides WRAT and inherits STATUS, CMODE, ORAT,
|
||||
# GRAT and VFP from January 15; COMMENT is event-local and is not inherited.
|
||||
@2024-01-20 WCONHIST WRAT=0.03
|
||||
|
||||
# WRFTPLT is passed through as a generic Eclipse keyword.
|
||||
@2024-05-01 WELTARG CMODE=ORAT VALUE=5000.0
|
||||
@2024-06-01 WRFTPLT OUTPUT_RFT=YES OUTPUT_PLT=NO OUTPUT_SEGMENT=NO
|
||||
|
||||
@@ -1381,6 +1381,7 @@ _NON_COALESCING_EVENT_TYPES = {
|
||||
"VALVE",
|
||||
"WELSPECS",
|
||||
}
|
||||
_NON_HISTORICAL_KEYWORD_ATTRS = {"COMMENT", "FILTER"}
|
||||
|
||||
|
||||
def coalesce_orion_document(document: OrionDocument) -> OrionDocument:
|
||||
@@ -1388,16 +1389,23 @@ def coalesce_orion_document(document: OrionDocument) -> OrionDocument:
|
||||
|
||||
Keyword events with the same owner, type and timestamp are merged. The first
|
||||
event retains its position, and attributes from later matching events are
|
||||
applied in source order. Events that create or expand domain objects are
|
||||
kept separate so, for example, same-date perforation intervals are not lost.
|
||||
applied in source order. Well keyword attributes are then carried forward
|
||||
chronologically to later events of the same type. Events that create or
|
||||
expand domain objects are kept separate so, for example, same-date
|
||||
perforation intervals are not lost.
|
||||
"""
|
||||
result = copy.deepcopy(document)
|
||||
|
||||
def merge_events(events: List[OrionEvent]) -> List[OrionEvent]:
|
||||
def merge_events(
|
||||
events: List[OrionEvent], *, inherit_history: bool = False
|
||||
) -> List[OrionEvent]:
|
||||
merged: List[OrionEvent] = []
|
||||
by_key: Dict[
|
||||
Tuple[Union[datetime.date, datetime.datetime], str], OrionEvent
|
||||
] = {}
|
||||
attribute_locs: Dict[
|
||||
Tuple[Union[datetime.date, datetime.datetime], str], Dict[str, SourceLoc]
|
||||
] = {}
|
||||
for event in events:
|
||||
event_type = event.event_type.upper()
|
||||
if event_type in _NON_COALESCING_EVENT_TYPES:
|
||||
@@ -1408,13 +1416,50 @@ def coalesce_orion_document(document: OrionDocument) -> OrionDocument:
|
||||
existing = by_key.get(key)
|
||||
if existing is None:
|
||||
by_key[key] = event
|
||||
attribute_locs[key] = {name: event.loc for name in event.attributes}
|
||||
merged.append(event)
|
||||
continue
|
||||
|
||||
repeated = (
|
||||
set(existing.attributes)
|
||||
& set(event.attributes) - _NON_HISTORICAL_KEYWORD_ATTRS
|
||||
)
|
||||
for name in sorted(repeated):
|
||||
previous = existing.attributes[name]
|
||||
replacement = event.attributes[name]
|
||||
if previous.value != replacement.value:
|
||||
result.warnings.append(
|
||||
ParseWarning(
|
||||
f"{_event_context(event)}: conflicting {event_type} "
|
||||
f"attribute '{name}' (previous value on line "
|
||||
f"{attribute_locs[key][name].line}); using "
|
||||
f"{replacement.raw!r}",
|
||||
event.loc,
|
||||
)
|
||||
)
|
||||
|
||||
existing.attributes.update(event.attributes)
|
||||
attribute_locs[key].update({name: event.loc for name in event.attributes})
|
||||
if "FILTER" in event.attributes:
|
||||
existing.filter = event.filter
|
||||
existing.loc = event.loc
|
||||
|
||||
if inherit_history:
|
||||
history: Dict[str, Dict[str, AttrValue]] = {}
|
||||
for event in sorted(merged, key=lambda item: _as_datetime(item.event_date)):
|
||||
event_type = event.event_type.upper()
|
||||
if event_type in _NON_COALESCING_EVENT_TYPES:
|
||||
continue
|
||||
|
||||
attributes = history.get(event_type, {}).copy()
|
||||
attributes.update(event.attributes)
|
||||
event.attributes = attributes
|
||||
history[event_type] = {
|
||||
name: value
|
||||
for name, value in attributes.items()
|
||||
if name not in _NON_HISTORICAL_KEYWORD_ATTRS
|
||||
}
|
||||
|
||||
return merged
|
||||
|
||||
def merge_well_blocks(blocks: List[WellBlock]) -> List[WellBlock]:
|
||||
@@ -1429,7 +1474,7 @@ def coalesce_orion_document(document: OrionDocument) -> OrionDocument:
|
||||
merged_blocks.append(block)
|
||||
by_name[block.well_name].events.extend(block_events)
|
||||
for block in merged_blocks:
|
||||
block.events = merge_events(block.events)
|
||||
block.events = merge_events(block.events, inherit_history=True)
|
||||
return merged_blocks
|
||||
|
||||
def merge_group_blocks(blocks: List[GroupBlock]) -> List[GroupBlock]:
|
||||
@@ -1568,6 +1613,9 @@ def apply_orion_document(
|
||||
document = coalesce_orion_document(document)
|
||||
report = ApplyReport()
|
||||
report.report_dates = sorted({d.isoformat() for d in document.report_dates})
|
||||
report.warnings.extend(
|
||||
f"Line {warning.loc.line}: {warning.message}" for warning in document.warnings
|
||||
)
|
||||
|
||||
ctx = _prepare_filter_context(document, project, case)
|
||||
|
||||
@@ -2241,7 +2289,8 @@ def _cli(argv: Optional[List[str]] = None) -> int:
|
||||
f"{len(document.schedule_events)} schedule event(s), "
|
||||
f"{len(document.report_dates)} report date(s)"
|
||||
)
|
||||
for warning in document.warnings:
|
||||
normalized = coalesce_orion_document(document)
|
||||
for warning in normalized.warnings:
|
||||
print(f" Warning line {warning.loc.line}: {warning.message}")
|
||||
return 0
|
||||
|
||||
|
||||
@@ -1046,6 +1046,69 @@ class TestApplying:
|
||||
assert timeline.keyword_calls[0]["keyword_data"]["ORAT"] == 100
|
||||
assert timeline.keyword_calls[0]["keyword_data"]["CMODE"] == "ORAT"
|
||||
|
||||
def test_well_keyword_history_is_inherited_chronologically(self):
|
||||
text = (
|
||||
"ORIONEVENTS 2.0\n"
|
||||
'WELL "55_33-A-1"\n'
|
||||
" @2024-01-20 WCONHIST WRAT=0.03\n"
|
||||
' @2024-01-15 WCONHIST STATUS=OPEN CMODE=RESV GRAT=4756545.5 COMMENT="Startup"\n'
|
||||
" @2024-01-15 WCONHIST ORAT=3999.99 WRAT=0.01 GRAT=550678.44 VFP=1\n"
|
||||
" @2024-01-15 WELTARG CMODE=ORAT VALUE=5000\n"
|
||||
'WELL "55_33-A-2"\n'
|
||||
" @2024-01-20 WCONHIST WRAT=0.04\n"
|
||||
)
|
||||
document = parse_orion_events(text)
|
||||
merged = coalesce_orion_document(document)
|
||||
|
||||
first_well_events = merged.wells[0].events
|
||||
january_15 = next(
|
||||
event
|
||||
for event in first_well_events
|
||||
if event.event_type.upper() == "WCONHIST"
|
||||
and event.event_date.isoformat() == "2024-01-15"
|
||||
)
|
||||
january_20 = next(
|
||||
event
|
||||
for event in first_well_events
|
||||
if event.event_type.upper() == "WCONHIST"
|
||||
and event.event_date.isoformat() == "2024-01-20"
|
||||
)
|
||||
|
||||
assert january_15.attributes["GRAT"].value == 550678.44
|
||||
assert january_15.attributes["COMMENT"].value == "Startup"
|
||||
assert january_20.attributes["STATUS"].value == "OPEN"
|
||||
assert january_20.attributes["CMODE"].value == "RESV"
|
||||
assert january_20.attributes["ORAT"].value == 3999.99
|
||||
assert january_20.attributes["WRAT"].value == 0.03
|
||||
assert january_20.attributes["GRAT"].value == 550678.44
|
||||
assert january_20.attributes["VFP"].value == 1
|
||||
assert "COMMENT" not in january_20.attributes
|
||||
|
||||
# Historical state is isolated by owner and keyword type.
|
||||
assert set(merged.wells[1].events[0].attributes) == {"WRAT"}
|
||||
weltarg = next(
|
||||
event
|
||||
for event in first_well_events
|
||||
if event.event_type.upper() == "WELTARG"
|
||||
)
|
||||
assert set(weltarg.attributes) == {"CMODE", "VALUE"}
|
||||
|
||||
assert len(merged.warnings) == 1
|
||||
warning = merged.warnings[0]
|
||||
assert warning.loc.line == 5
|
||||
assert "conflicting WCONHIST attribute 'GRAT'" in warning.message
|
||||
assert "previous value on line 4" in warning.message
|
||||
assert "using '550678.44'" in warning.message
|
||||
|
||||
timeline, report = self._apply(text)
|
||||
assert report.events_applied == 4
|
||||
assert len(report.warnings) == 1
|
||||
assert "conflicting WCONHIST attribute 'GRAT'" in report.warnings[0]
|
||||
january_20_call = timeline.keyword_calls[0]
|
||||
assert january_20_call["event_date"] == "2024-01-20"
|
||||
assert january_20_call["keyword_data"]["STATUS"] == "OPEN"
|
||||
assert january_20_call["keyword_data"]["WRAT"] == 0.03
|
||||
|
||||
def test_same_date_perforations_are_not_merged(self):
|
||||
text = (
|
||||
"ORIONEVENTS 2.0\n"
|
||||
|
||||
Reference in New Issue
Block a user