Skip to content

Commit 6daeeb5

Browse files
committed
fmuobs Support observation with localization metadata
1 parent 1a14d51 commit 6daeeb5

3 files changed

Lines changed: 216 additions & 5 deletions

File tree

src/subscript/fmuobs/parsers.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -358,15 +358,20 @@ def flatten_observation_unit(
358358

359359
for subunit in subunit_keys:
360360
if len(subunit.split()) < 2:
361-
# It must be two strings, like "OBS P1", or "SEGMENT FIRST_YEAR".
362-
raise ValueError("Wrong observation subunit syntax: " + str(subunit))
361+
# Single-word subunit keys (e.g. LOCALIZATION) have no label and
362+
# are not representable in the internal dataframe format; skip them.
363+
logger.debug("Ignoring unlabeled subunit block: %s", subunit)
364+
continue
363365
obs_subunits.append(
364366
{
365367
subunit.split()[0]: subunit.split()[1],
366368
**keyvalues,
367369
**obsunit[subunit],
368370
}
369371
)
372+
if not obs_subunits:
373+
# All subunits were single-word (unlabeled); treat as a plain observation.
374+
return [keyvalues]
370375
return obs_subunits
371376

372377

tests/test_fmuobs.py

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ def fixture_readonly_testdata_dir(monkeypatch):
3333
("ert-doc.csv", "csv"),
3434
("fmu-ensemble-obs.yml", "yaml"),
3535
("drogon_wbhp_rft_wct_gor_tracer_4d.obs", "ert"),
36+
("drogon_wbhp_rft_wct_gor_tracer_plt_local.obs", "ert"),
3637
],
3738
)
3839
def test_autoparse_file(filename, expected_format, readonly_testdata_dir):
@@ -161,6 +162,7 @@ def test_roundtrip_ertobs(filename, readonly_testdata_dir):
161162
("ert-doc.csv"),
162163
("fmu-ensemble-obs.yml"),
163164
("drogon_wbhp_rft_wct_gor_tracer_4d.obs"),
165+
("drogon_wbhp_rft_wct_gor_tracer_plt_local.obs"),
164166
],
165167
)
166168
def test_roundtrip_yaml(filename, readonly_testdata_dir):
@@ -206,6 +208,7 @@ def test_roundtrip_yaml(filename, readonly_testdata_dir):
206208
("ert-doc.csv"),
207209
("fmu-ensemble-obs.yml"),
208210
("drogon_wbhp_rft_wct_gor_tracer_4d.obs"),
211+
("drogon_wbhp_rft_wct_gor_tracer_plt_local.obs"),
209212
],
210213
)
211214
def test_roundtrip_resinsight(filename, readonly_testdata_dir):
@@ -228,19 +231,87 @@ def test_roundtrip_resinsight(filename, readonly_testdata_dir):
228231

229232
# LABEL is not part of the ResInsight format, and a made-up label
230233
# is obtained through the roundtrip (when importing back). Skip it
231-
# when comparing.
234+
# when comparing. ERROR_MODE is also not preserved in ResInsight format.
232235

233236
pd.testing.assert_frame_equal(
234237
ri_roundtrip_dframe.sort_index(axis="columns").drop(
235-
["LABEL", "COMMENT", "SUBCOMMENT"], axis="columns", errors="ignore"
238+
["LABEL", "COMMENT", "SUBCOMMENT", "ERROR_MODE"], axis="columns", errors="ignore"
236239
),
237240
dframe.sort_index(axis="columns").drop(
238-
["LABEL", "COMMENT", "SUBCOMMENT"], axis="columns", errors="ignore"
241+
["LABEL", "COMMENT", "SUBCOMMENT", "ERROR_MODE"], axis="columns", errors="ignore"
239242
),
240243
check_like=True,
241244
)
242245

243246

247+
248+
def test_drogon_plt_local_localization_ignored(readonly_testdata_dir):
249+
"""Test that LOCALIZATION sub-blocks inside SUMMARY_OBSERVATION are silently
250+
ignored, and that the rest of the observation file is parsed correctly.
251+
252+
The file contains PLT, RFT, WBHP, WWCT, WGOR, and tracer observations,
253+
with LOCALIZATION {EAST=...; NORTH=...; RADIUS=...;} blocks that are not
254+
representable in the internal dataframe format."""
255+
filename = "drogon_wbhp_rft_wct_gor_tracer_plt_local.obs"
256+
dframe = autoparse_file(filename)[1]
257+
258+
assert not dframe.empty
259+
assert "LOCALIZATION" not in dframe.columns
260+
assert set(dframe["CLASS"].unique()) == {
261+
"SUMMARY_OBSERVATION",
262+
"GENERAL_OBSERVATION",
263+
"RFT_OBSERVATION",
264+
}
265+
266+
# SUMMARY_OBSERVATION rows should have the expected key columns
267+
smry_df = dframe[dframe["CLASS"] == "SUMMARY_OBSERVATION"]
268+
assert not smry_df.empty
269+
for col in ["KEY", "VALUE", "ERROR", "DATE"]:
270+
assert col in smry_df.columns
271+
assert smry_df[col].notna().all()
272+
273+
# Roundtrip through ERT obs format for the classes df2ertobs supports
274+
supported_classes = {"SUMMARY_OBSERVATION", "GENERAL_OBSERVATION"}
275+
dframe_supported = dframe[dframe["CLASS"].isin(supported_classes)]
276+
ertobs_str = df2ertobs(dframe_supported)
277+
roundtrip_dframe = ertobs2df(ertobs_str).set_index("CLASS")
278+
dframe_supported = dframe_supported.set_index("CLASS")
279+
280+
for class_ in dframe_supported.index.unique():
281+
roundtrip_subframe = (
282+
roundtrip_dframe.loc[[class_]].dropna(axis=1, how="all").sort_index(axis=1)
283+
)
284+
subframe = (
285+
dframe_supported.loc[[class_]]
286+
.dropna(axis=1, how="all")
287+
.sort_index(axis=1)
288+
.set_index(
289+
list(
290+
{"LABEL", "OBS", "SEGMENT"}.intersection(
291+
set(dframe_supported.columns)
292+
)
293+
)
294+
)
295+
.sort_index()
296+
# ERROR_MODE and comments are not preserved through the ertobs roundtrip
297+
.drop(
298+
["COMMENT", "SUBCOMMENT", "ERROR_MODE"], axis="columns", errors="ignore"
299+
)
300+
)
301+
roundtrip_subframe = roundtrip_subframe.set_index(
302+
list(
303+
{"LABEL", "OBS", "SEGMENT"}.intersection(
304+
set(roundtrip_subframe.columns)
305+
)
306+
)
307+
).sort_index()
308+
pd.testing.assert_frame_equal(
309+
roundtrip_subframe,
310+
subframe,
311+
check_dtype=False,
312+
)
313+
314+
244315
@pytest.mark.integration
245316
def test_integration():
246317
"""Test that the endpoint is installed"""

0 commit comments

Comments
 (0)