Merge branch 'bugfix/empty_times'

This commit is contained in:
Daniel Schick 2023-12-01 10:02:40 +02:00
commit 956ba2832a
27 changed files with 954 additions and 325 deletions

View File

@ -1 +1 @@
0.9.6.0
1.0.0.0

View File

@ -8,8 +8,8 @@
<SignAssembly>True</SignAssembly>
<StartupObject>BreCalClient.App</StartupObject>
<AssemblyOriginatorKeyFile>..\..\misc\brecal.snk</AssemblyOriginatorKeyFile>
<AssemblyVersion>0.9.6.0</AssemblyVersion>
<FileVersion>0.9.6.0</FileVersion>
<AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>1.0.0.0</FileVersion>
<Title>Bremen calling client</Title>
<Description>A Windows WPF client for the Bremen calling API.</Description>
<ApplicationIcon>containership.ico</ApplicationIcon>

View File

@ -65,24 +65,15 @@
<ColumnDefinition Width=".5*" />
<ColumnDefinition Width=".5*" />
</Grid.ColumnDefinitions>
<ComboBox Name="comboBoxArrivalBerth" Grid.Column="0" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearValue}" Name="contextMenuItemDepartureBerth" Click="contextMenuItemDepartureBerth_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
<ComboBox Name="comboBoxDepartureBerth" Grid.Column="1" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearValue}" Name="contextMenuItemArrivalBerth" Click="contextMenuItemArrivalBerth_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
<ComboBox Name="comboBoxArrivalBerth" Grid.Column="0" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id" SelectionChanged="comboBoxArrivalBerth_SelectionChanged"/>
<ComboBox Name="comboBoxDepartureBerth" Grid.Column="1" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id" SelectionChanged="comboBoxDepartureBerth_SelectionChanged"/>
</Grid>
<xctk:DateTimePicker x:Name="datePickerETA" Grid.Column="3" Grid.Row="2" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm" IsEnabled="False"/>
<xctk:DateTimePicker x:Name="datePickerETD" Grid.Column="3" Grid.Row="3" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm" IsEnabled="False"/>
<xctk:DateTimePicker x:Name="datePickerETA" Grid.Column="3" Grid.Row="2" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm" IsEnabled="False" ValueChanged="datePickerETA_ValueChanged"/>
<xctk:DateTimePicker x:Name="datePickerETD" Grid.Column="3" Grid.Row="3" Margin="2" Format="Custom" FormatString="dd.MM. yyyy HH:mm" IsEnabled="False" ValueChanged="datePickerETD_ValueChanged"/>
<Label Content="{x:Static p:Resources.textAgency}" Grid.Column="2" Grid.Row="4" HorizontalContentAlignment="Right"/>
<ComboBox Name="comboBoxAgency" Grid.Column="3" Grid.Row="4" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id" SelectionChanged="comboBoxAgency_SelectionChanged">

View File

@ -67,8 +67,7 @@ namespace BreCalClient
}
private void comboBoxShip_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.buttonOK.IsEnabled = this.comboBoxShip.SelectedItem != null;
{
if (this.comboBoxShip.SelectedItem != null)
{
Ship? ship = this.comboBoxShip.SelectedItem as Ship;
@ -84,11 +83,13 @@ namespace BreCalClient
this.doubleUpDownLength.Value = null;
this.doubleUpDownWidth.Value = null;
}
this.CheckForCompletion();
}
private void comboBoxAgency_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.EnableControls();
this.CheckForCompletion();
}
private void comboBoxCategories_SelectionChanged(object? sender, SelectionChangedEventArgs? e)
@ -122,6 +123,7 @@ namespace BreCalClient
break;
}
}
this.CheckForCompletion();
}
#endregion
@ -133,22 +135,24 @@ namespace BreCalClient
this.comboBoxAgency.SelectedIndex = -1;
this.ShipcallModel.AssignedParticipants.Remove(Extensions.ParticipantType.AGENCY);
}
private void contextMenuItemArrivalBerth_Click(object sender, RoutedEventArgs e)
{
this.comboBoxArrivalBerth.SelectedIndex = -1;
this.ShipcallModel.Berth = "";
}
private void contextMenuItemDepartureBerth_Click(object sender, RoutedEventArgs e)
{
this.comboBoxDepartureBerth.SelectedIndex -= 1;
}
#endregion
#region private methods
void CheckForCompletion()
{
bool isEnabled = true;
isEnabled &= this.comboBoxShip.SelectedItem != null;
isEnabled &= this.comboBoxCategories.SelectedItem != null;
isEnabled &= ((this.comboBoxArrivalBerth.SelectedItem != null) || (this.comboBoxDepartureBerth.SelectedItem != null));
isEnabled &= (this.datePickerETA.Value.HasValue || this.datePickerETD.Value.HasValue);
isEnabled &= this.comboBoxAgency.SelectedItem != null;
this.buttonOK.IsEnabled = isEnabled;
}
private void CopyToModel()
{
if (this.ShipcallModel.Shipcall != null)
@ -293,5 +297,24 @@ namespace BreCalClient
#endregion
private void datePickerETA_ValueChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
this.CheckForCompletion();
}
private void datePickerETD_ValueChanged(object sender, RoutedPropertyChangedEventArgs<object> e)
{
this.CheckForCompletion();
}
private void comboBoxArrivalBerth_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.CheckForCompletion();
}
private void comboBoxDepartureBerth_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
this.CheckForCompletion();
}
}
}

View File

@ -73,37 +73,27 @@
<Label Content="{x:Static p:Resources.textAnchored}" Grid.Column="2" Grid.Row="0" HorizontalContentAlignment="Right"/>
<CheckBox x:Name="checkBoxAnchored" Grid.Column="3" Grid.Row="0" VerticalAlignment="Center" HorizontalAlignment="Left" />
<Label Content="{x:Static p:Resources.textTugRequired}" Grid.Column="2" Grid.Row="1" HorizontalContentAlignment="Right"/>
<Grid Grid.Column="3" Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="28" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<CheckBox x:Name="checkBoxTugRequired" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Left" />
<ComboBox Name="comboBoxTug" Grid.Column="1" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearTug" Click="contextMenuItemClearTug_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
</Grid>
<ComboBox Name="comboBoxTug" Grid.Column="3" Grid.Row="1" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearTug" Click="contextMenuItemClearTug_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
<Label Content="{x:Static p:Resources.textRecommendedTugs}" Grid.Column="2" Grid.Row="2" HorizontalContentAlignment="Right"/>
<xctk:IntegerUpDown x:Name="integerUpDownRecommendedTugs" Grid.Column="3" Grid.Row="2" Minimum="0" Margin="2" />
<Label Content="{x:Static p:Resources.textPilotRequired}" Grid.Column="2" Grid.Row="3" HorizontalContentAlignment="Right" />
<Grid Grid.Column="3" Grid.Row="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="28" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<CheckBox x:Name="checkBoxPilotRequired" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Left" />
<ComboBox Name="comboBoxPilot" Grid.Column="1" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearPilot" Click="contextMenuItemClearPilot_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
</Grid>
<ComboBox Name="comboBoxPilot" Grid.Column="3" Grid.Row="3" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearPilot" Click="contextMenuItemClearPilot_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
<Label Content="{x:Static p:Resources.textMooring}" Grid.Column="2" Grid.Row="4" HorizontalContentAlignment="Right"/>
<ComboBox Name="comboBoxMooring" Grid.Column="3" Grid.Row="4" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>

View File

@ -103,9 +103,9 @@ namespace BreCalClient
this.ShipcallModel.Shipcall.Canceled = this.checkBoxCanceled.IsChecked;
this.ShipcallModel.Shipcall.Anchored = this.checkBoxAnchored.IsChecked;
this.ShipcallModel.Shipcall.TugRequired = this.checkBoxTugRequired.IsChecked;
this.ShipcallModel.Shipcall.RecommendedTugs = this.integerUpDownRecommendedTugs.Value;
this.ShipcallModel.Shipcall.PilotRequired = this.checkBoxPilotRequired.IsChecked;
this.ShipcallModel.Shipcall.MooredLock = this.checkBoxMooredLock.IsChecked;
this.ShipcallModel.Shipcall.Bunkering = this.checkBoxBunkering.IsChecked;
this.ShipcallModel.Shipcall.ReplenishingTerminal = this.checkBoxReplenishingTerminal.IsChecked;
@ -190,9 +190,9 @@ namespace BreCalClient
this.checkBoxCanceled.IsChecked = this.ShipcallModel.Shipcall.Canceled ?? false;
this.checkBoxAnchored.IsChecked = this.ShipcallModel.Shipcall.Anchored ?? false;
this.checkBoxTugRequired.IsChecked = this.ShipcallModel.Shipcall.TugRequired ?? false;
this.integerUpDownRecommendedTugs.Value = this.ShipcallModel.Shipcall.RecommendedTugs;
this.checkBoxPilotRequired.IsChecked = this.ShipcallModel.Shipcall.PilotRequired ?? false;
this.checkBoxMooredLock.IsChecked = this.ShipcallModel.Shipcall.MooredLock ?? false;
@ -250,10 +250,10 @@ namespace BreCalClient
this.checkBoxCanceled.IsEnabled = isEnabled;
this.checkBoxAnchored.IsEnabled = isEnabled;
this.checkBoxTugRequired.IsEnabled = isEnabled;
this.comboBoxTug.IsEnabled = isEnabled;
this.integerUpDownRecommendedTugs.IsEnabled = isEnabled;
this.checkBoxPilotRequired.IsEnabled = isEnabled;
this.comboBoxPilot.IsEnabled = isEnabled;
this.comboBoxMooring.IsEnabled = isEnabled;
this.checkBoxMooredLock.IsEnabled = isEnabled;

View File

@ -67,37 +67,27 @@
<CheckBox x:Name="checkBoxCanceled" Grid.Column="1" Grid.Row="10" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="0,0,4,0" />
<Label Content="{x:Static p:Resources.textTugRequired}" Grid.Column="2" Grid.Row="1" HorizontalContentAlignment="Right"/>
<Grid Grid.Column="3" Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="28" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<CheckBox x:Name="checkBoxTugRequired" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Left" />
<ComboBox Name="comboBoxTug" Grid.Column="1" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearTug" Click="contextMenuItemClearTug_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
</Grid>
<ComboBox Name="comboBoxTug" Grid.Column="3" Grid.Row="1" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearTug" Click="contextMenuItemClearTug_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
<Label Content="{x:Static p:Resources.textRecommendedTugs}" Grid.Column="2" Grid.Row="2" HorizontalContentAlignment="Right"/>
<xctk:IntegerUpDown x:Name="integerUpDownRecommendedTugs" Grid.Column="3" Grid.Row="2" Minimum="0" Margin="2" />
<Label Content="{x:Static p:Resources.textPilotRequired}" Grid.Column="2" Grid.Row="3" HorizontalContentAlignment="Right" />
<Grid Grid.Column="3" Grid.Row="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="28" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<CheckBox x:Name="checkBoxPilotRequired" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Left" />
<ComboBox Name="comboBoxPilot" Grid.Column="1" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearPilot" Click="contextMenuItemClearPilot_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
</Grid>
<ComboBox Name="comboBoxPilot" Grid.Column="3" Grid.Row="3" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearPilot" Click="contextMenuItemClearPilot_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
<Label Content="{x:Static p:Resources.textMooring}" Grid.Column="2" Grid.Row="4" HorizontalContentAlignment="Right"/>
<ComboBox Name="comboBoxMooring" Grid.Column="3" Grid.Row="4" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>

View File

@ -100,9 +100,9 @@ namespace BreCalClient
this.ShipcallModel.Shipcall.TidalWindowTo = this.datePickerTidalWindowTo.Value;
this.ShipcallModel.Shipcall.Canceled = this.checkBoxCanceled.IsChecked;
this.ShipcallModel.Shipcall.TugRequired = this.checkBoxTugRequired.IsChecked;
this.ShipcallModel.Shipcall.RecommendedTugs = this.integerUpDownRecommendedTugs.Value;
this.ShipcallModel.Shipcall.PilotRequired = this.checkBoxPilotRequired.IsChecked;
this.ShipcallModel.Shipcall.MooredLock = this.checkBoxMooredLock.IsChecked;
this.ShipcallModel.Shipcall.RainSensitiveCargo = this.checkBoxRainsensitiveCargo.IsChecked;
if(!string.IsNullOrEmpty(this.textBoxRemarks.Text.Trim()))
@ -183,9 +183,9 @@ namespace BreCalClient
this.datePickerTidalWindowTo.Value = this.ShipcallModel.Shipcall.TidalWindowTo;
this.checkBoxCanceled.IsChecked = this.ShipcallModel.Shipcall.Canceled ?? false;
this.checkBoxTugRequired.IsChecked = this.ShipcallModel.Shipcall.TugRequired ?? false;
this.integerUpDownRecommendedTugs.Value = this.ShipcallModel.Shipcall.RecommendedTugs;
this.checkBoxPilotRequired.IsChecked = this.ShipcallModel.Shipcall.PilotRequired ?? false;
this.checkBoxMooredLock.IsChecked = this.ShipcallModel.Shipcall.MooredLock ?? false;
this.checkBoxRainsensitiveCargo.IsChecked = this.ShipcallModel.Shipcall.RainSensitiveCargo ?? false;
@ -238,10 +238,9 @@ namespace BreCalClient
this.datePickerTidalWindowTo.IsEnabled = isEnabled;
this.checkBoxCanceled.IsEnabled = isEnabled;
this.checkBoxTugRequired.IsEnabled = isEnabled;
this.comboBoxTug.IsEnabled = isEnabled;
this.integerUpDownRecommendedTugs.IsEnabled = isEnabled;
this.checkBoxPilotRequired.IsEnabled = isEnabled;
this.integerUpDownRecommendedTugs.IsEnabled = isEnabled;
this.comboBoxPilot.IsEnabled = isEnabled;
this.comboBoxMooring.IsEnabled = isEnabled;
this.checkBoxMooredLock.IsEnabled = isEnabled;

