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> <SignAssembly>True</SignAssembly>
<StartupObject>BreCalClient.App</StartupObject> <StartupObject>BreCalClient.App</StartupObject>
<AssemblyOriginatorKeyFile>..\..\misc\brecal.snk</AssemblyOriginatorKeyFile> <AssemblyOriginatorKeyFile>..\..\misc\brecal.snk</AssemblyOriginatorKeyFile>
<AssemblyVersion>0.9.6.0</AssemblyVersion> <AssemblyVersion>1.0.0.0</AssemblyVersion>
<FileVersion>0.9.6.0</FileVersion> <FileVersion>1.0.0.0</FileVersion>
<Title>Bremen calling client</Title> <Title>Bremen calling client</Title>
<Description>A Windows WPF client for the Bremen calling API.</Description> <Description>A Windows WPF client for the Bremen calling API.</Description>
<ApplicationIcon>containership.ico</ApplicationIcon> <ApplicationIcon>containership.ico</ApplicationIcon>

View File

@ -65,24 +65,15 @@
<ColumnDefinition Width=".5*" /> <ColumnDefinition Width=".5*" />
<ColumnDefinition Width=".5*" /> <ColumnDefinition Width=".5*" />
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<ComboBox Name="comboBoxArrivalBerth" Grid.Column="0" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id"> <ComboBox Name="comboBoxArrivalBerth" Grid.Column="0" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id" SelectionChanged="comboBoxArrivalBerth_SelectionChanged"/>
<ComboBox.ContextMenu>
<ContextMenu> <ComboBox Name="comboBoxDepartureBerth" Grid.Column="1" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id" SelectionChanged="comboBoxDepartureBerth_SelectionChanged"/>
<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>
</Grid> </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="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"/> <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"/> <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"> <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) private void comboBoxShip_SelectionChanged(object sender, SelectionChangedEventArgs e)
{ {
this.buttonOK.IsEnabled = this.comboBoxShip.SelectedItem != null;
if (this.comboBoxShip.SelectedItem != null) if (this.comboBoxShip.SelectedItem != null)
{ {
Ship? ship = this.comboBoxShip.SelectedItem as Ship; Ship? ship = this.comboBoxShip.SelectedItem as Ship;
@ -84,11 +83,13 @@ namespace BreCalClient
this.doubleUpDownLength.Value = null; this.doubleUpDownLength.Value = null;
this.doubleUpDownWidth.Value = null; this.doubleUpDownWidth.Value = null;
} }
this.CheckForCompletion();
} }
private void comboBoxAgency_SelectionChanged(object sender, SelectionChangedEventArgs e) private void comboBoxAgency_SelectionChanged(object sender, SelectionChangedEventArgs e)
{ {
this.EnableControls(); this.EnableControls();
this.CheckForCompletion();
} }
private void comboBoxCategories_SelectionChanged(object? sender, SelectionChangedEventArgs? e) private void comboBoxCategories_SelectionChanged(object? sender, SelectionChangedEventArgs? e)
@ -122,6 +123,7 @@ namespace BreCalClient
break; break;
} }
} }
this.CheckForCompletion();
} }
#endregion #endregion
@ -133,22 +135,24 @@ namespace BreCalClient
this.comboBoxAgency.SelectedIndex = -1; this.comboBoxAgency.SelectedIndex = -1;
this.ShipcallModel.AssignedParticipants.Remove(Extensions.ParticipantType.AGENCY); 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 #endregion
#region private methods #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() private void CopyToModel()
{ {
if (this.ShipcallModel.Shipcall != null) if (this.ShipcallModel.Shipcall != null)
@ -293,5 +297,24 @@ namespace BreCalClient
#endregion #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"/> <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" /> <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"/> <Label Content="{x:Static p:Resources.textTugRequired}" Grid.Column="2" Grid.Row="1" HorizontalContentAlignment="Right"/>
<Grid Grid.Column="3" Grid.Row="1">
<Grid.ColumnDefinitions> <ComboBox Name="comboBoxTug" Grid.Column="3" Grid.Row="1" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ColumnDefinition Width="28" /> <ComboBox.ContextMenu>
<ColumnDefinition Width="*" /> <ContextMenu>
</Grid.ColumnDefinitions> <MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearTug" Click="contextMenuItemClearTug_Click" />
<CheckBox x:Name="checkBoxTugRequired" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Left" /> </ContextMenu>
<ComboBox Name="comboBoxTug" Grid.Column="1" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id"> </ComboBox.ContextMenu>
<ComboBox.ContextMenu> </ComboBox>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearTug" Click="contextMenuItemClearTug_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
</Grid>
<Label Content="{x:Static p:Resources.textRecommendedTugs}" Grid.Column="2" Grid.Row="2" HorizontalContentAlignment="Right"/> <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" /> <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" /> <Label Content="{x:Static p:Resources.textPilotRequired}" Grid.Column="2" Grid.Row="3" HorizontalContentAlignment="Right" />
<Grid Grid.Column="3" Grid.Row="3">
<Grid.ColumnDefinitions> <ComboBox Name="comboBoxPilot" Grid.Column="3" Grid.Row="3" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ColumnDefinition Width="28" /> <ComboBox.ContextMenu>
<ColumnDefinition Width="*" /> <ContextMenu>
</Grid.ColumnDefinitions> <MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearPilot" Click="contextMenuItemClearPilot_Click" />
<CheckBox x:Name="checkBoxPilotRequired" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Left" /> </ContextMenu>
<ComboBox Name="comboBoxPilot" Grid.Column="1" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id"> </ComboBox.ContextMenu>
<ComboBox.ContextMenu> </ComboBox>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearPilot" Click="contextMenuItemClearPilot_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
</Grid>
<Label Content="{x:Static p:Resources.textMooring}" Grid.Column="2" Grid.Row="4" HorizontalContentAlignment="Right"/> <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 Name="comboBoxMooring" Grid.Column="3" Grid.Row="4" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu> <ComboBox.ContextMenu>

View File

@ -103,9 +103,9 @@ namespace BreCalClient
this.ShipcallModel.Shipcall.Canceled = this.checkBoxCanceled.IsChecked; this.ShipcallModel.Shipcall.Canceled = this.checkBoxCanceled.IsChecked;
this.ShipcallModel.Shipcall.Anchored = this.checkBoxAnchored.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.RecommendedTugs = this.integerUpDownRecommendedTugs.Value;
this.ShipcallModel.Shipcall.PilotRequired = this.checkBoxPilotRequired.IsChecked;
this.ShipcallModel.Shipcall.MooredLock = this.checkBoxMooredLock.IsChecked; this.ShipcallModel.Shipcall.MooredLock = this.checkBoxMooredLock.IsChecked;
this.ShipcallModel.Shipcall.Bunkering = this.checkBoxBunkering.IsChecked; this.ShipcallModel.Shipcall.Bunkering = this.checkBoxBunkering.IsChecked;
this.ShipcallModel.Shipcall.ReplenishingTerminal = this.checkBoxReplenishingTerminal.IsChecked; this.ShipcallModel.Shipcall.ReplenishingTerminal = this.checkBoxReplenishingTerminal.IsChecked;
@ -190,9 +190,9 @@ namespace BreCalClient
this.checkBoxCanceled.IsChecked = this.ShipcallModel.Shipcall.Canceled ?? false; this.checkBoxCanceled.IsChecked = this.ShipcallModel.Shipcall.Canceled ?? false;
this.checkBoxAnchored.IsChecked = this.ShipcallModel.Shipcall.Anchored ?? 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.integerUpDownRecommendedTugs.Value = this.ShipcallModel.Shipcall.RecommendedTugs;
this.checkBoxPilotRequired.IsChecked = this.ShipcallModel.Shipcall.PilotRequired ?? false;
this.checkBoxMooredLock.IsChecked = this.ShipcallModel.Shipcall.MooredLock ?? false; this.checkBoxMooredLock.IsChecked = this.ShipcallModel.Shipcall.MooredLock ?? false;
@ -250,10 +250,10 @@ namespace BreCalClient
this.checkBoxCanceled.IsEnabled = isEnabled; this.checkBoxCanceled.IsEnabled = isEnabled;
this.checkBoxAnchored.IsEnabled = isEnabled; this.checkBoxAnchored.IsEnabled = isEnabled;
this.checkBoxTugRequired.IsEnabled = isEnabled;
this.comboBoxTug.IsEnabled = isEnabled; this.comboBoxTug.IsEnabled = isEnabled;
this.integerUpDownRecommendedTugs.IsEnabled = isEnabled; this.integerUpDownRecommendedTugs.IsEnabled = isEnabled;
this.checkBoxPilotRequired.IsEnabled = isEnabled;
this.comboBoxPilot.IsEnabled = isEnabled; this.comboBoxPilot.IsEnabled = isEnabled;
this.comboBoxMooring.IsEnabled = isEnabled; this.comboBoxMooring.IsEnabled = isEnabled;
this.checkBoxMooredLock.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" /> <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"/> <Label Content="{x:Static p:Resources.textTugRequired}" Grid.Column="2" Grid.Row="1" HorizontalContentAlignment="Right"/>
<Grid Grid.Column="3" Grid.Row="1">
<Grid.ColumnDefinitions> <ComboBox Name="comboBoxTug" Grid.Column="3" Grid.Row="1" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ColumnDefinition Width="28" /> <ComboBox.ContextMenu>
<ColumnDefinition Width="*" /> <ContextMenu>
</Grid.ColumnDefinitions> <MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearTug" Click="contextMenuItemClearTug_Click" />
<CheckBox x:Name="checkBoxTugRequired" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Left" /> </ContextMenu>
<ComboBox Name="comboBoxTug" Grid.Column="1" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id"> </ComboBox.ContextMenu>
<ComboBox.ContextMenu> </ComboBox>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearTug" Click="contextMenuItemClearTug_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
</Grid>
<Label Content="{x:Static p:Resources.textRecommendedTugs}" Grid.Column="2" Grid.Row="2" HorizontalContentAlignment="Right"/> <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" /> <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" /> <Label Content="{x:Static p:Resources.textPilotRequired}" Grid.Column="2" Grid.Row="3" HorizontalContentAlignment="Right" />
<Grid Grid.Column="3" Grid.Row="3">
<Grid.ColumnDefinitions> <ComboBox Name="comboBoxPilot" Grid.Column="3" Grid.Row="3" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ColumnDefinition Width="28" /> <ComboBox.ContextMenu>
<ColumnDefinition Width="*" /> <ContextMenu>
</Grid.ColumnDefinitions> <MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearPilot" Click="contextMenuItemClearPilot_Click" />
<CheckBox x:Name="checkBoxPilotRequired" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Left" /> </ContextMenu>
<ComboBox Name="comboBoxPilot" Grid.Column="1" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id"> </ComboBox.ContextMenu>
<ComboBox.ContextMenu> </ComboBox>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearPilot" Click="contextMenuItemClearPilot_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
</Grid>
<Label Content="{x:Static p:Resources.textMooring}" Grid.Column="2" Grid.Row="4" HorizontalContentAlignment="Right"/> <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 Name="comboBoxMooring" Grid.Column="3" Grid.Row="4" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu> <ComboBox.ContextMenu>

View File

@ -100,9 +100,9 @@ namespace BreCalClient
this.ShipcallModel.Shipcall.TidalWindowTo = this.datePickerTidalWindowTo.Value; this.ShipcallModel.Shipcall.TidalWindowTo = this.datePickerTidalWindowTo.Value;
this.ShipcallModel.Shipcall.Canceled = this.checkBoxCanceled.IsChecked; this.ShipcallModel.Shipcall.Canceled = this.checkBoxCanceled.IsChecked;
this.ShipcallModel.Shipcall.TugRequired = this.checkBoxTugRequired.IsChecked;
this.ShipcallModel.Shipcall.RecommendedTugs = this.integerUpDownRecommendedTugs.Value; this.ShipcallModel.Shipcall.RecommendedTugs = this.integerUpDownRecommendedTugs.Value;
this.ShipcallModel.Shipcall.PilotRequired = this.checkBoxPilotRequired.IsChecked;
this.ShipcallModel.Shipcall.MooredLock = this.checkBoxMooredLock.IsChecked; this.ShipcallModel.Shipcall.MooredLock = this.checkBoxMooredLock.IsChecked;
this.ShipcallModel.Shipcall.RainSensitiveCargo = this.checkBoxRainsensitiveCargo.IsChecked; this.ShipcallModel.Shipcall.RainSensitiveCargo = this.checkBoxRainsensitiveCargo.IsChecked;
if(!string.IsNullOrEmpty(this.textBoxRemarks.Text.Trim())) if(!string.IsNullOrEmpty(this.textBoxRemarks.Text.Trim()))
@ -183,9 +183,9 @@ namespace BreCalClient
this.datePickerTidalWindowTo.Value = this.ShipcallModel.Shipcall.TidalWindowTo; 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.integerUpDownRecommendedTugs.Value = this.ShipcallModel.Shipcall.RecommendedTugs;
this.checkBoxPilotRequired.IsChecked = this.ShipcallModel.Shipcall.PilotRequired ?? false;
this.checkBoxMooredLock.IsChecked = this.ShipcallModel.Shipcall.MooredLock ?? false; this.checkBoxMooredLock.IsChecked = this.ShipcallModel.Shipcall.MooredLock ?? false;
this.checkBoxRainsensitiveCargo.IsChecked = this.ShipcallModel.Shipcall.RainSensitiveCargo ?? false; this.checkBoxRainsensitiveCargo.IsChecked = this.ShipcallModel.Shipcall.RainSensitiveCargo ?? false;
@ -238,10 +238,9 @@ namespace BreCalClient
this.datePickerTidalWindowTo.IsEnabled = isEnabled; this.datePickerTidalWindowTo.IsEnabled = isEnabled;
this.checkBoxCanceled.IsEnabled = isEnabled; this.checkBoxCanceled.IsEnabled = isEnabled;
this.checkBoxTugRequired.IsEnabled = isEnabled;
this.comboBoxTug.IsEnabled = isEnabled; this.comboBoxTug.IsEnabled = isEnabled;
this.integerUpDownRecommendedTugs.IsEnabled = isEnabled; this.integerUpDownRecommendedTugs.IsEnabled = isEnabled;
this.checkBoxPilotRequired.IsEnabled = isEnabled;
this.comboBoxPilot.IsEnabled = isEnabled; this.comboBoxPilot.IsEnabled = isEnabled;
this.comboBoxMooring.IsEnabled = isEnabled; this.comboBoxMooring.IsEnabled = isEnabled;
this.checkBoxMooredLock.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"/> <Label Content="{x:Static p:Resources.textTugRequired}" Grid.Column="2" Grid.Row="1" HorizontalContentAlignment="Right"/>
<Grid Grid.Column="3" Grid.Row="1">
<Grid.ColumnDefinitions> <ComboBox Name="comboBoxTug" Grid.Column="3" Grid.Row="1" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ColumnDefinition Width="28" /> <ComboBox.ContextMenu>
<ColumnDefinition Width="*" /> <ContextMenu>
</Grid.ColumnDefinitions> <MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearTug" Click="contextMenuItemClearTug_Click" />
<CheckBox x:Name="checkBoxTugRequired" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Left" /> </ContextMenu>
<ComboBox Name="comboBoxTug" Grid.Column="1" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id"> </ComboBox.ContextMenu>
<ComboBox.ContextMenu> </ComboBox>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearTug" Click="contextMenuItemClearTug_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
</Grid>
<Label Content="{x:Static p:Resources.textRecommendedTugs}" Grid.Column="2" Grid.Row="2" HorizontalContentAlignment="Right"/> <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" /> <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" /> <Label Content="{x:Static p:Resources.textPilotRequired}" Grid.Column="2" Grid.Row="3" HorizontalContentAlignment="Right" />
<Grid Grid.Column="3" Grid.Row="3">
<Grid.ColumnDefinitions> <ComboBox Name="comboBoxPilot" Grid.Column="3" Grid.Row="3" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ColumnDefinition Width="28" /> <ComboBox.ContextMenu>
<ColumnDefinition Width="*" /> <ContextMenu>
</Grid.ColumnDefinitions> <MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearPilot" Click="contextMenuItemClearPilot_Click" />
<CheckBox x:Name="checkBoxPilotRequired" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Left" /> </ContextMenu>
<ComboBox Name="comboBoxPilot" Grid.Column="1" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id"> </ComboBox.ContextMenu>
<ComboBox.ContextMenu> </ComboBox>
<ContextMenu>
<MenuItem Header="{x:Static p:Resources.textClearAssignment}" Name="contextMenuItemClearPilot" Click="contextMenuItemClearPilot_Click" />
</ContextMenu>
</ComboBox.ContextMenu>
</ComboBox>
</Grid>
<Label Content="{x:Static p:Resources.textMooring}" Grid.Column="2" Grid.Row="4" HorizontalContentAlignment="Right"/> <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 Name="comboBoxMooring" Grid.Column="3" Grid.Row="4" Margin="2" DisplayMemberPath="Name" SelectedValuePath="Id">
<ComboBox.ContextMenu> <ComboBox.ContextMenu>

View File

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

View File

@ -36,7 +36,7 @@
<Label Content="{x:Static p:Resources.textPassword}" Grid.Row="2" VerticalContentAlignment="Center" /> <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" /> <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="*"/> <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="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" /> <Button Name="buttonExit" Content="{x:Static p:Resources.textExit}" Grid.Row="4" Grid.Column="1" Margin="2" Click="buttonExit_Click" />
</Grid> </Grid>

View File

@ -17,6 +17,8 @@ using BreCalClient.misc.Model;
using static BreCalClient.Extensions; using static BreCalClient.Extensions;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using Newtonsoft.Json;
using System.Security.Principal;
namespace BreCalClient namespace BreCalClient
{ {
@ -129,7 +131,13 @@ namespace BreCalClient
} }
catch (ApiException ex) 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}"; labelGeneralStatus.Text = $"Connection {ConnectionStatus.FAILED}";
} }
catch (Exception ex) catch (Exception ex)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,7 +8,7 @@
<applicationSettings> <applicationSettings>
<RoleEditor.Properties.Settings> <RoleEditor.Properties.Settings>
<setting name="ConnectionString" serializeAs="String"> <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> </setting>
</RoleEditor.Properties.Settings> </RoleEditor.Properties.Settings>
</applicationSettings> </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.times_full import get_times_full_simple
from BreCal.stubs.df_times import get_df_times 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): def create_app(test_config=None):
@ -60,15 +61,20 @@ def create_app(test_config=None):
app.register_blueprint(user.bp) 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)) local_db.initPool(os.path.dirname(app.instance_path))
logging.info('App started') 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 return app
__all__ = [ __all__ = [
"get_project_root", "get_project_root",
"ensure_path", "ensure_path",
"execute_test_with_pytest", "execute_test_with_pytest",
"execute_coverage_test", "execute_coverage_test",
"difference_to_then", "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""" """determines the type of a participant"""
NONE = 0 NONE = 0
BSMD = 1 BSMD = 1

View File

@ -1,12 +1,26 @@
import numpy as np import numpy as np
import pandas as pd import pandas as pd
import datetime import datetime
import typing
from BreCal.schemas.model import Shipcall, Ship, Participant, Berth, User, Times from BreCal.schemas.model import Shipcall, Ship, Participant, Berth, User, Times
from BreCal.database.enums import ParticipantType from BreCal.database.enums import ParticipantType
def pandas_series_to_data_model(): def pandas_series_to_data_model():
return 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(): 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 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' # update the 'participants' column in 'shipcall'
self.initialize_shipcall_participant_list() self.initialize_shipcall_participant_list()
# update the 'type' in shipcall_participants_map
# fully deprecated
# self.add_participant_type_to_map()
return return
def build_full_mysql_df_dict(self, all_schemas): def build_full_mysql_df_dict(self, all_schemas):
@ -121,6 +139,52 @@ class SQLHandler():
lambda x: self.get_participants(x.name), lambda x: self.get_participants(x.name),
axis=1) axis=1)
return 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: def standardize_model_str(self, model_str:str)->str:
"""check if the 'model_str' is valid and apply lowercasing to the string""" """check if the 'model_str' is valid and apply lowercasing to the string"""
@ -159,7 +223,10 @@ class SQLHandler():
return all_data return all_data
def df_loc_to_data_model(self, df, id, model_str, loc_type:str="loc"): 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 # get a pandas series from the dataframe
series = df.loc[id] if loc_type=="loc" else df.iloc[id] series = df.loc[id] if loc_type=="loc" else df.iloc[id]
@ -174,9 +241,35 @@ class SQLHandler():
data = data_model(**data) data = data_model(**data)
return 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): def get_times_for_participant_type(self, df_times, participant_type:int):
filtered_series = df_times.loc[df_times["participant_type"]==participant_type] filtered_series = self.filter_df_by_participant_type(df_times, participant_type)
assert len(filtered_series)<=1, f"found multiple results" #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 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 return times
@ -198,10 +291,13 @@ class SQLHandler():
returns: participant_id_list, where every element is an int returns: participant_id_list, where every element is an int
""" """
df = self.df_dict.get("shipcall_participant_map") 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 # 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 return participant_id_list
def get_times_of_shipcall(self, shipcall)->pd.DataFrame: 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 df_times = df_times.loc[~df_times[non_null_column].isnull()] # NOT null filter
# filter by the agency participant_type # 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 return times_agency
def filter_df_by_key_value(self, df, key, value)->pd.DataFrame: 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)""" """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 # optional: rounding
if rounding is not None: if rounding is not None:
all_df_times.loc[:, query] = all_df_times.loc[:, query].dt.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 = times_agency[query].iloc[0].round(rounding)# e.g., 'min' 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 # 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 # takes 'times_agency' to sample, which value should match

