Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ApplicationLibCode/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,7 @@ set(UNITY_EXCLUDE_FILES
# https://cmake.org/cmake/help/latest/prop_tgt/AUTOGEN_BUILD_DIR.html
${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}_autogen/mocs_compilation.cpp
ProjectDataModel/RiaOpmParserTools.cpp
ProjectDataModel/RiaOpmKeywordTools.cpp
FileInterface/RifOsduWellPathReader.cpp
FileInterface/RifOsduWellLogReader.cpp
FileInterface/RifByteArrayArrowRandomAccessFile.cpp
Expand Down
1 change: 1 addition & 0 deletions ApplicationLibCode/ProjectDataModel/CMakeLists_files.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ set(SOURCE_GROUP_SOURCE_FILES
${CMAKE_CURRENT_LIST_DIR}/RimColorLegend.cpp
${CMAKE_CURRENT_LIST_DIR}/RimColorLegendItem.cpp
${CMAKE_CURRENT_LIST_DIR}/RiaOpmParserTools.cpp
${CMAKE_CURRENT_LIST_DIR}/RiaOpmKeywordTools.cpp
${CMAKE_CURRENT_LIST_DIR}/RimCaseDisplayNameTools.cpp
${CMAKE_CURRENT_LIST_DIR}/RimCustomObjectiveFunctionCollection.cpp
${CMAKE_CURRENT_LIST_DIR}/RimCustomObjectiveFunction.cpp
Expand Down
74 changes: 74 additions & 0 deletions ApplicationLibCode/ProjectDataModel/RiaOpmKeywordTools.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026- Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
// A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////

#include "RiaOpmKeywordTools.h"

#include "opm/input/eclipse/Parser/Parser.hpp"
#include "opm/input/eclipse/Parser/ParserItem.hpp"
#include "opm/input/eclipse/Parser/ParserKeyword.hpp"
#include "opm/input/eclipse/Parser/ParserRecord.hpp"

#include <iterator>
#include <set>

namespace
{
const Opm::Parser& sharedParser()
{
static const Opm::Parser parser( true );
return parser;
}
} // namespace

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::optional<RiaOpmKeywordInfo> RiaOpmKeywordTools::keywordInfo( const QString& keywordName )
{
const QString keyword = keywordName.toUpper();
const auto& parser = sharedParser();
const auto deckName = keyword.toStdString();

if ( !parser.isRecognizedKeyword( deckName ) ) return std::nullopt;

const auto& parserKeyword = parser.getParserKeywordFromDeckName( deckName );

RiaOpmKeywordInfo info;
info.name = QString::fromStdString( parserKeyword.getName() );

const auto recordCount = static_cast<size_t>( std::distance( parserKeyword.begin(), parserKeyword.end() ) );
if ( recordCount == 1 )
{
const auto& record = parserKeyword.getRecord( 0 );
info.acceptsArbitraryItems = record.size() == 1 && record.get( 0 ).sizeType() == Opm::ParserItem::item_size::ALL;
}

std::set<std::string> seenNames;
for ( const auto& record : parserKeyword )
{
for ( const auto& item : record )
{
if ( seenNames.insert( item.name() ).second )
{
info.itemNames.push_back( QString::fromStdString( item.name() ) );
}
}
}

return info;
}
36 changes: 36 additions & 0 deletions ApplicationLibCode/ProjectDataModel/RiaOpmKeywordTools.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2026- Equinor ASA
//
// ResInsight is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// ResInsight is distributed in the hope that it will be useful, but WITHOUT ANY
// WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
// A PARTICULAR PURPOSE.
//
// See the GNU General Public License at <http://www.gnu.org/licenses/gpl.html>
// for more details.
//
/////////////////////////////////////////////////////////////////////////////////

#pragma once

#include <QString>

#include <optional>
#include <vector>

struct RiaOpmKeywordInfo
{
QString name;
std::vector<QString> itemNames;
bool acceptsArbitraryItems = false;
};

namespace RiaOpmKeywordTools
{
std::optional<RiaOpmKeywordInfo> keywordInfo( const QString& keywordName );
} // namespace RiaOpmKeywordTools
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ RimWellEventPerf* RimWellEventTimeline::addPerforationEvent( RimWellPath* wellPa
event->setWellPath( wellPath );
event->setEventDate( date );
m_events.push_back( event );
updateConnectedEditors();
updateEditorsAfterEventChange();
return event;
}

Expand All @@ -238,7 +238,7 @@ RimWellEventValve* RimWellEventTimeline::addValveEvent( RimWellPath* wellPath, c
event->setWellPath( wellPath );
event->setEventDate( date );
m_events.push_back( event );
updateConnectedEditors();
updateEditorsAfterEventChange();
return event;
}

Expand All @@ -251,7 +251,7 @@ RimWellEventTubing* RimWellEventTimeline::addTubingEvent( RimWellPath* wellPath,
event->setWellPath( wellPath );
event->setEventDate( date );
m_events.push_back( event );
updateConnectedEditors();
updateEditorsAfterEventChange();
return event;
}

Expand All @@ -264,7 +264,7 @@ RimWellEventState* RimWellEventTimeline::addStateEvent( RimWellPath* wellPath, c
event->setWellPath( wellPath );
event->setEventDate( date );
m_events.push_back( event );
updateConnectedEditors();
updateEditorsAfterEventChange();
return event;
}

Expand All @@ -277,7 +277,7 @@ RimWellEventType* RimWellEventTimeline::addTypeEvent( RimWellPath* wellPath, con
event->setWellPath( wellPath );
event->setEventDate( date );
m_events.push_back( event );
updateConnectedEditors();
updateEditorsAfterEventChange();
return event;
}

Expand All @@ -290,7 +290,7 @@ RimWellEventControl* RimWellEventTimeline::addControlEvent( RimWellPath* wellPat
event->setWellPath( wellPath );
event->setEventDate( date );
m_events.push_back( event );
updateConnectedEditors();
updateEditorsAfterEventChange();
return event;
}

Expand All @@ -304,7 +304,7 @@ RimWellEventKeyword* RimWellEventTimeline::addWellKeywordEvent( RimWellPath* wel
event->setEventDate( date );
event->setKeywordName( keywordName );
m_events.push_back( event );
updateConnectedEditors();
updateEditorsAfterEventChange();
return event;
}

Expand All @@ -318,7 +318,7 @@ RimKeywordEvent* RimWellEventTimeline::addKeywordEvent( const QDateTime& date, c
event->setEventDate( date );
event->setKeywordName( keywordName );
m_events.push_back( event );
updateConnectedEditors();
updateEditorsAfterEventChange();
return event;
}

Expand All @@ -330,7 +330,7 @@ void RimWellEventTimeline::addEvent( RimWellEvent* event )
if ( event )
{
m_events.push_back( event );
updateConnectedEditors();
updateEditorsAfterEventChange();
}
}

Expand All @@ -341,7 +341,7 @@ void RimWellEventTimeline::removeEvent( RimWellEvent* event )
{
m_events.removeChild( event );
delete event;
updateConnectedEditors();
updateEditorsAfterEventChange();
}

//--------------------------------------------------------------------------------------------------
Expand All @@ -350,7 +350,20 @@ void RimWellEventTimeline::removeEvent( RimWellEvent* event )
void RimWellEventTimeline::clearAllEvents()
{
m_events.deleteChildren();
updateEditorsAfterEventChange();
}

//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
void RimWellEventTimeline::updateEditorsAfterEventChange()
{
updateConnectedEditors();

if ( auto* wellPathCollection = firstAncestorOrThisOfType<RimWellPathCollection>() )
{
wellPathCollection->updateConnectedEditors();
}
}

//--------------------------------------------------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ class RimWellEventTimeline : public caf::PdmObject
bool applyValveEvent( RimWellEventValve& event, RimWellPath& wellPath );

std::vector<RimWellEvent*> filteredAndSortedEventsForUi() const;
void updateEditorsAfterEventChange();

caf::PdmChildArrayField<RimWellEvent*> m_events;
QDateTime m_lastAppliedTimestamp;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "RimcWellEventTimeline.h"

#include "CompletionExportCommands/RicScheduleDataGenerator.h"
#include "RiaOpmKeywordTools.h"
#include "RimEclipseCase.h"
#include "RimKeywordEvent.h"
#include "RimWellEventControl.h"
Expand All @@ -36,10 +37,45 @@
#include "cafPdmFieldScriptingCapability.h"

#include <QDateTime>
#include <QStringList>

#include <algorithm>
#include <set>

namespace
{
std::expected<void, QString> validateKeywordItems( const QString& keywordName, const std::vector<QString>& itemNames )
{
auto keywordInfo = RiaOpmKeywordTools::keywordInfo( keywordName );
if ( !keywordInfo )
{
return std::unexpected( QString( "Keyword '%1' is not recognized by opm-common." ).arg( keywordName ) );
}

if ( keywordInfo->acceptsArbitraryItems ) return {};

QStringList invalidNames;
for ( const auto& itemName : itemNames )
{
if ( std::find( keywordInfo->itemNames.begin(), keywordInfo->itemNames.end(), itemName ) == keywordInfo->itemNames.end() )
{
invalidNames.push_back( itemName );
}
}

if ( invalidNames.empty() ) return {};

QStringList validNames;
for ( const auto& itemName : keywordInfo->itemNames )
{
validNames.push_back( itemName );
}

return std::unexpected( QString( "Keyword '%1' contains invalid item names: %2. Valid item names are: %3." )
.arg( keywordName.toUpper(), invalidNames.join( ", " ), validNames.join( ", " ) ) );
}
} // namespace

CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimWellEventTimeline, RimcWellEventTimeline_addPerfEvent, "AddPerfEvent" );

