#14688 Sim Events: Add API to query event dates and wells

Add event date and well queries with date, owner and event type/keyword
filters to SimulatorEventsDocument and WellEventTimeline, including
latest_event_date() and wells_with_msw_events() for building the
export_msw_for_wells list.
This commit is contained in:
Kristian Bendiksen committed 2026-10-19 12:00:28 +02:00
1 parent 360c3c69db
commit 8dc1d56bd3
11 files changed
+903 -3

No files matched your search

@@ -219,6 +219,50 @@ QDateTime RimWellEventTimeline::lastAppliedTimestamp() const
return m_lastAppliedTimestamp;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::vector<RimWellEvent*> RimWellEventTimeline::queryEvents( const EventQuery& query ) const
{
auto keywordName = []( const RimWellEvent* event ) -> QString
{
if ( auto keywordEvent = dynamic_cast<const RimWellEventKeyword*>( event ) ) return keywordEvent->keywordName().toUpper();
if ( auto scheduleKeywordEvent = dynamic_cast<const RimKeywordEvent*>( event ) )
return scheduleKeywordEvent->keywordName().toUpper();
return {};
};
auto matchesType = [&]( const RimWellEvent* event )
{
if ( query.eventTypes.empty() && query.keywords.empty() ) return true;
if ( query.eventTypes.contains( event->eventType() ) ) return true;
if ( query.keywords.empty() ) return false;
const QString keyword = keywordName( event );
return !keyword.isEmpty() && query.keywords.contains( keyword );
};
std::vector<RimWellEvent*> result;
for ( auto& event : m_events )
{
if ( !event || !event->eventDate().isValid() ) continue;
const QDateTime date = event->eventDate();
if ( query.startDate.isValid() && date < query.startDate ) continue;
if ( query.endDate.isValid() && date > query.endDate ) continue;
if ( !query.wellPaths.empty() && !query.wellPaths.contains( event->wellPath() ) ) continue;
if ( !matchesType( event ) ) continue;
result.push_back( event );
}
std::stable_sort( result.begin(),
result.end(),
[]( const RimWellEvent* a, const RimWellEvent* b ) { return a->eventDate() < b->eventDate(); } );
return result;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -26,6 +26,9 @@
#include "cafPdmPtrArrayField.h"
#include <QDateTime>
#include <QString>
#include <set>
#include <vector>
class RimWellEventPerf;
@@ -59,6 +62,16 @@ public:
TYPE
};
// Filter for queryEvents(). Invalid dates leave the range open, empty sets disable the filter.
struct EventQuery
{
QDateTime startDate;
QDateTime endDate;
std::set<const RimWellPath*> wellPaths;
std::set<RimWellEvent::EventType> eventTypes;
std::set<QString> keywords; // Upper case keyword names
};
RimWellEventTimeline();
~RimWellEventTimeline() override;
@@ -73,6 +86,9 @@ public:
std::vector<RimWellPath*> getWellPathsWithEvents() const;
std::vector<RimWellPath*> getWellPathsWithEventsUpToDate( const QDateTime& date ) const;
// Events matching all filters in the query, sorted by date. Ignores the applied timestamp.
std::vector<RimWellEvent*> queryEvents( const EventQuery& query ) const;
// Add event methods (return the created event for further configuration)
RimWellEventPerf* addPerforationEvent( RimWellPath* wellPath, const QDateTime& date );
RimWellEventValve* addValveEvent( RimWellPath* wellPath, const QDateTime& date );
@@ -48,6 +48,52 @@
namespace
{
//--------------------------------------------------------------------------------------------------
/// Builds a timeline query from the scripting arguments. Empty dates leave the range open. Each event
/// type is either an event type name (SEGMENT, PERF, ...) or a keyword name (WCONHIST, ...).
//--------------------------------------------------------------------------------------------------
std::expected<RimWellEventTimeline::EventQuery, QString> createEventQuery( const QString& startDate,
const QString& endDate,
const std::vector<RimWellPath*>& wellPaths,
const std::vector<QString>& eventTypes )
{
RimWellEventTimeline::EventQuery query;
auto parseDate = []( const QString& text, QDateTime& date ) -> std::expected<void, QString>
{
if ( text.trimmed().isEmpty() ) return {};
date = QDateTime::fromString( text.trimmed(), Qt::ISODate );
if ( !date.isValid() ) return std::unexpected( QString( "Invalid date format: %1. Expected YYYY-MM-DD" ).arg( text ) );
return {};
};
if ( auto result = parseDate( startDate, query.startDate ); !result ) return std::unexpected( result.error() );
if ( auto result = parseDate( endDate, query.endDate ); !result ) return std::unexpected( result.error() );
for ( auto wellPath : wellPaths )
{
if ( wellPath ) query.wellPaths.insert( wellPath );
}
for ( const auto& eventType : eventTypes )
{
const QString name = eventType.trimmed().toUpper();
if ( name.isEmpty() ) continue;
if ( caf::AppEnum<RimWellEvent::EventType>::isValid( name ) )
{
query.eventTypes.insert( caf::AppEnum<RimWellEvent::EventType>::fromText( name ) );
}
else
{
query.keywords.insert( name );
}
}
return query;
}
std::expected<void, QString> validateKeywordItems( const QString& keywordName, const std::vector<QString>& itemNames )
{
auto keywordInfo = RiaOpmKeywordTools::keywordInfo( keywordName );
@@ -898,3 +944,134 @@ QString RimcWellEventTimeline_generateSchedule::classKeywordReturnedType() const
{
return RimcDataContainerString::classKeywordStatic();
}
CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimWellEventTimeline, RimcWellEventTimeline_eventDates, "QueryEventDates" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimcWellEventTimeline_eventDates::RimcWellEventTimeline_eventDates( caf::PdmObjectHandle* self )
: caf::PdmObjectMethod( self, PdmObjectMethod::NullPointerType::NULL_IS_INVALID, PdmObjectMethod::ResultType::PERSISTENT_FALSE )
{
CAF_PDM_InitObject( "Event Dates", "", "", "Sorted, unique dates of the events matching the filters, as ISO date-time strings" );
CAF_PDM_InitScriptableField( &m_startDate, "StartDate", QString(), "", "", "", "Earliest date to include (inclusive). Empty: no limit" );
CAF_PDM_InitScriptableField( &m_endDate,
"EndDate",
QString(),
"",
"",
"",
"Latest date to include (inclusive). A date-only value means midnight, excluding events later that day. "
"Empty: no limit" );
CAF_PDM_InitScriptableFieldNoDefault( &m_wellPaths,
"WellPaths",
"",
"",
"",
"Only include events for these wells. Empty: events for all wells and schedule events" );
CAF_PDM_InitScriptableFieldNoDefault( &m_eventTypes,
"EventTypes",
"",
"",
"",
"Event types (SEGMENT, PERF, VALVE, ...) or keyword names (WCONHIST, ...). Empty: all events" );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::expected<caf::PdmObjectHandle*, QString> RimcWellEventTimeline_eventDates::execute()
{
auto timeline = self<RimWellEventTimeline>();
auto query = createEventQuery( m_startDate(), m_endDate(), m_wellPaths.ptrReferencedObjectsByType(), m_eventTypes() );
if ( !query ) return std::unexpected( query.error() );
std::set<QDateTime> dates;
for ( auto event : timeline->queryEvents( *query ) )
{
dates.insert( event->eventDate() );
}
// Explicit format without a time zone suffix, as the event dates are stored in local time
std::vector<QString> dateTexts;
for ( const auto& date : dates )
{
dateTexts.push_back( date.toString( "yyyy-MM-ddTHH:mm:ss" ) );
}
auto* dataObject = new RimcDataContainerString();
dataObject->m_stringValues = dateTexts;
return dataObject;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimcWellEventTimeline_eventDates::classKeywordReturnedType() const
{
return RimcDataContainerString::classKeywordStatic();
}
CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimWellEventTimeline, RimcWellEventTimeline_wellsWithEvents, "QueryWellsWithEvents" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimcWellEventTimeline_wellsWithEvents::RimcWellEventTimeline_wellsWithEvents( caf::PdmObjectHandle* self )
: caf::PdmObjectMethod( self, PdmObjectMethod::NullPointerType::NULL_IS_INVALID, PdmObjectMethod::ResultType::PERSISTENT_FALSE )
{
CAF_PDM_InitObject( "Wells With Events",
"",
"",
"Sorted names of the well paths with events matching the filters. "
"Well path names must be unique within the well path collection" );
CAF_PDM_InitScriptableField( &m_startDate, "StartDate", QString(), "", "", "", "Earliest date to include (inclusive). Empty: no limit" );
CAF_PDM_InitScriptableField( &m_endDate,
"EndDate",
QString(),
"",
"",
"",
"Latest date to include (inclusive). A date-only value means midnight, excluding events later that day. "
"Empty: no limit" );
CAF_PDM_InitScriptableFieldNoDefault( &m_eventTypes,
"EventTypes",
"",
"",
"",
"Event types (SEGMENT, PERF, VALVE, ...) or keyword names (WCONHIST, ...). Empty: all events" );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::expected<caf::PdmObjectHandle*, QString> RimcWellEventTimeline_wellsWithEvents::execute()
{
auto timeline = self<RimWellEventTimeline>();
auto query = createEventQuery( m_startDate(), m_endDate(), {}, m_eventTypes() );
if ( !query ) return std::unexpected( query.error() );
std::set<QString> wellNames;
for ( auto event : timeline->queryEvents( *query ) )
{
if ( event->wellPath() ) wellNames.insert( event->wellPath()->name() );
}
auto* dataObject = new RimcDataContainerString();
dataObject->m_stringValues = std::vector<QString>( wellNames.begin(), wellNames.end() );
return dataObject;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimcWellEventTimeline_wellsWithEvents::classKeywordReturnedType() const
{
return RimcDataContainerString::classKeywordStatic();
}
@@ -300,3 +300,42 @@ private:
caf::PdmField<bool> m_firstDateAsComment;
caf::PdmField<bool> m_alignColumns;
};
//==================================================================================================
///
//==================================================================================================
class RimcWellEventTimeline_eventDates : public caf::PdmObjectMethod
{
CAF_PDM_HEADER_INIT;
public:
RimcWellEventTimeline_eventDates( caf::PdmObjectHandle* self );
std::expected<caf::PdmObjectHandle*, QString> execute() override;
QString classKeywordReturnedType() const override;
private:
caf::PdmField<QString> m_startDate;
caf::PdmField<QString> m_endDate;
caf::PdmPtrArrayField<RimWellPath*> m_wellPaths;
caf::PdmField<std::vector<QString>> m_eventTypes;
};
//==================================================================================================
///
//==================================================================================================
class RimcWellEventTimeline_wellsWithEvents : public caf::PdmObjectMethod
{
CAF_PDM_HEADER_INIT;
public:
RimcWellEventTimeline_wellsWithEvents( caf::PdmObjectHandle* self );
std::expected<caf::PdmObjectHandle*, QString> execute() override;
QString classKeywordReturnedType() const override;
private:
caf::PdmField<QString> m_startDate;
caf::PdmField<QString> m_endDate;
caf::PdmField<std::vector<QString>> m_eventTypes;
};