Files
ResInsight/ApplicationLibCode/ProjectDataModelCommands/RimcWellEventTimeline.cpp
T
Kristian Bendiksen 8dc1d56bd3 #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.
2026-10-19 12:00:28 +02:00

1078 lines
49 KiB
C++

/////////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2025- 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 "RimcWellEventTimeline.h"
#include "CompletionExportCommands/RicScheduleDataGenerator.h"
#include "RiaOpmKeywordTools.h"
#include "RimEclipseCase.h"
#include "RimKeywordEvent.h"
#include "RimWellEventControl.h"
#include "RimWellEventInsertDate.h"
#include "RimWellEventKeyword.h"
#include "RimWellEventPerf.h"
#include "RimWellEventRawText.h"
#include "RimWellEventSegment.h"
#include "RimWellEventState.h"
#include "RimWellEventTimeline.h"
#include "RimWellEventValve.h"
#include "RimWellEventWellSpec.h"
#include "RimWellPath.h"
#include "RimWellPathCompletionSettings.h"
#include "RimcDataContainerString.h"
#include "cafAppEnum.h"
#include "cafPdmAbstractFieldScriptingCapability.h"
#include "cafPdmFieldScriptingCapability.h"
#include <QDateTime>
#include <QStringList>
#include <algorithm>
#include <set>
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 );
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 );
}
// COMMENT is accepted on all keywords. It is stored on the event and emitted as a schedule comment instead of being
// passed as keyword data, so it is not part of the item names reported by opm-common. Include it in the list of
// valid names to help spot typos like COMMENTS.
const QString commentItemName( "COMMENT" );
if ( !validNames.contains( commentItemName ) ) validNames.push_back( commentItemName );
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" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimcWellEventTimeline_addPerfEvent::RimcWellEventTimeline_addPerfEvent( caf::PdmObjectHandle* self )
: caf::PdmObjectCreationMethod( self )
{
CAF_PDM_InitObject( "Add Perforation Event", "", "", "Add a perforation event to the timeline" );
CAF_PDM_InitScriptableField( &m_eventDate, "EventDate", QString( "2024-01-01" ), "", "", "", "Event Date (YYYY-MM-DD)" );
CAF_PDM_InitScriptableFieldNoDefault( &m_wellPath, "WellPath", "", "", "", "Well Path" );
CAF_PDM_InitScriptableField( &m_startMd, "StartMd", 0.0, "", "", "", "Start Measured Depth" );
CAF_PDM_InitScriptableField( &m_endMd, "EndMd", 0.0, "", "", "", "End Measured Depth" );
CAF_PDM_InitScriptableField( &m_diameter, "Diameter", 0.216, "", "", "", "Diameter [m]" );
CAF_PDM_InitScriptableField( &m_skinFactor, "SkinFactor", 0.0, "", "", "", "Skin Factor" );
auto defaultState = RimWellEventPerf::State::OPEN;
CAF_PDM_InitScriptableField( &m_state, "State", defaultState, "", "", "", "State" );
CAF_PDM_InitScriptableField( &m_completionNumber, "CompletionNumber", 0, "", "", "", "Completion Number (for COMPLUMP, 0 = none)" );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::expected<caf::PdmObjectHandle*, QString> RimcWellEventTimeline_addPerfEvent::execute()
{
auto timeline = self<RimWellEventTimeline>();
if ( !m_wellPath() )
{
return std::unexpected( QString( "Well path is required" ) );
}
QDateTime date = QDateTime::fromString( m_eventDate(), Qt::ISODate );
if ( !date.isValid() )
{
return std::unexpected( QString( "Invalid date format: %1. Expected YYYY-MM-DD" ).arg( m_eventDate() ) );
}
auto* event = timeline->addPerforationEvent( m_wellPath(), date );
event->setStartMD( m_startMd() );
event->setEndMD( m_endMd() );
event->setDiameter( m_diameter() );
event->setSkinFactor( m_skinFactor() );
event->setState( m_state() );
event->setCompletionNumber( m_completionNumber() );
return event;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimcWellEventTimeline_addPerfEvent::classKeywordReturnedType() const
{
return RimWellEventPerf::classKeywordStatic();
}
CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimWellEventTimeline, RimcWellEventTimeline_addValveEvent, "AddValveEvent" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimcWellEventTimeline_addValveEvent::RimcWellEventTimeline_addValveEvent( caf::PdmObjectHandle* self )
: caf::PdmObjectCreationMethod( self )
{
CAF_PDM_InitObject( "Add Valve Event", "", "", "Add a valve event to the timeline" );
CAF_PDM_InitScriptableField( &m_eventDate, "EventDate", QString( "2024-01-01" ), "", "", "", "Event Date (YYYY-MM-DD)" );
CAF_PDM_InitScriptableFieldNoDefault( &m_wellPath, "WellPath", "", "", "", "Well Path" );
CAF_PDM_InitScriptableField( &m_measuredDepth, "MeasuredDepth", 0.0, "", "", "", "Measured Depth" );
auto defaultValveType = RimWellEventValve::ValveType::ICV;
CAF_PDM_InitScriptableField( &m_valveType, "ValveType", defaultValveType, "", "", "", "Valve Type" );
auto defaultState = RimWellEventValve::State::OPEN;
CAF_PDM_InitScriptableField( &m_state, "State", defaultState, "", "", "", "State" );
CAF_PDM_InitScriptableField( &m_flowCoefficient, "FlowCoefficient", 0.7, "", "", "", "Flow Coefficient" );
CAF_PDM_InitScriptableField( &m_area, "Area", 0.0001, "", "", "", "Area [m2]" );
CAF_PDM_InitScriptableField( &m_aicdStrength, "AicdStrength", 0.00021, "", "", "", "AICD Strength" );
CAF_PDM_InitScriptableField( &m_aicdDensityCalibFluid, "AicdDensityCalibFluid", 1000.0, "", "", "", "AICD Density of Calibration Fluid [kg/m3]" );
CAF_PDM_InitScriptableField( &m_aicdViscosityCalibFluid, "AicdViscosityCalibFluid", 1.0, "", "", "", "AICD Viscosity of Calibration Fluid [cP]" );
CAF_PDM_InitScriptableField( &m_aicdVolFlowExp, "AicdVolFlowExp", 2.1, "", "", "", "AICD Volume Flow Rate Exponent" );
CAF_PDM_InitScriptableField( &m_aicdViscFuncExp, "AicdViscFuncExp", 0.5, "", "", "", "AICD Viscosity Function Exponent" );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::expected<caf::PdmObjectHandle*, QString> RimcWellEventTimeline_addValveEvent::execute()
{
auto timeline = self<RimWellEventTimeline>();
if ( !m_wellPath() )
{
return std::unexpected( QString( "Well path is required" ) );
}
QDateTime date = QDateTime::fromString( m_eventDate(), Qt::ISODate );
if ( !date.isValid() )
{
return std::unexpected( QString( "Invalid date format: %1. Expected YYYY-MM-DD" ).arg( m_eventDate() ) );
}
auto* event = timeline->addValveEvent( m_wellPath(), date );
event->setMeasuredDepth( m_measuredDepth() );
event->setFlowCoefficient( m_flowCoefficient() );
event->setArea( m_area() );
event->setValveType( m_valveType() );
event->setState( m_state() );
event->setAicdStrength( m_aicdStrength() );
event->setAicdDensityCalibFluid( m_aicdDensityCalibFluid() );
event->setAicdViscosityCalibFluid( m_aicdViscosityCalibFluid() );
event->setAicdVolFlowExp( m_aicdVolFlowExp() );
event->setAicdViscFuncExp( m_aicdViscFuncExp() );
return event;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimcWellEventTimeline_addValveEvent::classKeywordReturnedType() const
{
return RimWellEventValve::classKeywordStatic();
}
CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimWellEventTimeline, RimcWellEventTimeline_addStateEvent, "AddStateEvent" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimcWellEventTimeline_addStateEvent::RimcWellEventTimeline_addStateEvent( caf::PdmObjectHandle* self )
: caf::PdmObjectCreationMethod( self )
{
CAF_PDM_InitObject( "Add State Event", "", "", "Add a well state event to the timeline" );
CAF_PDM_InitScriptableField( &m_eventDate, "EventDate", QString( "2024-01-01" ), "", "", "", "Event Date (YYYY-MM-DD)" );
CAF_PDM_InitScriptableFieldNoDefault( &m_wellPath, "WellPath", "", "", "", "Well Path" );
auto defaultWellState = RimWellEventState::WellState::OPEN;
CAF_PDM_InitScriptableField( &m_wellState, "WellState", defaultWellState, "", "", "", "Well State" );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::expected<caf::PdmObjectHandle*, QString> RimcWellEventTimeline_addStateEvent::execute()
{
auto timeline = self<RimWellEventTimeline>();
if ( !m_wellPath() )
{
return std::unexpected( QString( "Well path is required" ) );
}
QDateTime date = QDateTime::fromString( m_eventDate(), Qt::ISODate );
if ( !date.isValid() )
{
return std::unexpected( QString( "Invalid date format: %1. Expected YYYY-MM-DD" ).arg( m_eventDate() ) );
}
auto* event = timeline->addStateEvent( m_wellPath(), date );
event->setWellState( m_wellState() );
return event;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimcWellEventTimeline_addStateEvent::classKeywordReturnedType() const
{
return RimWellEventState::classKeywordStatic();
}
CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimWellEventTimeline, RimcWellEventTimeline_addControlEvent, "AddControlEvent" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimcWellEventTimeline_addControlEvent::RimcWellEventTimeline_addControlEvent( caf::PdmObjectHandle* self )
: caf::PdmObjectCreationMethod( self )
{
CAF_PDM_InitObject( "Add Control Event", "", "", "Add a well control event to the timeline" );
CAF_PDM_InitScriptableField( &m_eventDate, "EventDate", QString( "2024-01-01" ), "", "", "", "Event Date (YYYY-MM-DD)" );
CAF_PDM_InitScriptableFieldNoDefault( &m_wellPath, "WellPath", "", "", "", "Well Path" );
auto defaultControlMode = RimWellEventControl::ControlMode::ORAT;
CAF_PDM_InitScriptableField( &m_controlMode, "ControlMode", defaultControlMode, "", "", "", "Control Mode" );
CAF_PDM_InitScriptableField( &m_controlValue, "ControlValue", 0.0, "", "", "", "Control Value" );
CAF_PDM_InitScriptableField( &m_bhpLimit, "BhpLimit", 0.0, "", "", "", "BHP Limit [bar]" );
CAF_PDM_InitScriptableField( &m_oilRate, "OilRate", 0.0, "", "", "", "Oil Rate [Sm3/day]" );
CAF_PDM_InitScriptableField( &m_waterRate, "WaterRate", 0.0, "", "", "", "Water Rate [Sm3/day]" );
CAF_PDM_InitScriptableField( &m_gasRate, "GasRate", 0.0, "", "", "", "Gas Rate [Sm3/day]" );
CAF_PDM_InitScriptableField( &m_isProducer, "IsProducer", true, "", "", "", "Is Producer" );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::expected<caf::PdmObjectHandle*, QString> RimcWellEventTimeline_addControlEvent::execute()
{
auto timeline = self<RimWellEventTimeline>();
if ( !m_wellPath() )
{
return std::unexpected( QString( "Well path is required" ) );
}
QDateTime date = QDateTime::fromString( m_eventDate(), Qt::ISODate );
if ( !date.isValid() )
{
return std::unexpected( QString( "Invalid date format: %1. Expected YYYY-MM-DD" ).arg( m_eventDate() ) );
}
auto* event = timeline->addControlEvent( m_wellPath(), date );
event->setControlValue( m_controlValue() );
event->setBhpLimit( m_bhpLimit() );
event->setOilRate( m_oilRate() );
event->setWaterRate( m_waterRate() );
event->setGasRate( m_gasRate() );
event->setIsProducer( m_isProducer() );
event->setControlMode( m_controlMode() );
return event;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimcWellEventTimeline_addControlEvent::classKeywordReturnedType() const
{
return RimWellEventControl::classKeywordStatic();
}
CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimWellEventTimeline, RimcWellEventTimeline_addSegmentEvent, "AddSegmentEvent" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimcWellEventTimeline_addSegmentEvent::RimcWellEventTimeline_addSegmentEvent( caf::PdmObjectHandle* self )
: caf::PdmObjectCreationMethod( self )
{
CAF_PDM_InitObject( "Add Segment Event", "", "", "Add a segment event to the timeline" );
CAF_PDM_InitScriptableField( &m_eventDate, "EventDate", QString( "2024-01-01" ), "", "", "", "Event Date (YYYY-MM-DD)" );
CAF_PDM_InitScriptableFieldNoDefault( &m_wellPath, "WellPath", "", "", "", "Well Path" );
CAF_PDM_InitScriptableField( &m_startMd, "StartMd", 0.0, "", "", "", "Start Measured Depth" );
CAF_PDM_InitScriptableField( &m_endMd, "EndMd", 0.0, "", "", "", "End Measured Depth" );
CAF_PDM_InitScriptableField( &m_innerDiameter, "InnerDiameter", 0.15, "", "", "", "Inner Diameter [m]" );
CAF_PDM_InitScriptableField( &m_roughness, "Roughness", 1.0e-5, "", "", "", "Roughness [m]" );
CAF_PDM_InitScriptableField( &m_fixedSegmentLength, "FixedSegmentLength", 0.0, "", "", "", "Fixed Segment Length [m] (0 = not set)" );
CAF_PDM_InitScriptableField( &m_minSegmentLength, "MinSegmentLength", 0.0, "", "", "", "Min Segment Length [m] (0 = not set)" );
CAF_PDM_InitScriptableField( &m_maxSegmentLength, "MaxSegmentLength", 0.0, "", "", "", "Max Segment Length [m] (0 = not set)" );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::expected<caf::PdmObjectHandle*, QString> RimcWellEventTimeline_addSegmentEvent::execute()
{
auto timeline = self<RimWellEventTimeline>();
if ( !m_wellPath() )
{
return std::unexpected( QString( "Well path is required" ) );
}
QDateTime date = QDateTime::fromString( m_eventDate(), Qt::ISODate );
if ( !date.isValid() )
{
return std::unexpected( QString( "Invalid date format: %1. Expected YYYY-MM-DD" ).arg( m_eventDate() ) );
}
if ( m_fixedSegmentLength() < 0.0 || m_minSegmentLength() < 0.0 || m_maxSegmentLength() < 0.0 )
{
return std::unexpected( QString( "Segment lengths must be positive (0 = not set)" ) );
}
auto toOptional = []( double length ) { return length > 0.0 ? std::optional<double>( length ) : std::nullopt; };
const auto fixedLength = toOptional( m_fixedSegmentLength() );
const auto minLength = toOptional( m_minSegmentLength() );
const auto maxLength = toOptional( m_maxSegmentLength() );
if ( fixedLength && ( minLength || maxLength ) )
{
return std::unexpected( QString( "Fixed segment length cannot be combined with min/max segment length" ) );
}
if ( minLength && maxLength && *minLength > *maxLength )
{
return std::unexpected( QString( "Min segment length cannot exceed max segment length" ) );
}
auto* event = timeline->addSegmentEvent( m_wellPath(), date );
event->setStartMD( m_startMd() );
event->setEndMD( m_endMd() );
event->setInnerDiameter( m_innerDiameter() );
event->setRoughness( m_roughness() );
event->setFixedSegmentLength( fixedLength );
event->setMinSegmentLength( minLength );
event->setMaxSegmentLength( maxLength );
return event;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimcWellEventTimeline_addSegmentEvent::classKeywordReturnedType() const
{
return RimWellEventSegment::classKeywordStatic();
}
CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimWellEventTimeline, RimcWellEventTimeline_addWellSpecEvent, "AddWellspecEvent" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimcWellEventTimeline_addWellSpecEvent::RimcWellEventTimeline_addWellSpecEvent( caf::PdmObjectHandle* self )
: caf::PdmObjectCreationMethod( self )
{
CAF_PDM_InitObject( "Add Well Specification Event", "", "", "Add a WELLSPEC event to the timeline" );
CAF_PDM_InitScriptableField( &m_eventDate, "EventDate", QString( "2024-01-01" ), "", "", "", "Event Date (YYYY-MM-DD)" );
CAF_PDM_InitScriptableFieldNoDefault( &m_wellPath, "WellPath", "", "", "", "Well Path" );
CAF_PDM_InitScriptableField( &m_groupName, "GroupName", QString(), "", "", "", "Group Name" );
CAF_PDM_InitScriptableField( &m_allowCrossFlow, "AllowCrossFlow", true, "", "", "", "Allow Well Cross-Flow" );
CAF_PDM_InitScriptableFieldNoDefault( &m_referenceDepth, "ReferenceDepth", "", "", "", "Reference Depth" );
CAF_PDM_InitScriptableField( &m_wellType,
"WellType",
caf::AppEnum<RimWellPathCompletionSettings::WellType>( RimWellPathCompletionSettings::OIL ),
"",
"",
"",
"Preferred Fluid Phase" );
CAF_PDM_InitScriptableField( &m_pressureDrop,
"PressureDrop",
QString(),
"",
"",
"",
"WELSEGS pressure drop components (H--, HF- or HFA). Empty keeps the current completion setting" );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::expected<caf::PdmObjectHandle*, QString> RimcWellEventTimeline_addWellSpecEvent::execute()
{
auto timeline = self<RimWellEventTimeline>();
if ( !m_wellPath() ) return std::unexpected( QString( "Well path is required" ) );
QDateTime date = QDateTime::fromString( m_eventDate(), Qt::ISODate );
if ( !date.isValid() )
{
return std::unexpected( QString( "Invalid date format: %1. Expected YYYY-MM-DD" ).arg( m_eventDate() ) );
}
for ( const auto* existing : timeline->events() )
{
if ( existing->eventType() == RimWellEvent::EventType::WELLSPEC && existing->wellPath() == m_wellPath() && existing->eventDate() == date )
{
return std::unexpected(
QString( "A WELLSPEC event already exists for well '%1' at %2" ).arg( m_wellPath()->name(), m_eventDate() ) );
}
}
auto pressureDrop = m_wellPath()->completionSettings()->pressureDrop();
if ( !m_pressureDrop().isEmpty() )
{
if ( !RimWellPathCompletionSettings::PressureDropEnum::isValid( m_pressureDrop() ) )
{
return std::unexpected( QString( "Invalid pressure drop: %1. Expected H--, HF- or HFA" ).arg( m_pressureDrop() ) );
}
pressureDrop = RimWellPathCompletionSettings::PressureDropEnum::fromText( m_pressureDrop() );
}
auto* event = timeline->addWellSpecEvent( m_wellPath(), date );
event->setWellSpecData( { m_groupName(), m_allowCrossFlow(), m_referenceDepth(), m_wellType(), pressureDrop } );
return event;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimcWellEventTimeline_addWellSpecEvent::classKeywordReturnedType() const
{
return RimWellEventWellSpec::classKeywordStatic();
}
CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimWellEventTimeline, RimcWellEventTimeline_addWellKeywordEvent, "AddWellKeywordEventInternal" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimcWellEventTimeline_addWellKeywordEvent::RimcWellEventTimeline_addWellKeywordEvent( caf::PdmObjectHandle* self )
: caf::PdmObjectCreationMethod( self )
{
CAF_PDM_InitObject( "Add Well Keyword Event", "", "", "Add a well keyword event to the timeline" );
CAF_PDM_InitScriptableField( &m_eventDate, "EventDate", QString( "2024-01-01" ), "", "", "", "Event Date (YYYY-MM-DD)" );
CAF_PDM_InitScriptableFieldNoDefault( &m_wellPath, "WellPath", "", "", "", "Well Path" );
CAF_PDM_InitScriptableField( &m_keywordName, "KeywordName", QString(), "", "", "", "Keyword Name" );
CAF_PDM_InitScriptableFieldNoDefault( &m_itemNames, "ItemNames", "", "", "", "Item Names" );
CAF_PDM_InitScriptableFieldNoDefault( &m_itemTypes, "ItemTypes", "", "", "", "Item Types" );
CAF_PDM_InitScriptableFieldNoDefault( &m_itemValues, "ItemValues", "", "", "", "Item Values" );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::expected<caf::PdmObjectHandle*, QString> RimcWellEventTimeline_addWellKeywordEvent::execute()
{
auto timeline = self<RimWellEventTimeline>();
if ( !m_wellPath() )
{
return std::unexpected( QString( "Well path is required" ) );
}
if ( m_keywordName().isEmpty() )
{
return std::unexpected( QString( "Keyword name is required" ) );
}
QDateTime date = QDateTime::fromString( m_eventDate(), Qt::ISODate );
if ( !date.isValid() )
{
return std::unexpected( QString( "Invalid date format: %1. Expected YYYY-MM-DD" ).arg( m_eventDate() ) );
}
// Validate array lengths
if ( m_itemNames().size() != m_itemTypes().size() || m_itemNames().size() != m_itemValues().size() )
{
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() );
// Add items with type conversion
for ( size_t i = 0; i < m_itemNames().size(); ++i )
{
QString type = m_itemTypes()[i].toUpper();
QString name = m_itemNames()[i];
QString value = m_itemValues()[i];
if ( type == "STRING" )
{
event->addStringItem( name, value );
}
else if ( type == "INT" )
{
bool ok;
int intValue = value.toInt( &ok );
if ( !ok )
{
return std::unexpected( QString( "Invalid integer value for '%1': %2" ).arg( name ).arg( value ) );
}
event->addIntItem( name, intValue );
}
else if ( type == "DOUBLE" )
{
bool ok;
double doubleValue = value.toDouble( &ok );
if ( !ok )
{
return std::unexpected( QString( "Invalid double value for '%1': %2" ).arg( name ).arg( value ) );
}
event->addDoubleItem( name, doubleValue );
}
else if ( type == "FLAG" )
{
event->addFlagItem( name );
}
else
{
return std::unexpected( QString( "Unknown item type: %1. Must be STRING, INT, DOUBLE, or FLAG" ).arg( type ) );
}
}
return event;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimcWellEventTimeline_addWellKeywordEvent::classKeywordReturnedType() const
{
return RimWellEventKeyword::classKeywordStatic();
}
CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimWellEventTimeline, RimcWellEventTimeline_addKeywordEvent, "AddKeywordEventInternal" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimcWellEventTimeline_addKeywordEvent::RimcWellEventTimeline_addKeywordEvent( caf::PdmObjectHandle* self )
: caf::PdmObjectCreationMethod( self )
{
CAF_PDM_InitObject( "Add Keyword Event", "", "", "Add a schedule-level keyword event to the timeline (not tied to a well)" );
CAF_PDM_InitScriptableField( &m_eventDate, "EventDate", QString( "2024-01-01" ), "", "", "", "Event Date (YYYY-MM-DD)" );
CAF_PDM_InitScriptableField( &m_keywordName, "KeywordName", QString(), "", "", "", "Keyword Name" );
CAF_PDM_InitScriptableFieldNoDefault( &m_itemNames, "ItemNames", "", "", "", "Item Names" );
CAF_PDM_InitScriptableFieldNoDefault( &m_itemTypes, "ItemTypes", "", "", "", "Item Types" );
CAF_PDM_InitScriptableFieldNoDefault( &m_itemValues, "ItemValues", "", "", "", "Item Values" );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::expected<caf::PdmObjectHandle*, QString> RimcWellEventTimeline_addKeywordEvent::execute()
{
auto timeline = self<RimWellEventTimeline>();
if ( m_keywordName().isEmpty() )
{
return std::unexpected( QString( "Keyword name is required" ) );
}
QDateTime date = QDateTime::fromString( m_eventDate(), Qt::ISODate );
if ( !date.isValid() )
{
return std::unexpected( QString( "Invalid date format: %1. Expected YYYY-MM-DD" ).arg( m_eventDate() ) );
}
// Validate array lengths
if ( m_itemNames().size() != m_itemTypes().size() || m_itemNames().size() != m_itemValues().size() )
{
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() );
// Add items with type conversion
for ( size_t i = 0; i < m_itemNames().size(); ++i )
{
QString type = m_itemTypes()[i].toUpper();
QString name = m_itemNames()[i];
QString value = m_itemValues()[i];
if ( type == "STRING" )
{
event->addStringItem( name, value );
}
else if ( type == "INT" )
{
bool ok;
int intValue = value.toInt( &ok );
if ( !ok )
{
return std::unexpected( QString( "Invalid integer value for '%1': %2" ).arg( name ).arg( value ) );
}
event->addIntItem( name, intValue );
}
else if ( type == "DOUBLE" )
{
bool ok;
double doubleValue = value.toDouble( &ok );
if ( !ok )
{
return std::unexpected( QString( "Invalid double value for '%1': %2" ).arg( name ).arg( value ) );
}
event->addDoubleItem( name, doubleValue );
}
else if ( type == "FLAG" )
{
event->addFlagItem( name );
}
else
{
return std::unexpected( QString( "Unknown item type: %1. Must be STRING, INT, DOUBLE, or FLAG" ).arg( type ) );
}
}
return event;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimcWellEventTimeline_addKeywordEvent::classKeywordReturnedType() const
{
return RimKeywordEvent::classKeywordStatic();
}
CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimWellEventTimeline, RimcWellEventTimeline_addRawTextEvent, "AddRawTextEventInternal" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimcWellEventTimeline_addRawTextEvent::RimcWellEventTimeline_addRawTextEvent( caf::PdmObjectHandle* self )
: caf::PdmObjectCreationMethod( self )
{
CAF_PDM_InitObject( "Add Raw Text Event", "", "", "Add raw text at a specific position in a dated schedule section" );
CAF_PDM_InitScriptableField( &m_eventDate, "EventDate", QString( "2024-01-01" ), "", "", "", "Event Date (YYYY-MM-DD)" );
CAF_PDM_InitScriptableField( &m_text, "Text", QString(), "", "", "", "Raw schedule text" );
CAF_PDM_InitScriptableField( &m_placement,
"Placement",
RimWellEventRawText::Placement::AFTER_DATE,
"",
"",
"",
"Position in the dated schedule section" );
CAF_PDM_InitScriptableField( &m_anchorKeyword, "AnchorKeyword", QString(), "", "", "", "Keyword used for before/after placement" );
CAF_PDM_InitScriptableField( &m_priority, "Priority", 0, "", "", "", "Ascending order among raw text events at the same position" );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::expected<caf::PdmObjectHandle*, QString> RimcWellEventTimeline_addRawTextEvent::execute()
{
auto timeline = self<RimWellEventTimeline>();
QDateTime date = QDateTime::fromString( m_eventDate(), Qt::ISODate );
if ( !date.isValid() )
{
return std::unexpected( QString( "Invalid date format: %1. Expected YYYY-MM-DD" ).arg( m_eventDate() ) );
}
if ( m_text().isEmpty() )
{
return std::unexpected( QString( "Raw text is required" ) );
}
const bool anchored = m_placement() == RimWellEventRawText::Placement::BEFORE_KEYWORD ||
m_placement() == RimWellEventRawText::Placement::AFTER_KEYWORD;
if ( anchored && m_anchorKeyword().trimmed().isEmpty() )
{
return std::unexpected( QString( "Anchor keyword is required for BEFORE_KEYWORD and AFTER_KEYWORD placement" ) );
}
if ( !anchored && !m_anchorKeyword().trimmed().isEmpty() )
{
return std::unexpected( QString( "Anchor keyword is only valid for BEFORE_KEYWORD and AFTER_KEYWORD placement" ) );
}
auto* event = timeline->addRawTextEvent( date );
event->setText( m_text() );
event->setPlacement( m_placement() );
event->setAnchorKeyword( m_anchorKeyword().trimmed() );
event->setPriority( m_priority() );
return event;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimcWellEventTimeline_addRawTextEvent::classKeywordReturnedType() const
{
return RimWellEventRawText::classKeywordStatic();
}
CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimWellEventTimeline, RimcWellEventTimeline_addInsertDateEvent, "AddInsertDateEventInternal" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimcWellEventTimeline_addInsertDateEvent::RimcWellEventTimeline_addInsertDateEvent( caf::PdmObjectHandle* self )
: caf::PdmObjectCreationMethod( self )
{
CAF_PDM_InitObject( "Add Insert Date Event", "", "", "Add a date that is emitted as a DATES keyword even when no other event falls on it" );
CAF_PDM_InitScriptableField( &m_eventDate, "EventDate", QString( "2024-01-01" ), "", "", "", "Event Date (YYYY-MM-DD)" );
CAF_PDM_InitScriptableField( &m_comment, "Comment", QString(), "", "", "", "Comment emitted below the generated date" );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::expected<caf::PdmObjectHandle*, QString> RimcWellEventTimeline_addInsertDateEvent::execute()
{
auto timeline = self<RimWellEventTimeline>();
QDateTime date = QDateTime::fromString( m_eventDate(), Qt::ISODate );
if ( !date.isValid() )
{
return std::unexpected( QString( "Invalid date format: %1. Expected YYYY-MM-DD" ).arg( m_eventDate() ) );
}
auto* event = timeline->addInsertDateEvent( date );
event->setComment( m_comment() );
return event;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
QString RimcWellEventTimeline_addInsertDateEvent::classKeywordReturnedType() const
{
return RimWellEventInsertDate::classKeywordStatic();
}
CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimWellEventTimeline, RimcWellEventTimeline_setTimestamp, "SetTimestamp" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimcWellEventTimeline_setTimestamp::RimcWellEventTimeline_setTimestamp( caf::PdmObjectHandle* self )
: caf::PdmVoidObjectMethod( self )
{
CAF_PDM_InitObject( "Set Timestamp", "", "", "Apply well events up to a given timestamp" );
CAF_PDM_InitScriptableField( &m_timestamp, "Timestamp", QString( "2024-01-01" ), "", "", "", "Timestamp (YYYY-MM-DD)" );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::expected<caf::PdmObjectHandle*, QString> RimcWellEventTimeline_setTimestamp::execute()
{
auto timeline = self<RimWellEventTimeline>();
QDateTime date = QDateTime::fromString( m_timestamp(), Qt::ISODate );
if ( !date.isValid() )
{
return std::unexpected( QString( "Invalid date format: %1. Expected YYYY-MM-DD" ).arg( m_timestamp() ) );
}
bool success = timeline->applyEventsUpToDate( date );
if ( !success )
{
return std::unexpected( QString( "No events were applied up to date: %1" ).arg( m_timestamp() ) );
}
return nullptr; // Return nullptr on success (no specific object to return)
}
CAF_PDM_OBJECT_METHOD_SOURCE_INIT( RimWellEventTimeline, RimcWellEventTimeline_generateSchedule, "GenerateSchedule" );
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
RimcWellEventTimeline_generateSchedule::RimcWellEventTimeline_generateSchedule( caf::PdmObjectHandle* self )
: caf::PdmObjectMethod( self, PdmObjectMethod::NullPointerType::NULL_IS_INVALID, PdmObjectMethod::ResultType::PERSISTENT_FALSE )
{
CAF_PDM_InitObject( "Generate Schedule", "", "", "Generate Eclipse schedule text for all wells in the collection" );
CAF_PDM_InitScriptableFieldNoDefault( &m_eclipseCase,
"EclipseCase",
"",
"",
"",
"Optional grid case (Eclipse, Roff or corner point). Without a case, the grid-dependent keywords "
"(WELSPECS, COMPORD, COMPDAT, COMPLUMP, WELSEGS, COMPSEGS, WSEGVALV, WSEGAICD) are skipped and "
"reported as WARNING comments in the schedule header" );
CAF_PDM_InitScriptableFieldNoDefault( &m_exportMswForWells,
"ExportMswForWells",
"",
"",
"",
"Wells for which multi-segment-well keywords (WELSEGS, COMPSEGS, WSEGVALV, WSEGAICD) are "
"exported" );
CAF_PDM_InitScriptableField( &m_firstDateAsComment,
"FirstDateAsComment",
true,
"",
"",
"",
"Emit the first (simulation-start) date as a comment instead of a DATES keyword" );
CAF_PDM_InitScriptableField( &m_alignColumns,
"AlignColumns",
false,
"",
"",
"",
"Emit a column-header comment and right-aligned, fixed-width columns instead of the compact form" );
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
std::expected<caf::PdmObjectHandle*, QString> RimcWellEventTimeline_generateSchedule::execute()
{
auto timeline = self<RimWellEventTimeline>();
// The dates and wells are collected from the timeline, honoring any set_timestamp() call.
std::vector<RimWellPath*> mswWellPaths = m_exportMswForWells.ptrReferencedObjectsByType();
std::set<const RimWellPath*> mswWells( mswWellPaths.begin(), mswWellPaths.end() );
auto scheduleText =
RicScheduleDataGenerator::generateScheduleForTimeline( *timeline, m_eclipseCase(), mswWells, m_firstDateAsComment(), m_alignColumns() );
if ( !scheduleText ) return std::unexpected( scheduleText.error() );
// Return the schedule text in a data container
auto* dataObject = new RimcDataContainerString();
dataObject->m_stringValues = { *scheduleText };
return dataObject;
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
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();
}