//--------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -373,6 +409,12 @@ std::expected<caf::PdmObjectHandle*, QString> RimcWellEventTimeline_addWellKeywo
return std::unexpected( QString( "Item arrays must have same length" ) );
}

auto validationResult = validateKeywordItems( m_keywordName(), m_itemNames() );
if ( !validationResult )
{
return std::unexpected( validationResult.error() );
}

// Create event
auto* event = timeline->addWellKeywordEvent( m_wellPath(), date, m_keywordName() );

Expand Down Expand Up @@ -469,6 +511,12 @@ std::expected<caf::PdmObjectHandle*, QString> RimcWellEventTimeline_addKeywordEv
return std::unexpected( QString( "Item arrays must have same length" ) );
}

auto validationResult = validateKeywordItems( m_keywordName(), m_itemNames() );
if ( !validationResult )
{
return std::unexpected( validationResult.error() );
}

// Create event (note: no wellPath parameter)
auto* event = timeline->addKeywordEvent( date, m_keywordName() );

Expand Down Expand Up @@ -592,6 +640,13 @@ RimcWellEventTimeline_generateSchedule::RimcWellEventTimeline_generateSchedule(
"",
"",
"Emit a column-header comment and right-aligned, fixed-width columns instead of the compact form" );
CAF_PDM_InitScriptableFieldNoDefault( &m_additionalDates,
"AdditionalDates",
"",
"",
"",
"Additional dates (YYYY-MM-DD or full ISO timestamp) emitted as DATES keywords, e.g. to "
"force summary reports at those dates" );
}