View File

@ -103,37 +103,27 @@
<Label Content="{x:Static p:Resources.textTugRequired}" Grid.Column="2" Grid.Row="1" HorizontalContentAlignment="Right"/>
<Grid Grid.Column="3" Grid.Row="1">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="28" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<CheckBox x:Name="checkBoxTugRequired" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Left" />
<ComboBox Name="comboBoxTug" Grid.Column="1" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearTug" Click="contextMenuItemClearTug_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
</Grid>
<ComboBox Name="comboBoxTug" Grid.Column="3" Grid.Row="1" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearTug" Click="contextMenuItemClearTug_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
<Label Content="{x:Static p:Resources.textRecommendedTugs}" Grid.Column="2" Grid.Row="2" HorizontalContentAlignment="Right"/>
<xctk:IntegerUpDown x:Name="integerUpDownRecommendedTugs" Grid.Column="3" Grid.Row="2" Minimum="0" Margin="2" />
<Label Content="{x:Static p:Resources.textPilotRequired}" Grid.Column="2" Grid.Row="3" HorizontalContentAlignment="Right" />
<Grid Grid.Column="3" Grid.Row="3">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="28" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<CheckBox x:Name="checkBoxPilotRequired" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Left" />
<ComboBox Name="comboBoxPilot" Grid.Column="1" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearPilot" Click="contextMenuItemClearPilot_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
</Grid>
<ComboBox Name="comboBoxPilot" Grid.Column="3" Grid.Row="3" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearPilot" Click="contextMenuItemClearPilot_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
<Label Content="{x:Static p:Resources.textMooring}" Grid.Column="2" Grid.Row="4" HorizontalContentAlignment="Right"/>
<ComboBox Name="comboBoxMooring" Grid.Column="3" Grid.Row="4" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu>

View File

@ -104,9 +104,9 @@ namespace BreCalClient
this.ShipcallModel.Shipcall.Canceled = this.checkBoxCanceled.IsChecked;
this.ShipcallModel.Shipcall.TugRequired = this.checkBoxTugRequired.IsChecked;
this.ShipcallModel.Shipcall.RecommendedTugs = this.integerUpDownRecommendedTugs.Value;
this.ShipcallModel.Shipcall.PilotRequired = this.checkBoxPilotRequired.IsChecked;
this.ShipcallModel.Shipcall.MooredLock = this.checkBoxMooredLock.IsChecked;
this.ShipcallModel.Shipcall.RainSensitiveCargo = this.checkBoxRainsensitiveCargo.IsChecked;
if(!string.IsNullOrEmpty(this.textBoxRemarks.Text.Trim()))
@ -197,11 +197,10 @@ namespace BreCalClient
this.doubleUpDownDraft.Value = this.ShipcallModel.Shipcall.Draft;
this.datePickerTidalWindowFrom.Value = this.ShipcallModel.Shipcall.TidalWindowFrom;
this.datePickerTidalWindowTo.Value = this.ShipcallModel.Shipcall.TidalWindowTo;
this.checkBoxCanceled.IsChecked = this.ShipcallModel.Shipcall.Canceled ?? false;
this.checkBoxCanceled.IsChecked = this.ShipcallModel.Shipcall.Canceled ?? false;
this.checkBoxTugRequired.IsChecked = this.ShipcallModel.Shipcall.TugRequired ?? false;
this.integerUpDownRecommendedTugs.Value = this.ShipcallModel.Shipcall.RecommendedTugs;
this.checkBoxPilotRequired.IsChecked = this.ShipcallModel.Shipcall.PilotRequired ?? false;
this.checkBoxMooredLock.IsChecked = this.ShipcallModel.Shipcall.MooredLock ?? false;
this.checkBoxRainsensitiveCargo.IsChecked = this.ShipcallModel.Shipcall.RainSensitiveCargo ?? false;
@ -255,10 +254,9 @@ namespace BreCalClient
this.textBoxBerthRemarksArrival.IsEnabled = isEnabled;
this.checkBoxCanceled.IsEnabled = isEnabled;
this.checkBoxTugRequired.IsEnabled = isEnabled;
this.comboBoxTug.IsEnabled = isEnabled;
this.integerUpDownRecommendedTugs.IsEnabled = isEnabled;
this.checkBoxPilotRequired.IsEnabled = isEnabled;
this.integerUpDownRecommendedTugs.IsEnabled = isEnabled;
this.comboBoxPilot.IsEnabled = isEnabled;
this.comboBoxMooring.IsEnabled = isEnabled;
this.checkBoxMooredLock.IsEnabled = isEnabled;

View File

@ -36,7 +36,7 @@
<Label Content="{x:Static p:Resources.textPassword}" Grid.Row="2" VerticalContentAlignment="Center" />
<TextBox Name="textUsername" Grid.Row="1" Grid.Column="1" Margin="2" VerticalContentAlignment="Center" />
<PasswordBox Name="textPassword" Grid.Row="2" Grid.Column="1" Margin="2" VerticalContentAlignment="Center" PasswordChar="*"/>
<Label Name="labelLoginResult" Grid.Row="3" Grid.ColumnSpan="2" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" />
<Label Name="labelLoginResult" Grid.Row="3" Grid.ColumnSpan="2" HorizontalContentAlignment="Center" VerticalContentAlignment="Center" FontWeight="Bold" />
<Button Name="buttonLogin" Content="{x:Static p:Resources.textLogin}" Grid.Row="4" Grid.Column="0" Margin="2" Click="buttonLogin_Click" IsDefault="True" />
<Button Name="buttonExit" Content="{x:Static p:Resources.textExit}" Grid.Row="4" Grid.Column="1" Margin="2" Click="buttonExit_Click" />
</Grid>

View File

@ -17,6 +17,8 @@ using BreCalClient.misc.Model;
using static BreCalClient.Extensions;
using System.Collections.Concurrent;
using Newtonsoft.Json;
using System.Security.Principal;
namespace BreCalClient
{
@ -129,7 +131,13 @@ namespace BreCalClient
}
catch (ApiException ex)
{
this.labelLoginResult.Content = ex.Message;
if ((ex.ErrorContent != null && ((string)ex.ErrorContent).StartsWith("{"))) {
Error? anError = JsonConvert.DeserializeObject<Error>((string)ex.ErrorContent);
this.labelLoginResult.Content = anError?.Message ?? ex.Message;
}
else {
this.labelLoginResult.Content = ex.Message;
}
labelGeneralStatus.Text = $"Connection {ConnectionStatus.FAILED}";
}
catch (Exception ex)

View File

@ -5,7 +5,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Project>
<PropertyGroup>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>0.9.6.0</ApplicationVersion>
<ApplicationVersion>1.0.0.0</ApplicationVersion>
<BootstrapperEnabled>True</BootstrapperEnabled>
<Configuration>Debug</Configuration>
<CreateDesktopShortcut>True</CreateDesktopShortcut>

View File

@ -5,7 +5,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Project>
<PropertyGroup>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>0.9.6.0</ApplicationVersion>
<ApplicationVersion>1.0.0.0</ApplicationVersion>
<BootstrapperEnabled>False</BootstrapperEnabled>
<Configuration>Release</Configuration>
<CreateWebPageOnPublish>True</CreateWebPageOnPublish>
@ -39,6 +39,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<SuiteName>Bremen calling client</SuiteName>
<SupportUrl>https://www.bsmd-emswe.eu/</SupportUrl>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SkipPublishVerification>false</SkipPublishVerification>
</PropertyGroup>
<ItemGroup>
<PublishFile Include="containership.ico">

View File

@ -5,7 +5,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Project>
<PropertyGroup>
<ApplicationRevision>0</ApplicationRevision>
<ApplicationVersion>0.9.6.*</ApplicationVersion>
<ApplicationVersion>1.0.0.*</ApplicationVersion>
<BootstrapperEnabled>True</BootstrapperEnabled>
<Configuration>Debug</Configuration>
<CreateDesktopShortcut>True</CreateDesktopShortcut>

View File

@ -182,7 +182,7 @@
<value>Ändern</value>
</data>
<data name="textChangePassword" xml:space="preserve">
<value>Password ändern</value>
<value>Passwort ändern</value>
</data>
<data name="textClose" xml:space="preserve">
<value>Schliessen</value>

View File

@ -5,8 +5,8 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:p = "clr-namespace:BreCalClient.Resources"
xmlns:sets="clr-namespace:BreCalClient.Properties"
xmlns:db="clr-namespace:BreCalClient;assembly=BreCalClient"
mc:Ignorable="d"
xmlns:db="clr-namespace:BreCalClient;assembly=BreCalClient"
mc:Ignorable="d"
d:DesignHeight="120" d:DesignWidth="800" Loaded="UserControl_Loaded">
<Border BorderBrush="LightGray" Margin="1" BorderThickness="1">
<Grid>

View File

@ -8,7 +8,7 @@
<applicationSettings>
<RoleEditor.Properties.Settings>
<setting name="ConnectionString" serializeAs="String">
<value>Server=localhost;User ID=ds;Password=HalloWach_2323XXL!!;Database=bremen_calling_devel;Port=33306</value>
<value>Server=localhost;User ID=ds;Password=HalloWach_2323XXL!!;Database=bremen_calling;Port=33306</value>
</setting>
</RoleEditor.Properties.Settings>
</applicationSettings>

View File