View File

@ -1,6 +1,6 @@
import json import json
import logging import logging
import traceback
import pydapper import pydapper
from ..schemas import model from ..schemas import model
@ -36,6 +36,7 @@ def GetShipcalls(options):
pooledConnection.close() pooledConnection.close()
except Exception as ex: except Exception as ex:
logging.error(traceback.format_exc())
logging.error(ex) logging.error(ex)
print(ex) print(ex)
result = {} result = {}
@ -115,6 +116,7 @@ def PostShipcalls(schemaModel):
return json.dumps({"id" : new_id}), 201, {'Content-Type': 'application/json; charset=utf-8'} return json.dumps({"id" : new_id}), 201, {'Content-Type': 'application/json; charset=utf-8'}
except Exception as ex: except Exception as ex:
logging.error(traceback.format_exc())
logging.error(ex) logging.error(ex)
print(ex) print(ex)
result = {} result = {}
@ -199,6 +201,7 @@ def PutShipcalls(schemaModel):
return json.dumps({"id" : schemaModel["id"]}), 200 return json.dumps({"id" : schemaModel["id"]}), 200
except Exception as ex: except Exception as ex:
logging.error(traceback.format_exc())
logging.error(ex) logging.error(ex)
print(ex) print(ex)
result = {} 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 # a human interpretable dictionary for error messages. In this case, the English language is preferred
error_message_dict = { error_message_dict = {
# 0001 A-M # 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_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":"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_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":"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_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":"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_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":"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_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":"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_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":"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_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":"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_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":"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_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":"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_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":"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_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":"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_terminal_berth_etd":"Shipcall departs soon (<20 hours). The terminal did not provide a time yet (ETD) {Rule #0001M}", # M
# 0002 A+B+C # 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}", "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 returns: string
""" """
return self.error_message_dict.get(key,key) 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: 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") 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 # 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 delta <= threshold
# Violation, if 0 <= delta <= threshold violation_state = (delta<=threshold)
violation_state = (delta >= 0) and (delta<=threshold)
return violation_state return violation_state
def check_participants_agree_on_estimated_time(self, shipcall, query, df_times, applicable_shipcall_type)->bool: 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. - Checks, if times_agency.eta_berth is filled in.
- Measures the difference between 'now' and 'shipcall.eta'. - Measures the difference between 'now' and 'shipcall.eta'.
""" """
# check, if the header is filled in (agency) if not shipcall.type in [ShipcallType.INCOMING.value]:
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value])]) != 1: return self.get_no_violation_default_output()
return (StatusFlags.GREEN, None)
# 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 # 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_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value)
query_time = shipcall.eta 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 threshold = ParticipantwiseTimeDelta.AGENCY
violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold) violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
if violation_state: 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) return (StatusFlags.YELLOW, validation_name)
else: 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): 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. - Checks, if times_agency.etd_berth is filled in.
- Measures the difference between 'now' and 'shipcall.etd'. - Measures the difference between 'now' and 'shipcall.etd'.
""" """
# check, if the header is filled in (agency) if not shipcall.type in [ShipcallType.OUTGOING.value, ShipcallType.SHIFTING.value]:
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value])]) != 1: return self.get_no_violation_default_output()
return (StatusFlags.GREEN, None)
# 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 # 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_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value)
query_time = shipcall.etd 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 threshold = ParticipantwiseTimeDelta.AGENCY
violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold) violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
if violation_state: 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) return (StatusFlags.YELLOW, validation_name)
else: 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): 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. - Checks, if times_mooring.eta_berth is filled in.
- Measures the difference between 'now' and 'times_agency.eta_berth'. - Measures the difference between 'now' and 'times_agency.eta_berth'.
""" """
# check, if the header is filled in (agency & MOORING) if not shipcall.type in [ShipcallType.INCOMING.value]:
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.MOORING.value])]) != 2: return self.get_no_violation_default_output()
return (StatusFlags.GREEN, None)
# 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 # 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_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) times_mooring = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.MOORING.value)
query_time = times_agency.eta_berth query_time = times_agency.eta_berth if times_agency is not None else None
key_time = times_mooring.eta_berth key_time = times_mooring.eta_berth if times_mooring is not None else None
threshold = ParticipantwiseTimeDelta.MOORING threshold = ParticipantwiseTimeDelta.MOORING
violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold) violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
if violation_state: 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) return (StatusFlags.YELLOW, validation_name)
else: 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): 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. - Checks, if times_mooring.etd_berth is filled in.
- Measures the difference between 'now' and 'times_agency.etd_berth'. - Measures the difference between 'now' and 'times_agency.etd_berth'.
""" """
# check, if the header is filled in (agency & MOORING) if not shipcall.type in [ShipcallType.OUTGOING.value, ShipcallType.SHIFTING.value]:
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.MOORING.value])]) != 2: return self.get_no_violation_default_output()
return (StatusFlags.GREEN, None)
# 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 # 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_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) times_mooring = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.MOORING.value)
query_time = times_agency.etd_berth query_time = times_agency.etd_berth if times_agency is not None else None
key_time = times_mooring.etd_berth key_time = times_mooring.etd_berth if times_mooring is not None else None
threshold = ParticipantwiseTimeDelta.MOORING threshold = ParticipantwiseTimeDelta.MOORING
violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold) violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
if violation_state: 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) return (StatusFlags.YELLOW, validation_name)
else: 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): 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. - Checks, if times_port_administration.eta_berth is filled in.
- Measures the difference between 'now' and 'times_agency.eta_berth'. - Measures the difference between 'now' and 'times_agency.eta_berth'.
""" """
# check, if the header is filled in (agency & PORT_ADMINISTRATION) if not shipcall.type in [ShipcallType.INCOMING.value]:
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.PORT_ADMINISTRATION.value])]) != 2: return self.get_no_violation_default_output()
return (StatusFlags.GREEN, None)
# 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 # 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_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) 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 query_time = times_agency.eta_berth if times_agency is not None else None
key_time = times_port_administration.eta_berth key_time = times_port_administration.eta_berth if times_port_administration is not None else None
threshold = ParticipantwiseTimeDelta.PORT_ADMINISTRATION 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) violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
if violation_state: 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) return (StatusFlags.YELLOW, validation_name)
else: 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): 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. - Checks, if times_port_administration.etd_berth is filled in.
- Measures the difference between 'now' and 'times_agency.etd_berth'. - Measures the difference between 'now' and 'times_agency.etd_berth'.
""" """
# check, if the header is filled in (agency & PORT_ADMINISTRATION) if not shipcall.type in [ShipcallType.OUTGOING.value, ShipcallType.SHIFTING.value]:
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.PORT_ADMINISTRATION.value])]) != 2: return self.get_no_violation_default_output()
return (StatusFlags.GREEN, None)
# 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 # 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_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) 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 query_time = times_agency.etd_berth if times_agency is not None else None
key_time = times_port_administration.etd_berth key_time = times_port_administration.etd_berth if times_port_administration is not None else None
threshold = ParticipantwiseTimeDelta.PORT_ADMINISTRATION 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) violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
if violation_state: 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) return (StatusFlags.YELLOW, validation_name)
else: 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): 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. - Checks, if times_pilot.eta_berth is filled in.
- Measures the difference between 'now' and 'times_agency.eta_berth'. - Measures the difference between 'now' and 'times_agency.eta_berth'.
""" """
# check, if the header is filled in (agency & PILOT) if not shipcall.type in [ShipcallType.INCOMING.value]:
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.PILOT.value])]) != 2: return self.get_no_violation_default_output()
return (StatusFlags.GREEN, None)
# 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 # 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_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) times_pilot = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.PILOT.value)
query_time = times_agency.eta_berth query_time = times_agency.eta_berth if times_agency is not None else None
key_time = times_pilot.eta_berth key_time = times_pilot.eta_berth if times_pilot is not None else None
threshold = ParticipantwiseTimeDelta.PILOT threshold = ParticipantwiseTimeDelta.PILOT
violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold) violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
if violation_state: 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) return (StatusFlags.YELLOW, validation_name)
else: 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): 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. - Checks, if times_pilot.etd_berth is filled in.
- Measures the difference between 'now' and 'times_agency.etd_berth'. - Measures the difference between 'now' and 'times_agency.etd_berth'.
""" """
# check, if the header is filled in (agency & PILOT) if not shipcall.type in [ShipcallType.OUTGOING.value, ShipcallType.SHIFTING.value]:
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.PILOT.value])]) != 2: return self.get_no_violation_default_output()
return (StatusFlags.GREEN, None)
# 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 # 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_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) times_pilot = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.PILOT.value)
query_time = times_agency.etd_berth query_time = times_agency.etd_berth if times_agency is not None else None
key_time = times_pilot.etd_berth key_time = times_pilot.etd_berth if times_pilot is not None else None
threshold = ParticipantwiseTimeDelta.PILOT threshold = ParticipantwiseTimeDelta.PILOT
violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold) violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
if violation_state: 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) return (StatusFlags.YELLOW, validation_name)
else: 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): 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. - Checks, if times_tug.eta_berth is filled in.
- Measures the difference between 'now' and 'times_agency.eta_berth'. - Measures the difference between 'now' and 'times_agency.eta_berth'.
""" """
# check, if the header is filled in (agency & TUG) if not shipcall.type in [ShipcallType.INCOMING.value]:
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.TUG.value])]) != 2: return self.get_no_violation_default_output()
return (StatusFlags.GREEN, None)
# 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 # 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_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) times_tug = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.TUG.value)
query_time = times_agency.eta_berth query_time = times_agency.eta_berth if times_agency is not None else None
key_time = times_tug.eta_berth key_time = times_tug.eta_berth if times_tug is not None else None
threshold = ParticipantwiseTimeDelta.TUG threshold = ParticipantwiseTimeDelta.TUG
violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold) violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
if violation_state: 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) return (StatusFlags.YELLOW, validation_name)
else: 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): 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. - Checks, if times_tug.etd_berth is filled in.
- Measures the difference between 'now' and 'times_agency.etd_berth'. - Measures the difference between 'now' and 'times_agency.etd_berth'.
""" """
# check, if the header is filled in (agency & TUG) if not shipcall.type in [ShipcallType.OUTGOING.value, ShipcallType.SHIFTING.value]:
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.TUG.value])]) != 2: return self.get_no_violation_default_output()
return (StatusFlags.GREEN, None)
# 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 # 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_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) times_tug = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.TUG.value)
query_time = times_agency.etd_berth query_time = times_agency.etd_berth if times_agency is not None else None
key_time = times_tug.etd_berth key_time = times_tug.etd_berth if times_tug is not None else None
threshold = ParticipantwiseTimeDelta.TUG threshold = ParticipantwiseTimeDelta.TUG
violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold) violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
if violation_state: 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) return (StatusFlags.YELLOW, validation_name)
else: 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): 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 a certain threshold (e.g., 20 hours), a violation occurs
0001-L: 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'. - Measures the difference between 'now' and 'times_agency.eta_berth'.
""" """
# check, if the header is filled in (agency & terminal) if not shipcall.type in [ShipcallType.INCOMING.value]:
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.TERMINAL.value])]) != 2: return self.get_no_violation_default_output()
return (StatusFlags.GREEN, None)
# 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 # 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_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) times_terminal = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.TERMINAL.value)
query_time = times_agency.eta_berth query_time = times_agency.eta_berth if times_agency is not None else None
key_time = times_terminal.eta_berth 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 threshold = ParticipantwiseTimeDelta.TERMINAL
violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold) violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
if violation_state: 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) return (StatusFlags.YELLOW, validation_name)
else: 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): 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 a certain threshold (e.g., 20 hours), a violation occurs
0001-M: 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'. - Measures the difference between 'now' and 'times_agency.etd_berth'.
""" """
# check, if the header is filled in (agency & terminal) if not shipcall.type in [ShipcallType.OUTGOING.value, ShipcallType.SHIFTING.value]:
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value, ParticipantType.TERMINAL.value])]) != 2: return self.get_no_violation_default_output()
return (StatusFlags.GREEN, None)
# 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 # 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_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) times_terminal = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.TERMINAL.value)
query_time = times_agency.etd_berth query_time = times_agency.etd_berth if times_agency is not None else None
key_time = times_terminal.etd_berth 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 threshold = ParticipantwiseTimeDelta.TERMINAL
violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold) violation_state = self.check_time_delta_violation_query_time_to_now(query_time=query_time, key_time=key_time, threshold=threshold)
if violation_state: 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) return (StatusFlags.YELLOW, validation_name)
else: 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): 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: 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) return (StatusFlags.RED, validation_name)
else: 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): 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: 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) return (StatusFlags.RED, validation_name)
else: 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): 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) violation_state = (violation_state_eta) or (violation_state_etd)
if violation_state: 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) return (StatusFlags.RED, validation_name)
else: 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): 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) query time: eta_berth (times_agency)
start_time & end_time: operations_start & operations_end (times_terminal) 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) # 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: if len(df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]) != 1:
return (StatusFlags.GREEN, None) 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 # get agency & terminal times
times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value) 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) 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): 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 # check, whether the start of operations is AFTER the estimated arrival time
violation_state = times_terminal.operations_start < times_agency.eta_berth violation_state = times_terminal.operations_start < times_agency.eta_berth
if violation_state: 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) return (StatusFlags.RED, validation_name)
else: 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): 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) query time: eta_berth (times_agency)
start_time & end_time: operations_start & operations_end (times_terminal) 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) # 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: if len(df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]) != 1:
return (StatusFlags.GREEN, None) 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 # get agency & terminal times
times_agency = self.sql_handler.get_times_for_participant_type(df_times, participant_type=ParticipantType.AGENCY.value) 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) 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): 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 # check, whether the end of operations is AFTER the estimated departure time
violation_state = times_terminal.operations_end > times_agency.etd_berth violation_state = times_terminal.operations_end > times_agency.etd_berth
if violation_state: 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) return (StatusFlags.RED, validation_name)
else: 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): 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) query time: eta_berth (times_agency)
start_time & end_time: tidal_window_from & tidal_window_to (shipcall) 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) # check, if the header is filled in (agency)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value])]) != 1: 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) 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 # 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? 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 # check, whether the query time is between start & end time
# a violation is observed, when the time is NOT between start & end # 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) 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: 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) return (StatusFlags.RED, validation_name)
else: 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): 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) query time: eta_berth (times_agency)
start_time & end_time: tidal_window_from & tidal_window_to (shipcall) 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) # check, if the header is filled in (agency)
if len(df_times.loc[df_times["participant_type"].isin([ParticipantType.AGENCY.value])]) != 1: 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) 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 # 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? 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 # check, whether the query time is between start & end time
# a violation is observed, when the time is NOT between start & end # 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) 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: 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) return (StatusFlags.RED, validation_name)
else: 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): 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] times_agency = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]
# check, if the header is filled in (agency) # check, if the header is filled in (agency)
if len(times_agency) != 1: 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 # when ANY of the unique values exceeds the threshold, a violation is observed
query = "eta_berth" 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) 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: 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) return (StatusFlags.YELLOW, validation_name)
else: 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): 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] times_agency = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]
# check, if the header is filled in (agency) # check, if the header is filled in (agency)
if len(times_agency) != 1: 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 # when ANY of the unique values exceeds the threshold, a violation is observed
query = "etd_berth" 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) 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: 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) return (StatusFlags.YELLOW, validation_name)
else: 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): 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. 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) # 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: if len(df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]) == 0:
return (StatusFlags.GREEN, None) 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_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) 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 # 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): 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 # 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)): 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]: 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 # only incoming shipcalls matter. The other ones are not relevant for the berth selection
violation_state = times_agency.berth_id!=times_terminal.berth_id violation_state = times_agency.berth_id!=times_terminal.berth_id
if violation_state: 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) return (StatusFlags.YELLOW, validation_name)
else: 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): 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. 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) # 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: if len(df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]) == 0:
return (StatusFlags.GREEN, None) 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_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) 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 # 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): if (shipcall.pier_side is None) or (times_terminal.pier_side 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 # 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)): if (pd.isnull(shipcall.pier_side)) or (pd.isnull(times_terminal.pier_side)):
return (StatusFlags.GREEN, None) return self.get_no_violation_default_output()
# only incoming shipcalls matter. The other ones are not relevant for the pier_side selection # 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]: 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: 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) return (StatusFlags.YELLOW, validation_name)
else: else:
return (StatusFlags.GREEN, None) return self.get_no_violation_default_output()