//--------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -641,6 +696,24 @@ std::expected<caf::PdmObjectHandle*, QString> RimcWellEventTimeline_generateSche
return std::unexpected( QString( "No well paths with events found" ) );
}

// Merge in user-specified additional dates: each becomes a DATES keyword even when no events
// fall on it (e.g. to force a summary report). They are deliberately not filtered by the last
// applied timestamp.
if ( !m_additionalDates().empty() )
{
std::set<QDateTime> mergedDates( dates.begin(), dates.end() );
for ( const QString& dateString : m_additionalDates() )
{
QDateTime additionalDate = QDateTime::fromString( dateString, Qt::ISODate );
if ( !additionalDate.isValid() )
{
return std::unexpected( QString( "Invalid date format: %1. Expected YYYY-MM-DD" ).arg( dateString ) );
}
mergedDates.insert( additionalDate );
Comment thread
kriben marked this conversation as resolved.
}
dates.assign( mergedDates.begin(), mergedDates.end() );
Comment thread
kriben marked this conversation as resolved.
}

std::vector<RimWellPath*> mswWellPaths = m_exportMswForWells.ptrReferencedObjectsByType();
std::set<const RimWellPath*> mswWells( mswWellPaths.begin(), mswWellPaths.end() );

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -231,4 +231,5 @@ class RimcWellEventTimeline_generateSchedule : public caf::PdmObjectMethod
caf::PdmPtrArrayField<RimWellPath*> m_exportMswForWells;
caf::PdmField<bool> m_firstDateAsComment;
caf::PdmField<bool> m_alignColumns;
caf::PdmField<std::vector<QString>> m_additionalDates;
};
Loading