@ -31,6 +31,7 @@ from BreCal.stubs.times_tug import get_times_tug
from BreCal.stubs.times_full import get_times_full_simple
from BreCal.stubs.df_times import get_df_times
from BreCal.services.schedule_routines import setup_schedule, run_schedule_permanently_in_background
def create_app(test_config=None):
@ -60,15 +61,20 @@ def create_app(test_config=None):
app.register_blueprint(user.bp)
logging.basicConfig(filename='brecal.log', level=logging.DEBUG, format='%(asctime)s | %(name)s | %(levelname)s | %(message)s')
logging.basicConfig(filename='brecaltest.log', level=logging.DEBUG, format='%(asctime)s | %(name)s | %(levelname)s | %(message)s')
local_db.initPool(os.path.dirname(app.instance_path))
logging.info('App started')
# Setup Routine jobs (e.g., reevaluation of shipcalls)
setup_schedule(update_shipcalls_interval_in_minutes=60)
run_schedule_permanently_in_background(latency=30)
logging.info('Routine Jobs are defined.')
return app
__all__ = [
"get_project_root",
"ensure_path",
"get_project_root",
"ensure_path",
"execute_test_with_pytest",
"execute_coverage_test",
"difference_to_then",

View File

@ -1,6 +1,6 @@
from enum import Enum
from enum import Enum, IntFlag
class ParticipantType(Enum):
class ParticipantType(IntFlag):
"""determines the type of a participant"""
NONE = 0
BSMD = 1

View File

@ -1,12 +1,26 @@
import numpy as np
import pandas as pd
import datetime
import typing
from BreCal.schemas.model import Shipcall, Ship, Participant, Berth, User, Times
from BreCal.database.enums import ParticipantType
def pandas_series_to_data_model():
return
def set_participant_type(x, participant_df)->int:
"""
when iterating over each row entry x in the shipcall_participant_map,
one can update the 'type' column by extracting the matching data from a participant dataframe
returns: participant_type
"""
participant_id = x["participant_id"]
participant_type = participant_df.loc[participant_id, "type"]
return participant_type
class SQLHandler():
"""
An object that reads SQL queries from the sql_connection and stores it in pandas DataFrames. The object can read all available tables
@ -95,6 +109,10 @@ class SQLHandler():
# update the 'participants' column in 'shipcall'
self.initialize_shipcall_participant_list()
# update the 'type' in shipcall_participants_map
# fully deprecated
# self.add_participant_type_to_map()
return
def build_full_mysql_df_dict(self, all_schemas):
@ -121,6 +139,52 @@ class SQLHandler():
lambda x: self.get_participants(x.name),
axis=1)
return
def add_participant_type_to_map(self):
"""
applies a lambda function, where the 'type'-column in the shipcall_participant_map is updated by reading the
respective data from the participants. Updates the shipcall_participant_map inplace.
"""
raise Exception("deprecated! Overwriting the shipcall_participant_map may cause harm, as a participant with multi-flag might be wrongfully assigned to multiple roles simultaneously.")
#spm = self.df_dict["shipcall_participant_map"]
#participant_df = self.df_dict["participant"]
#spm.loc[:,"type"] = spm.loc[:].apply(lambda x: set_participant_type(x, participant_df=participant_df),axis=1)
#self.df_dict["shipcall_participant_map"] = spm
return
def get_assigned_participants(self, shipcall)->pd.DataFrame:
"""return each participant of a respective shipcall, filtered by the shipcall id"""
# get the shipcall_participant_map
spm = self.df_dict["shipcall_participant_map"]
assigned_participants = spm.loc[spm["shipcall_id"]==shipcall.id]
return assigned_participants
def get_assigned_participants_by_type(self, assigned_participants:pd.DataFrame, participant_type:ParticipantType):
"""filters a dataframe of assigned_participants by the provided type enumerator"""
if isinstance(participant_type,int):
participant_type = ParticipantType(participant_type)
assigned_participants_of_type = assigned_participants.loc[[participant_type in ParticipantType(int(pt_)) for pt_ in list(assigned_participants["type"].values)]]
#assigned_participants_of_type = assigned_participants.loc[assigned_participants["type"]==participant_type.value]
return assigned_participants_of_type
def check_if_any_participant_of_type_is_unassigned(self, shipcall, *args:list[ParticipantType])->bool:
"""
given a list of input arguments, where item is a participant type, the function determines, whether at least one participant
was assigned for the type. Function returns a boolean, whether any of the required participants in unassigned.
This method is extensively used for the validation rules 0001, where the header is checked beforehand to identify, whether
the respective participant type is assigned already.
"""
assigned_participants = self.get_assigned_participants(shipcall)
unassigned = [] # becomes a list of booleans
for participant_type in args:
assignments_of_type = self.get_assigned_participants_by_type(assigned_participants, participant_type=participant_type)
unassignment = len(assignments_of_type)==0 # a participant type does not exist, when there is no match
unassigned.append(unassignment)
return any(unassigned) # returns a single boolean, whether ANY of the types is not assigned
def standardize_model_str(self, model_str:str)->str:
"""check if the 'model_str' is valid and apply lowercasing to the string"""
@ -159,7 +223,10 @@ class SQLHandler():
return all_data
def df_loc_to_data_model(self, df, id, model_str, loc_type:str="loc"):
assert len(df)>0, f"empty dataframe"
if not len(df)>0:
import warnings
warnings.warn(f"empty dataframe in SQLHandler.df_loc_to_data_model for model type: {model_str}\n")
return df
# get a pandas series from the dataframe
series = df.loc[id] if loc_type=="loc" else df.iloc[id]
@ -174,9 +241,35 @@ class SQLHandler():
data = data_model(**data)
return data
def filter_df_by_participant_type(self, df, participant_type:typing.Union[int, ParticipantType])->pd.DataFrame:
"""
As ParticipantTypes are Flag objects, a dataframe's integer might resemble multiple participant types simultaneously.
This function allows for more complex filters, as the IntFlag allows more complex queries
e.g.:
ParticipantType(6) is 2,4 (2+4 = 6)
Participant(2) in Participant(6) = True # 6 is both, 2 and 4
Participant(1) in Participant(6) = False # 6 is both, 2 and 4, but not 1
"""
if isinstance(participant_type,int):
participant_type = ParticipantType(participant_type)
filtered_df = df.loc[[participant_type in ParticipantType(df_pt) for df_pt in list(df["participant_type"].values)]]
return filtered_df
def get_times_for_participant_type(self, df_times, participant_type:int):
filtered_series = df_times.loc[df_times["participant_type"]==participant_type]
assert len(filtered_series)<=1, f"found multiple results"
filtered_series = self.filter_df_by_participant_type(df_times, participant_type)
#filtered_series = df_times.loc[df_times["participant_type"]==participant_type]
if len(filtered_series)==0:
return None
if not len(filtered_series)<=1:
# correcting the error: ERROR:root:found multiple results
# however, a warning will still be issued
import warnings
warnings.warn(f"found multiple results in function SQLHandler.get_times_for_participant_type\nConsidering only the first match!\nAffected Times Indexes: {filtered_series.index}")
times = self.df_loc_to_data_model(filtered_series, id=0, model_str='times', loc_type="iloc") # use iloc! to retrieve the first result
return times
@ -198,10 +291,13 @@ class SQLHandler():
returns: participant_id_list, where every element is an int
"""
df = self.df_dict.get("shipcall_participant_map")
df = df.set_index('shipcall_id', inplace=False)
if 'shipcall_id' in list(df.columns):
df = df.set_index('shipcall_id', inplace=False)
# the 'if' call is needed to ensure, that no Exception is raised, when the shipcall_id is not present in the df
participant_id_list = df.loc[shipcall_id, "participant_id"].to_list() if shipcall_id in list(df.index) else []
participant_id_list = df.loc[shipcall_id, "participant_id"].tolist() if shipcall_id in list(df.index) else []
if not isinstance(participant_id_list,list):
participant_id_list = [participant_id_list]
return participant_id_list
def get_times_of_shipcall(self, shipcall)->pd.DataFrame:
@ -227,7 +323,8 @@ class SQLHandler():
df_times = df_times.loc[~df_times[non_null_column].isnull()] # NOT null filter
# filter by the agency participant_type
times_agency = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]
times_agency = self.filter_df_by_participant_type(df_times, ParticipantType.AGENCY.value)
#times_agency = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]
return times_agency
def filter_df_by_key_value(self, df, key, value)->pd.DataFrame:
@ -237,8 +334,8 @@ class SQLHandler():
"""given a dataframe of all agency times, get all unique ship counts, their values (datetime) and the string tags. returns a tuple (values,unique,counts)"""
# optional: rounding
if rounding is not None:
all_df_times.loc[:, query] = all_df_times.loc[:, query].dt.round(rounding) # e.g., 'min'
query_time_agency = times_agency[query].iloc[0].round(rounding)# e.g., 'min'
all_df_times.loc[:, query] = pd.to_datetime(all_df_times.loc[:, query]).dt.round(rounding) # e.g., 'min' --- # correcting the error: 'AttributeError: Can only use .dt accessor with datetimelike values'
query_time_agency = pd.to_datetime(times_agency[query]).iloc[0].round(rounding)# e.g., 'min'
# after rounding, filter {all_df_times}, so only those, which match the current query are of interest
# takes 'times_agency' to sample, which value should match

View File

@ -1,6 +1,6 @@
import json
import logging
import traceback
import pydapper
from ..schemas import model
@ -36,6 +36,7 @@ def GetShipcalls(options):
pooledConnection.close()
except Exception as ex:
logging.error(traceback.format_exc())
logging.error(ex)
print(ex)
result = {}
@ -115,6 +116,7 @@ def PostShipcalls(schemaModel):
return json.dumps({"id" : new_id}), 201, {'Content-Type': 'application/json; charset=utf-8'}
except Exception as ex:
logging.error(traceback.format_exc())
logging.error(ex)
print(ex)
result = {}
@ -199,6 +201,7 @@ def PutShipcalls(schemaModel):
return json.dumps({"id" : schemaModel["id"]}), 200
except Exception as ex:
logging.error(traceback.format_exc())
logging.error(ex)
print(ex)
result = {}

View File

@ -0,0 +1,81 @@
import logging
import pydapper
from BreCal.schemas import model
from BreCal.local_db import getPoolConnection
from BreCal.database.update_database import evaluate_shipcall_state
import threading
import schedule
import time
options = {'past_days':7}
def UpdateShipcalls(options:dict = {'past_days':2}):
"""
performs a single update step of every shipcall that matches the criteria.
1.) opens an SQL connection
2.) queries every shipcall that fulfills the {options} criteria
3.) iterates over each shipcall
4.) closes the SQL connection
options:
key: 'past_days'. Is used to execute a filtered query of all available shipcalls. Defaults to 2 (days)
"""
try:
pooledConnection = getPoolConnection()
commands = pydapper.using(pooledConnection)
query = ("SELECT id, ship_id, type, eta, voyage, etd, arrival_berth_id, departure_berth_id, tug_required, pilot_required, "
"flags, pier_side, bunkering, replenishing_terminal, replenishing_lock, draft, tidal_window_from, tidal_window_to, rain_sensitive_cargo, recommended_tugs, "
"anchored, moored_lock, canceled, evaluation, evaluation_message, created, modified FROM shipcall WHERE ((type = 1 OR type = 3) AND eta >= DATE(NOW() - INTERVAL %d DAY)"
"OR (type = 2 AND etd >= DATE(NOW() - INTERVAL %d DAY))) "
"ORDER BY eta") % (options["past_days"], options["past_days"])
# obtain data from the MYSQL database
data = commands.query(query, model=model.Shipcall)
# get the shipcall ids, which are of interest
shipcall_ids = [dat.id for dat in data]
# iterate over each shipcall id and (re-) evaluate it
for shipcall_id in shipcall_ids:
# apply 'Traffic Light' evaluation to obtain 'GREEN', 'YELLOW' or 'RED' evaluation state. The function internally updates the mysql database
evaluate_shipcall_state(mysql_connector_instance=pooledConnection, shipcall_id=shipcall_id) # new_id (last insert id) refers to the shipcall id
pooledConnection.close()
except Exception as ex:
logging.error(ex)
return
def add_function_to_schedule__update_shipcalls(interval_in_minutes:int, options:dict={'past_days':2}):
kwargs_ = {"options":options}
schedule.every(interval_in_minutes).minutes.do(UpdateShipcalls, **kwargs_)
return
def setup_schedule(update_shipcalls_interval_in_minutes:int=60):
schedule.clear() # clear all routine jobs. This prevents jobs from being created multiple times
# update the evaluation state in every recent shipcall
add_function_to_schedule__update_shipcalls(update_shipcalls_interval_in_minutes)
# placeholder: create/send notifications
# add_function_to_schedule__send_notifications(...)
return
def run_schedule_permanently(latency:int=30):
"""permanently runs the scheduler. Any time a routine task is 'due', it will be executed. There is an interval between each iteration to reduce workload"""
while True:
schedule.run_pending()
time.sleep(latency)
return
def run_schedule_permanently_in_background(latency:int=30, thread_name:str="run_schedule_in_background"):
"""create a (daemon) thread in the background, which permanently executes the scheduled functions"""
thread_names = [thread_.name for thread_ in threading.enumerate()]
if not thread_name in thread_names:
bg_thread = threading.Thread(target=run_schedule_permanently, name=thread_name, kwargs={"latency":latency})
bg_thread.daemon = True # 'daemon': stops, when Python's main thread stops.
bg_thread.start()
return

View File