View File

@ -1,4 +1,5 @@
import copy import copy
import re
import numpy as np import numpy as np
import pandas as pd import pandas as pd
from BreCal.database.enums import StatusFlags from BreCal.database.enums import StatusFlags
@ -29,7 +30,14 @@ class ValidationRules(ValidationRuleFunctions):
returns: (evaluation_state, violations) returns: (evaluation_state, violations)
""" """
# prepare df_times, which every validation rule tends to use # 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 # filter by shipcall id
df_times = self.sql_handler.get_times_of_shipcall(shipcall) 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. # 'translate' all error codes into readable, human-understandable format.
evaluation_results = [(state, self.describe_error_message(msg)) for (state, msg) in evaluation_results] 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 # 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] evaluation_verbosity = [result[1] for result in evaluation_results]
return (evaluation_state, evaluation_verbosity) return (evaluation_state, evaluation_verbosity)
@ -74,11 +76,27 @@ class ValidationRules(ValidationRuleFunctions):
# unbundle individual results. evaluation_state becomes an integer, violation # unbundle individual results. evaluation_state becomes an integer, violation
evaluation_state = [StatusFlags(res[0]).value for res in results] 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"] = evaluation_state
shipcall_df.loc[:,"evaluation_message"] = violations shipcall_df.loc[:,"evaluation_message"] = violations
return shipcall_df 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: 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/brecal/src/server')
sys.path.insert(0, '/var/www/venv/lib/python3.10/site-packages/') sys.path.insert(0, '/var/www/venv/lib/python3.10/site-packages/')
import schedule
# set the key # set the key
os.environ['SECRET_KEY'] = 'zdiTz8P3jXOc7jztIQAoelK4zztyuCpJ' os.environ['SECRET_KEY'] = 'zdiTz8P3jXOc7jztIQAoelK4zztyuCpJ'
# Set up logging # Set up logging
logging.basicConfig(stream=sys.stderr, level=logging.DEBUG) logging.basicConfig(stream=sys.stderr, level=logging.DEBUG)
# Set up Scheduled Jobs
# Import and run the Flask app # Import and run the Flask app
from BreCal import create_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 # set times agency to be undetermined
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "eta_berth"] = None 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 # apply the validation rule
(state, msg) = vr.validation_rule_fct_missing_time_agency_berth_eta(shipcall=shipcall, df_times=df_times) (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 # set times agency to be undetermined
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "eta_berth"] = None 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 # apply the validation rule
(state, msg) = vr.validation_rule_fct_missing_time_agency_berth_eta(shipcall=shipcall, df_times=df_times) (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 # set times agency to be undetermined
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "eta_berth"] = None 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 # apply the validation rule
(state, msg) = vr.validation_rule_fct_missing_time_agency_berth_eta(shipcall=shipcall, df_times=df_times) (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) df_times = get_df_times(shipcall)
# the shipcall etd is 'soon' # the shipcall etd is 'soon'
shipcall.type = ShipcallType.OUTGOING.value
shipcall.etd = datetime.datetime.now() + datetime.timedelta(minutes=ParticipantwiseTimeDelta.AGENCY-10) shipcall.etd = datetime.datetime.now() + datetime.timedelta(minutes=ParticipantwiseTimeDelta.AGENCY-10)
# set times agency to be undetermined # set times agency to be undetermined
df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value, "etd_berth"] = None 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 # apply the validation rule
(state, msg) = vr.validation_rule_fct_missing_time_agency_berth_etd(shipcall=shipcall, df_times=df_times) (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 # set times agency to be undetermined
df_times.loc[df_times["participant_type"]==ParticipantType.MOORING.value, "eta_berth"] = None 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 # apply the validation rule
(state, msg) = vr.validation_rule_fct_missing_time_mooring_berth_eta(shipcall=shipcall, df_times=df_times) (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'] vr = build_sql_proxy_connection['vr']
shipcall = get_shipcall_simple() shipcall = get_shipcall_simple()
shipcall.type = ShipcallType.OUTGOING.value
df_times = get_df_times(shipcall) df_times = get_df_times(shipcall)
# according to the agency, a shipcall takes place soon (ETA/ETD) # 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 # set times agency to be undetermined
df_times.loc[df_times["participant_type"]==ParticipantType.MOORING.value, "etd_berth"] = None 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 # apply the validation rule
(state, msg) = vr.validation_rule_fct_missing_time_mooring_berth_etd(shipcall=shipcall, df_times=df_times) (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 # set times agency to be undetermined
df_times.loc[df_times["participant_type"]==ParticipantType.PORT_ADMINISTRATION.value, "eta_berth"] = None 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 # apply the validation rule
(state, msg) = vr.validation_rule_fct_missing_time_portadministration_berth_eta(shipcall=shipcall, df_times=df_times) (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'] vr = build_sql_proxy_connection['vr']
shipcall = get_shipcall_simple() shipcall = get_shipcall_simple()
shipcall.type = ShipcallType.SHIFTING.value
df_times = get_df_times(shipcall) df_times = get_df_times(shipcall)
# according to the agency, a shipcall takes place soon (ETA/ETD) # 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 # set times agency to be undetermined
df_times.loc[df_times["participant_type"]==ParticipantType.PORT_ADMINISTRATION.value, "etd_berth"] = None 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 # apply the validation rule
(state, msg) = vr.validation_rule_fct_missing_time_portadministration_berth_etd(shipcall=shipcall, df_times=df_times) (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 # set times agency to be undetermined
df_times.loc[df_times["participant_type"]==ParticipantType.PILOT.value, "eta_berth"] = None 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 # apply the validation rule
(state, msg) = vr.validation_rule_fct_missing_time_pilot_berth_eta(shipcall=shipcall, df_times=df_times) (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'] vr = build_sql_proxy_connection['vr']
shipcall = get_shipcall_simple() shipcall = get_shipcall_simple()
shipcall.type = ShipcallType.OUTGOING.value
df_times = get_df_times(shipcall) df_times = get_df_times(shipcall)
# according to the agency, a shipcall takes place soon (ETA/ETD) # 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 # set times agency to be undetermined
df_times.loc[df_times["participant_type"]==ParticipantType.PILOT.value, "etd_berth"] = None 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 # apply the validation rule
(state, msg) = vr.validation_rule_fct_missing_time_pilot_berth_etd(shipcall=shipcall, df_times=df_times) (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)" 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 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): 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""" """0001-J validation_rule_fct_missing_time_tug_berth_eta"""
vr = build_sql_proxy_connection['vr'] vr = build_sql_proxy_connection['vr']
shipcall = get_shipcall_simple() shipcall = get_shipcall_simple()
shipcall.type = ShipcallType.INCOMING.value
df_times = get_df_times(shipcall) df_times = get_df_times(shipcall)
# according to the agency, a shipcall takes place soon (ETA/ETD) # 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 # set times agency to be undetermined
df_times.loc[df_times["participant_type"]==ParticipantType.TUG.value, "eta_berth"] = None 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 # apply the validation rule
(state, msg) = vr.validation_rule_fct_missing_time_tug_berth_eta(shipcall=shipcall, df_times=df_times) (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'] vr = build_sql_proxy_connection['vr']
shipcall = get_shipcall_simple() shipcall = get_shipcall_simple()
shipcall.type = ShipcallType.OUTGOING.value
df_times = get_df_times(shipcall) df_times = get_df_times(shipcall)
# according to the agency, a shipcall takes place soon (ETA/ETD) # 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 # set times agency to be undetermined
df_times.loc[df_times["participant_type"]==ParticipantType.TUG.value, "etd_berth"] = None 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 # apply the validation rule
(state, msg) = vr.validation_rule_fct_missing_time_tug_berth_etd(shipcall=shipcall, df_times=df_times) (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) 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 # 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 # apply the validation rule
(state, msg) = vr.validation_rule_fct_missing_time_terminal_berth_eta(shipcall=shipcall, df_times=df_times) (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'] vr = build_sql_proxy_connection['vr']
shipcall = get_shipcall_simple() shipcall = get_shipcall_simple()
shipcall.type = ShipcallType.OUTGOING.value
df_times = get_df_times(shipcall) df_times = get_df_times(shipcall)
# according to the agency, a shipcall takes place soon (ETA/ETD) # 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) 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 # 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 # apply the validation rule
(state, msg) = vr.validation_rule_fct_missing_time_terminal_berth_etd(shipcall=shipcall, df_times=df_times) (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""" """0003-B validation_rule_fct_etd_time_not_in_operation_window"""
vr = build_sql_proxy_connection['vr'] vr = build_sql_proxy_connection['vr']
shipcall = get_shipcall_simple() shipcall = get_shipcall_simple()
shipcall.type = ShipcallType.SHIFTING.value
df_times = get_df_times(shipcall) df_times = get_df_times(shipcall)
t0_time = datetime.datetime.now() # reference time for easier readability 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'] vr = build_sql_proxy_connection['vr']
import random import random
shipcall = get_shipcall_simple() shipcall = get_shipcall_simple()
shipcall.type = ShipcallType.SHIFTING.value
df_times = get_df_times(shipcall) df_times = get_df_times(shipcall)
t0_time = datetime.datetime.now() 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""" """0004-B validation_rule_fct_etd_time_not_in_tidal_window"""
vr = build_sql_proxy_connection['vr'] vr = build_sql_proxy_connection['vr']
shipcall = get_shipcall_simple() shipcall = get_shipcall_simple()
shipcall.type = ShipcallType.OUTGOING.value
df_times = get_df_times(shipcall) df_times = get_df_times(shipcall)
t0_time = datetime.datetime.now() 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'] vr = build_sql_proxy_connection['vr']
shipcall = get_shipcall_simple() shipcall = get_shipcall_simple()
df_times = get_df_times(shipcall) 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 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) (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'] vr = build_sql_proxy_connection['vr']
shipcall = get_shipcall_simple() shipcall = get_shipcall_simple()
df_times = get_df_times(shipcall) 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 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) (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" assert code==StatusFlags.YELLOW, f"status should be 'yellow', because agency and terminal do not agree on the selected pier side"
return 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): 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 t2.participant_type = ParticipantType.TERMINAL.value
# agreement # agreement
t1.pier_side = True shipcall.pier_side = True
# t1.pier_side = True
t2.pier_side = True t2.pier_side = True
time_objects = [t1, t2] 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 t2.participant_type = ParticipantType.TERMINAL.value
# disagreement # disagreement
t1.pier_side = True shipcall.pier_side = True
# t1.pier_side = True
t2.pier_side = False t2.pier_side = False
time_objects = [t1, t2] time_objects = [t1, t2]