diff --git a/ApplicationLibCode/Commands/CompletionExportCommands/RicScheduleDataGenerator.cpp b/ApplicationLibCode/Commands/CompletionExportCommands/RicScheduleDataGenerator.cpp index bf0b3def7f..2c447e4e5b 100644 --- a/ApplicationLibCode/Commands/CompletionExportCommands/RicScheduleDataGenerator.cpp +++ b/ApplicationLibCode/Commands/CompletionExportCommands/RicScheduleDataGenerator.cpp @@ -34,6 +34,7 @@ #include "opm/input/eclipse/Deck/DeckKeyword.hpp" +#include #include //-------------------------------------------------------------------------------------------------- @@ -95,53 +96,51 @@ QString RicScheduleDataGenerator::generateDateSection( const RimWellEventTimelin const std::vector& wellPaths, const QDateTime& date ) { + // Keyword priority order for output + static const std::vector keywordOrder = { "WELSPECS", + "COMPORD", + "GRUPTREE", + "COMPDAT", + "COMPLUMP", + "WELSEGS", + "COMPSEGS", + "WCONHIST", + "WCONINJH", + "WCONPROD", + "WCONINJE", + "WRFTPLT", + "TUNING", + "RPTSCHED", + "RPTRST", + "WSEGVALV", + "WSEGAICD" }; + QString result; // Generate DATES keyword result += RimKeywordFactory::deckKeywordToString( RimKeywordFactory::datesKeyword( date ) ) + "\n"; - // Collect all output for this date - QString compdatData; - QString mswData; - QString wellControlData; + // Collect all keyword output into a map keyed by keyword name + std::map keywordBlocks; for ( auto* well : wellPaths ) { if ( !well ) continue; - QString wellCompdat = generateCompdatForWell( timeline, eclipseCase, *well, date ); - if ( !wellCompdat.isEmpty() ) + QString welspecsKw = generateWelspecsForWell( timeline, eclipseCase, *well, date ); + if ( !welspecsKw.isEmpty() ) { - compdatData += wellCompdat; + keywordBlocks["WELSPECS"] += welspecsKw; } - QString wellMsw = generateMswForWell( timeline, eclipseCase, *well, date ); - if ( !wellMsw.isEmpty() ) + QString wellCompdat = generateCompdatForWell( timeline, eclipseCase, *well, date ); + if ( !wellCompdat.isEmpty() ) { - mswData += wellMsw; + keywordBlocks["COMPDAT"] += wellCompdat; } - QString wellControl = generateWellControlForWell( timeline, *well, date ); - if ( !wellControl.isEmpty() ) - { - wellControlData += wellControl; - } - } - - // Output collected data - if ( !compdatData.isEmpty() ) - { - result += compdatData; - } - - if ( !mswData.isEmpty() ) - { - result += mswData; - } - - if ( !wellControlData.isEmpty() ) - { - result += wellControlData; + generateMswForWell( timeline, eclipseCase, *well, date, keywordBlocks ); + generateWellControlForWell( timeline, *well, date, keywordBlocks ); } // Process schedule-level keyword events (not tied to a specific well) @@ -156,13 +155,68 @@ QString RicScheduleDataGenerator::generateDateSection( const RimWellEventTimelin QString keywordStr = keywordEvent->generateScheduleKeyword( "" ); if ( !keywordStr.isEmpty() ) { - result += keywordStr; - result += "\n"; + keywordBlocks[keywordEvent->keywordName().toUpper()] += keywordStr + "\n"; } } } } + // Output keywords in priority order + std::set emitted; + for ( const auto& kw : keywordOrder ) + { + auto it = keywordBlocks.find( kw ); + if ( it != keywordBlocks.end() && !it->second.isEmpty() ) + { + result += it->second; + emitted.insert( kw ); + } + } + + // Output remaining keywords not in priority list + for ( const auto& [kw, data] : keywordBlocks ) + { + if ( emitted.find( kw ) == emitted.end() && !data.isEmpty() ) + { + result += data; + } + } + + return result; +} + +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString RicScheduleDataGenerator::generateWelspecsForWell( const RimWellEventTimeline& timeline, + RimEclipseCase& eclipseCase, + RimWellPath& well, + const QDateTime& date ) +{ + // Get perforation events at this exact date for this well + auto events = timeline.getEventsAtDate( date ); + + bool hasEvents = false; + for ( auto* event : events ) + { + if ( ( event->eventType() == RimWellEvent::EventType::PERF || event->eventType() == RimWellEvent::EventType::VALVE || + event->eventType() == RimWellEvent::EventType::TUBING ) && + event->wellName() == well.name() ) + { + hasEvents = true; + break; + } + } + + if ( !hasEvents ) return QString(); + + std::string wellGroupName = well.completionSettings()->groupNameForExport().toStdString(); + auto welspecsKw = RimKeywordFactory::welspecsKeyword( wellGroupName, &eclipseCase, &well ); + + QString result; + result += RimKeywordFactory::deckKeywordToString( welspecsKw ); + result += "\n"; + return result; } @@ -203,17 +257,18 @@ QString RicScheduleDataGenerator::generateCompdatForWell( const RimWellEventTime //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -QString RicScheduleDataGenerator::generateMswForWell( const RimWellEventTimeline& timeline, - RimEclipseCase& eclipseCase, - RimWellPath& wellPath, - const QDateTime& date ) +void RicScheduleDataGenerator::generateMswForWell( const RimWellEventTimeline& timeline, + RimEclipseCase& eclipseCase, + RimWellPath& wellPath, + const QDateTime& date, + std::map& keywordBlocks ) { // Check if the well has MSW configured (from any previous tubing events applied via set_timestamp) // If MSW is enabled, we should generate WELSEGS/COMPSEGS instead of COMPDAT auto* mswParams = wellPath.mswCompletionParameters(); if ( !mswParams ) { - return QString(); + return; } // Also check if there are valve or tubing events at this specific date for this well @@ -239,7 +294,7 @@ QString RicScheduleDataGenerator::generateMswForWell( const RimWellEventTimeline // This ensures wells only appear in schedule sections at their event dates if ( !hasMswEvents && !hasPerfEvents ) { - return QString(); + return; } // Extract MSW data using the existing infrastructure @@ -251,59 +306,52 @@ QString RicScheduleDataGenerator::generateMswForWell( const RimWellEventTimeline RicWellPathExportMswTableData::CompletionType::ALL, date ); - if ( !mswDataResult.has_value() ) return QString(); + if ( !mswDataResult.has_value() ) return; const auto& mswData = mswDataResult.value(); - QString result; - // Generate WELSEGS keyword int maxSegments = 0; int maxBranches = 0; Opm::DeckKeyword welsegsKw = RimKeywordFactory::welsegsKeyword( mswData, maxSegments, maxBranches ); if ( welsegsKw.isDataKeyword() || welsegsKw.size() > 0 ) { - result += RimKeywordFactory::deckKeywordToString( welsegsKw ); - result += "\n"; + keywordBlocks["WELSEGS"] += RimKeywordFactory::deckKeywordToString( welsegsKw ) + "\n"; } // Generate COMPSEGS keyword Opm::DeckKeyword compsegsKw = RimKeywordFactory::compsegsKeyword( mswData ); if ( compsegsKw.isDataKeyword() || compsegsKw.size() > 0 ) { - result += RimKeywordFactory::deckKeywordToString( compsegsKw ); - result += "\n"; + keywordBlocks["COMPSEGS"] += RimKeywordFactory::deckKeywordToString( compsegsKw ) + "\n"; } // Generate WSEGVALV keyword (for valve events) Opm::DeckKeyword wsegvalvKw = RimKeywordFactory::wsegvalvKeyword( mswData ); if ( wsegvalvKw.isDataKeyword() || wsegvalvKw.size() > 0 ) { - result += RimKeywordFactory::deckKeywordToString( wsegvalvKw ); - result += "\n"; + keywordBlocks["WSEGVALV"] += RimKeywordFactory::deckKeywordToString( wsegvalvKw ) + "\n"; } // Generate WSEGAICD keyword (if AICD data present) Opm::DeckKeyword wsegaicdKw = RimKeywordFactory::wsegaicdKeyword( mswData ); if ( wsegaicdKw.isDataKeyword() || wsegaicdKw.size() > 0 ) { - result += RimKeywordFactory::deckKeywordToString( wsegaicdKw ); - result += "\n"; + keywordBlocks["WSEGAICD"] += RimKeywordFactory::deckKeywordToString( wsegaicdKw ) + "\n"; } - - return result; } //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -QString RicScheduleDataGenerator::generateWellControlForWell( const RimWellEventTimeline& timeline, const RimWellPath& well, const QDateTime& date ) +void RicScheduleDataGenerator::generateWellControlForWell( const RimWellEventTimeline& timeline, + const RimWellPath& well, + const QDateTime& date, + std::map& keywordBlocks ) { // Get state and control events at this exact date for this well auto events = timeline.getEventsAtDate( date ); - QString result; - for ( auto* event : events ) { if ( event->wellName() != well.name() ) continue; @@ -311,10 +359,28 @@ QString RicScheduleDataGenerator::generateWellControlForWell( const RimWellEvent QString keywordStr = RifEventKeywordFormatter::formatWellEvent( event, well.name() ); if ( !keywordStr.isEmpty() ) { - result += keywordStr; - result += "\n"; + QString kwName = extractKeywordName( keywordStr ); + if ( !kwName.isEmpty() ) + { + keywordBlocks[kwName] += keywordStr + "\n"; + } } } +} - return result; +//-------------------------------------------------------------------------------------------------- +/// +//-------------------------------------------------------------------------------------------------- +QString RicScheduleDataGenerator::extractKeywordName( const QString& block ) +{ + const auto lines = block.split( '\n' ); + for ( const auto& line : lines ) + { + QString trimmed = line.trimmed(); + if ( !trimmed.isEmpty() && !trimmed.startsWith( "--" ) ) + { + return trimmed; + } + } + return {}; } diff --git a/ApplicationLibCode/Commands/CompletionExportCommands/RicScheduleDataGenerator.h b/ApplicationLibCode/Commands/CompletionExportCommands/RicScheduleDataGenerator.h index 4e252875af..ed4512af94 100644 --- a/ApplicationLibCode/Commands/CompletionExportCommands/RicScheduleDataGenerator.h +++ b/ApplicationLibCode/Commands/CompletionExportCommands/RicScheduleDataGenerator.h @@ -21,6 +21,7 @@ #include #include +#include #include class RimEclipseCase; @@ -52,14 +53,26 @@ class RicScheduleDataGenerator const std::vector& wellPaths, const QDateTime& date ); + static QString + generateWelspecsForWell( const RimWellEventTimeline& timeline, RimEclipseCase& eclipseCase, RimWellPath& well, const QDateTime& date ); + // Generate COMPDAT for a well at a specific date based on events static QString generateCompdatForWell( const RimWellEventTimeline& timeline, RimEclipseCase& eclipseCase, RimWellPath& well, const QDateTime& date ); - // Generate WELSEGS and COMPSEGS for a well at a specific date - static QString - generateMswForWell( const RimWellEventTimeline& timeline, RimEclipseCase& eclipseCase, RimWellPath& well, const QDateTime& date ); + // Generate WELSEGS and COMPSEGS for a well at a specific date, populating keyword blocks map + static void generateMswForWell( const RimWellEventTimeline& timeline, + RimEclipseCase& eclipseCase, + RimWellPath& well, + const QDateTime& date, + std::map& keywordBlocks ); + + // Generate well control keywords (WCONPROD, WCONINJE, WELOPEN) for a well at a specific date, populating keyword blocks map + static void generateWellControlForWell( const RimWellEventTimeline& timeline, + const RimWellPath& well, + const QDateTime& date, + std::map& keywordBlocks ); - // Generate well control keywords (WCONPROD, WCONINJE, WELOPEN) for a well at a specific date - static QString generateWellControlForWell( const RimWellEventTimeline& timeline, const RimWellPath& well, const QDateTime& date ); + // Extract keyword name from the first non-comment line of a keyword block + static QString extractKeywordName( const QString& block ); }; diff --git a/GrpcInterface/RiaGrpcWellPathService.cpp b/GrpcInterface/RiaGrpcWellPathService.cpp index 2cf40524ba..ea13b0e02c 100644 --- a/GrpcInterface/RiaGrpcWellPathService.cpp +++ b/GrpcInterface/RiaGrpcWellPathService.cpp @@ -89,8 +89,7 @@ void populateReply( RimEclipseCase* eclipseCase, RimWellPath* wellPath, ::rips:: // Multisegment wells - int timeStep = 0; - auto mswDataContainer = RicWellPathExportMswTableData::extractSingleWellMswData( eclipseCase, wellPath, timeStep ); + auto mswDataContainer = RicWellPathExportMswTableData::extractSingleWellMswData( eclipseCase, wellPath ); if ( mswDataContainer.has_value() ) { auto tables = mswDataContainer.value();