@ -10,18 +10,18 @@ from BreCal.database.enums import StatusFlags
# a human interpretable dictionary for error messages. In this case, the English language is preferred
error_message_dict = {
# 0001 A-M
"validation_rule_fct_missing_time_agency_berth_eta":"The shipcall arrives in less than 20 hours, but there are still missing times by the agency. Please add the estimated time of arrival (ETA) {Rule #0001A}", # A
"validation_rule_fct_missing_time_agency_berth_etd":"The shipcall departs in less than 20 hours, but there are still missing times by the agency. Please add the estimated time of departure (ETD) {Rule #0001B}", # B
"validation_rule_fct_missing_time_mooring_berth_eta":"The shipcall arrives in less than 16 hours, but there are still missing times by the mooring. Please add the estimated time of arrival (ETA) {Rule #0001C}", # C
"validation_rule_fct_missing_time_mooring_berth_etd":"The shipcall departs in less than 16 hours, but there are still missing times by the mooring. Please add the estimated time of departure (ETD) {Rule #0001D}", # D
"validation_rule_fct_missing_time_portadministration_berth_eta":"The shipcall arrives in less than 16 hours, but there are still missing times by the port administration. Please add the estimated time of arrival (ETA) {Rule #0001F}", # F
"validation_rule_fct_missing_time_portadministration_berth_etd":"The shipcall departs in less than 16 hours, but there are still missing times by the port administration. Please add the estimated time of departure (ETD) {Rule #0001G}", # G
"validation_rule_fct_missing_time_pilot_berth_eta":"The shipcall arrives in less than 16 hours, but there are still missing times by the pilot. Please add the estimated time of arrival (ETA) {Rule #0001H}", # H
"validation_rule_fct_missing_time_pilot_berth_etd":"The shipcall departs in less than 16 hours, but there are still missing times by the pilot. Please add the estimated time of departure (ETD) {Rule #0001I}", # I
"validation_rule_fct_missing_time_tug_berth_eta":"The shipcall arrives in less than 16 hours, but there are still missing times by the tugs. Please add the estimated time of arrival (ETA) {Rule #0001J}", # J
"validation_rule_fct_missing_time_tug_berth_etd":"The shipcall departs in less than 16 hours, but there are still missing times by the tugs. Please add the estimated time of departure (ETD) {Rule #0001K}", # K
"validation_rule_fct_missing_time_terminal_berth_eta":"The shipcall arrives in less than 16 hours, but there are still missing times by the terminal. Please add the estimated time of arrival (ETA) {Rule #0001L}", # L
"validation_rule_fct_missing_time_terminal_berth_etd":"The shipcall departs in less than 16 hours, but there are still missing times by the terminal. Please add the estimated time of departure (ETD) {Rule #0001M}", # M
"validation_rule_fct_missing_time_agency_berth_eta":"Shipcall arrives soon (<20 hours). The agency did not provide a time yet (ETA) {Rule #0001A}", # A
"validation_rule_fct_missing_time_agency_berth_etd":"Shipcall departs soon (<20 hours). The agency did not provide a time yet (ETD) {Rule #0001B}", # B
"validation_rule_fct_missing_time_mooring_berth_eta":"Shipcall arrives soon (<16 hours). The mooring did not provide a time yet (ETA) {Rule #0001C}", # C
"validation_rule_fct_missing_time_mooring_berth_etd":"Shipcall departs soon (<16 hours). The mooring did not provide a time yet (ETD) {Rule #0001D}", # D
"validation_rule_fct_missing_time_portadministration_berth_eta":"Shipcall arrives soon (<16 hours). The port administration did not provide a time yet (ETA) {Rule #0001F}", # F
"validation_rule_fct_missing_time_portadministration_berth_etd":"Shipcall departs soon (<20 hours). The port administration did not provide a time yet (ETD) {Rule #0001G}", # G
"validation_rule_fct_missing_time_pilot_berth_eta":"Shipcall arrives soon (<16 hours). The pilot did not provide a time yet (ETA) {Rule #0001H}", # H
"validation_rule_fct_missing_time_pilot_berth_etd":"Shipcall departs soon (<20 hours). The pilot did not provide a time yet (ETD) {Rule #0001I}", # I
"validation_rule_fct_missing_time_tug_berth_eta":"Shipcall arrives soon (<16 hours). The tugs did not provide a time yet (ETA) {Rule #0001J}", # J
"validation_rule_fct_missing_time_tug_berth_etd":"Shipcall departs soon (<20 hours). The tugs did not provide a time yet (ETD) {Rule #0001K}", # K
"validation_rule_fct_missing_time_terminal_berth_eta":"Shipcall arrives soon (<16 hours). The terminal did not provide a time yet (ETA) {Rule #0001L}", # L
"validation_rule_fct_missing_time_terminal_berth_etd":"Shipcall departs soon (<20 hours). The terminal did not provide a time yet (ETD) {Rule #0001M}", # M
# 0002 A+B+C
"validation_rule_fct_shipcall_incoming_participants_disagree_on_eta":"There are deviating times between agency, mooring, port authority, pilot and tug for the estimated time of arrival (ETA) {Rule #0002A}",
@ -63,6 +63,10 @@ class ValidationRuleBaseFunctions():
returns: string
"""
return self.error_message_dict.get(key,key)
def get_no_violation_default_output(self):
"""return the default output of a validation function with no validation: a tuple of (GREEN state, None)"""
return (StatusFlags.GREEN, None)
def check_time_delta_violation_query_time_to_now(self, query_time:pd.Timestamp, key_time:pd.Timestamp, threshold:float)->bool:
"""
@ -95,9 +99,8 @@ class ValidationRuleBaseFunctions():
delta = self.time_logic.time_delta_from_now_to_tgt(tgt_time=query_time, unit="m")
# a violation occurs, when the delta (in minutes) exceeds the specified threshold of a participant
# to prevent past-events from triggering violations, negative values are ignored
# Violation, if 0 <= delta <= threshold
violation_state = (delta >= 0) and (delta<=threshold)
# Violation, if delta <= threshold
violation_state = (delta<=threshold)
return violation_state
def check_participants_agree_on_estimated_time(self, shipcall, query, df_times, applicable_shipcall_type)->bool:
@ -207,22 +210,26 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
- Checks, if times_agency.eta_berth is filled in.
- Measures the difference between 'now' and 'shipcall.eta'.
"""
# check, if the header is filled in (agency)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value])]) != 1:
return (StatusFlags.GREEN, None)
if not shipcall.type in [ShipcallType.INCOMING.value]:
return self.get_no_violation_default_output()
# check, if the header is filled in
unassigned = self.sql_handler.check_if_any_participant_of_type_is_unassigned(shipcall, *[ParticipantType.AGENCY])
if unassigned:
return self.get_no_violation_default_output()
# preparation: obtain the correct times of the participant, define the query time and the key time
times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value)
query_time = shipcall.eta
key_time = times_agency.eta_berth
key_time = times_agency.eta_berth if times_agency is not None else None
threshold = ParticipantwiseTimeDelta.AGENCY
violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
validation_name = "validation_rule_fct_missing_time_agency_berth_eta"
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
return self.get_no_violation_default_output()
def validation_rule_fct_missing_time_agency_berth_etd(self, shipcall, df_times, *args, **kwargs):
"""
@ -235,22 +242,26 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
- Checks, if times_agency.etd_berth is filled in.
- Measures the difference between 'now' and 'shipcall.etd'.
"""
# check, if the header is filled in (agency)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value])]) != 1:
return (StatusFlags.GREEN, None)
if not shipcall.type in [ShipcallType.OUTGOING.value, ShipcallType.SHIFTING.value]:
return self.get_no_violation_default_output()
# check, if the header is filled in
unassigned = self.sql_handler.check_if_any_participant_of_type_is_unassigned(shipcall, *[ParticipantType.AGENCY])
if unassigned:
return self.get_no_violation_default_output()
# preparation: obtain the correct times of the participant, define the query time and the key time
times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value)
query_time = shipcall.etd
key_time = times_agency.etd_berth
key_time = times_agency.etd_berth if times_agency is not None else None
threshold = ParticipantwiseTimeDelta.AGENCY
violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
validation_name = "validation_rule_fct_missing_time_agency_berth_etd"
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
return self.get_no_violation_default_output()
def validation_rule_fct_missing_time_mooring_berth_eta(self, shipcall, df_times, *args, **kwargs):
"""
@ -263,24 +274,28 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
- Checks, if times_mooring.eta_berth is filled in.
- Measures the difference between 'now' and 'times_agency.eta_berth'.
"""
# check, if the header is filled in (agency & MOORING)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.MOORING.value])]) != 2:
return (StatusFlags.GREEN, None)
if not shipcall.type in [ShipcallType.INCOMING.value]:
return self.get_no_violation_default_output()
# check, if the header is filled in
unassigned = self.sql_handler.check_if_any_participant_of_type_is_unassigned(shipcall, *[ParticipantType.AGENCY, ParticipantType.MOORING])
if unassigned:
return self.get_no_violation_default_output()
# preparation: obtain the correct times of the participant, define the query time and the key time
times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value)
times_mooring = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.MOORING.value)
query_time = times_agency.eta_berth
key_time = times_mooring.eta_berth
query_time = times_agency.eta_berth if times_agency is not None else None
key_time = times_mooring.eta_berth if times_mooring is not None else None
threshold = ParticipantwiseTimeDelta.MOORING
violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
validation_name = "validation_rule_fct_missing_time_mooring_berth_eta"
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
return self.get_no_violation_default_output()
def validation_rule_fct_missing_time_mooring_berth_etd(self, shipcall, df_times, *args, **kwargs):
"""
@ -293,24 +308,28 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
- Checks, if times_mooring.etd_berth is filled in.
- Measures the difference between 'now' and 'times_agency.etd_berth'.
"""
# check, if the header is filled in (agency & MOORING)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.MOORING.value])]) != 2:
return (StatusFlags.GREEN, None)
if not shipcall.type in [ShipcallType.OUTGOING.value, ShipcallType.SHIFTING.value]:
return self.get_no_violation_default_output()
# check, if the header is filled in
unassigned = self.sql_handler.check_if_any_participant_of_type_is_unassigned(shipcall, *[ParticipantType.AGENCY, ParticipantType.MOORING])
if unassigned:
return self.get_no_violation_default_output()
# preparation: obtain the correct times of the participant, define the query time and the key time
times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value)
times_mooring = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.MOORING.value)
query_time = times_agency.etd_berth
key_time = times_mooring.etd_berth
query_time = times_agency.etd_berth if times_agency is not None else None
key_time = times_mooring.etd_berth if times_mooring is not None else None
threshold = ParticipantwiseTimeDelta.MOORING
violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
validation_name = "validation_rule_fct_missing_time_mooring_berth_etd"
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
return self.get_no_violation_default_output()
def validation_rule_fct_missing_time_portadministration_berth_eta(self, shipcall, df_times, *args, **kwargs):
"""
@ -323,24 +342,28 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
- Checks, if times_port_administration.eta_berth is filled in.
- Measures the difference between 'now' and 'times_agency.eta_berth'.
"""
# check, if the header is filled in (agency & PORT_ADMINISTRATION)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.PORT_ADMINISTRATION.value])]) != 2:
return (StatusFlags.GREEN, None)
if not shipcall.type in [ShipcallType.INCOMING.value]:
return self.get_no_violation_default_output()
# check, if the header is filled in
unassigned = self.sql_handler.check_if_any_participant_of_type_is_unassigned(shipcall, *[ParticipantType.AGENCY, ParticipantType.PORT_ADMINISTRATION])
if unassigned:
return self.get_no_violation_default_output()
# preparation: obtain the correct times of the participant, define the query time and the key time
times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value)
times_port_administration = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.PORT_ADMINISTRATION.value)
query_time = times_agency.eta_berth
key_time = times_port_administration.eta_berth
query_time = times_agency.eta_berth if times_agency is not None else None
key_time = times_port_administration.eta_berth if times_port_administration is not None else None
threshold = ParticipantwiseTimeDelta.PORT_ADMINISTRATION
violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
validation_name = "validation_rule_fct_missing_time_portadministration_berth_eta"
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
return self.get_no_violation_default_output()
def validation_rule_fct_missing_time_portadministration_berth_etd(self, shipcall, df_times, *args, **kwargs):
"""
@ -353,24 +376,29 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
- Checks, if times_port_administration.etd_berth is filled in.
- Measures the difference between 'now' and 'times_agency.etd_berth'.
"""
# check, if the header is filled in (agency & PORT_ADMINISTRATION)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.PORT_ADMINISTRATION.value])]) != 2:
return (StatusFlags.GREEN, None)
if not shipcall.type in [ShipcallType.OUTGOING.value, ShipcallType.SHIFTING.value]:
return self.get_no_violation_default_output()
# check, if the header is filled in
unassigned = self.sql_handler.check_if_any_participant_of_type_is_unassigned(shipcall, *[ParticipantType.AGENCY, ParticipantType.PORT_ADMINISTRATION])
if unassigned:
return self.get_no_violation_default_output()
# preparation: obtain the correct times of the participant, define the query time and the key time
# when there are no times, the function returns None
times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value)
times_port_administration = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.PORT_ADMINISTRATION.value)
query_time = times_agency.etd_berth
key_time = times_port_administration.etd_berth
query_time = times_agency.etd_berth if times_agency is not None else None
key_time = times_port_administration.etd_berth if times_port_administration is not None else None
threshold = ParticipantwiseTimeDelta.PORT_ADMINISTRATION
violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
validation_name = "validation_rule_fct_missing_time_portadministration_berth_etd"
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
return self.get_no_violation_default_output()
def validation_rule_fct_missing_time_pilot_berth_eta(self, shipcall, df_times, *args, **kwargs):
"""
@ -383,24 +411,28 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
- Checks, if times_pilot.eta_berth is filled in.
- Measures the difference between 'now' and 'times_agency.eta_berth'.
"""
# check, if the header is filled in (agency & PILOT)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.PILOT.value])]) != 2:
return (StatusFlags.GREEN, None)
if not shipcall.type in [ShipcallType.INCOMING.value]:
return self.get_no_violation_default_output()
# check, if the header is filled in
unassigned = self.sql_handler.check_if_any_participant_of_type_is_unassigned(shipcall, *[ParticipantType.AGENCY, ParticipantType.PILOT])
if unassigned:
return self.get_no_violation_default_output()
# preparation: obtain the correct times of the participant, define the query time and the key time
times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value)
times_pilot = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.PILOT.value)
query_time = times_agency.eta_berth
key_time = times_pilot.eta_berth
query_time = times_agency.eta_berth if times_agency is not None else None
key_time = times_pilot.eta_berth if times_pilot is not None else None
threshold = ParticipantwiseTimeDelta.PILOT
violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
validation_name = "validation_rule_fct_missing_time_pilot_berth_eta"
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
return self.get_no_violation_default_output()
def validation_rule_fct_missing_time_pilot_berth_etd(self, shipcall, df_times, *args, **kwargs):
"""
@ -413,24 +445,28 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
- Checks, if times_pilot.etd_berth is filled in.
- Measures the difference between 'now' and 'times_agency.etd_berth'.
"""
# check, if the header is filled in (agency & PILOT)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.PILOT.value])]) != 2:
return (StatusFlags.GREEN, None)
if not shipcall.type in [ShipcallType.OUTGOING.value, ShipcallType.SHIFTING.value]:
return self.get_no_violation_default_output()
# check, if the header is filled in
unassigned = self.sql_handler.check_if_any_participant_of_type_is_unassigned(shipcall, *[ParticipantType.AGENCY, ParticipantType.PILOT])
if unassigned:
return self.get_no_violation_default_output()
# preparation: obtain the correct times of the participant, define the query time and the key time
times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value)
times_pilot = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.PILOT.value)
query_time = times_agency.etd_berth
key_time = times_pilot.etd_berth
query_time = times_agency.etd_berth if times_agency is not None else None
key_time = times_pilot.etd_berth if times_pilot is not None else None
threshold = ParticipantwiseTimeDelta.PILOT
violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
validation_name = "validation_rule_fct_missing_time_pilot_berth_etd"
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
return self.get_no_violation_default_output()
def validation_rule_fct_missing_time_tug_berth_eta(self, shipcall, df_times, *args, **kwargs):
"""
@ -443,24 +479,28 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
- Checks, if times_tug.eta_berth is filled in.
- Measures the difference between 'now' and 'times_agency.eta_berth'.
"""
# check, if the header is filled in (agency & TUG)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.TUG.value])]) != 2:
return (StatusFlags.GREEN, None)
if not shipcall.type in [ShipcallType.INCOMING.value]:
return self.get_no_violation_default_output()
# check, if the header is filled in
unassigned = self.sql_handler.check_if_any_participant_of_type_is_unassigned(shipcall, *[ParticipantType.AGENCY, ParticipantType.TUG])
if unassigned:
return self.get_no_violation_default_output()
# preparation: obtain the correct times of the participant, define the query time and the key time
times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value)
times_tug = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.TUG.value)
query_time = times_agency.eta_berth
key_time = times_tug.eta_berth
query_time = times_agency.eta_berth if times_agency is not None else None
key_time = times_tug.eta_berth if times_tug is not None else None
threshold = ParticipantwiseTimeDelta.TUG
violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
validation_name = "validation_rule_fct_missing_time_tug_berth_eta"
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
return self.get_no_violation_default_output()
def validation_rule_fct_missing_time_tug_berth_etd(self, shipcall, df_times, *args, **kwargs):
"""
@ -473,24 +513,28 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
- Checks, if times_tug.etd_berth is filled in.
- Measures the difference between 'now' and 'times_agency.etd_berth'.
"""
# check, if the header is filled in (agency & TUG)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.TUG.value])]) != 2:
return (StatusFlags.GREEN, None)
if not shipcall.type in [ShipcallType.OUTGOING.value, ShipcallType.SHIFTING.value]:
return self.get_no_violation_default_output()
# check, if the header is filled in
unassigned = self.sql_handler.check_if_any_participant_of_type_is_unassigned(shipcall, *[ParticipantType.AGENCY, ParticipantType.TUG])
if unassigned:
return self.get_no_violation_default_output()
# preparation: obtain the correct times of the participant, define the query time and the key time
times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value)
times_tug = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.TUG.value)
query_time = times_agency.etd_berth
key_time = times_tug.etd_berth
query_time = times_agency.etd_berth if times_agency is not None else None
key_time = times_tug.etd_berth if times_tug is not None else None
threshold = ParticipantwiseTimeDelta.TUG
violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
validation_name = "validation_rule_fct_missing_time_tug_berth_etd"
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
return self.get_no_violation_default_output()
def validation_rule_fct_missing_time_terminal_berth_eta(self, shipcall, df_times, *args, **kwargs):
"""
@ -500,27 +544,31 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
a certain threshold (e.g., 20 hours), a violation occurs
0001-L:
- Checks, if times_terminal.eta_berth is filled in.
- Checks, if times_terminal.operations_start is filled in.
- Measures the difference between 'now' and 'times_agency.eta_berth'.
"""
# check, if the header is filled in (agency & terminal)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.TERMINAL.value])]) != 2:
return (StatusFlags.GREEN, None)
if not shipcall.type in [ShipcallType.INCOMING.value]:
return self.get_no_violation_default_output()
# check, if the header is filled in
unassigned = self.sql_handler.check_if_any_participant_of_type_is_unassigned(shipcall, *[ParticipantType.AGENCY, ParticipantType.TERMINAL])
if unassigned:
return self.get_no_violation_default_output()
# preparation: obtain the correct times of the participant, define the query time and the key time
times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value)
times_terminal = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.TERMINAL.value)
query_time = times_agency.eta_berth
key_time = times_terminal.eta_berth
query_time = times_agency.eta_berth if times_agency is not None else None
key_time = times_terminal.operations_start if times_terminal is not None else None # eta_berth does not exist in times_terminal! Instead, it is called operations_start
threshold = ParticipantwiseTimeDelta.TERMINAL
violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
validation_name = "validation_rule_fct_missing_time_terminal_berth_eta"
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
return self.get_no_violation_default_output()
def validation_rule_fct_missing_time_terminal_berth_etd(self, shipcall, df_times, *args, **kwargs):
"""
@ -530,28 +578,31 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
a certain threshold (e.g., 20 hours), a violation occurs
0001-M:
- Checks, if times_terminal.etd_berth is filled in.
- Checks, if times_terminal.operations_end is filled in.
- Measures the difference between 'now' and 'times_agency.etd_berth'.
"""
# check, if the header is filled in (agency & terminal)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.TERMINAL.value])]) != 2:
return (StatusFlags.GREEN, None)
if not shipcall.type in [ShipcallType.OUTGOING.value, ShipcallType.SHIFTING.value]:
return self.get_no_violation_default_output()
# check, if the header is filled in
unassigned = self.sql_handler.check_if_any_participant_of_type_is_unassigned(shipcall, *[ParticipantType.AGENCY, ParticipantType.TERMINAL])
if unassigned:
return self.get_no_violation_default_output()
# preparation: obtain the correct times of the participant, define the query time and the key time
times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value)
times_terminal = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.TERMINAL.value)
query_time = times_agency.etd_berth
key_time = times_terminal.etd_berth
query_time = times_agency.etd_berth if times_agency is not None else None
key_time = times_terminal.operations_end if times_terminal is not None else None # etd_berth does not exist in times_terminal! Instead, it is called operations_end
threshold = ParticipantwiseTimeDelta.TERMINAL
violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
validation_name = "validation_rule_fct_missing_time_terminal_berth_etd"
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
return self.get_no_violation_default_output()
def validation_rule_fct_shipcall_incoming_participants_disagree_on_eta(self, shipcall, df_times, *args, **kwargs):
"""
@ -571,10 +622,10 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
validation_name = "validation_rule_fct_shipcall_incoming_participants_disagree_on_eta"
return (StatusFlags.RED, validation_name)
else:
return (StatusFlags.GREEN, None)
return self.get_no_violation_default_output()
def validation_rule_fct_shipcall_outgoing_participants_disagree_on_etd(self, shipcall, df_times, *args, **kwargs):
"""
@ -594,10 +645,10 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
validation_name = "validation_rule_fct_shipcall_outgoing_participants_disagree_on_etd"
return (StatusFlags.RED, validation_name)
else:
return (StatusFlags.GREEN, None)
return self.get_no_violation_default_output()
def validation_rule_fct_shipcall_shifting_participants_disagree_on_eta_or_etd(self, shipcall, df_times, *args, **kwargs):
"""
@ -631,10 +682,10 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
violation_state = (violation_state_eta) or (violation_state_etd)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
validation_name = "validation_rule_fct_shipcall_shifting_participants_disagree_on_eta_or_etd"
return (StatusFlags.RED, validation_name)
else:
return (StatusFlags.GREEN, None)
return self.get_no_violation_default_output()
def validation_rule_fct_eta_time_not_in_operation_window(self, shipcall, df_times, *args, **kwargs):
"""
@ -645,25 +696,31 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
query time: eta_berth (times_agency)
start_time & end_time: operations_start & operations_end (times_terminal)
"""
if not shipcall.type in [ShipcallType.INCOMING.value]:
return self.get_no_violation_default_output()
# check, if the header is filled in (agency & terminal)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.TERMINAL.value])]) != 2:
return (StatusFlags.GREEN, None)
if len(df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]) != 1:
return self.get_no_violation_default_output() # rule not applicable
if len(df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value]) != 1:
return self.get_no_violation_default_output() # rule not applicable
# get agency & terminal times
times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value)
times_terminal = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.TERMINAL.value)
if self.check_is_not_a_time_or_is_none(times_terminal.operations_start) or self.check_is_not_a_time_or_is_none(times_agency.eta_berth):
return (StatusFlags.GREEN, None)
return self.get_no_violation_default_output()
# check, whether the start of operations is AFTER the estimated arrival time
violation_state = times_terminal.operations_start < times_agency.eta_berth
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
validation_name = "validation_rule_fct_eta_time_not_in_operation_window"
return (StatusFlags.RED, validation_name)
else:
return (StatusFlags.GREEN, None)
return self.get_no_violation_default_output()
def validation_rule_fct_etd_time_not_in_operation_window(self, shipcall, df_times, *args, **kwargs):
"""
@ -674,25 +731,31 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
query time: eta_berth (times_agency)
start_time & end_time: operations_start & operations_end (times_terminal)
"""
if not shipcall.type in [ShipcallType.OUTGOING.value, ShipcallType.SHIFTING.value]:
return self.get_no_violation_default_output()
# check, if the header is filled in (agency & terminal)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.TERMINAL.value])]) != 2:
return (StatusFlags.GREEN, None)
if len(df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]) != 1:
return self.get_no_violation_default_output() # rule not applicable
if len(df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value]) != 1:
return self.get_no_violation_default_output() # rule not applicable
# get agency & terminal times
times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value)
times_terminal = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.TERMINAL.value)
if self.check_is_not_a_time_or_is_none(times_terminal.operations_end) or self.check_is_not_a_time_or_is_none(times_agency.etd_berth):
return (StatusFlags.GREEN, None)
return self.get_no_violation_default_output()
# check, whether the end of operations is AFTER the estimated departure time
violation_state = times_terminal.operations_end > times_agency.etd_berth
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
validation_name = "validation_rule_fct_etd_time_not_in_operation_window"
return (StatusFlags.RED, validation_name)
else:
return (StatusFlags.GREEN, None)
return self.get_no_violation_default_output()
def validation_rule_fct_eta_time_not_in_tidal_window(self, shipcall, df_times, *args, **kwargs):
"""
@ -703,24 +766,27 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
query time: eta_berth (times_agency)
start_time & end_time: tidal_window_from & tidal_window_to (shipcall)
"""
if not shipcall.type in [ShipcallType.INCOMING.value]:
return self.get_no_violation_default_output()
# check, if the header is filled in (agency)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value])]) != 1:
return (StatusFlags.GREEN, None)
return self.get_no_violation_default_output()
times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value)
# requirements: tidal window (from & to) is filled in
if self.check_is_not_a_time_or_is_none(shipcall.tidal_window_from) or self.check_is_not_a_time_or_is_none(shipcall.tidal_window_to) or self.check_is_not_a_time_or_is_none(times_agency.eta_berth): # 202310310: note: this should check times_agency, shouldn't it?
return (StatusFlags.GREEN, None)
return self.get_no_violation_default_output()
# check, whether the query time is between start & end time
# a violation is observed, when the time is NOT between start & end
violation_state = not self.time_logic.time_inbetween(query_time=times_agency.eta_berth, start_time=shipcall.tidal_window_from, end_time=shipcall.tidal_window_to)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
validation_name = "validation_rule_fct_eta_time_not_in_tidal_window"
return (StatusFlags.RED, validation_name)
else:
return (StatusFlags.GREEN, None)
return self.get_no_violation_default_output()
def validation_rule_fct_etd_time_not_in_tidal_window(self, shipcall, df_times, *args, **kwargs):
"""
@ -731,24 +797,27 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
query time: eta_berth (times_agency)
start_time & end_time: tidal_window_from & tidal_window_to (shipcall)
"""
if not shipcall.type in [ShipcallType.OUTGOING.value, ShipcallType.SHIFTING.value]:
return self.get_no_violation_default_output()
# check, if the header is filled in (agency)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value])]) != 1:
return (StatusFlags.GREEN, None)
return self.get_no_violation_default_output()
times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value)
# requirements: tidal window (from & to) is filled in
if self.check_is_not_a_time_or_is_none(shipcall.tidal_window_from) or self.check_is_not_a_time_or_is_none(shipcall.tidal_window_to) or self.check_is_not_a_time_or_is_none(times_agency.etd_berth): # 202310310: note: this should check times_agency, shouldn't it?
return (StatusFlags.GREEN, None)
return self.get_no_violation_default_output()
# check, whether the query time is between start & end time
# a violation is observed, when the time is NOT between start & end
violation_state = not self.time_logic.time_inbetween(query_time=times_agency.etd_berth, start_time=shipcall.tidal_window_from, end_time=shipcall.tidal_window_to)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
validation_name = "validation_rule_fct_etd_time_not_in_tidal_window"
return (StatusFlags.RED, validation_name)
else:
return (StatusFlags.GREEN, None)
return self.get_no_violation_default_output()
def validation_rule_fct_too_many_identical_eta_times(self, shipcall, df_times, rounding = "min", maximum_threshold = 3, all_times_agency=None, *args, **kwargs):
"""
@ -759,17 +828,17 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
times_agency = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]
# check, if the header is filled in (agency)
if len(times_agency) != 1:
return (StatusFlags.GREEN, None)
return self.get_no_violation_default_output()
# when ANY of the unique values exceeds the threshold, a violation is observed
query = "eta_berth"
violation_state = self.check_unique_shipcall_counts(query, times_agency=times_agency, rounding=rounding, maximum_threshold=maximum_threshold, all_times_agency=all_times_agency)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
validation_name = "validation_rule_fct_too_many_identical_eta_times"
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
return self.get_no_violation_default_output()
def validation_rule_fct_too_many_identical_etd_times(self, shipcall, df_times, rounding = "min", maximum_threshold = 3, all_times_agency=None, *args, **kwargs):
"""
@ -780,17 +849,17 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
times_agency = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]
# check, if the header is filled in (agency)
if len(times_agency) != 1:
return (StatusFlags.GREEN, None)
return self.get_no_violation_default_output()
# when ANY of the unique values exceeds the threshold, a violation is observed
query = "etd_berth"
violation_state = self.check_unique_shipcall_counts(query, times_agency=times_agency, rounding=rounding, maximum_threshold=maximum_threshold, all_times_agency=all_times_agency)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
validation_name = "validation_rule_fct_too_many_identical_etd_times"
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
return self.get_no_violation_default_output()
def validation_rule_fct_agency_and_terminal_berth_id_disagreement(self, shipcall, df_times, *args, **kwargs):
"""
@ -799,31 +868,34 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
Description: This validation rule checks, whether agency and terminal agree with their designated berth place by checking berth_id.
"""
# check, if the header is filled in (agency & terminal)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.TERMINAL.value])]) != 2:
return (StatusFlags.GREEN, None)
if len(df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]) == 0:
return self.get_no_violation_default_output() # rule not applicable
if len(df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value]) == 0:
return self.get_no_violation_default_output() # rule not applicable
times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value)
times_terminal = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.TERMINAL.value)
# when one of the two values is null, the state is GREEN
if (times_agency.berth_id is None) or (times_terminal.berth_id is None):
return (StatusFlags.GREEN, None)
return self.get_no_violation_default_output()
# when one of the two values is null, the state is GREEN
if (pd.isnull(times_agency.berth_id)) or (pd.isnull(times_terminal.berth_id)):
return (StatusFlags.GREEN, None)
return self.get_no_violation_default_output()
if shipcall.type in [ShipcallType.OUTGOING.value, ShipcallType.SHIFTING.value]:
return (StatusFlags.GREEN, None)
return self.get_no_violation_default_output()
# only incoming shipcalls matter. The other ones are not relevant for the berth selection
violation_state = times_agency.berth_id!=times_terminal.berth_id
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
validation_name = "validation_rule_fct_agency_and_terminal_berth_id_disagreement"
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
return self.get_no_violation_default_output()
def validation_rule_fct_agency_and_terminal_pier_side_disagreement(self, shipcall, df_times, *args, **kwargs):
"""
@ -832,30 +904,33 @@ class ValidationRuleFunctions(ValidationRuleBaseFunctions):
Description: This validation rule checks, whether agency and terminal agree with their designated pier side by checking pier_side.
"""
# check, if the header is filled in (agency & terminal)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.TERMINAL.value])]) != 2:
return (StatusFlags.GREEN, None)
if len(df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]) == 0:
return self.get_no_violation_default_output() # rule not applicable
if len(df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value]) == 0:
return self.get_no_violation_default_output() # rule not applicable
times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value)
times_terminal = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.TERMINAL.value)
# when one of the two values is null, the state is GREEN
if (times_agency.pier_side is None) or (times_terminal.pier_side is None):
return (StatusFlags.GREEN, None)
if (shipcall.pier_side is None) or (times_terminal.pier_side is None):
return self.get_no_violation_default_output()
# when one of the two values is null, the state is GREEN
if (pd.isnull(times_agency.pier_side)) or (pd.isnull(times_terminal.pier_side)):
return (StatusFlags.GREEN, None)
if (pd.isnull(shipcall.pier_side)) or (pd.isnull(times_terminal.pier_side)):
return self.get_no_violation_default_output()
# only incoming shipcalls matter. The other ones are not relevant for the pier_side selection
if shipcall.type in [ShipcallType.OUTGOING.value, ShipcallType.SHIFTING.value]:
return (StatusFlags.GREEN, None)
return self.get_no_violation_default_output()
violation_state = times_agency.pier_side!=times_terminal.pier_side
violation_state = bool(shipcall.pier_side)!=bool(times_terminal.pier_side)
if violation_state:
validation_name = inspect.currentframe().f_code.co_name
validation_name = "validation_rule_fct_agency_and_terminal_pier_side_disagreement"
return (StatusFlags.YELLOW, validation_name)
else:
return (StatusFlags.GREEN, None)
return self.get_no_violation_default_output()

