#14637 #14641 Orion Events: Emit END first after date

This commit is contained in:
Kristian Bendiksen
2026-09-01 11:56:59 +02:00
committed by Magne Sjaastad
parent d226e10e89
commit ebf0ef0cc0
4 changed files with 62 additions and 4 deletions
@@ -45,6 +45,14 @@
#include <map>
#include <set>
namespace
{
bool shouldEmitKeyword( const Opm::DeckKeyword& keyword )
{
return keyword.size() > 0 || keyword.isDataKeyword() || RifEventKeywordFormatter::isRecordlessKeyword( keyword );
}
} // namespace
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -146,7 +154,7 @@ std::vector<QDateTime> RicScheduleDataGenerator::collectAllDates( const RimWellE
//--------------------------------------------------------------------------------------------------
void RicScheduleDataGenerator::mergeKeyword( std::map<QString, Opm::DeckKeyword>& acc, const QString& name, Opm::DeckKeyword kw )
{
if ( kw.size() == 0 && !kw.isDataKeyword() ) return;
if ( !shouldEmitKeyword( kw ) ) return;
auto it = acc.find( name );
if ( it == acc.end() )
@@ -172,7 +180,8 @@ std::expected<QString, QString> RicScheduleDataGenerator::generateDateSection( c
bool alignColumns )
{
// Keyword priority order for output
static const std::vector<QString> keywordOrder = { "WELSPECS",
static const std::vector<QString> keywordOrder = { "END",
"WELSPECS",
"COMPORD",
"GRUPTREE",
"COMPDAT",
@@ -333,7 +342,7 @@ std::expected<QString, QString> RicScheduleDataGenerator::generateDateSection( c
auto appendKeywordText = [&]( const Opm::DeckKeyword& kw )
{
if ( kw.size() == 0 && !kw.isDataKeyword() ) return;
if ( !shouldEmitKeyword( kw ) ) return;
appendEventComments( QString::fromStdString( kw.name() ) );
result += serializeKeyword( kw );
result += "\n";
@@ -56,6 +56,23 @@ QString keywordToString( const Opm::DeckKeyword& kw )
}
} // namespace
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
bool RifEventKeywordFormatter::isRecordlessKeyword( const Opm::DeckKeyword& keyword )
{
try
{
static const Opm::Parser parser;
const auto& parserKeyword = parser.getKeyword( keyword.name() );
return parserKeyword.begin() == parserKeyword.end();
}
catch ( const std::exception& )
{
return false;
}
}
//--------------------------------------------------------------------------------------------------
///
//--------------------------------------------------------------------------------------------------
@@ -71,7 +88,17 @@ std::optional<Opm::DeckKeyword> RifEventKeywordFormatter::buildKeyword( const QS
const Opm::ParserKeyword& parserKw = parser.getKeyword( kwName );
Opm::DeckKeyword kw( parserKw );
const size_t numRecords = static_cast<size_t>( std::distance( parserKw.begin(), parserKw.end() ) );
const size_t numRecords = static_cast<size_t>( std::distance( parserKw.begin(), parserKw.end() ) );
if ( numRecords == 0 )
{
if ( !items.empty() )
{
RiaLogging::error( std::format( "Recordless keyword '{}' does not accept items.", keyword ) );
return std::nullopt;
}
return kw;
}
const Opm::ParserRecord& parserRecord = parserKw.getRecord( 0 );
auto stringValue = []( const RimWellEventKeywordItem* item ) -> std::string
@@ -33,6 +33,8 @@ class RimWellEventKeywordItem;
namespace RifEventKeywordFormatter
{
bool isRecordlessKeyword( const Opm::DeckKeyword& keyword );
// build*: produce a structured Opm::DeckKeyword (used by callers that merge records across wells).
std::optional<Opm::DeckKeyword> buildKeyword( const QString& keywordName, const std::vector<RimWellEventKeywordItem*>& items );
std::optional<Opm::DeckKeyword> buildWconprod( const RimWellEventControl* controlEvent, const QString& wellName );
@@ -2169,6 +2169,26 @@ class TestOrionEventsIntegration:
assert "1 'FEB' 2024" in schedule
assert "1 'JUN' 2024" in schedule
def test_end_is_first_keyword_after_date(self, project_with_case_and_wells):
project, case, timeline = project_with_case_and_wells
document = parse_orion_events(
"ORIONEVENTS 2.0\nSCHEDULE\n"
"2024-01-01 RPTRST BASIC=2 FREQ=1\n"
"2024-01-01 END\n"
)
report = apply_orion_document(document, timeline, project)
assert report.errors == []
schedule = timeline.generate_schedule_text(
eclipse_case=case,
first_date_as_comment=False,
)
date_position = schedule.index("1 'JAN' 2024")
end_position = schedule.index("\nEND\n", date_position)
rptrst_position = schedule.index("\nRPTRST\n", date_position)
assert date_position < end_position < rptrst_position
def test_apply_creates_perforations_and_schedule(self, project_with_case_and_wells):
"""End-to-end: parse -> apply -> set_timestamp -> generate schedule."""
project, case, timeline = project_with_case_and_wells