Files
ResInsight/ApplicationLibCode/ProjectDataModelCommands/RimcWellEventTimeline.cpp
T
Magne Sjaastad c04edc9d39 #14963: Order COMPDAT connections mainbore-first, then laterals
Completions could interleave mainbore and lateral connections, since the
final sort ordered by well name, completion type and measured depth, and
each lateral has its own independent MD scale. Connections for a well's
mainbore should all come before connections for its side steps.

Added RicScheduleDataGenerator::sortCompletionsMainBoreFirst(), which
groups completions by owning well path (via
RigCompletionData::sourcePdmObject()) and emits them in the traversal
order of RimWellPath::allWellPathLaterals(), mainbore first. Sibling
laterals are ordered by tie-in measured depth, as allWellPathLaterals()
itself is not deterministic. The sort is applied in
completionDataForWellPaths() so schedule export, COMPDAT/COMPLUMP export
and the gRPC/Python query all get the same ordering.
2026-10-20 13:06:58 +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 "Commands/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();
}