View File

@ -1,4 +1,5 @@
import copy
import re
import numpy as np
import pandas as pd
from BreCal.database.enums import StatusFlags
@ -29,7 +30,14 @@ class ValidationRules(ValidationRuleFunctions):
returns: (evaluation_state, violations)
"""
# prepare df_times, which every validation rule tends to use
df_times = self.sql_handler.df_dict.get('times') # -> pd.DataFrame
df_times = self.sql_handler.df_dict.get('times', pd.DataFrame()) # -> pd.DataFrame
if len(df_times)==0:
return (StatusFlags.GREEN.value, [])
spm = self.sql_handler.df_dict["shipcall_participant_map"]
if len(spm.loc[spm["shipcall_id"]==shipcall.id])==0:
return (StatusFlags.GREEN.value, [])
# filter by shipcall id
df_times = self.sql_handler.get_times_of_shipcall(shipcall)
@ -43,15 +51,9 @@ class ValidationRules(ValidationRuleFunctions):
# 'translate' all error codes into readable, human-understandable format.
evaluation_results = [(state, self.describe_error_message(msg)) for (state, msg) in evaluation_results]
""" # deprecated
# check, if ANY of the evaluation results (evaluation_state) is larger than the .GREEN state. This means, that .YELLOW and .RED
# would return 'True'. Numpy arrays and functions are used to accelerate the comparison.
# np.any returns a boolean.
#evaluation_state = not np.any(np.greater(np.array([result[0] for result in evaluation_results]), ValidationRuleState.GREEN))
"""
# check, what the maximum state flag is and return it
evaluation_state = np.max(np.array([result[0].value for result in evaluation_results])) if len(evaluation_results)>0 else 1
evaluation_state = np.max(np.array([result[0].value for result in evaluation_results])) if len(evaluation_results)>0 else StatusFlags.GREEN.value
evaluation_verbosity = [result[1] for result in evaluation_results]
return (evaluation_state, evaluation_verbosity)
@ -74,11 +76,27 @@ class ValidationRules(ValidationRuleFunctions):
# unbundle individual results. evaluation_state becomes an integer, violation
evaluation_state = [StatusFlags(res[0]).value for res in results]
violations = [",".join(res[1]) if len(res[1])>0 else None for res in results]
violations = [",\r\n".join(res[1]) if len(res[1])>0 else None for res in results]
violations = [self.concise_evaluation_message_if_too_long(violation) for violation in violations]
shipcall_df.loc[:,"evaluation"] = evaluation_state
shipcall_df.loc[:,"evaluation_message"] = violations
return shipcall_df
def concise_evaluation_message_if_too_long(self, violation):
"""
when many validation rules are violated at once, the resulting evaluation message may exceed 512 characters (which the mysql database allows)
in these cases, use a regular expression to provide a concise message, where the 'concise' description is only the list of violated rools
"""
if violation is None:
return violation
if len(violation)>=512:
concise = re.findall(r'{(.*?)\}', violation)
# e.g.: Evaluation message too long. Violated Rules: ['Rule #0001C', 'Rule #0001H', 'Rule #0001F', 'Rule #0001G', 'Rule #0001L', 'Rule #0001M', 'Rule #0001J', 'Rule #0001K']
violation = f"Evaluation message too long. Violated Rules: {concise}"
return violation
def determine_validation_state(self) -> str:
"""

View File

@ -5,12 +5,16 @@ import logging
sys.path.insert(0, '/var/www/brecal/src/server')
sys.path.insert(0, '/var/www/venv/lib/python3.10/site-packages/')
import schedule
# set the key
os.environ['SECRET_KEY'] = 'zdiTz8P3jXOc7jztIQAoelK4zztyuCpJ'
# Set up logging
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
# Set up Scheduled Jobs
# Import and run the Flask app
from BreCal import create_app
application = create_app()
application = create_app()

View File

@ -117,6 +117,19 @@ def test_validation_rule_fct_missing_time_agency_berth_eta__shipcall_eta_dangero
# set times agency to be undetermined
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "eta_berth"] = None
# must adapt the shipcall_participant_map, so it suits the test
agency_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "participant_id"].iloc[0]
spm = vr.sql_handler.df_dict["shipcall_participant_map"]
df = pd.DataFrame(
[
{"id":10001, "shipcall_id":shipcall.id, "participant_id":agency_participant_id, "type":ParticipantType.AGENCY.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None},
]
)
df.set_index("id", inplace=True)
spm = pd.concat([spm, df], axis=0, ignore_index=True)
vr.sql_handler.df_dict["shipcall_participant_map"] = spm
# apply the validation rule
(state, msg) = vr.validation_rule_fct_missing_time_agency_berth_eta(shipcall=shipcall, df_times=df_times)
@ -137,6 +150,19 @@ def test_validation_rule_fct_missing_time_agency_berth_eta__shipcall_eta_distant
# set times agency to be undetermined
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "eta_berth"] = None
# must adapt the shipcall_participant_map, so it suits the test
agency_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "participant_id"].iloc[0]
spm = vr.sql_handler.df_dict["shipcall_participant_map"]
df = pd.DataFrame(
[
{"id":10001, "shipcall_id":shipcall.id, "participant_id":agency_participant_id, "type":ParticipantType.AGENCY.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None},
]
)
df.set_index("id", inplace=True)
spm = pd.concat([spm, df], axis=0, ignore_index=True)
vr.sql_handler.df_dict["shipcall_participant_map"] = spm
# apply the validation rule
(state, msg) = vr.validation_rule_fct_missing_time_agency_berth_eta(shipcall=shipcall, df_times=df_times)
@ -157,6 +183,19 @@ def test_validation_rule_fct_missing_time_agency_berth_eta__shipcall_eta_is_unde
# set times agency to be undetermined
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "eta_berth"] = None
# must adapt the shipcall_participant_map, so it suits the test
agency_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "participant_id"].iloc[0]
spm = vr.sql_handler.df_dict["shipcall_participant_map"]
df = pd.DataFrame(
[
{"id":10001, "shipcall_id":shipcall.id, "participant_id":agency_participant_id, "type":ParticipantType.AGENCY.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None},
]
)
df.set_index("id", inplace=True)
spm = pd.concat([spm, df], axis=0, ignore_index=True)
vr.sql_handler.df_dict["shipcall_participant_map"] = spm
# apply the validation rule
(state, msg) = vr.validation_rule_fct_missing_time_agency_berth_eta(shipcall=shipcall, df_times=df_times)
@ -172,11 +211,25 @@ def test_validation_rule_fct_missing_time_agency_berth_etd__shipcall_etd_is_unde
df_times = get_df_times(shipcall)
# the shipcall etd is 'soon'
shipcall.type = ShipcallType.OUTGOING.value
shipcall.etd = datetime.datetime.now() + datetime.timedelta(minutes=ParticipantwiseTimeDelta.AGENCY-10)
# set times agency to be undetermined
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "etd_berth"] = None
# must adapt the shipcall_participant_map, so it suits the test
agency_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "participant_id"].iloc[0]
spm = vr.sql_handler.df_dict["shipcall_participant_map"]
df = pd.DataFrame(
[
{"id":10001, "shipcall_id":shipcall.id, "participant_id":agency_participant_id, "type":ParticipantType.AGENCY.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None},
]
)
df.set_index("id", inplace=True)
spm = pd.concat([spm, df], axis=0, ignore_index=True)
vr.sql_handler.df_dict["shipcall_participant_map"] = spm
# apply the validation rule
(state, msg) = vr.validation_rule_fct_missing_time_agency_berth_etd(shipcall=shipcall, df_times=df_times)
@ -197,6 +250,21 @@ def test_validation_rule_fct_missing_time_mooring_berth_eta__shipcall_soon_but_p
# set times agency to be undetermined
df_times.loc[df_times["participant_type"]==ParticipantType.MOORING.value, "eta_berth"] = None
# must adapt the shipcall_participant_map, so it suits the test
agency_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "participant_id"].iloc[0]
mooring_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.MOORING.value, "participant_id"].iloc[0]
spm = vr.sql_handler.df_dict["shipcall_participant_map"]
df = pd.DataFrame(
[
{"id":10001, "shipcall_id":shipcall.id, "participant_id":agency_participant_id, "type":ParticipantType.AGENCY.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None},
{"id":10002, "shipcall_id":shipcall.id, "participant_id":mooring_participant_id, "type":ParticipantType.MOORING.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None}
]
)
df.set_index("id", inplace=True)
spm = pd.concat([spm, df], axis=0, ignore_index=True)
vr.sql_handler.df_dict["shipcall_participant_map"] = spm
# apply the validation rule
(state, msg) = vr.validation_rule_fct_missing_time_mooring_berth_eta(shipcall=shipcall, df_times=df_times)
@ -211,6 +279,7 @@ def test_validation_rule_fct_missing_time_mooring_berth_etd__shipcall_soon_but_p
vr = build_sql_proxy_connection['vr']
shipcall = get_shipcall_simple()
shipcall.type = ShipcallType.OUTGOING.value
df_times = get_df_times(shipcall)
# according to the agency, a shipcall takes place soon (ETA/ETD)
@ -219,6 +288,21 @@ def test_validation_rule_fct_missing_time_mooring_berth_etd__shipcall_soon_but_p
# set times agency to be undetermined
df_times.loc[df_times["participant_type"]==ParticipantType.MOORING.value, "etd_berth"] = None
# must adapt the shipcall_participant_map, so it suits the test
agency_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "participant_id"].iloc[0]
mooring_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.MOORING.value, "participant_id"].iloc[0]
spm = vr.sql_handler.df_dict["shipcall_participant_map"]
df = pd.DataFrame(
[
{"id":10001, "shipcall_id":shipcall.id, "participant_id":agency_participant_id, "type":ParticipantType.AGENCY.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None},
{"id":10002, "shipcall_id":shipcall.id, "participant_id":mooring_participant_id, "type":ParticipantType.MOORING.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None}
]
)
df.set_index("id", inplace=True)
spm = pd.concat([spm, df], axis=0, ignore_index=True)
vr.sql_handler.df_dict["shipcall_participant_map"] = spm
# apply the validation rule
(state, msg) = vr.validation_rule_fct_missing_time_mooring_berth_etd(shipcall=shipcall, df_times=df_times)
@ -241,6 +325,21 @@ def test_validation_rule_fct_missing_time_portadministration_berth_eta__shipcall
# set times agency to be undetermined
df_times.loc[df_times["participant_type"]==ParticipantType.PORT_ADMINISTRATION.value, "eta_berth"] = None
# must adapt the shipcall_participant_map, so it suits the test
agency_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "participant_id"].iloc[0]
pa_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.PORT_ADMINISTRATION.value, "participant_id"].iloc[0]
spm = vr.sql_handler.df_dict["shipcall_participant_map"]
df = pd.DataFrame(
[
{"id":10001, "shipcall_id":shipcall.id, "participant_id":agency_participant_id, "type":ParticipantType.AGENCY.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None},
{"id":10002, "shipcall_id":shipcall.id, "participant_id":pa_participant_id, "type":ParticipantType.PORT_ADMINISTRATION.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None}
]
)
df.set_index("id", inplace=True)
spm = pd.concat([spm, df], axis=0, ignore_index=True)
vr.sql_handler.df_dict["shipcall_participant_map"] = spm
# apply the validation rule
(state, msg) = vr.validation_rule_fct_missing_time_portadministration_berth_eta(shipcall=shipcall, df_times=df_times)
@ -255,6 +354,7 @@ def test_validation_rule_fct_missing_time_portadministration_berth_etd__shipcall
vr = build_sql_proxy_connection['vr']
shipcall = get_shipcall_simple()
shipcall.type = ShipcallType.SHIFTING.value
df_times = get_df_times(shipcall)
# according to the agency, a shipcall takes place soon (ETA/ETD)
@ -263,6 +363,21 @@ def test_validation_rule_fct_missing_time_portadministration_berth_etd__shipcall
# set times agency to be undetermined
df_times.loc[df_times["participant_type"]==ParticipantType.PORT_ADMINISTRATION.value, "etd_berth"] = None
# must adapt the shipcall_participant_map, so it suits the test
agency_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "participant_id"].iloc[0]
pa_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.PORT_ADMINISTRATION.value, "participant_id"].iloc[0]
spm = vr.sql_handler.df_dict["shipcall_participant_map"]
df = pd.DataFrame(
[
{"id":10001, "shipcall_id":shipcall.id, "participant_id":agency_participant_id, "type":ParticipantType.AGENCY.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None},
{"id":10002, "shipcall_id":shipcall.id, "participant_id":pa_participant_id, "type":ParticipantType.PORT_ADMINISTRATION.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None}
]
)
df.set_index("id", inplace=True)
spm = pd.concat([spm, df], axis=0, ignore_index=True)
vr.sql_handler.df_dict["shipcall_participant_map"] = spm
# apply the validation rule
(state, msg) = vr.validation_rule_fct_missing_time_portadministration_berth_etd(shipcall=shipcall, df_times=df_times)
@ -283,6 +398,21 @@ def test_validation_rule_fct_missing_time_pilot_berth_eta__shipcall_soon_but_par
# set times agency to be undetermined
df_times.loc[df_times["participant_type"]==ParticipantType.PILOT.value, "eta_berth"] = None
# must adapt the shipcall_participant_map, so it suits the test
agency_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "participant_id"].iloc[0]
pilot_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.PILOT.value, "participant_id"].iloc[0]
spm = vr.sql_handler.df_dict["shipcall_participant_map"]
df = pd.DataFrame(
[
{"id":10001, "shipcall_id":shipcall.id, "participant_id":agency_participant_id, "type":ParticipantType.AGENCY.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None},
{"id":10002, "shipcall_id":shipcall.id, "participant_id":pilot_participant_id, "type":ParticipantType.PILOT.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None}
]
)
df.set_index("id", inplace=True)
spm = pd.concat([spm, df], axis=0, ignore_index=True)
vr.sql_handler.df_dict["shipcall_participant_map"] = spm
# apply the validation rule
(state, msg) = vr.validation_rule_fct_missing_time_pilot_berth_eta(shipcall=shipcall, df_times=df_times)
@ -295,6 +425,7 @@ def test_validation_rule_fct_missing_time_pilot_berth_etd__shipcall_soon_but_par
vr = build_sql_proxy_connection['vr']
shipcall = get_shipcall_simple()
shipcall.type = ShipcallType.OUTGOING.value
df_times = get_df_times(shipcall)
# according to the agency, a shipcall takes place soon (ETA/ETD)
@ -303,6 +434,21 @@ def test_validation_rule_fct_missing_time_pilot_berth_etd__shipcall_soon_but_par
# set times agency to be undetermined
df_times.loc[df_times["participant_type"]==ParticipantType.PILOT.value, "etd_berth"] = None
# must adapt the shipcall_participant_map, so it suits the test
agency_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "participant_id"].iloc[0]
pilot_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.PILOT.value, "participant_id"].iloc[0]
spm = vr.sql_handler.df_dict["shipcall_participant_map"]
df = pd.DataFrame(
[
{"id":10001, "shipcall_id":shipcall.id, "participant_id":agency_participant_id, "type":ParticipantType.AGENCY.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None},
{"id":10002, "shipcall_id":shipcall.id, "participant_id":pilot_participant_id, "type":ParticipantType.PILOT.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None}
]
)
df.set_index("id", inplace=True)
spm = pd.concat([spm, df], axis=0, ignore_index=True)
vr.sql_handler.df_dict["shipcall_participant_map"] = spm
# apply the validation rule
(state, msg) = vr.validation_rule_fct_missing_time_pilot_berth_etd(shipcall=shipcall, df_times=df_times)
@ -310,13 +456,127 @@ def test_validation_rule_fct_missing_time_pilot_berth_etd__shipcall_soon_but_par
assert state==StatusFlags.YELLOW, f"function should return 'yellow', because the participant did not provide a time and the shipcall takes place soon (according to the agency)"
return
def test_validation_rule_fct_missing_time_pilot_berth_etd__shipcall_soon_but_participant_unassigned__return_green(build_sql_proxy_connection):
"""0001-I validation_rule_fct_missing_time_pilot_berth_etd"""
vr = build_sql_proxy_connection['vr']
shipcall = get_shipcall_simple()
shipcall.type = ShipcallType.OUTGOING.value
df_times = get_df_times(shipcall)
# according to the agency, a shipcall takes place soon (ETA/ETD)
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "etd_berth"] = datetime.datetime.now() + datetime.timedelta(minutes=ParticipantwiseTimeDelta.PILOT-10)
# set times agency to be undetermined
df_times.loc[df_times["participant_type"]==ParticipantType.PILOT.value, "etd_berth"] = None
# must adapt the shipcall_participant_map, so it suits the test
agency_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "participant_id"].iloc[0]
vr.sql_handler.read_all(vr.sql_handler.all_schemas)
spm = vr.sql_handler.df_dict["shipcall_participant_map"]
df = pd.DataFrame(
[
{"id":10001, "shipcall_id":shipcall.id, "participant_id":agency_participant_id, "type":ParticipantType.AGENCY.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None}
]
)
df.set_index("id", inplace=True)
spm = pd.concat([spm, df], axis=0, ignore_index=True)
vr.sql_handler.df_dict["shipcall_participant_map"] = spm
# apply the validation rule
(state, msg) = vr.validation_rule_fct_missing_time_pilot_berth_etd(shipcall=shipcall, df_times=df_times)
# expectation: green state, no msg
assert state==StatusFlags.GREEN, f"function should return 'green', because the pilot is not assigned yet"
return
def test_validation_rule_fct_missing_time_pilot_berth_etd__shipcall_soon_but_participant_estimated_time_undefined_multiple_pilot_assignments_due_to_bug(build_sql_proxy_connection):
"""
0001-I validation_rule_fct_missing_time_pilot_berth_etd. Checks, whether the function still works in case of a buggy input. When there is more than one pilot
assignment, the validation rule should still work and return 'yellow' properly.
"""
vr = build_sql_proxy_connection['vr']
shipcall = get_shipcall_simple()
shipcall.type = ShipcallType.OUTGOING.value
df_times = get_df_times(shipcall)
# according to the agency, a shipcall takes place soon (ETA/ETD)
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "etd_berth"] = datetime.datetime.now() + datetime.timedelta(minutes=ParticipantwiseTimeDelta.PILOT-10)
# set times agency to be undetermined
df_times.loc[df_times["participant_type"]==ParticipantType.PILOT.value, "etd_berth"] = None
# must adapt the shipcall_participant_map, so it suits the test
agency_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "participant_id"].iloc[0]
pilot_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.PILOT.value, "participant_id"].iloc[0]
spm = vr.sql_handler.df_dict["shipcall_participant_map"]
df = pd.DataFrame(
[
{"id":10001, "shipcall_id":shipcall.id, "participant_id":agency_participant_id, "type":ParticipantType.AGENCY.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None},
{"id":10002, "shipcall_id":shipcall.id, "participant_id":pilot_participant_id, "type":ParticipantType.PILOT.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None},
{"id":10003, "shipcall_id":shipcall.id, "participant_id":pilot_participant_id, "type":ParticipantType.PILOT.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None},
{"id":10004, "shipcall_id":shipcall.id, "participant_id":pilot_participant_id, "type":ParticipantType.PILOT.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None}
]
)
df.set_index("id", inplace=True)
spm = pd.concat([spm, df], axis=0, ignore_index=True)
vr.sql_handler.df_dict["shipcall_participant_map"] = spm
# apply the validation rule
(state, msg) = vr.validation_rule_fct_missing_time_pilot_berth_etd(shipcall=shipcall, df_times=df_times)
# expectation: green state, no msg
assert state==StatusFlags.YELLOW, f"function should return 'yellow', because the participant did not provide a time and the shipcall takes place soon (according to the agency)"
return
def test_validation_rule_fct_missing_time_pilot_berth_etd__agency_and_pilot_assigned_pilot_no_times_returns_yellow(build_sql_proxy_connection):
"""
0001-I validation_rule_fct_missing_time_pilot_berth_etd. Checks the default behaviour, where an agency's time might exist,
while a time by pilot may not exist. In these cases, a yellow state is expected.
"""
vr = build_sql_proxy_connection['vr']
shipcall = get_shipcall_simple()
shipcall.type = ShipcallType.OUTGOING.value
df_times = get_df_times(shipcall)
# according to the agency, a shipcall takes place soon (ETA/ETD)
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "etd_berth"] = datetime.datetime.now() + datetime.timedelta(minutes=ParticipantwiseTimeDelta.PILOT-10)
# must adapt the shipcall_participant_map, so it suits the test
agency_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "participant_id"].iloc[0]
pilot_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.PILOT.value, "participant_id"].iloc[0]
# set times of PILOT: should not exist
df_times.loc[df_times["participant_type"]==ParticipantType.PILOT.value,"participant_type"] = ParticipantType.BSMD.value
spm = vr.sql_handler.df_dict["shipcall_participant_map"]
df = pd.DataFrame(
[
{"id":10001, "shipcall_id":shipcall.id, "participant_id":agency_participant_id, "type":ParticipantType.AGENCY.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None},
{"id":10002, "shipcall_id":shipcall.id, "participant_id":pilot_participant_id, "type":ParticipantType.PILOT.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None}
]
)
df.set_index("id", inplace=True)
spm = pd.concat([spm, df], axis=0, ignore_index=True)
vr.sql_handler.df_dict["shipcall_participant_map"] = spm
# apply the validation rule
(state, msg) = vr.validation_rule_fct_missing_time_pilot_berth_etd(shipcall=shipcall, df_times=df_times)
# expectation: green state, no msg
assert state==StatusFlags.YELLOW, f"function should return 'yellow', because the participant did not provide a time and the shipcall takes place soon (according to the agency)"
return
def test_validation_rule_fct_missing_time_tug_berth_eta__shipcall_soon_but_participant_estimated_time_undefined(build_sql_proxy_connection):
"""0001-J validation_rule_fct_missing_time_tug_berth_eta"""
vr = build_sql_proxy_connection['vr']
shipcall = get_shipcall_simple()
shipcall.type = ShipcallType.INCOMING.value
df_times = get_df_times(shipcall)
# according to the agency, a shipcall takes place soon (ETA/ETD)
@ -325,6 +585,21 @@ def test_validation_rule_fct_missing_time_tug_berth_eta__shipcall_soon_but_parti
# set times agency to be undetermined
df_times.loc[df_times["participant_type"]==ParticipantType.TUG.value, "eta_berth"] = None
# must adapt the shipcall_participant_map, so it suits the test
agency_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "participant_id"].iloc[0]
tug_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.TUG.value, "participant_id"].iloc[0]
spm = vr.sql_handler.df_dict["shipcall_participant_map"]
df = pd.DataFrame(
[
{"id":10001, "shipcall_id":shipcall.id, "participant_id":agency_participant_id, "type":ParticipantType.AGENCY.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None},
{"id":10002, "shipcall_id":shipcall.id, "participant_id":tug_participant_id, "type":ParticipantType.TUG.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None}
]
)
df.set_index("id", inplace=True)
spm = pd.concat([spm, df], axis=0, ignore_index=True)
vr.sql_handler.df_dict["shipcall_participant_map"] = spm
# apply the validation rule
(state, msg) = vr.validation_rule_fct_missing_time_tug_berth_eta(shipcall=shipcall, df_times=df_times)
@ -339,6 +614,7 @@ def test_validation_rule_fct_missing_time_tug_berth_etd__shipcall_soon_but_parti
vr = build_sql_proxy_connection['vr']
shipcall = get_shipcall_simple()
shipcall.type = ShipcallType.OUTGOING.value
df_times = get_df_times(shipcall)
# according to the agency, a shipcall takes place soon (ETA/ETD)
@ -347,6 +623,21 @@ def test_validation_rule_fct_missing_time_tug_berth_etd__shipcall_soon_but_parti
# set times agency to be undetermined
df_times.loc[df_times["participant_type"]==ParticipantType.TUG.value, "etd_berth"] = None
# must adapt the shipcall_participant_map, so it suits the test
agency_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "participant_id"].iloc[0]
tug_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.TUG.value, "participant_id"].iloc[0]
spm = vr.sql_handler.df_dict["shipcall_participant_map"]
df = pd.DataFrame(
[
{"id":10001, "shipcall_id":shipcall.id, "participant_id":agency_participant_id, "type":ParticipantType.AGENCY.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None},
{"id":10002, "shipcall_id":shipcall.id, "participant_id":tug_participant_id, "type":ParticipantType.TUG.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None}
]
)
df.set_index("id", inplace=True)
spm = pd.concat([spm, df], axis=0, ignore_index=True)
vr.sql_handler.df_dict["shipcall_participant_map"] = spm
# apply the validation rule
(state, msg) = vr.validation_rule_fct_missing_time_tug_berth_etd(shipcall=shipcall, df_times=df_times)
@ -367,7 +658,22 @@ def test_validation_rule_fct_missing_time_terminal_berth_eta__shipcall_soon_but_
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "eta_berth"] = datetime.datetime.now() + datetime.timedelta(minutes=ParticipantwiseTimeDelta.TERMINAL-10)
# set times agency to be undetermined
df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value, "eta_berth"] = None
df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value, "operations_start"] = None # previously: eta_berth, which does not exist in times_terminal
# must adapt the shipcall_participant_map, so it suits the test
agency_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "participant_id"].iloc[0]
terminal_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value, "participant_id"].iloc[0]
spm = vr.sql_handler.df_dict["shipcall_participant_map"]
df = pd.DataFrame(
[
{"id":10001, "shipcall_id":shipcall.id, "participant_id":agency_participant_id, "type":ParticipantType.AGENCY.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None},
{"id":10002, "shipcall_id":shipcall.id, "participant_id":terminal_participant_id, "type":ParticipantType.TERMINAL.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None}
]
)
df.set_index("id", inplace=True)
spm = pd.concat([spm, df], axis=0, ignore_index=True)
vr.sql_handler.df_dict["shipcall_participant_map"] = spm
# apply the validation rule
(state, msg) = vr.validation_rule_fct_missing_time_terminal_berth_eta(shipcall=shipcall, df_times=df_times)
@ -383,13 +689,30 @@ def test_validation_rule_fct_missing_time_terminal_berth_etd__shipcall_soon_but_
vr = build_sql_proxy_connection['vr']
shipcall = get_shipcall_simple()
shipcall.type = ShipcallType.OUTGOING.value
df_times = get_df_times(shipcall)
# according to the agency, a shipcall takes place soon (ETA/ETD)
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "etd_berth"] = datetime.datetime.now() + datetime.timedelta(minutes=ParticipantwiseTimeDelta.TERMINAL-10)
# set times agency to be undetermined
df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value, "etd_berth"] = None
df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value, "operations_end"] = None # previously: etd_berth, which does not exist in times_terminal
# must adapt the shipcall_participant_map, so it suits the test
agency_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "participant_id"].iloc[0]
terminal_participant_id = df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value, "participant_id"].iloc[0]
spm = vr.sql_handler.df_dict["shipcall_participant_map"]
df = pd.DataFrame(
[
{"id":10001, "shipcall_id":shipcall.id, "participant_id":agency_participant_id, "type":ParticipantType.AGENCY.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None},
{"id":10002, "shipcall_id":shipcall.id, "participant_id":terminal_participant_id, "type":ParticipantType.TERMINAL.value, "created":pd.Timestamp(datetime.datetime.now().isoformat()), "modified":None}
]
)
df.set_index("id", inplace=True)
spm = pd.concat([spm, df], axis=0, ignore_index=True)
vr.sql_handler.df_dict["shipcall_participant_map"] = spm
# apply the validation rule
(state, msg) = vr.validation_rule_fct_missing_time_terminal_berth_etd(shipcall=shipcall, df_times=df_times)
@ -602,6 +925,7 @@ def test_validation_rule_fct_etd_time_not_in_operation_window__times_dont_match(
"""0003-B validation_rule_fct_etd_time_not_in_operation_window"""
vr = build_sql_proxy_connection['vr']
shipcall = get_shipcall_simple()
shipcall.type = ShipcallType.SHIFTING.value
df_times = get_df_times(shipcall)
t0_time = datetime.datetime.now() # reference time for easier readability
@ -623,6 +947,7 @@ def test_validation_rule_fct_eta_time_not_in_operation_window_and_validation_rul
vr = build_sql_proxy_connection['vr']
import random
shipcall = get_shipcall_simple()
shipcall.type = ShipcallType.SHIFTING.value
df_times = get_df_times(shipcall)
t0_time = datetime.datetime.now()
@ -718,6 +1043,7 @@ def test_validation_rule_fct_etd_time_not_in_tidal_window__etd_outside_tidal_win
"""0004-B validation_rule_fct_etd_time_not_in_tidal_window"""
vr = build_sql_proxy_connection['vr']
shipcall = get_shipcall_simple()
shipcall.type = ShipcallType.OUTGOING.value
df_times = get_df_times(shipcall)
t0_time = datetime.datetime.now()
@ -801,7 +1127,8 @@ def test_validation_rule_fct_agency_and_terminal_pier_side_disagreement__agency_
vr = build_sql_proxy_connection['vr']
shipcall = get_shipcall_simple()
df_times = get_df_times(shipcall)
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "pier_side"] = True
shipcall.pier_side = True
# df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "pier_side"] = True
df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value, "pier_side"] = True
(code, msg) = vr.validation_rule_fct_agency_and_terminal_pier_side_disagreement(shipcall=shipcall, df_times=df_times)
@ -813,13 +1140,39 @@ def test_validation_rule_fct_agency_and_terminal_pier_side_disagreement__agency_
vr = build_sql_proxy_connection['vr']
shipcall = get_shipcall_simple()
df_times = get_df_times(shipcall)
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "pier_side"] = True
shipcall.pier_side = True
#df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "pier_side"] = True
df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value, "pier_side"] = False
(code, msg) = vr.validation_rule_fct_agency_and_terminal_pier_side_disagreement(shipcall=shipcall, df_times=df_times)
assert code==StatusFlags.YELLOW, f"status should be 'yellow', because agency and terminal do not agree on the selected pier side"
return
def test_validation_rule_fct_agency_and_terminal_pier_side_disagreement__agency_and_terminal_disagree_terminal_is_none(build_sql_proxy_connection):
"""0006-B validation_rule_fct_agency_and_terminal_pier_side_disagreement"""
vr = build_sql_proxy_connection['vr']
shipcall = get_shipcall_simple()
df_times = get_df_times(shipcall)
shipcall.pier_side = True
# df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "pier_side"] = True
df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value, "pier_side"] = None
(code, msg) = vr.validation_rule_fct_agency_and_terminal_pier_side_disagreement(shipcall=shipcall, df_times=df_times)
assert code==StatusFlags.GREEN, f"status should be 'green', because the terminal's pier_side is not provided"
return
def test_validation_rule_fct_agency_and_terminal_pier_side_disagreement__agency_and_terminal_disagree_terminal_is_nan(build_sql_proxy_connection):
"""0006-B validation_rule_fct_agency_and_terminal_pier_side_disagreement"""
vr = build_sql_proxy_connection['vr']
shipcall = get_shipcall_simple()
df_times = get_df_times(shipcall)
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "pier_side"] = True
df_times.loc[df_times["participant_type"]==ParticipantType.TERMINAL.value, "pier_side"] = float("nan")
(code, msg) = vr.validation_rule_fct_agency_and_terminal_pier_side_disagreement(shipcall=shipcall, df_times=df_times)
assert code==StatusFlags.GREEN, f"status should be 'green', because the terminal's pier_side is not provided"
return
def test_validation_rule_fct_agency_and_terminal_pier_side_agreement(build_sql_proxy_connection):
@ -841,7 +1194,8 @@ def test_validation_rule_fct_agency_and_terminal_pier_side_agreement(build_sql_p
t2.participant_type = ParticipantType.TERMINAL.value
# agreement
t1.pier_side = True
shipcall.pier_side = True
# t1.pier_side = True
t2.pier_side = True
time_objects = [t1, t2]
@ -872,7 +1226,8 @@ def test_validation_rule_fct_agency_and_terminal_pier_side_disagreement(build_sq
t2.participant_type = ParticipantType.TERMINAL.value
# disagreement
t1.pier_side = True
shipcall.pier_side = True
# t1.pier_side = True
t2.pier_side = False
time_objects = [t1, t2]