From 451700562b0f5c4d903d95b76f5531370abeb57e Mon Sep 17 00:00:00 2001 From: LFLaverty Date: Thu, 28 Mar 2024 17:54:36 +0000 Subject: [PATCH 01/10] WIP, moving to a model based ship --- .../CalculatorTests.cs | 15 ++- .../Calculator.cs | 114 ++++++++---------- .../Extensions/ReductionFactorExtensions.cs | 51 ++++++++ .../Models/CalculationResult.cs | 63 ++++++++++ .../Models/Enums/ImoCiiBoundary.cs | 10 ++ .../Models/Ship.cs | 25 ---- .../Models/ShipModels/Ship.cs | 87 +++++++++++++ .../IShipCapacityCalculatorService.cs | 4 +- ...rbonIntensityIndicatorCalculatorService.cs | 46 +------ .../Services/Impl/RatingBoundariesService.cs | 30 +++++ .../Impl/ShipCapacityCalculatorService.cs | 4 +- 11 files changed, 311 insertions(+), 138 deletions(-) create mode 100644 EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Extensions/ReductionFactorExtensions.cs create mode 100644 EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/CalculationResult.cs create mode 100644 EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/Enums/ImoCiiBoundary.cs delete mode 100644 EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/Ship.cs create mode 100644 EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/ShipModels/Ship.cs create mode 100644 EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/RatingBoundariesService.cs diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/CalculatorTests.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/CalculatorTests.cs index d35d5a2..ee51d92 100644 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/CalculatorTests.cs +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/CalculatorTests.cs @@ -1,4 +1,5 @@ using EtiveMor.OpenImoCiiCalculator.Core.Models.Enums; +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Linq; @@ -21,10 +22,20 @@ public void TestCalculator() deadweightTonnage: 0, distanceTravelled: 150000, TypeOfFuel.DIESEL_OR_GASOIL, - fuelConsumption: 1.9e+10 + fuelConsumption: 1.9e+10, + 2019 ); - Assert.AreNotEqual(ImoCiiRating.ERR, result); + System.Diagnostics.Debug.WriteLine("result is"); + + string json = JsonConvert.SerializeObject(result, Formatting.Indented); + System.Diagnostics.Debug.WriteLine(json); + + Assert.IsNotNull(result); + Assert.AreEqual(result.Results.Count(), 12); + + Assert.IsTrue(result.Results.Count(result => result.IsMeasuredYear) == 1); + Assert.IsTrue(result.Results.Count(result => result.IsEstimatedYear) == 11); } } diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Calculator.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Calculator.cs index 99dd5d0..e2647c3 100644 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Calculator.cs +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Calculator.cs @@ -1,4 +1,6 @@ -using EtiveMor.OpenImoCiiCalculator.Core.Models.Enums; +using EtiveMor.OpenImoCiiCalculator.Core.Extensions; +using EtiveMor.OpenImoCiiCalculator.Core.Models; +using EtiveMor.OpenImoCiiCalculator.Core.Models.Enums; using EtiveMor.OpenImoCiiCalculator.Core.Services; using EtiveMor.OpenImoCiiCalculator.Core.Services.Impl; @@ -10,12 +12,14 @@ public class Calculator IShipCapacityCalculatorService _shipCapacityService; IShipTransportWorkCalculatorService _shipTransportWorkService; ICarbonIntensityIndicatorCalculatorService _carbonIntensityIndicatorService; + IRatingBoundariesService _ratingBoundariesService; public Calculator() { _shipMassOfCo2EmissionsService = new ShipMassOfCo2EmissionsCalculatorService(); _shipCapacityService = new ShipCapacityCalculatorService(); _shipTransportWorkService = new ShipTransportWorkCalculatorService(); _carbonIntensityIndicatorService = new CarbonIntensityIndicatorCalculatorService(); + _ratingBoundariesService = new RatingBoundariesService(); } /// @@ -28,100 +32,80 @@ public Calculator() /// /// quantity of fuel consumed in grams /// - public ImoCiiRating CalculateAttainedCiiRating(ShipType shipType, double grossTonnage, double deadweightTonnage, double distanceTravelled, TypeOfFuel fuelType, double fuelConsumption) + public CalculationResult CalculateAttainedCiiRating( + ShipType shipType, + double grossTonnage, + double deadweightTonnage, + double distanceTravelled, + TypeOfFuel fuelType, + double fuelConsumption, + int targetYear) { var shipCo2Emissions = _shipMassOfCo2EmissionsService.GetMassOfCo2Emissions(fuelType, fuelConsumption); var shipCapacity = _shipCapacityService.GetShipCapacity(shipType, deadweightTonnage, grossTonnage); var transportWork = _shipTransportWorkService.GetShipTransportWork(shipCapacity, distanceTravelled); - var attainedCii = _carbonIntensityIndicatorService.GetAttainedCarbonIntensity(shipCo2Emissions, transportWork); - var requiredCii = _carbonIntensityIndicatorService.GetRequiredCarbonIntensity(shipType, shipCapacity, 2019); - var attainedRequiredRatio = attainedCii / requiredCii; - - - - return ImoCiiRating.ERR; - } + List results = new List(); + for (int year = 2019; year <= 2030; year++) + { + var attainedCiiInYear = _carbonIntensityIndicatorService.GetAttainedCarbonIntensity(shipCo2Emissions, transportWork); + var requiredCiiInYear = _carbonIntensityIndicatorService.GetRequiredCarbonIntensity(shipType, shipCapacity, year); - /// - /// CII = (annualFuelConsumption * co2eqEmissionsFactor) / (distanceSailed * capacity) - /// - /// - /// - /// - /// - /// - /// - /// - public ImoCiiRating CalculateImoCiiRating(double annualFuelConsumption, double co2eqEmissionsFactor, double distanceSailed, double capacity, double deadweightTonnage, double grossTonnage, ShipType shipType) - { - double massOfCo2Emissions = annualFuelConsumption * co2eqEmissionsFactor; - double transportWork = annualFuelConsumption * co2eqEmissionsFactor; - var cii = massOfCo2Emissions / transportWork; + results.Add(new ResultYear + { + IsMeasuredYear = targetYear == year, + Year = year, + AttainedCii = attainedCiiInYear, + RequiredCii = requiredCiiInYear, + Rating = GetImoCiiRatingInYear(attainedCiiInYear, requiredCiiInYear, year), + Boundaries = GetBoundaries(shipType, requiredCiiInYear) + }); + } - return ImoCiiRating.ERR; + return new CalculationResult(results); } - - - /// - /// Calculates the mass of CO2 emissions from a ship, given the mass of CO2eq emissions, and the transport work undertaken by the ship in a full calendar year. - /// - /// - /// - /// - /// - /// - public ImoCiiRating CalculateImoCiiRating(decimal massOfCo2Emissions, decimal transportWork) + private ImoCiiRating GetImoCiiRatingInYear(double attainedCiiInYear, double requiredCiiInYear, int year) { - double cii = (double)(massOfCo2Emissions / transportWork); + var gradeLowerBoundaries = GetBoundaries(ShipType.RoRoCruisePassengerShip, requiredCiiInYear); - if (cii < 0.0001) + if (attainedCiiInYear < gradeLowerBoundaries[ImoCiiBoundary.Superior]) { + // lower than the "superior" boundary return ImoCiiRating.A; } - else if (cii >= 0.0001 && cii < 0.0002) + else if (attainedCiiInYear < gradeLowerBoundaries[ImoCiiBoundary.Lower]) { + // lower than the "lower" boundary return ImoCiiRating.B; } - else if (cii >= 0.0002 && cii < 0.0003) + else if (attainedCiiInYear < gradeLowerBoundaries[ImoCiiBoundary.Upper]) { + // lower than the "upper" boundary return ImoCiiRating.C; } - else if (cii >= 0.0003 && cii < 0.0004) + else if (attainedCiiInYear < gradeLowerBoundaries[ImoCiiBoundary.Inferior]) { + // lower than the "inferior" boundary return ImoCiiRating.D; } - else if (cii >= 0.0004) - { - return ImoCiiRating.E; - } else { - return ImoCiiRating.ERR; + // higher than the inferior boundary + return ImoCiiRating.E; } } - /// - /// Calculates the transport work undertaken by a ship, given its deadweight tonnage and distance sailed in a calendar year. - /// - /// - /// The Capacity of the ship in metric Tons - /// - /// For cargo ships submit the Deadweight Tonnage - /// For cruise ships submit the Gross Tonnage - /// - /// - /// The distance travelled by the ship across one full calendar year - /// - /// - public decimal CalculateTransportWork(decimal deadweightTonnage, decimal distanceSailedCalendarYear, ShipType shipType) + private Dictionary GetBoundaries(ShipType shipType, double requiredCiiInYear) { - return deadweightTonnage * distanceSailedCalendarYear; + return new Dictionary { + { ImoCiiBoundary.Superior, 0.72 * requiredCiiInYear }, + { ImoCiiBoundary.Lower, 0.90 * requiredCiiInYear}, + { ImoCiiBoundary.Upper, 1.12 * requiredCiiInYear}, + { ImoCiiBoundary.Inferior, 1.41 * requiredCiiInYear} + }; } - - - } + } diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Extensions/ReductionFactorExtensions.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Extensions/ReductionFactorExtensions.cs new file mode 100644 index 0000000..0a9bcb3 --- /dev/null +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Extensions/ReductionFactorExtensions.cs @@ -0,0 +1,51 @@ +namespace EtiveMor.OpenImoCiiCalculator.Core.Extensions +{ + public static class ReductionFactorExtensions + { + /// + /// Gets an annual reduction factor for a given year, according to MEPC.338(76) + /// + /// the calendar year being analysed + /// the reduction factor + /// + /// Thrown if a year outside of the range 2019-2030 (inclusive) is provided + /// + public static double GetAnnualReductionFactor(this int year) + { + switch (year) + { + case 2019: + return 0.00; + case 2020: + return 0.01; + case 2021: + return 0.02; + case 2022: + return 0.03; + case 2023: + return 0.05; + case 2024: + return 0.07; + case 2025: + return 0.09; + case 2026: + return 0.11; + case 2027: + return 0.13; + case 2028: + return 0.15; + case 2029: + return 0.17; + case 2030: + return 0.19; + default: + throw new NotSupportedException($"Year {year} is not supported"); + } + } + + public static double ApplyAnnualReductionFactor(this double value, int year) + { + return value * (1 - year.GetAnnualReductionFactor()); + } + } +} diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/CalculationResult.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/CalculationResult.cs new file mode 100644 index 0000000..8449d1f --- /dev/null +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/CalculationResult.cs @@ -0,0 +1,63 @@ +using EtiveMor.OpenImoCiiCalculator.Core.Models.Enums; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EtiveMor.OpenImoCiiCalculator.Core.Models +{ + public class CalculationResult + { + + public CalculationResult(IEnumerable results) + { + Results = results; + } + + + + /// + /// Contains a collection of CII Ratings for each year + /// between 2019 and 2030 + /// + public IEnumerable Results { get; set; } + + + } + + + public class ResultYear + { + /// + /// Indicates if this year is a measured year, + /// + /// if true, the CII rating, and all other values are measured + /// if false, the CII rating, and all other values are estimates + /// + public bool IsMeasuredYear { get; set; } + + /// + /// Indicates if this year is an estimated year, + /// + /// if true, the CII rating, and all other values are estimates + /// if false, the CII rating, and all other values are measured + /// + public bool IsEstimatedYear { get { return !IsMeasuredYear; } } + public int Year { get; set; } + public ImoCiiRating Rating { get; set; } + public double RequiredCii { get; set; } + public double AttainedCii { get; set; } + + /// + /// This is the ratio of Attained:Required CII + /// + public double AttainedRequiredRatio { get + { + return AttainedCii / RequiredCii; + } + } + + public required Dictionary Boundaries { get; set; } + } +} diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/Enums/ImoCiiBoundary.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/Enums/ImoCiiBoundary.cs new file mode 100644 index 0000000..dfea3b8 --- /dev/null +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/Enums/ImoCiiBoundary.cs @@ -0,0 +1,10 @@ +namespace EtiveMor.OpenImoCiiCalculator.Core.Models.Enums +{ + public enum ImoCiiBoundary + { + Superior = 1 , + Lower = 2, + Upper =3, + Inferior = 4 + } +} diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/Ship.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/Ship.cs deleted file mode 100644 index f1a53d5..0000000 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/Ship.cs +++ /dev/null @@ -1,25 +0,0 @@ -using EtiveMor.OpenImoCiiCalculator.Core.Models.Enums; - -namespace EtiveMor.OpenImoCiiCalculator.Core.Models -{ - public class Ship - { - /// - /// The type of the ship (e.g., BulkCarrier, RoRoCargoShip, CruisePassengerShip, etc.) /// - public ShipType ShipType { get; set; } - - /// - /// The deadweight tonnage (DWT) of the ship, which represents the sum of the weights - /// of cargo, fuel, fresh water, ballast water, provisions, passengers, and crew. - /// - public double DeadweightTonnage { get; set; } - - /// - /// The gross tonnage (GT) of the ship, which is a measure of the ship's overall internal - /// volume. GT is not a weight measurement but is used to determine various other - /// shipping-related values, such as crew size, safety requirements, and registration - /// fees. - /// - public double GrossTonnage { get; set; } - } -} diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/ShipModels/Ship.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/ShipModels/Ship.cs new file mode 100644 index 0000000..0df6821 --- /dev/null +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/ShipModels/Ship.cs @@ -0,0 +1,87 @@ +using EtiveMor.OpenImoCiiCalculator.Core.Models.Enums; + +namespace EtiveMor.OpenImoCiiCalculator.Core.Models.ShipModels +{ + public class Ship + { + public Ship( + double deadweightTonnage, + double grossTonnage, + double a, + double c, + double capacity + ) + { + if (ShipType == ShipType.UNKNOWN) + { + throw new System.InvalidOperationException("ShipType must be set. Do not create a Ship directly, use an inheriting ship type"); + } + DeadweightTonnage = deadweightTonnage; + GrossTonnage = grossTonnage; + + } + + /// + /// The type of the ship (e.g., BulkCarrier, RoRoCargoShip, CruisePassengerShip, etc.) /// + public ShipType ShipType { get; protected set; } + + /// + /// The deadweight tonnage (DWT) of the ship, which represents the sum of the weights + /// of cargo, fuel, fresh water, ballast water, provisions, passengers, and crew. + /// + public double DeadweightTonnage { get; set; } + + /// + /// The gross tonnage (GT) of the ship, which is a measure of the ship's overall internal + /// volume. GT is not a weight measurement but is used to determine various other + /// shipping-related values, such as crew size, safety requirements, and registration + /// fees. + /// + public double GrossTonnage { get; set; } + + + + /// + /// The ship's dd vectors as outlined in MEPC.354(78) + /// + public required Dictionary BoundaryDdVectors2019 { get; set; } + + + public double a { get; private set; } + + public double c { get; private set; } + + public double Capacity { get; protected set; } + public CapacityUnit CapacityUnit { get; protected set; } + } + + + public enum CapacityUnit + { + ERR, + DWT, + DWT_CAP_HIGH, + GT, + GT_CAP_LOW + } + + + public class BulkCarrier : Ship + { + public BulkCarrier( + double deadweightTonnage, + double grossTonnage + ) : base(deadweightTonnage, grossTonnage) + { + ShipType = ShipType.BulkCarrier; + BoundaryDdVectors2019 = new Dictionary + { + { ImoCiiBoundary.Superior, 0.76 }, + { ImoCiiBoundary.Lower, 0.92 }, + { ImoCiiBoundary.Upper, 1.14 }, + { ImoCiiBoundary.Inferior, 1.30 } + }; + + } + } +} diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/IShipCapacityCalculatorService.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/IShipCapacityCalculatorService.cs index d9f0de4..63401da 100644 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/IShipCapacityCalculatorService.cs +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/IShipCapacityCalculatorService.cs @@ -1,5 +1,5 @@ -using EtiveMor.OpenImoCiiCalculator.Core.Models; -using EtiveMor.OpenImoCiiCalculator.Core.Models.Enums; +using EtiveMor.OpenImoCiiCalculator.Core.Models.Enums; +using EtiveMor.OpenImoCiiCalculator.Core.Models.ShipModels; namespace EtiveMor.OpenImoCiiCalculator.Core.Services { diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/CarbonIntensityIndicatorCalculatorService.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/CarbonIntensityIndicatorCalculatorService.cs index 8065672..940424e 100644 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/CarbonIntensityIndicatorCalculatorService.cs +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/CarbonIntensityIndicatorCalculatorService.cs @@ -1,4 +1,5 @@ -using EtiveMor.OpenImoCiiCalculator.Core.Models.Enums; +using EtiveMor.OpenImoCiiCalculator.Core.Extensions; +using EtiveMor.OpenImoCiiCalculator.Core.Models.Enums; namespace EtiveMor.OpenImoCiiCalculator.Core.Services.Impl { @@ -81,48 +82,9 @@ public double GetRequiredCarbonIntensity(ShipType shipType, double capacity, int double ciiReference = a * Math.Pow(capacity, -c); - return ciiReference * (1 - GetAnnualReductionFactor(year)); + return ciiReference.ApplyAnnualReductionFactor(year); } - /// - /// Gets an annual reduction factor for a given year, according to MEPC.338(76) - /// - /// the calendar year being analysed - /// the reduction factor - /// - /// Thrown if a year outside of the range 2019-2030 (inclusive) is provided - /// - private double GetAnnualReductionFactor(int year) - { - switch (year) { - case 2019: - return 0.00; - case 2020: - return 0.01; - case 2021: - return 0.02; - case 2022: - return 0.03; - case 2023: - return 0.05; - case 2024: - return 0.07; - case 2025: - return 0.09; - case 2026: - return 0.11; - case 2027: - return 0.13; - case 2028: - return 0.15; - case 2029: - return 0.17; - case 2030: - return 0.19; - default: - throw new NotSupportedException($"Year {year} is not supported"); - } - } /// /// Gets either the `a` or `c` value for a given ship type and capacity @@ -345,7 +307,7 @@ private double GetRoRoCargoShipValue(ValType valType, double capacity) /// private double GetRoRoPassengerShipValue(ValType valType, double capacity) { - return valType == ValType.a ? 1012 : 0.460; + return valType == ValType.a ? 2023 : 0.460; } /// diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/RatingBoundariesService.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/RatingBoundariesService.cs new file mode 100644 index 0000000..1b45329 --- /dev/null +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/RatingBoundariesService.cs @@ -0,0 +1,30 @@ +using EtiveMor.OpenImoCiiCalculator.Core.Models.Enums; + +namespace EtiveMor.OpenImoCiiCalculator.Core.Services.Impl +{ + public interface IRatingBoundariesService + { + Dictionary GetBoundaries(ShipType shipType, double requiredCiiInYear); + + } + public class RatingBoundariesService : IRatingBoundariesService + { + public Dictionary GetBoundaries(ShipType shipType, double requiredCiiInYear) + { + switch (shipType) + { + case ShipType.RoRoPassengerShip: + { + return new Dictionary { + { ImoCiiBoundary.Superior, 0.76 * requiredCiiInYear }, + { ImoCiiBoundary.Lower, 0.92 * requiredCiiInYear}, + { ImoCiiBoundary.Upper, 1.14 * requiredCiiInYear}, + { ImoCiiBoundary.Inferior, 1.30 * requiredCiiInYear} + }; + } + default: + throw new NotSupportedException($"Ship type '{shipType}' not supported"); + } + } + } +} diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/ShipCapacityCalculatorService.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/ShipCapacityCalculatorService.cs index 446e39f..5e7e0dc 100644 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/ShipCapacityCalculatorService.cs +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/ShipCapacityCalculatorService.cs @@ -1,5 +1,5 @@ -using EtiveMor.OpenImoCiiCalculator.Core.Models; -using EtiveMor.OpenImoCiiCalculator.Core.Models.Enums; +using EtiveMor.OpenImoCiiCalculator.Core.Models.Enums; +using EtiveMor.OpenImoCiiCalculator.Core.Models.ShipModels; namespace EtiveMor.OpenImoCiiCalculator.Core.Services.Impl { From 6315bbbea097e8f3933abea6c647779e927c58b6 Mon Sep 17 00:00:00 2001 From: LFLaverty Date: Fri, 29 Mar 2024 07:07:23 +0000 Subject: [PATCH 02/10] WIP --- .../ShipCapacityTests.cs | 41 +-- .../Calculator.cs | 2 +- .../Models/ShipModels/Ship.cs | 240 ++++++++++++++++-- .../IShipCapacityCalculatorService.cs | 2 +- .../Impl/ShipCapacityCalculatorService.cs | 77 +++--- 5 files changed, 277 insertions(+), 85 deletions(-) diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/ShipCapacityTests.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/ShipCapacityTests.cs index bd599f7..d315be6 100644 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/ShipCapacityTests.cs +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/ShipCapacityTests.cs @@ -1,5 +1,6 @@ using EtiveMor.OpenImoCiiCalculator.Core.Models; using EtiveMor.OpenImoCiiCalculator.Core.Models.Enums; +using EtiveMor.OpenImoCiiCalculator.Core.Models.ShipModels; using EtiveMor.OpenImoCiiCalculator.Core.Services.Impl; namespace EtiveMor.OpenImoCiiCalculator.Core.Tests @@ -21,12 +22,7 @@ public class ShipCapacityTests [TestMethod] public void TestCalculateCapacity_BulkCarrier() { - var ship = new Ship - { - ShipType = ShipType.BulkCarrier, - DeadweightTonnage = 250000, - GrossTonnage = 0 - }; + var ship = new BulkCarrier(deadweightTonnage: 250000, grossTonnage: 0); var capacity = new ShipCapacityCalculatorService().GetShipCapacity(ship); @@ -58,12 +54,15 @@ public void TestCalculateCapacity_BulkCarrier() [DataRow(ShipType.RoRoCruisePassengerShip, 250000, 0)] public void TestValidateTonnageParamsSet_ArgumentOutOfRangeException(ShipType shipType, double deadweightTonnage, double grossTonnage) { - var ship = new Ship - { - ShipType = shipType, - DeadweightTonnage = deadweightTonnage, - GrossTonnage = grossTonnage - }; + var ship = new BulkCarrier(deadweightTonnage: deadweightTonnage, grossTonnage: grossTonnage); + + //var ship = new Ship + //{ + // ShipType = shipType, + // DeadweightTonnage = deadweightTonnage, + // GrossTonnage = grossTonnage + //}; + new ShipCapacityCalculatorService().GetShipCapacity(ship); } @@ -93,16 +92,20 @@ public void TestValidateTonnageParamsSet_ArgumentOutOfRangeException(ShipType sh [DataRow(ShipType.RoRoCruisePassengerShip, 0, 100000, 100000)] public void TestCalculateCapacity(ShipType shipType, double deadweightTonnage, double grossTonnage, double expectedCapacity) { - var ship = new Ship - { - ShipType = shipType, - DeadweightTonnage = deadweightTonnage, - GrossTonnage = grossTonnage - }; + var ship = new BulkCarrier(deadweightTonnage: deadweightTonnage, grossTonnage: grossTonnage); + + //var ship = new Ship + //{ + // ShipType = shipType, + // DeadweightTonnage = deadweightTonnage, + // GrossTonnage = grossTonnage + //}; + var capacity = new ShipCapacityCalculatorService().GetShipCapacity(ship); - Assert.AreEqual(expectedCapacity, capacity); + Assert.AreEqual(capacity, ship.Capacity); + Assert.AreEqual(expectedCapacity, ship.Capacity); } } } diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Calculator.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Calculator.cs index e2647c3..e45a3ad 100644 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Calculator.cs +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Calculator.cs @@ -42,7 +42,7 @@ public CalculationResult CalculateAttainedCiiRating( int targetYear) { var shipCo2Emissions = _shipMassOfCo2EmissionsService.GetMassOfCo2Emissions(fuelType, fuelConsumption); - var shipCapacity = _shipCapacityService.GetShipCapacity(shipType, deadweightTonnage, grossTonnage); + var shipCapacity = 0; // _shipCapacityService.GetShipCapacity(shipType, deadweightTonnage, grossTonnage); var transportWork = _shipTransportWorkService.GetShipTransportWork(shipCapacity, distanceTravelled); List results = new List(); diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/ShipModels/Ship.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/ShipModels/Ship.cs index 0df6821..9a62d8c 100644 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/ShipModels/Ship.cs +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/ShipModels/Ship.cs @@ -2,14 +2,35 @@ namespace EtiveMor.OpenImoCiiCalculator.Core.Models.ShipModels { + + public class BulkCarrier : Ship + { + public BulkCarrier( + double deadweightTonnage, + double grossTonnage + ) : base(deadweightTonnage, grossTonnage) + { + ShipType = ShipType.BulkCarrier; + BoundaryDdVectors2019 = new Dictionary + { + { ImoCiiBoundary.Superior, 0.86 }, + { ImoCiiBoundary.Lower, 0.94 }, + { ImoCiiBoundary.Upper, 1.06 }, + { ImoCiiBoundary.Inferior, 1.18 } + }; + a = 4745; + c = 0.622; + + Capacity = GetShipCapacity(ShipType, deadweightTonnage, grossTonnage); + } + } + + public class Ship { public Ship( double deadweightTonnage, - double grossTonnage, - double a, - double c, - double capacity + double grossTonnage ) { if (ShipType == ShipType.UNKNOWN) @@ -19,10 +40,22 @@ double capacity DeadweightTonnage = deadweightTonnage; GrossTonnage = grossTonnage; + + Capacity = 0; + + + + if (BoundaryDdVectors2019 == null) + { + throw new Exception("BoundaryDdVectors2019 must be set. Do not create a Ship directly, use an inheriting ship type"); + } } + + /// - /// The type of the ship (e.g., BulkCarrier, RoRoCargoShip, CruisePassengerShip, etc.) /// + /// The type of the ship (e.g., BulkCarrier, RoRoCargoShip, CruisePassengerShip, etc.) + /// public ShipType ShipType { get; protected set; } /// @@ -44,15 +77,187 @@ double capacity /// /// The ship's dd vectors as outlined in MEPC.354(78) /// - public required Dictionary BoundaryDdVectors2019 { get; set; } + public Dictionary BoundaryDdVectors2019 { get; set; } - public double a { get; private set; } + public double a { get; protected set; } - public double c { get; private set; } + public double c { get; protected set; } public double Capacity { get; protected set; } public CapacityUnit CapacityUnit { get; protected set; } + + + + /// + /// Calculates the ship's capacity according to the MEPC.353(78)guidelines + /// + /// + /// The type of the ship (e.g., BulkCarrier, RoRoCargoShip, CruisePassengerShip, etc.) + /// + /// + /// The deadweight tonnage (DWT) of the ship, which represents the sum of the weights + /// of cargo, fuel, fresh water, ballast water, provisions, passengers, and crew. + /// + /// + /// The gross tonnage (GT) of the ship, which is a measure of the ship's overall internal + /// volume. GT is not a weight measurement but is used to determine various other + /// shipping-related values, such as crew size, safety requirements, and registration + /// fees. + /// + /// + /// The ship's type capacity according to MEPC.353(78) + /// + public double GetShipCapacity(ShipType shipType, double deadweightTonnage, double grossTonnage) + { + ValidateTonnageParamsSet(shipType, deadweightTonnage, grossTonnage); + + + switch (shipType) + { + case ShipType.BulkCarrier: + return deadweightTonnage >= 279000 ? 279000 : deadweightTonnage; + case ShipType.GasCarrier: + return deadweightTonnage; + case ShipType.Tanker: + return deadweightTonnage; + case ShipType.ContainerShip: + return deadweightTonnage; + case ShipType.GeneralCargoShip: + return deadweightTonnage; + case ShipType.RefrigeratedCargoCarrier: + return deadweightTonnage; + case ShipType.CombinationCarrier: + return deadweightTonnage; + case ShipType.LngCarrier: + return deadweightTonnage < 65000 ? 65000 : deadweightTonnage; + case ShipType.RoRoCargoShipVehicleCarrier: + return deadweightTonnage >= 57700 ? 57700 : grossTonnage; + case ShipType.RoRoCargoShip: + return grossTonnage; + case ShipType.RoRoPassengerShip: + return grossTonnage; + case ShipType.RoRoPassengerShip_HighSpeedSOLAS: + return grossTonnage; + case ShipType.RoRoCruisePassengerShip: + return grossTonnage; + default: + throw new ArgumentException($"Unsupported {nameof(shipType)}: {shipType}"); + } + } + + + + /// + /// + /// + /// + /// + /// The ship's deadweight tonnage + /// Required to be above 0 for ship types: + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// - + /// + /// + /// The ship's grossTonnage. + /// + /// Required to be above 0 for ship types: + /// - + /// - + /// - + /// + /// + /// Thrown if the weight value is equal or lower than 0 if it is required to be above 0 + private void ValidateTonnageParamsSet(ShipType shipType, double deadweightTonnage, double grossTonnage) + { + _ = shipType switch + { + ShipType.BulkCarrier => ValidateTonnage(deadweightTonnage, nameof(deadweightTonnage), shipType) + ? deadweightTonnage + : throw new InvalidOperationException(), + + ShipType.GasCarrier => ValidateTonnage(deadweightTonnage, nameof(deadweightTonnage), shipType) + ? deadweightTonnage + : throw new InvalidOperationException(), + + ShipType.Tanker => ValidateTonnage(deadweightTonnage, nameof(deadweightTonnage), shipType) + ? deadweightTonnage + : throw new InvalidOperationException(), + + ShipType.ContainerShip => ValidateTonnage(deadweightTonnage, nameof(deadweightTonnage), shipType) + ? deadweightTonnage + : throw new InvalidOperationException(), + + ShipType.GeneralCargoShip => ValidateTonnage(deadweightTonnage, nameof(deadweightTonnage), shipType) + ? deadweightTonnage + : throw new InvalidOperationException(), + + ShipType.RefrigeratedCargoCarrier => ValidateTonnage(deadweightTonnage, nameof(deadweightTonnage), shipType) + ? deadweightTonnage + : throw new InvalidOperationException(), + + ShipType.CombinationCarrier => ValidateTonnage(deadweightTonnage, nameof(deadweightTonnage), shipType) + ? deadweightTonnage + : throw new InvalidOperationException(), + + ShipType.LngCarrier => ValidateTonnage(deadweightTonnage, nameof(deadweightTonnage), shipType) + ? deadweightTonnage + : throw new InvalidOperationException(), + + ShipType.RoRoCargoShipVehicleCarrier => ValidateTonnage(grossTonnage, nameof(grossTonnage), shipType) + ? grossTonnage + : throw new InvalidOperationException(), + + ShipType.RoRoCargoShip => ValidateTonnage(grossTonnage, nameof(grossTonnage), shipType) + ? grossTonnage + : throw new InvalidOperationException(), + + ShipType.RoRoPassengerShip => ValidateTonnage(grossTonnage, nameof(grossTonnage), shipType) + ? grossTonnage + : throw new InvalidOperationException(), + + ShipType.RoRoPassengerShip_HighSpeedSOLAS => ValidateTonnage(grossTonnage, nameof(grossTonnage), shipType) + ? grossTonnage + : throw new InvalidOperationException(), + + ShipType.RoRoCruisePassengerShip => ValidateTonnage(grossTonnage, nameof(grossTonnage), shipType) + ? grossTonnage + : throw new InvalidOperationException(), + + _ => throw new ArgumentOutOfRangeException(nameof(shipType), shipType, $"Unsupported {nameof(shipType)}: {shipType}") + + + + }; + } + + /// + /// Validates that the tonnage param is greater than 0 + /// + /// + /// The tonnage value (accepts either gross tonnage or deadweight) + /// The ship's tonnage name (accepts either "gross" or "deadweight" + /// The ship type + /// + /// true if the tonnage is greater than 0 + /// throws an exception if the tonnage is less than or equal to 0 + /// + /// Thrown if the value is equal or lower than 0 + private bool ValidateTonnage(double tonnage, string tonnageName, ShipType shipType) + { + if (tonnage <= 0) + { + throw new ArgumentOutOfRangeException(tonnageName, tonnage, $"{tonnageName} must be greater than 0 if {nameof(shipType)} is set to {shipType} "); + } + return true; + } } @@ -66,22 +271,5 @@ public enum CapacityUnit } - public class BulkCarrier : Ship - { - public BulkCarrier( - double deadweightTonnage, - double grossTonnage - ) : base(deadweightTonnage, grossTonnage) - { - ShipType = ShipType.BulkCarrier; - BoundaryDdVectors2019 = new Dictionary - { - { ImoCiiBoundary.Superior, 0.76 }, - { ImoCiiBoundary.Lower, 0.92 }, - { ImoCiiBoundary.Upper, 1.14 }, - { ImoCiiBoundary.Inferior, 1.30 } - }; - - } - } + } diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/IShipCapacityCalculatorService.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/IShipCapacityCalculatorService.cs index 63401da..09c1ea2 100644 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/IShipCapacityCalculatorService.cs +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/IShipCapacityCalculatorService.cs @@ -6,6 +6,6 @@ namespace EtiveMor.OpenImoCiiCalculator.Core.Services public interface IShipCapacityCalculatorService { double GetShipCapacity(Ship ship); - double GetShipCapacity(ShipType shipType, double deadweightTonnage, double grossTonnage); + // double GetShipCapacity(ShipType shipType, double deadweightTonnage, double grossTonnage); } } \ No newline at end of file diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/ShipCapacityCalculatorService.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/ShipCapacityCalculatorService.cs index 5e7e0dc..ea57634 100644 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/ShipCapacityCalculatorService.cs +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/ShipCapacityCalculatorService.cs @@ -18,7 +18,8 @@ public class ShipCapacityCalculatorService : IShipCapacityCalculatorService /// public double GetShipCapacity(Ship ship) { - return GetShipCapacity(ship.ShipType, ship.DeadweightTonnage, ship.GrossTonnage); + return ship.Capacity; + // return GetShipCapacity(ship.ShipType, ship.DeadweightTonnage, ship.GrossTonnage); } /// @@ -40,43 +41,43 @@ public double GetShipCapacity(Ship ship) /// /// The ship's type capacity according to MEPC.353(78) /// - public double GetShipCapacity(ShipType shipType, double deadweightTonnage, double grossTonnage) - { - ValidateTonnageParamsSet(shipType, deadweightTonnage, grossTonnage); - - - switch (shipType) - { - case ShipType.BulkCarrier: - return deadweightTonnage >= 279000 ? 279000 : deadweightTonnage; - case ShipType.GasCarrier: - return deadweightTonnage; - case ShipType.Tanker: - return deadweightTonnage; - case ShipType.ContainerShip: - return deadweightTonnage; - case ShipType.GeneralCargoShip: - return deadweightTonnage; - case ShipType.RefrigeratedCargoCarrier: - return deadweightTonnage; - case ShipType.CombinationCarrier: - return deadweightTonnage; - case ShipType.LngCarrier: - return deadweightTonnage < 65000 ? 65000 : deadweightTonnage; - case ShipType.RoRoCargoShipVehicleCarrier: - return deadweightTonnage >= 57700 ? 57700 : grossTonnage; - case ShipType.RoRoCargoShip: - return grossTonnage; - case ShipType.RoRoPassengerShip: - return grossTonnage; - case ShipType.RoRoPassengerShip_HighSpeedSOLAS: - return grossTonnage; - case ShipType.RoRoCruisePassengerShip: - return grossTonnage; - default: - throw new ArgumentException($"Unsupported {nameof(shipType)}: {shipType}"); - } - } + //public double GetShipCapacity(ShipType shipType, double deadweightTonnage, double grossTonnage) + //{ + // ValidateTonnageParamsSet(shipType, deadweightTonnage, grossTonnage); + + + // switch (shipType) + // { + // case ShipType.BulkCarrier: + // return deadweightTonnage >= 279000 ? 279000 : deadweightTonnage; + // case ShipType.GasCarrier: + // return deadweightTonnage; + // case ShipType.Tanker: + // return deadweightTonnage; + // case ShipType.ContainerShip: + // return deadweightTonnage; + // case ShipType.GeneralCargoShip: + // return deadweightTonnage; + // case ShipType.RefrigeratedCargoCarrier: + // return deadweightTonnage; + // case ShipType.CombinationCarrier: + // return deadweightTonnage; + // case ShipType.LngCarrier: + // return deadweightTonnage < 65000 ? 65000 : deadweightTonnage; + // case ShipType.RoRoCargoShipVehicleCarrier: + // return deadweightTonnage >= 57700 ? 57700 : grossTonnage; + // case ShipType.RoRoCargoShip: + // return grossTonnage; + // case ShipType.RoRoPassengerShip: + // return grossTonnage; + // case ShipType.RoRoPassengerShip_HighSpeedSOLAS: + // return grossTonnage; + // case ShipType.RoRoCruisePassengerShip: + // return grossTonnage; + // default: + // throw new ArgumentException($"Unsupported {nameof(shipType)}: {shipType}"); + // } + //} /// From 02465ee99a2d6c4c2a238cb815631ff06501e886 Mon Sep 17 00:00:00 2001 From: LFLaverty Date: Fri, 29 Mar 2024 07:24:38 +0000 Subject: [PATCH 03/10] reverts the ship model approach --- .../ShipCapacityTests.cs | 31 +-- .../Models/ShipModels/Ship.cs | 227 +----------------- .../Impl/ShipCapacityCalculatorService.cs | 77 +++--- 3 files changed, 63 insertions(+), 272 deletions(-) diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/ShipCapacityTests.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/ShipCapacityTests.cs index d315be6..90e3359 100644 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/ShipCapacityTests.cs +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/ShipCapacityTests.cs @@ -22,7 +22,9 @@ public class ShipCapacityTests [TestMethod] public void TestCalculateCapacity_BulkCarrier() { - var ship = new BulkCarrier(deadweightTonnage: 250000, grossTonnage: 0); + var ship = new Ship( + ShipType.BulkCarrier, + deadweightTonnage: 250000, grossTonnage: 0); var capacity = new ShipCapacityCalculatorService().GetShipCapacity(ship); @@ -54,15 +56,10 @@ public void TestCalculateCapacity_BulkCarrier() [DataRow(ShipType.RoRoCruisePassengerShip, 250000, 0)] public void TestValidateTonnageParamsSet_ArgumentOutOfRangeException(ShipType shipType, double deadweightTonnage, double grossTonnage) { - var ship = new BulkCarrier(deadweightTonnage: deadweightTonnage, grossTonnage: grossTonnage); - - //var ship = new Ship - //{ - // ShipType = shipType, - // DeadweightTonnage = deadweightTonnage, - // GrossTonnage = grossTonnage - //}; - + var ship = new Ship( + shipType, + deadweightTonnage, + grossTonnage); new ShipCapacityCalculatorService().GetShipCapacity(ship); } @@ -92,20 +89,16 @@ public void TestValidateTonnageParamsSet_ArgumentOutOfRangeException(ShipType sh [DataRow(ShipType.RoRoCruisePassengerShip, 0, 100000, 100000)] public void TestCalculateCapacity(ShipType shipType, double deadweightTonnage, double grossTonnage, double expectedCapacity) { - var ship = new BulkCarrier(deadweightTonnage: deadweightTonnage, grossTonnage: grossTonnage); + var ship = new Ship( + shipType, + deadweightTonnage, + grossTonnage); - //var ship = new Ship - //{ - // ShipType = shipType, - // DeadweightTonnage = deadweightTonnage, - // GrossTonnage = grossTonnage - //}; var capacity = new ShipCapacityCalculatorService().GetShipCapacity(ship); - Assert.AreEqual(capacity, ship.Capacity); - Assert.AreEqual(expectedCapacity, ship.Capacity); + Assert.AreEqual(expectedCapacity, capacity); } } } diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/ShipModels/Ship.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/ShipModels/Ship.cs index 9a62d8c..db77885 100644 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/ShipModels/Ship.cs +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/ShipModels/Ship.cs @@ -3,51 +3,22 @@ namespace EtiveMor.OpenImoCiiCalculator.Core.Models.ShipModels { - public class BulkCarrier : Ship - { - public BulkCarrier( - double deadweightTonnage, - double grossTonnage - ) : base(deadweightTonnage, grossTonnage) - { - ShipType = ShipType.BulkCarrier; - BoundaryDdVectors2019 = new Dictionary - { - { ImoCiiBoundary.Superior, 0.86 }, - { ImoCiiBoundary.Lower, 0.94 }, - { ImoCiiBoundary.Upper, 1.06 }, - { ImoCiiBoundary.Inferior, 1.18 } - }; - a = 4745; - c = 0.622; - - Capacity = GetShipCapacity(ShipType, deadweightTonnage, grossTonnage); - } - } - public class Ship { public Ship( + ShipType shipType, double deadweightTonnage, double grossTonnage ) { - if (ShipType == ShipType.UNKNOWN) - { - throw new System.InvalidOperationException("ShipType must be set. Do not create a Ship directly, use an inheriting ship type"); - } + ShipType = shipType; DeadweightTonnage = deadweightTonnage; GrossTonnage = grossTonnage; - - Capacity = 0; - - - - if (BoundaryDdVectors2019 == null) + if (shipType == ShipType.UNKNOWN) { - throw new Exception("BoundaryDdVectors2019 must be set. Do not create a Ship directly, use an inheriting ship type"); + throw new System.InvalidOperationException("ShipType must be set"); } } @@ -56,13 +27,13 @@ double grossTonnage /// /// The type of the ship (e.g., BulkCarrier, RoRoCargoShip, CruisePassengerShip, etc.) /// - public ShipType ShipType { get; protected set; } + public ShipType ShipType { get; private set; } /// /// The deadweight tonnage (DWT) of the ship, which represents the sum of the weights /// of cargo, fuel, fresh water, ballast water, provisions, passengers, and crew. /// - public double DeadweightTonnage { get; set; } + public double DeadweightTonnage { get; private set; } /// /// The gross tonnage (GT) of the ship, which is a measure of the ship's overall internal @@ -70,194 +41,22 @@ double grossTonnage /// shipping-related values, such as crew size, safety requirements, and registration /// fees. /// - public double GrossTonnage { get; set; } + public double GrossTonnage { get; private set; } /// /// The ship's dd vectors as outlined in MEPC.354(78) - /// - public Dictionary BoundaryDdVectors2019 { get; set; } - - - public double a { get; protected set; } - - public double c { get; protected set; } - - public double Capacity { get; protected set; } - public CapacityUnit CapacityUnit { get; protected set; } - - - - /// - /// Calculates the ship's capacity according to the MEPC.353(78)guidelines - /// - /// - /// The type of the ship (e.g., BulkCarrier, RoRoCargoShip, CruisePassengerShip, etc.) - /// - /// - /// The deadweight tonnage (DWT) of the ship, which represents the sum of the weights - /// of cargo, fuel, fresh water, ballast water, provisions, passengers, and crew. - /// - /// - /// The gross tonnage (GT) of the ship, which is a measure of the ship's overall internal - /// volume. GT is not a weight measurement but is used to determine various other - /// shipping-related values, such as crew size, safety requirements, and registration - /// fees. - /// - /// - /// The ship's type capacity according to MEPC.353(78) - /// - public double GetShipCapacity(ShipType shipType, double deadweightTonnage, double grossTonnage) - { - ValidateTonnageParamsSet(shipType, deadweightTonnage, grossTonnage); - + ///// + //public Dictionary BoundaryDdVectors2019 { get; set; } - switch (shipType) - { - case ShipType.BulkCarrier: - return deadweightTonnage >= 279000 ? 279000 : deadweightTonnage; - case ShipType.GasCarrier: - return deadweightTonnage; - case ShipType.Tanker: - return deadweightTonnage; - case ShipType.ContainerShip: - return deadweightTonnage; - case ShipType.GeneralCargoShip: - return deadweightTonnage; - case ShipType.RefrigeratedCargoCarrier: - return deadweightTonnage; - case ShipType.CombinationCarrier: - return deadweightTonnage; - case ShipType.LngCarrier: - return deadweightTonnage < 65000 ? 65000 : deadweightTonnage; - case ShipType.RoRoCargoShipVehicleCarrier: - return deadweightTonnage >= 57700 ? 57700 : grossTonnage; - case ShipType.RoRoCargoShip: - return grossTonnage; - case ShipType.RoRoPassengerShip: - return grossTonnage; - case ShipType.RoRoPassengerShip_HighSpeedSOLAS: - return grossTonnage; - case ShipType.RoRoCruisePassengerShip: - return grossTonnage; - default: - throw new ArgumentException($"Unsupported {nameof(shipType)}: {shipType}"); - } - } - - - - /// - /// - /// - /// - /// - /// The ship's deadweight tonnage - /// Required to be above 0 for ship types: - /// - - /// - - /// - - /// - - /// - - /// - - /// - - /// - - /// - - /// - /// - /// The ship's grossTonnage. - /// - /// Required to be above 0 for ship types: - /// - - /// - - /// - - /// - /// - /// Thrown if the weight value is equal or lower than 0 if it is required to be above 0 - private void ValidateTonnageParamsSet(ShipType shipType, double deadweightTonnage, double grossTonnage) - { - _ = shipType switch - { - ShipType.BulkCarrier => ValidateTonnage(deadweightTonnage, nameof(deadweightTonnage), shipType) - ? deadweightTonnage - : throw new InvalidOperationException(), - - ShipType.GasCarrier => ValidateTonnage(deadweightTonnage, nameof(deadweightTonnage), shipType) - ? deadweightTonnage - : throw new InvalidOperationException(), - - ShipType.Tanker => ValidateTonnage(deadweightTonnage, nameof(deadweightTonnage), shipType) - ? deadweightTonnage - : throw new InvalidOperationException(), - - ShipType.ContainerShip => ValidateTonnage(deadweightTonnage, nameof(deadweightTonnage), shipType) - ? deadweightTonnage - : throw new InvalidOperationException(), - - ShipType.GeneralCargoShip => ValidateTonnage(deadweightTonnage, nameof(deadweightTonnage), shipType) - ? deadweightTonnage - : throw new InvalidOperationException(), - - ShipType.RefrigeratedCargoCarrier => ValidateTonnage(deadweightTonnage, nameof(deadweightTonnage), shipType) - ? deadweightTonnage - : throw new InvalidOperationException(), - - ShipType.CombinationCarrier => ValidateTonnage(deadweightTonnage, nameof(deadweightTonnage), shipType) - ? deadweightTonnage - : throw new InvalidOperationException(), - ShipType.LngCarrier => ValidateTonnage(deadweightTonnage, nameof(deadweightTonnage), shipType) - ? deadweightTonnage - : throw new InvalidOperationException(), + //public double a { get; protected set; } - ShipType.RoRoCargoShipVehicleCarrier => ValidateTonnage(grossTonnage, nameof(grossTonnage), shipType) - ? grossTonnage - : throw new InvalidOperationException(), + //public double c { get; protected set; } - ShipType.RoRoCargoShip => ValidateTonnage(grossTonnage, nameof(grossTonnage), shipType) - ? grossTonnage - : throw new InvalidOperationException(), - - ShipType.RoRoPassengerShip => ValidateTonnage(grossTonnage, nameof(grossTonnage), shipType) - ? grossTonnage - : throw new InvalidOperationException(), - - ShipType.RoRoPassengerShip_HighSpeedSOLAS => ValidateTonnage(grossTonnage, nameof(grossTonnage), shipType) - ? grossTonnage - : throw new InvalidOperationException(), - - ShipType.RoRoCruisePassengerShip => ValidateTonnage(grossTonnage, nameof(grossTonnage), shipType) - ? grossTonnage - : throw new InvalidOperationException(), - - _ => throw new ArgumentOutOfRangeException(nameof(shipType), shipType, $"Unsupported {nameof(shipType)}: {shipType}") - - - - }; - } - - /// - /// Validates that the tonnage param is greater than 0 - /// - /// - /// The tonnage value (accepts either gross tonnage or deadweight) - /// The ship's tonnage name (accepts either "gross" or "deadweight" - /// The ship type - /// - /// true if the tonnage is greater than 0 - /// throws an exception if the tonnage is less than or equal to 0 - /// - /// Thrown if the value is equal or lower than 0 - private bool ValidateTonnage(double tonnage, string tonnageName, ShipType shipType) - { - if (tonnage <= 0) - { - throw new ArgumentOutOfRangeException(tonnageName, tonnage, $"{tonnageName} must be greater than 0 if {nameof(shipType)} is set to {shipType} "); - } - return true; - } + //public double Capacity { get; protected set; } + //public CapacityUnit CapacityUnit { get; protected set; } } diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/ShipCapacityCalculatorService.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/ShipCapacityCalculatorService.cs index ea57634..fdf42ab 100644 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/ShipCapacityCalculatorService.cs +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/ShipCapacityCalculatorService.cs @@ -18,8 +18,7 @@ public class ShipCapacityCalculatorService : IShipCapacityCalculatorService /// public double GetShipCapacity(Ship ship) { - return ship.Capacity; - // return GetShipCapacity(ship.ShipType, ship.DeadweightTonnage, ship.GrossTonnage); + return GetShipCapacity(ship.ShipType, ship.DeadweightTonnage, ship.GrossTonnage); } /// @@ -41,43 +40,43 @@ public double GetShipCapacity(Ship ship) /// /// The ship's type capacity according to MEPC.353(78) /// - //public double GetShipCapacity(ShipType shipType, double deadweightTonnage, double grossTonnage) - //{ - // ValidateTonnageParamsSet(shipType, deadweightTonnage, grossTonnage); - - - // switch (shipType) - // { - // case ShipType.BulkCarrier: - // return deadweightTonnage >= 279000 ? 279000 : deadweightTonnage; - // case ShipType.GasCarrier: - // return deadweightTonnage; - // case ShipType.Tanker: - // return deadweightTonnage; - // case ShipType.ContainerShip: - // return deadweightTonnage; - // case ShipType.GeneralCargoShip: - // return deadweightTonnage; - // case ShipType.RefrigeratedCargoCarrier: - // return deadweightTonnage; - // case ShipType.CombinationCarrier: - // return deadweightTonnage; - // case ShipType.LngCarrier: - // return deadweightTonnage < 65000 ? 65000 : deadweightTonnage; - // case ShipType.RoRoCargoShipVehicleCarrier: - // return deadweightTonnage >= 57700 ? 57700 : grossTonnage; - // case ShipType.RoRoCargoShip: - // return grossTonnage; - // case ShipType.RoRoPassengerShip: - // return grossTonnage; - // case ShipType.RoRoPassengerShip_HighSpeedSOLAS: - // return grossTonnage; - // case ShipType.RoRoCruisePassengerShip: - // return grossTonnage; - // default: - // throw new ArgumentException($"Unsupported {nameof(shipType)}: {shipType}"); - // } - //} + public double GetShipCapacity(ShipType shipType, double deadweightTonnage, double grossTonnage) + { + ValidateTonnageParamsSet(shipType, deadweightTonnage, grossTonnage); + + + switch (shipType) + { + case ShipType.BulkCarrier: + return deadweightTonnage >= 279000 ? 279000 : deadweightTonnage; + case ShipType.GasCarrier: + return deadweightTonnage; + case ShipType.Tanker: + return deadweightTonnage; + case ShipType.ContainerShip: + return deadweightTonnage; + case ShipType.GeneralCargoShip: + return deadweightTonnage; + case ShipType.RefrigeratedCargoCarrier: + return deadweightTonnage; + case ShipType.CombinationCarrier: + return deadweightTonnage; + case ShipType.LngCarrier: + return deadweightTonnage < 65000 ? 65000 : deadweightTonnage; + case ShipType.RoRoCargoShipVehicleCarrier: + return deadweightTonnage >= 57700 ? 57700 : grossTonnage; + case ShipType.RoRoCargoShip: + return grossTonnage; + case ShipType.RoRoPassengerShip: + return grossTonnage; + case ShipType.RoRoPassengerShip_HighSpeedSOLAS: + return grossTonnage; + case ShipType.RoRoCruisePassengerShip: + return grossTonnage; + default: + throw new ArgumentException($"Unsupported {nameof(shipType)}: {shipType}"); + } + } /// From e42170eac99a93476ebe93518103d59bf8cfda75 Mon Sep 17 00:00:00 2001 From: LFLaverty Date: Fri, 29 Mar 2024 08:16:58 +0000 Subject: [PATCH 04/10] Adds a service to get ship CII boundaries --- .../RatingBoundariesServiceTests.cs | 77 +++++ .../ShipCapacityTests.cs | 4 +- .../Calculator.cs | 2 +- .../Models/Enums/ShipType.cs | 6 +- .../Models/ShipModels/Ship.cs | 14 +- .../Services/IRatingBoundariesService.cs | 11 + ...rbonIntensityIndicatorCalculatorService.cs | 2 +- .../Services/Impl/RatingBoundariesService.cs | 288 +++++++++++++++++- .../Impl/ShipCapacityCalculatorService.cs | 6 +- 9 files changed, 372 insertions(+), 38 deletions(-) create mode 100644 EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/RatingBoundariesServiceTests.cs create mode 100644 EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/IRatingBoundariesService.cs diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/RatingBoundariesServiceTests.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/RatingBoundariesServiceTests.cs new file mode 100644 index 0000000..0b59b54 --- /dev/null +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/RatingBoundariesServiceTests.cs @@ -0,0 +1,77 @@ +using EtiveMor.OpenImoCiiCalculator.Core.Models.Enums; +using EtiveMor.OpenImoCiiCalculator.Core.Models.ShipModels; +using EtiveMor.OpenImoCiiCalculator.Core.Services.Impl; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EtiveMor.OpenImoCiiCalculator.Core.Tests +{ + [TestClass] + public class RatingBoundariesServiceTests + { + + /// + /// This method tests that ShipType enum values are considered by the + /// GetBoundaries method. If a new value is added to the Enum, this method + /// will fail until the RatingsBoundariesService is updated to handle the new value. + /// + [TestMethod] + public void TestGetBoundariesProcessesAllEnumValues() + { + ShipType[] possibleShipTypeEnums = (ShipType[])Enum.GetValues(typeof(ShipType)); + + for (int i = 0; i < possibleShipTypeEnums.Length; i++) + { + if (possibleShipTypeEnums[i] == ShipType.UNKNOWN) + { + // intentionally ignore the UNKNOWN value + continue; + } + var ship = new Ship(possibleShipTypeEnums[i], 250000, 0); + var service = new RatingBoundariesService(); + var boundaries = service.GetBoundaries(ship, 0.5); + + Assert.IsNotNull(boundaries); + } + } + + + /// + /// Method checks that an exception is thrown when an unknown ShipType + /// is passed to the GetBoundaries method. + /// + [TestMethod] + public void TestGetBoundariesFailsOnUnknownShipType() + { + var ship = new Ship(ShipType.UNKNOWN, 250000, 0); + var service = new RatingBoundariesService(); + + Assert.ThrowsException(() => service.GetBoundaries(ship, 0.5)); + } + + + [DataRow(ShipType.BulkCarrier, CapacityUnit.DWT)] + [DataRow(ShipType.GasCarrier, CapacityUnit.DWT)] + [DataRow(ShipType.Tanker, CapacityUnit.DWT)] + [DataRow(ShipType.ContainerShip, CapacityUnit.DWT)] + [DataRow(ShipType.GeneralCargoShip, CapacityUnit.DWT)] + [DataRow(ShipType.RefrigeratedCargoCarrier, CapacityUnit.DWT)] + [DataRow(ShipType.CombinationCarrier, CapacityUnit.DWT)] + [DataRow(ShipType.LngCarrier, CapacityUnit.DWT)] + [DataRow(ShipType.RoRoCargoShipVehicleCarrier, CapacityUnit.GT)] + [DataRow(ShipType.RoRoPassengerShip, CapacityUnit.GT)] + [DataRow(ShipType.CruisePassengerShip, CapacityUnit.GT)] + [TestMethod] + public void TestGrossTonnageCapacityShipTypesAreHandledCorrectly(ShipType shipType, CapacityUnit expectedCapacityUnit) + { + var ship = new Ship(shipType, 250000, 0); + var service = new RatingBoundariesService(); + var boundaries = service.GetBoundaries(ship, 0.5); + + Assert.AreEqual(expectedCapacityUnit, boundaries.CapacityUnit); + } + } +} diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/ShipCapacityTests.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/ShipCapacityTests.cs index 90e3359..0bdbec6 100644 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/ShipCapacityTests.cs +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/ShipCapacityTests.cs @@ -53,7 +53,7 @@ public void TestCalculateCapacity_BulkCarrier() [DataRow(ShipType.LngCarrier, 0, 100000)] [DataRow(ShipType.RoRoCargoShipVehicleCarrier, 250000, 0)] [DataRow(ShipType.RoRoPassengerShip, 250000, 0)] - [DataRow(ShipType.RoRoCruisePassengerShip, 250000, 0)] + [DataRow(ShipType.CruisePassengerShip, 250000, 0)] public void TestValidateTonnageParamsSet_ArgumentOutOfRangeException(ShipType shipType, double deadweightTonnage, double grossTonnage) { var ship = new Ship( @@ -86,7 +86,7 @@ public void TestValidateTonnageParamsSet_ArgumentOutOfRangeException(ShipType sh [DataRow(ShipType.RoRoPassengerShip_HighSpeedSOLAS, 0, 250000, 250000)] [DataRow(ShipType.RoRoCargoShipVehicleCarrier, 0, 100000, 100000)] [DataRow(ShipType.RoRoPassengerShip, 0, 100000, 100000)] - [DataRow(ShipType.RoRoCruisePassengerShip, 0, 100000, 100000)] + [DataRow(ShipType.CruisePassengerShip, 0, 100000, 100000)] public void TestCalculateCapacity(ShipType shipType, double deadweightTonnage, double grossTonnage, double expectedCapacity) { var ship = new Ship( diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Calculator.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Calculator.cs index e45a3ad..f6156ce 100644 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Calculator.cs +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Calculator.cs @@ -68,7 +68,7 @@ public CalculationResult CalculateAttainedCiiRating( private ImoCiiRating GetImoCiiRatingInYear(double attainedCiiInYear, double requiredCiiInYear, int year) { - var gradeLowerBoundaries = GetBoundaries(ShipType.RoRoCruisePassengerShip, requiredCiiInYear); + var gradeLowerBoundaries = GetBoundaries(ShipType.CruisePassengerShip, requiredCiiInYear); if (attainedCiiInYear < gradeLowerBoundaries[ImoCiiBoundary.Superior]) { diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/Enums/ShipType.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/Enums/ShipType.cs index 8e71295..a60eddf 100644 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/Enums/ShipType.cs +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/Enums/ShipType.cs @@ -75,13 +75,13 @@ public enum ShipType /// A type of ship designed to carry both wheeled cargo and passengers, with /// built-in ramps for loading and unloading vehicles. /// - /// + /// RoRoPassengerShip = 110, /// /// A type of high-speed ship designed to conform to SOLAS Chapter X standards /// - /// + /// RoRoPassengerShip_HighSpeedSOLAS = 111, /// @@ -90,7 +90,7 @@ public enum ShipType /// venues, and recreational facilities. /// /// - RoRoCruisePassengerShip = 120 + CruisePassengerShip = 120 } } diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/ShipModels/Ship.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/ShipModels/Ship.cs index db77885..f5655e5 100644 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/ShipModels/Ship.cs +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/ShipModels/Ship.cs @@ -15,11 +15,6 @@ double grossTonnage ShipType = shipType; DeadweightTonnage = deadweightTonnage; GrossTonnage = grossTonnage; - - if (shipType == ShipType.UNKNOWN) - { - throw new System.InvalidOperationException("ShipType must be set"); - } } @@ -60,14 +55,7 @@ double grossTonnage } - public enum CapacityUnit - { - ERR, - DWT, - DWT_CAP_HIGH, - GT, - GT_CAP_LOW - } + diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/IRatingBoundariesService.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/IRatingBoundariesService.cs new file mode 100644 index 0000000..0e885c1 --- /dev/null +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/IRatingBoundariesService.cs @@ -0,0 +1,11 @@ +using EtiveMor.OpenImoCiiCalculator.Core.Models.ShipModels; +using EtiveMor.OpenImoCiiCalculator.Core.Services.Impl; + +namespace EtiveMor.OpenImoCiiCalculator.Core.Services +{ + public interface IRatingBoundariesService + { + DdVectorDataTableRow GetBoundaries(Ship ship, double requiredCiiInYear); + + } +} \ No newline at end of file diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/CarbonIntensityIndicatorCalculatorService.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/CarbonIntensityIndicatorCalculatorService.cs index 940424e..d64171d 100644 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/CarbonIntensityIndicatorCalculatorService.cs +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/CarbonIntensityIndicatorCalculatorService.cs @@ -121,7 +121,7 @@ private double GetValue(ValType valType, ShipType shipType, double capacity) ShipType.RoRoCargoShip => GetRoRoCargoShipValue(valType, capacity), ShipType.RoRoPassengerShip => GetRoRoPassengerShipValue(valType, capacity), ShipType.RoRoPassengerShip_HighSpeedSOLAS => GetRoRoPassengerShip_HighSpeedSOLASValue(valType, capacity), - ShipType.RoRoCruisePassengerShip => GetRoRoCruisePassengerShipValue(valType, capacity), + ShipType.CruisePassengerShip => GetRoRoCruisePassengerShipValue(valType, capacity), ShipType.UNKNOWN => throw new NotSupportedException($"Unsupported {nameof(shipType)} '{shipType}'"), _ => throw new NotSupportedException($"Unsupported {nameof(shipType)} '{shipType}'") }; diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/RatingBoundariesService.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/RatingBoundariesService.cs index 1b45329..a3bb738 100644 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/RatingBoundariesService.cs +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/RatingBoundariesService.cs @@ -1,30 +1,288 @@ using EtiveMor.OpenImoCiiCalculator.Core.Models.Enums; +using EtiveMor.OpenImoCiiCalculator.Core.Models.ShipModels; namespace EtiveMor.OpenImoCiiCalculator.Core.Services.Impl { - public interface IRatingBoundariesService - { - Dictionary GetBoundaries(ShipType shipType, double requiredCiiInYear); - - } public class RatingBoundariesService : IRatingBoundariesService { - public Dictionary GetBoundaries(ShipType shipType, double requiredCiiInYear) + /// + /// Returns the boundaries for the given ship and required CII in the given year. + /// + /// + /// + /// + /// + public DdVectorDataTableRow GetBoundaries(Ship ship, double requiredCiiInYear) { - switch (shipType) + switch (ship.ShipType) { + case ShipType.BulkCarrier: + { + return new DdVectorDataTableRow( + ship.ShipType, + new WeightClassification(0, int.MaxValue), + CapacityUnit.DWT, + new Dictionary + { + { ImoCiiBoundary.Superior, 0.86 * requiredCiiInYear }, + { ImoCiiBoundary.Lower, 0.94 * requiredCiiInYear }, + { ImoCiiBoundary.Upper, 1.06 * requiredCiiInYear }, + { ImoCiiBoundary.Inferior, 1.18 * requiredCiiInYear } + }); + } + case ShipType.GasCarrier: + { + if (ship.DeadweightTonnage >= 65000) + { + return new DdVectorDataTableRow( + ship.ShipType, + new WeightClassification(65000, int.MaxValue), + CapacityUnit.DWT, + new Dictionary + { + { ImoCiiBoundary.Superior, 0.81 * requiredCiiInYear }, + { ImoCiiBoundary.Lower, 0.91 * requiredCiiInYear }, + { ImoCiiBoundary.Upper, 1.12 * requiredCiiInYear }, + { ImoCiiBoundary.Inferior, 1.44 * requiredCiiInYear } + }); + } + else + { + return new DdVectorDataTableRow( + ship.ShipType, + new WeightClassification(0, 65000 - 1), + CapacityUnit.DWT, + new Dictionary + { + { ImoCiiBoundary.Superior, 0.85 * requiredCiiInYear }, + { ImoCiiBoundary.Lower, 0.95 * requiredCiiInYear }, + { ImoCiiBoundary.Upper, 1.06 * requiredCiiInYear }, + { ImoCiiBoundary.Inferior, 1.25 * requiredCiiInYear } + }); + } + } + case ShipType.Tanker: + { + return new DdVectorDataTableRow( + ship.ShipType, + new WeightClassification(0, int.MaxValue), + CapacityUnit.DWT, + new Dictionary + { + { ImoCiiBoundary.Superior, 0.82 * requiredCiiInYear }, + { ImoCiiBoundary.Lower, 0.93 * requiredCiiInYear }, + { ImoCiiBoundary.Upper, 1.08 * requiredCiiInYear }, + { ImoCiiBoundary.Inferior, 1.28 * requiredCiiInYear } + }); + } + case ShipType.ContainerShip: + { + return new DdVectorDataTableRow( + ship.ShipType, + new WeightClassification(0, int.MaxValue), + CapacityUnit.DWT, + new Dictionary + { + { ImoCiiBoundary.Superior, 0.83 * requiredCiiInYear }, + { ImoCiiBoundary.Lower, 0.94 * requiredCiiInYear }, + { ImoCiiBoundary.Upper, 1.07 * requiredCiiInYear }, + { ImoCiiBoundary.Inferior, 1.19 * requiredCiiInYear } + }); + } + case ShipType.GeneralCargoShip: + { + return new DdVectorDataTableRow( + ship.ShipType, + new WeightClassification(0, int.MaxValue), + CapacityUnit.DWT, + new Dictionary + { + { ImoCiiBoundary.Superior, 0.83 * requiredCiiInYear }, + { ImoCiiBoundary.Lower, 0.94 * requiredCiiInYear }, + { ImoCiiBoundary.Upper, 1.06 * requiredCiiInYear }, + { ImoCiiBoundary.Inferior, 1.19 * requiredCiiInYear } + }); + } + case ShipType.RefrigeratedCargoCarrier: + { + return new DdVectorDataTableRow( + ship.ShipType, + new WeightClassification(0, int.MaxValue), + CapacityUnit.DWT, + new Dictionary + { + { ImoCiiBoundary.Superior, 0.78 * requiredCiiInYear }, + { ImoCiiBoundary.Lower, 0.91 * requiredCiiInYear }, + { ImoCiiBoundary.Upper, 1.07 * requiredCiiInYear }, + { ImoCiiBoundary.Inferior, 1.20 * requiredCiiInYear } + }); + } + case ShipType.CombinationCarrier: + { + return new DdVectorDataTableRow( + ship.ShipType, + new WeightClassification(0, int.MaxValue), + CapacityUnit.DWT, + new Dictionary + { + { ImoCiiBoundary.Superior, 0.87 * requiredCiiInYear }, + { ImoCiiBoundary.Lower, 0.96 * requiredCiiInYear }, + { ImoCiiBoundary.Upper, 1.06 * requiredCiiInYear }, + { ImoCiiBoundary.Inferior, 1.14 * requiredCiiInYear } + }); + } + case ShipType.LngCarrier: + { + if (ship.DeadweightTonnage >= 100000) + { + return new DdVectorDataTableRow( + ship.ShipType, + new WeightClassification(100000, int.MaxValue), + CapacityUnit.DWT, + new Dictionary + { + { ImoCiiBoundary.Superior, 0.89 * requiredCiiInYear }, + { ImoCiiBoundary.Lower, 0.98 * requiredCiiInYear }, + { ImoCiiBoundary.Upper, 1.06 * requiredCiiInYear }, + { ImoCiiBoundary.Inferior, 1.13 * requiredCiiInYear } + }); + } + else + { + return new DdVectorDataTableRow( + ship.ShipType, + new WeightClassification(0, 100000 - 1), + CapacityUnit.DWT, + new Dictionary + { + { ImoCiiBoundary.Superior, 0.78 * requiredCiiInYear }, + { ImoCiiBoundary.Lower, 0.92 * requiredCiiInYear }, + { ImoCiiBoundary.Upper, 1.10 * requiredCiiInYear }, + { ImoCiiBoundary.Inferior, 1.37 * requiredCiiInYear } + }); + } + } + case ShipType.RoRoCargoShipVehicleCarrier: + { + return new DdVectorDataTableRow( + ship.ShipType, + new WeightClassification(0, int.MaxValue), + CapacityUnit.GT, + new Dictionary + { + { ImoCiiBoundary.Superior, 0.86 * requiredCiiInYear }, + { ImoCiiBoundary.Lower, 0.94 * requiredCiiInYear }, + { ImoCiiBoundary.Upper, 1.06 * requiredCiiInYear }, + { ImoCiiBoundary.Inferior, 1.16 * requiredCiiInYear } + }); + } + case ShipType.RoRoCargoShip: + { + return new DdVectorDataTableRow( + ship.ShipType, + new WeightClassification(0, int.MaxValue), + CapacityUnit.GT, + new Dictionary + { + { ImoCiiBoundary.Superior, 0.76 * requiredCiiInYear }, + { ImoCiiBoundary.Lower, 0.89 * requiredCiiInYear }, + { ImoCiiBoundary.Upper, 1.08 * requiredCiiInYear }, + { ImoCiiBoundary.Inferior, 1.27 * requiredCiiInYear } + }); + } case ShipType.RoRoPassengerShip: { - return new Dictionary { - { ImoCiiBoundary.Superior, 0.76 * requiredCiiInYear }, - { ImoCiiBoundary.Lower, 0.92 * requiredCiiInYear}, - { ImoCiiBoundary.Upper, 1.14 * requiredCiiInYear}, - { ImoCiiBoundary.Inferior, 1.30 * requiredCiiInYear} - }; + return new DdVectorDataTableRow( + ship.ShipType, + new WeightClassification(0, int.MaxValue), + CapacityUnit.GT, + new Dictionary + { + { ImoCiiBoundary.Superior, 0.76 * requiredCiiInYear }, + { ImoCiiBoundary.Lower, 0.92 * requiredCiiInYear }, + { ImoCiiBoundary.Upper, 1.14 * requiredCiiInYear }, + { ImoCiiBoundary.Inferior, 1.30 * requiredCiiInYear } + }); } + case ShipType.RoRoPassengerShip_HighSpeedSOLAS: + { + return new DdVectorDataTableRow( + ship.ShipType, + new WeightClassification(0, int.MaxValue), + CapacityUnit.GT, + new Dictionary + { + { ImoCiiBoundary.Superior, 0.76 * requiredCiiInYear }, + { ImoCiiBoundary.Lower, 0.92 * requiredCiiInYear }, + { ImoCiiBoundary.Upper, 1.14 * requiredCiiInYear }, + { ImoCiiBoundary.Inferior, 1.30 * requiredCiiInYear } + }); + } + case ShipType.CruisePassengerShip: + { + return new DdVectorDataTableRow( + ship.ShipType, + new WeightClassification(0, int.MaxValue), + CapacityUnit.GT, + new Dictionary + { + { ImoCiiBoundary.Superior, 0.87 * requiredCiiInYear }, + { ImoCiiBoundary.Lower, 0.95 * requiredCiiInYear }, + { ImoCiiBoundary.Upper, 1.06 * requiredCiiInYear }, + { ImoCiiBoundary.Inferior, 1.16 * requiredCiiInYear } + }); + } + + case ShipType.UNKNOWN: + throw new NotSupportedException($"Ship type '{ship.ShipType}' not supported"); default: - throw new NotSupportedException($"Ship type '{shipType}' not supported"); + throw new NotSupportedException($"Ship type '{ship.ShipType}' not supported"); } } } -} + + + + + public class DdVectorDataTableRow + { + public DdVectorDataTableRow( + ShipType shipType, + WeightClassification weightClassification, + CapacityUnit capacityUnit, + Dictionary boundaryDdVectors2019) + { + ShipType = shipType; + WeightClassification = weightClassification; + CapacityUnit = capacityUnit; + BoundaryDdVectors2019 = boundaryDdVectors2019; + } + + public ShipType ShipType { get; set; } + public WeightClassification WeightClassification { get; set; } + public CapacityUnit CapacityUnit { get; set; } + + public Dictionary BoundaryDdVectors2019 { get; set; } + } + + public class WeightClassification + { + public WeightClassification(int upperLimit, int lowerLimit) + { + UpperLimit = upperLimit; + LowerLimit = lowerLimit; + } + + public int UpperLimit { get; private set; } + public int LowerLimit { get; private set; } + } + + public enum CapacityUnit + { + ERR, + DWT, + DWT_CAP_HIGH, + GT, + GT_CAP_LOW + } +} \ No newline at end of file diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/ShipCapacityCalculatorService.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/ShipCapacityCalculatorService.cs index fdf42ab..b11e409 100644 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/ShipCapacityCalculatorService.cs +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/ShipCapacityCalculatorService.cs @@ -71,7 +71,7 @@ public double GetShipCapacity(ShipType shipType, double deadweightTonnage, doubl return grossTonnage; case ShipType.RoRoPassengerShip_HighSpeedSOLAS: return grossTonnage; - case ShipType.RoRoCruisePassengerShip: + case ShipType.CruisePassengerShip: return grossTonnage; default: throw new ArgumentException($"Unsupported {nameof(shipType)}: {shipType}"); @@ -102,7 +102,7 @@ public double GetShipCapacity(ShipType shipType, double deadweightTonnage, doubl /// Required to be above 0 for ship types: /// - /// - - /// - + /// - /// /// /// Thrown if the weight value is equal or lower than 0 if it is required to be above 0 @@ -158,7 +158,7 @@ private void ValidateTonnageParamsSet(ShipType shipType, double deadweightTonnag ? grossTonnage : throw new InvalidOperationException(), - ShipType.RoRoCruisePassengerShip => ValidateTonnage(grossTonnage, nameof(grossTonnage), shipType) + ShipType.CruisePassengerShip => ValidateTonnage(grossTonnage, nameof(grossTonnage), shipType) ? grossTonnage : throw new InvalidOperationException(), From 0d55ad30a6daaee4762a29bb8c9280bd36c4509f Mon Sep 17 00:00:00 2001 From: LFLaverty Date: Fri, 29 Mar 2024 08:18:11 +0000 Subject: [PATCH 05/10] renames ddVectorDataTableRow to ShipDdVectorBoundaries --- .../Services/IRatingBoundariesService.cs | 2 +- .../Services/Impl/RatingBoundariesService.cs | 36 +++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/IRatingBoundariesService.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/IRatingBoundariesService.cs index 0e885c1..496e5ac 100644 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/IRatingBoundariesService.cs +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/IRatingBoundariesService.cs @@ -5,7 +5,7 @@ namespace EtiveMor.OpenImoCiiCalculator.Core.Services { public interface IRatingBoundariesService { - DdVectorDataTableRow GetBoundaries(Ship ship, double requiredCiiInYear); + ShipDdVectorBoundaries GetBoundaries(Ship ship, double requiredCiiInYear); } } \ No newline at end of file diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/RatingBoundariesService.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/RatingBoundariesService.cs index a3bb738..6ca6559 100644 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/RatingBoundariesService.cs +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/RatingBoundariesService.cs @@ -12,13 +12,13 @@ public class RatingBoundariesService : IRatingBoundariesService /// /// /// - public DdVectorDataTableRow GetBoundaries(Ship ship, double requiredCiiInYear) + public ShipDdVectorBoundaries GetBoundaries(Ship ship, double requiredCiiInYear) { switch (ship.ShipType) { case ShipType.BulkCarrier: { - return new DdVectorDataTableRow( + return new ShipDdVectorBoundaries( ship.ShipType, new WeightClassification(0, int.MaxValue), CapacityUnit.DWT, @@ -34,7 +34,7 @@ public DdVectorDataTableRow GetBoundaries(Ship ship, double requiredCiiInYear) { if (ship.DeadweightTonnage >= 65000) { - return new DdVectorDataTableRow( + return new ShipDdVectorBoundaries( ship.ShipType, new WeightClassification(65000, int.MaxValue), CapacityUnit.DWT, @@ -48,7 +48,7 @@ public DdVectorDataTableRow GetBoundaries(Ship ship, double requiredCiiInYear) } else { - return new DdVectorDataTableRow( + return new ShipDdVectorBoundaries( ship.ShipType, new WeightClassification(0, 65000 - 1), CapacityUnit.DWT, @@ -63,7 +63,7 @@ public DdVectorDataTableRow GetBoundaries(Ship ship, double requiredCiiInYear) } case ShipType.Tanker: { - return new DdVectorDataTableRow( + return new ShipDdVectorBoundaries( ship.ShipType, new WeightClassification(0, int.MaxValue), CapacityUnit.DWT, @@ -77,7 +77,7 @@ public DdVectorDataTableRow GetBoundaries(Ship ship, double requiredCiiInYear) } case ShipType.ContainerShip: { - return new DdVectorDataTableRow( + return new ShipDdVectorBoundaries( ship.ShipType, new WeightClassification(0, int.MaxValue), CapacityUnit.DWT, @@ -91,7 +91,7 @@ public DdVectorDataTableRow GetBoundaries(Ship ship, double requiredCiiInYear) } case ShipType.GeneralCargoShip: { - return new DdVectorDataTableRow( + return new ShipDdVectorBoundaries( ship.ShipType, new WeightClassification(0, int.MaxValue), CapacityUnit.DWT, @@ -105,7 +105,7 @@ public DdVectorDataTableRow GetBoundaries(Ship ship, double requiredCiiInYear) } case ShipType.RefrigeratedCargoCarrier: { - return new DdVectorDataTableRow( + return new ShipDdVectorBoundaries( ship.ShipType, new WeightClassification(0, int.MaxValue), CapacityUnit.DWT, @@ -119,7 +119,7 @@ public DdVectorDataTableRow GetBoundaries(Ship ship, double requiredCiiInYear) } case ShipType.CombinationCarrier: { - return new DdVectorDataTableRow( + return new ShipDdVectorBoundaries( ship.ShipType, new WeightClassification(0, int.MaxValue), CapacityUnit.DWT, @@ -135,7 +135,7 @@ public DdVectorDataTableRow GetBoundaries(Ship ship, double requiredCiiInYear) { if (ship.DeadweightTonnage >= 100000) { - return new DdVectorDataTableRow( + return new ShipDdVectorBoundaries( ship.ShipType, new WeightClassification(100000, int.MaxValue), CapacityUnit.DWT, @@ -149,7 +149,7 @@ public DdVectorDataTableRow GetBoundaries(Ship ship, double requiredCiiInYear) } else { - return new DdVectorDataTableRow( + return new ShipDdVectorBoundaries( ship.ShipType, new WeightClassification(0, 100000 - 1), CapacityUnit.DWT, @@ -164,7 +164,7 @@ public DdVectorDataTableRow GetBoundaries(Ship ship, double requiredCiiInYear) } case ShipType.RoRoCargoShipVehicleCarrier: { - return new DdVectorDataTableRow( + return new ShipDdVectorBoundaries( ship.ShipType, new WeightClassification(0, int.MaxValue), CapacityUnit.GT, @@ -178,7 +178,7 @@ public DdVectorDataTableRow GetBoundaries(Ship ship, double requiredCiiInYear) } case ShipType.RoRoCargoShip: { - return new DdVectorDataTableRow( + return new ShipDdVectorBoundaries( ship.ShipType, new WeightClassification(0, int.MaxValue), CapacityUnit.GT, @@ -192,7 +192,7 @@ public DdVectorDataTableRow GetBoundaries(Ship ship, double requiredCiiInYear) } case ShipType.RoRoPassengerShip: { - return new DdVectorDataTableRow( + return new ShipDdVectorBoundaries( ship.ShipType, new WeightClassification(0, int.MaxValue), CapacityUnit.GT, @@ -206,7 +206,7 @@ public DdVectorDataTableRow GetBoundaries(Ship ship, double requiredCiiInYear) } case ShipType.RoRoPassengerShip_HighSpeedSOLAS: { - return new DdVectorDataTableRow( + return new ShipDdVectorBoundaries( ship.ShipType, new WeightClassification(0, int.MaxValue), CapacityUnit.GT, @@ -220,7 +220,7 @@ public DdVectorDataTableRow GetBoundaries(Ship ship, double requiredCiiInYear) } case ShipType.CruisePassengerShip: { - return new DdVectorDataTableRow( + return new ShipDdVectorBoundaries( ship.ShipType, new WeightClassification(0, int.MaxValue), CapacityUnit.GT, @@ -244,9 +244,9 @@ public DdVectorDataTableRow GetBoundaries(Ship ship, double requiredCiiInYear) - public class DdVectorDataTableRow + public class ShipDdVectorBoundaries { - public DdVectorDataTableRow( + public ShipDdVectorBoundaries( ShipType shipType, WeightClassification weightClassification, CapacityUnit capacityUnit, From 66a79318b137c5d7f12c47d60c8adbaf8e1b0844 Mon Sep 17 00:00:00 2001 From: LFLaverty Date: Fri, 29 Mar 2024 08:20:26 +0000 Subject: [PATCH 06/10] updates the ShipCapacity interface to include both methods to get a ship's capacity --- .../EtiveMor.OpenImoCiiCalculator.Core/Calculator.cs | 2 +- .../Services/IShipCapacityCalculatorService.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Calculator.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Calculator.cs index f6156ce..3ae83c7 100644 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Calculator.cs +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Calculator.cs @@ -42,7 +42,7 @@ public CalculationResult CalculateAttainedCiiRating( int targetYear) { var shipCo2Emissions = _shipMassOfCo2EmissionsService.GetMassOfCo2Emissions(fuelType, fuelConsumption); - var shipCapacity = 0; // _shipCapacityService.GetShipCapacity(shipType, deadweightTonnage, grossTonnage); + var shipCapacity = _shipCapacityService.GetShipCapacity(shipType, deadweightTonnage, grossTonnage); var transportWork = _shipTransportWorkService.GetShipTransportWork(shipCapacity, distanceTravelled); List results = new List(); diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/IShipCapacityCalculatorService.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/IShipCapacityCalculatorService.cs index 09c1ea2..63401da 100644 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/IShipCapacityCalculatorService.cs +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/IShipCapacityCalculatorService.cs @@ -6,6 +6,6 @@ namespace EtiveMor.OpenImoCiiCalculator.Core.Services public interface IShipCapacityCalculatorService { double GetShipCapacity(Ship ship); - // double GetShipCapacity(ShipType shipType, double deadweightTonnage, double grossTonnage); + double GetShipCapacity(ShipType shipType, double deadweightTonnage, double grossTonnage); } } \ No newline at end of file From 25561b1f5f236dfc17eb7a9e3f2d037472d19c36 Mon Sep 17 00:00:00 2001 From: LFLaverty Date: Fri, 29 Mar 2024 09:10:10 +0000 Subject: [PATCH 07/10] Moves models into their correct locations, adds extensive unit tests for the RatingBoundariesService --- .../RatingBoundariesServiceTests.cs | 94 ++++- .../ReductionFactorTests.cs | 380 ++++++++++++++++++ .../Calculator.cs | 3 +- .../Models/Enums/CapacityUnit.cs | 11 + .../ShipDdVectorBoundaries.cs | 37 ++ .../Services/IRatingBoundariesService.cs | 4 +- .../Services/Impl/RatingBoundariesService.cs | 90 +++-- 7 files changed, 568 insertions(+), 51 deletions(-) create mode 100644 EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/ReductionFactorTests.cs create mode 100644 EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/Enums/CapacityUnit.cs create mode 100644 EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/MeasurementModels/ShipDdVectorBoundaries.cs diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/RatingBoundariesServiceTests.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/RatingBoundariesServiceTests.cs index 0b59b54..e24c827 100644 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/RatingBoundariesServiceTests.cs +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/RatingBoundariesServiceTests.cs @@ -1,11 +1,6 @@ using EtiveMor.OpenImoCiiCalculator.Core.Models.Enums; using EtiveMor.OpenImoCiiCalculator.Core.Models.ShipModels; using EtiveMor.OpenImoCiiCalculator.Core.Services.Impl; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace EtiveMor.OpenImoCiiCalculator.Core.Tests { @@ -13,6 +8,50 @@ namespace EtiveMor.OpenImoCiiCalculator.Core.Tests public class RatingBoundariesServiceTests { + [DataRow(ShipType.GasCarrier, 65000)] + [DataRow(ShipType.LngCarrier, 100000)] + /// + /// Tests that a ship with a weight capacity difference in MEPC354(78) has different boundaries + /// returned before and after the boundary. + /// + /// For exmaple, that GasCarrirs with a deadweight tonnage of 65000 or more + /// have different boundaries to GasCarriers with a deadweight tonnage of 64999 or less. + /// + [TestMethod] + public void TestShipWithCapacityDifferencesHasDifferentBoundariesReturnedBeforeAndAfterBoundary( + ShipType shipType, double boundaryTonnage + ) + { + var smallShip = new Ship(shipType, boundaryTonnage -1, 0); + var largeShip = new Ship(shipType, boundaryTonnage, 0); + + var service = new RatingBoundariesService(); + + var smallBoundaries = service.GetBoundaries(smallShip, 0.5); + var largeBoundaries = service.GetBoundaries(largeShip, 0.5); + + Assert.AreNotEqual(smallBoundaries.BoundaryDdVectors2019, largeBoundaries.BoundaryDdVectors2019); + + Assert.AreNotEqual( + smallBoundaries.BoundaryDdVectors2019[ImoCiiBoundary.Inferior], + largeBoundaries.BoundaryDdVectors2019[ImoCiiBoundary.Inferior]); + + + Assert.AreNotEqual( + smallBoundaries.BoundaryDdVectors2019[ImoCiiBoundary.Upper], + largeBoundaries.BoundaryDdVectors2019[ImoCiiBoundary.Upper]); + + Assert.AreNotEqual( + smallBoundaries.BoundaryDdVectors2019[ImoCiiBoundary.Lower], + largeBoundaries.BoundaryDdVectors2019[ImoCiiBoundary.Lower]); + + Assert.AreNotEqual( + smallBoundaries.BoundaryDdVectors2019[ImoCiiBoundary.Superior], + largeBoundaries.BoundaryDdVectors2019[ImoCiiBoundary.Superior]); + } + + + /// /// This method tests that ShipType enum values are considered by the /// GetBoundaries method. If a new value is added to the Enum, this method @@ -30,7 +69,7 @@ public void TestGetBoundariesProcessesAllEnumValues() // intentionally ignore the UNKNOWN value continue; } - var ship = new Ship(possibleShipTypeEnums[i], 250000, 0); + var ship = new Ship(possibleShipTypeEnums[i], 250000, 250000); var service = new RatingBoundariesService(); var boundaries = service.GetBoundaries(ship, 0.5); @@ -53,6 +92,11 @@ public void TestGetBoundariesFailsOnUnknownShipType() } + /// + /// This method tests that the CapacityUnit is correctly set for each ShipType. + /// + /// + /// [DataRow(ShipType.BulkCarrier, CapacityUnit.DWT)] [DataRow(ShipType.GasCarrier, CapacityUnit.DWT)] [DataRow(ShipType.Tanker, CapacityUnit.DWT)] @@ -67,11 +111,47 @@ public void TestGetBoundariesFailsOnUnknownShipType() [TestMethod] public void TestGrossTonnageCapacityShipTypesAreHandledCorrectly(ShipType shipType, CapacityUnit expectedCapacityUnit) { - var ship = new Ship(shipType, 250000, 0); + + var ship = new Ship(shipType, + expectedCapacityUnit == CapacityUnit.DWT ? 250000 : 0, + expectedCapacityUnit == CapacityUnit.GT ? 250000 : 0); var service = new RatingBoundariesService(); var boundaries = service.GetBoundaries(ship, 0.5); Assert.AreEqual(expectedCapacityUnit, boundaries.CapacityUnit); } + + + + /// + /// this method checks that RatingBoundariesService verifies the ship's tonnage is + /// correctly set + /// + /// It ensures that DeadweightTonnage is set for all ship types with a DWT Capacity type, and + /// gross tonnage is set for all ship types with a GT Capacity type + /// + /// + /// + /// + [TestMethod] + [DataRow(ShipType.BulkCarrier, 1, 0)] + [DataRow(ShipType.GasCarrier, 2, 0)] + [DataRow(ShipType.Tanker, 3, 0)] + [DataRow(ShipType.ContainerShip, 4, 0)] + [DataRow(ShipType.GeneralCargoShip, 5, 0)] + [DataRow(ShipType.RefrigeratedCargoCarrier, 6, 0)] + [DataRow(ShipType.CombinationCarrier, 7, 0)] + [DataRow(ShipType.LngCarrier, 8, 0)] + [DataRow(ShipType.RoRoCargoShipVehicleCarrier, 0, 1)] + [DataRow(ShipType.RoRoCargoShip, 0, 2)] + [DataRow(ShipType.RoRoPassengerShip, 0, 3)] + [DataRow(ShipType.RoRoPassengerShip_HighSpeedSOLAS, 0, 4)] + [DataRow(ShipType.CruisePassengerShip, 0, 5)] + public void TestValidateShipTonnageValid(ShipType shipType, int deadweightTonnage, int grossTonnage) + { + var ship = new Ship(shipType, deadweightTonnage, grossTonnage); + var service = new RatingBoundariesService(); + var boundaries = service.GetBoundaries(ship, 0.5); + } } } diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/ReductionFactorTests.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/ReductionFactorTests.cs new file mode 100644 index 0000000..bc1fa97 --- /dev/null +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/ReductionFactorTests.cs @@ -0,0 +1,380 @@ +using EtiveMor.OpenImoCiiCalculator.Core.Extensions; + +namespace EtiveMor.OpenImoCiiCalculator.Core.Tests +{ + [TestClass] + public class ReductionFactorTests + { + + [TestMethod] + public void GetAnnualReductionFactor_2019_ReturnsZero() + { + // Arrange + int year = 2019; + double expected = 0.00; + + // Act + double result = year.GetAnnualReductionFactor(); + + // Assert + Assert.AreEqual(expected, result); + } + + [TestMethod] + public void GetAnnualReductionFactor_2020_ReturnsOnePercent() + { + // Arrange + int year = 2020; + double expected = 0.01; + + // Act + double result = year.GetAnnualReductionFactor(); + + // Assert + Assert.AreEqual(expected, result); + } + + [TestMethod] + public void GetAnnualReductionFactor_2021_ReturnsTwoPercent() + { + // Arrange + int year = 2021; + double expected = 0.02; + + // Act + double result = year.GetAnnualReductionFactor(); + + // Assert + Assert.AreEqual(expected, result); + } + + [TestMethod] + public void GetAnnualReductionFactor_2022_ReturnsThreePercent() + { + // Arrange + int year = 2022; + double expected = 0.03; + + // Act + double result = year.GetAnnualReductionFactor(); + + // Assert + Assert.AreEqual(expected, result); + } + + [TestMethod] + public void GetAnnualReductionFactor_2023_ReturnsFivePercent() + { + // Arrange + int year = 2023; + double expected = 0.05; + + // Act + double result = year.GetAnnualReductionFactor(); + + // Assert + Assert.AreEqual(expected, result); + } + + [TestMethod] + public void GetAnnualReductionFactor_2024_ReturnsSevenPercent() + { + // Arrange + int year = 2024; + double expected = 0.07; + + // Act + double result = year.GetAnnualReductionFactor(); + + // Assert + Assert.AreEqual(expected, result); + } + + [TestMethod] + public void GetAnnualReductionFactor_2025_ReturnsNinePercent() + { + // Arrange + int year = 2025; + double expected = 0.09; + + // Act + double result = year.GetAnnualReductionFactor(); + + // Assert + Assert.AreEqual(expected, result); + } + + [TestMethod] + public void GetAnnualReductionFactor_2026_ReturnsElevenPercent() + { + // Arrange + int year = 2026; + double expected = 0.11; + + // Act + double result = year.GetAnnualReductionFactor(); + + // Assert + Assert.AreEqual(expected, result); + } + + [TestMethod] + public void GetAnnualReductionFactor_2027_ReturnsThirteenPercent() + { + // Arrange + int year = 2027; + double expected = 0.13; + + // Act + double result = year.GetAnnualReductionFactor(); + + // Assert + Assert.AreEqual(expected, result); + } + + [TestMethod] + public void GetAnnualReductionFactor_2028_ReturnsFifteenPercent() + { + // Arrange + int year = 2028; + double expected = 0.15; + + // Act + double result = year.GetAnnualReductionFactor(); + + // Assert + Assert.AreEqual(expected, result); + } + + [TestMethod] + public void GetAnnualReductionFactor_2029_ReturnsSeventeenPercent() + { + // Arrange + int year = 2029; + double expected = 0.17; + + // Act + double result = year.GetAnnualReductionFactor(); + + // Assert + Assert.AreEqual(expected, result); + } + + [TestMethod] + public void GetAnnualReductionFactor_2030_ReturnsNineteenPercent() + { + // Arrange + int year = 2030; + double expected = 0.19; + + // Act + double result = year.GetAnnualReductionFactor(); + + // Assert + Assert.AreEqual(expected, result); + } + + [TestMethod] + [ExpectedException(typeof(NotSupportedException))] + public void GetAnnualReductionFactor_UnsupportedYear_ThrowsException() + { + // Arrange + int year = 2018; + + // Act & Assert + year.GetAnnualReductionFactor(); + } + + + [TestMethod] + public void ApplyAnnualReductionFactor_2018_ThrowsException() + { + // Arrange + double value = 100.0; + int year = 2018; + + // Act & Assert + Assert.ThrowsException(() => value.ApplyAnnualReductionFactor(year)); + } + + [TestMethod] + public void ApplyAnnualReductionFactor_2019_ReturnsOriginalValue() + { + // Arrange + double value = 100.0; + int year = 2019; + double expected = 100.0; + + // Act + double result = value.ApplyAnnualReductionFactor(year); + + // Assert + Assert.AreEqual(expected, result); + } + + [TestMethod] + public void ApplyAnnualReductionFactor_2020_Returns99PercentOfOriginalValue() + { + // Arrange + double value = 100.0; + int year = 2020; + double expected = 99.0; + + // Act + double result = value.ApplyAnnualReductionFactor(year); + + // Assert + Assert.AreEqual(expected, result, 0.01); + } + + [TestMethod] + public void ApplyAnnualReductionFactor_2021_Returns98PercentOfOriginalValue() + { + // Arrange + double value = 100.0; + int year = 2021; + double expected = 98.0; + + // Act + double result = value.ApplyAnnualReductionFactor(year); + + // Assert + Assert.AreEqual(expected, result, 0.01); + } + + [TestMethod] + public void ApplyAnnualReductionFactor_2022_Returns97PercentOfOriginalValue() + { + // Arrange + double value = 100.0; + int year = 2022; + double expected = 97.0; + + // Act + double result = value.ApplyAnnualReductionFactor(year); + + // Assert + Assert.AreEqual(expected, result, 0.01); + } + + [TestMethod] + public void ApplyAnnualReductionFactor_2023_Returns95PercentOfOriginalValue() + { + // Arrange + double value = 100.0; + int year = 2023; + double expected = 95.0; + + // Act + double result = value.ApplyAnnualReductionFactor(year); + + // Assert + Assert.AreEqual(expected, result, 0.01); + } + + [TestMethod] + public void ApplyAnnualReductionFactor_2024_Returns93PercentOfOriginalValue() + { + // Arrange + double value = 100.0; + int year = 2024; + double expected = 93.0; + + // Act + double result = value.ApplyAnnualReductionFactor(year); + + // Assert + Assert.AreEqual(expected, result, 0.01); + } + + [TestMethod] + public void ApplyAnnualReductionFactor_2025_Returns91PercentOfOriginalValue() + { + // Arrange + double value = 100.0; + int year = 2025; + double expected = 91.0; + + // Act + double result = value.ApplyAnnualReductionFactor(year); + + // Assert + Assert.AreEqual(expected, result, 0.01); + } + + [TestMethod] + public void ApplyAnnualReductionFactor_2026_Returns89PercentOfOriginalValue() + { + // Arrange + double value = 100.0; + int year = 2026; + double expected = 89.0; + + // Act + double result = value.ApplyAnnualReductionFactor(year); + + // Assert + Assert.AreEqual(expected, result, 0.01); + } + + [TestMethod] + public void ApplyAnnualReductionFactor_2027_Returns87PercentOfOriginalValue() + { + // Arrange + double value = 100.0; + int year = 2027; + double expected = 87.0; + + // Act + double result = value.ApplyAnnualReductionFactor(year); + + // Assert + Assert.AreEqual(expected, result, 0.01); + } + + [TestMethod] + public void ApplyAnnualReductionFactor_2028_Returns85PercentOfOriginalValue() + { + // Arrange + double value = 100.0; + int year = 2028; + double expected = 85.0; + + // Act + double result = value.ApplyAnnualReductionFactor(year); + + // Assert + Assert.AreEqual(expected, result, 0.01); + } + + [TestMethod] + public void ApplyAnnualReductionFactor_2029_Returns83PercentOfOriginalValue() + { + // Arrange + double value = 100.0; + int year = 2029; + double expected = 83.0; + + // Act + double result = value.ApplyAnnualReductionFactor(year); + + // Assert + Assert.AreEqual(expected, result, 0.01); + } + + [TestMethod] + public void ApplyAnnualReductionFactor_2030_Returns81PercentOfOriginalValue() + { + // Arrange + double value = 100.0; + int year = 2030; + double expected = 81.0; + + // Act + double result = value.ApplyAnnualReductionFactor(year); + + // Assert + Assert.AreEqual(expected, result, 0.01); + } + } +} diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Calculator.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Calculator.cs index 3ae83c7..41084f8 100644 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Calculator.cs +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Calculator.cs @@ -1,5 +1,4 @@ -using EtiveMor.OpenImoCiiCalculator.Core.Extensions; -using EtiveMor.OpenImoCiiCalculator.Core.Models; +using EtiveMor.OpenImoCiiCalculator.Core.Models; using EtiveMor.OpenImoCiiCalculator.Core.Models.Enums; using EtiveMor.OpenImoCiiCalculator.Core.Services; using EtiveMor.OpenImoCiiCalculator.Core.Services.Impl; diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/Enums/CapacityUnit.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/Enums/CapacityUnit.cs new file mode 100644 index 0000000..caa7660 --- /dev/null +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/Enums/CapacityUnit.cs @@ -0,0 +1,11 @@ +namespace EtiveMor.OpenImoCiiCalculator.Core.Models.Enums +{ + public enum CapacityUnit + { + ERR, + DWT, + DWT_CAP_HIGH, + GT, + GT_CAP_LOW + } +} diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/MeasurementModels/ShipDdVectorBoundaries.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/MeasurementModels/ShipDdVectorBoundaries.cs new file mode 100644 index 0000000..153d797 --- /dev/null +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/MeasurementModels/ShipDdVectorBoundaries.cs @@ -0,0 +1,37 @@ +using EtiveMor.OpenImoCiiCalculator.Core.Models.Enums; + +namespace EtiveMor.OpenImoCiiCalculator.Core.Models.MeasurementModels +{ + public class ShipDdVectorBoundaries + { + public ShipDdVectorBoundaries( + ShipType shipType, + WeightClassification weightClassification, + CapacityUnit capacityUnit, + Dictionary boundaryDdVectors2019) + { + ShipType = shipType; + WeightClassification = weightClassification; + CapacityUnit = capacityUnit; + BoundaryDdVectors2019 = boundaryDdVectors2019; + } + + public ShipType ShipType { get; set; } + public WeightClassification WeightClassification { get; set; } + public CapacityUnit CapacityUnit { get; set; } + + public Dictionary BoundaryDdVectors2019 { get; set; } + } + + public class WeightClassification + { + public WeightClassification(int upperLimit, int lowerLimit) + { + UpperLimit = upperLimit; + LowerLimit = lowerLimit; + } + + public int UpperLimit { get; private set; } + public int LowerLimit { get; private set; } + } +} diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/IRatingBoundariesService.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/IRatingBoundariesService.cs index 496e5ac..e287fed 100644 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/IRatingBoundariesService.cs +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/IRatingBoundariesService.cs @@ -1,5 +1,5 @@ -using EtiveMor.OpenImoCiiCalculator.Core.Models.ShipModels; -using EtiveMor.OpenImoCiiCalculator.Core.Services.Impl; +using EtiveMor.OpenImoCiiCalculator.Core.Models.MeasurementModels; +using EtiveMor.OpenImoCiiCalculator.Core.Models.ShipModels; namespace EtiveMor.OpenImoCiiCalculator.Core.Services { diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/RatingBoundariesService.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/RatingBoundariesService.cs index 6ca6559..216e429 100644 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/RatingBoundariesService.cs +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/RatingBoundariesService.cs @@ -1,4 +1,5 @@ using EtiveMor.OpenImoCiiCalculator.Core.Models.Enums; +using EtiveMor.OpenImoCiiCalculator.Core.Models.MeasurementModels; using EtiveMor.OpenImoCiiCalculator.Core.Models.ShipModels; namespace EtiveMor.OpenImoCiiCalculator.Core.Services.Impl @@ -6,7 +7,8 @@ namespace EtiveMor.OpenImoCiiCalculator.Core.Services.Impl public class RatingBoundariesService : IRatingBoundariesService { /// - /// Returns the boundaries for the given ship and required CII in the given year. + /// Returns the ship grading boundaries ouelines in MEPC354(78) for a given + /// ship and required CII in a year. /// /// /// @@ -14,10 +16,14 @@ public class RatingBoundariesService : IRatingBoundariesService /// public ShipDdVectorBoundaries GetBoundaries(Ship ship, double requiredCiiInYear) { + ValidateShipTonnageValid(ship); + switch (ship.ShipType) { case ShipType.BulkCarrier: { + if (ship.DeadweightTonnage <= 0) throw new NotSupportedException($"Deadweight tonnage must be greater than 0 for ship type {ship.ShipType}"); + return new ShipDdVectorBoundaries( ship.ShipType, new WeightClassification(0, int.MaxValue), @@ -238,51 +244,55 @@ public ShipDdVectorBoundaries GetBoundaries(Ship ship, double requiredCiiInYear) default: throw new NotSupportedException($"Ship type '{ship.ShipType}' not supported"); } - } - } - - - public class ShipDdVectorBoundaries - { - public ShipDdVectorBoundaries( - ShipType shipType, - WeightClassification weightClassification, - CapacityUnit capacityUnit, - Dictionary boundaryDdVectors2019) - { - ShipType = shipType; - WeightClassification = weightClassification; - CapacityUnit = capacityUnit; - BoundaryDdVectors2019 = boundaryDdVectors2019; + /// + /// Checks that the ship tonnage is valid for the ship type. + /// + static void ValidateShipTonnageValid(Ship ship) + { + switch (ship.ShipType) + { + case ShipType.BulkCarrier + or ShipType.GasCarrier + or ShipType.Tanker + or ShipType.ContainerShip + or ShipType.GeneralCargoShip + or ShipType.RefrigeratedCargoCarrier + or ShipType.CombinationCarrier + or ShipType.LngCarrier: + { + if (ship.DeadweightTonnage <= 0) + { + throw new NotSupportedException($"Deadweight tonnage must be greater than 0 for ship type {ship.ShipType}. Was provided {ship.DeadweightTonnage}"); + } + break; + } + case ShipType.RoRoCargoShipVehicleCarrier + or ShipType.RoRoCargoShip + or ShipType.RoRoPassengerShip + or ShipType.RoRoPassengerShip_HighSpeedSOLAS + or ShipType.CruisePassengerShip: + { + if (ship.GrossTonnage <= 0) + { + throw new NotSupportedException($"Gross tonnage must be greater than 0 for ship type {ship.ShipType} Was provided {ship.GrossTonnage}"); + } + break; + } + case ShipType.UNKNOWN: + throw new NotSupportedException($"Ship type '{ship.ShipType}' not supported"); + default: + throw new NotSupportedException($"Ship type '{ship.ShipType}' not supported"); + } + } } + } - public ShipType ShipType { get; set; } - public WeightClassification WeightClassification { get; set; } - public CapacityUnit CapacityUnit { get; set; } - public Dictionary BoundaryDdVectors2019 { get; set; } - } - public class WeightClassification - { - public WeightClassification(int upperLimit, int lowerLimit) - { - UpperLimit = upperLimit; - LowerLimit = lowerLimit; - } - public int UpperLimit { get; private set; } - public int LowerLimit { get; private set; } - } + - public enum CapacityUnit - { - ERR, - DWT, - DWT_CAP_HIGH, - GT, - GT_CAP_LOW - } + } \ No newline at end of file From 01d820a80abb6ed8edcc25d8985aa401019e518c Mon Sep 17 00:00:00 2001 From: LFLaverty Date: Fri, 29 Mar 2024 09:21:42 +0000 Subject: [PATCH 08/10] adds comments to the vector boundaries class --- .../RatingBoundariesServiceTests.cs | 18 +++---- .../Calculator.cs | 3 +- .../Models/CalculationResult.cs | 6 ++- .../ShipDdVectorBoundaries.cs | 51 +++++++++++++++++-- 4 files changed, 64 insertions(+), 14 deletions(-) diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/RatingBoundariesServiceTests.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/RatingBoundariesServiceTests.cs index e24c827..07c3b29 100644 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/RatingBoundariesServiceTests.cs +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/RatingBoundariesServiceTests.cs @@ -30,24 +30,24 @@ public void TestShipWithCapacityDifferencesHasDifferentBoundariesReturnedBeforeA var smallBoundaries = service.GetBoundaries(smallShip, 0.5); var largeBoundaries = service.GetBoundaries(largeShip, 0.5); - Assert.AreNotEqual(smallBoundaries.BoundaryDdVectors2019, largeBoundaries.BoundaryDdVectors2019); + Assert.AreNotEqual(smallBoundaries.BoundaryDdVectors, largeBoundaries.BoundaryDdVectors); Assert.AreNotEqual( - smallBoundaries.BoundaryDdVectors2019[ImoCiiBoundary.Inferior], - largeBoundaries.BoundaryDdVectors2019[ImoCiiBoundary.Inferior]); + smallBoundaries.BoundaryDdVectors[ImoCiiBoundary.Inferior], + largeBoundaries.BoundaryDdVectors[ImoCiiBoundary.Inferior]); Assert.AreNotEqual( - smallBoundaries.BoundaryDdVectors2019[ImoCiiBoundary.Upper], - largeBoundaries.BoundaryDdVectors2019[ImoCiiBoundary.Upper]); + smallBoundaries.BoundaryDdVectors[ImoCiiBoundary.Upper], + largeBoundaries.BoundaryDdVectors[ImoCiiBoundary.Upper]); Assert.AreNotEqual( - smallBoundaries.BoundaryDdVectors2019[ImoCiiBoundary.Lower], - largeBoundaries.BoundaryDdVectors2019[ImoCiiBoundary.Lower]); + smallBoundaries.BoundaryDdVectors[ImoCiiBoundary.Lower], + largeBoundaries.BoundaryDdVectors[ImoCiiBoundary.Lower]); Assert.AreNotEqual( - smallBoundaries.BoundaryDdVectors2019[ImoCiiBoundary.Superior], - largeBoundaries.BoundaryDdVectors2019[ImoCiiBoundary.Superior]); + smallBoundaries.BoundaryDdVectors[ImoCiiBoundary.Superior], + largeBoundaries.BoundaryDdVectors[ImoCiiBoundary.Superior]); } diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Calculator.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Calculator.cs index 41084f8..46053f8 100644 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Calculator.cs +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Calculator.cs @@ -58,7 +58,8 @@ public CalculationResult CalculateAttainedCiiRating( AttainedCii = attainedCiiInYear, RequiredCii = requiredCiiInYear, Rating = GetImoCiiRatingInYear(attainedCiiInYear, requiredCiiInYear, year), - Boundaries = GetBoundaries(shipType, requiredCiiInYear) + VectorBoundariesForYear = _ratingBoundariesService.GetBoundaries(new Models.ShipModels.Ship(shipType, deadweightTonnage, grossTonnage), requiredCiiInYear) + // Boundaries = _ratingBoundariesService.GetBoundaries(new Models.ShipModels.Ship(shipType, deadweightTonnage, grossTonnage), requiredCiiInYear). }); } diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/CalculationResult.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/CalculationResult.cs index 8449d1f..cf311bb 100644 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/CalculationResult.cs +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/CalculationResult.cs @@ -1,4 +1,5 @@ using EtiveMor.OpenImoCiiCalculator.Core.Models.Enums; +using EtiveMor.OpenImoCiiCalculator.Core.Models.MeasurementModels; using System; using System.Collections.Generic; using System.Linq; @@ -58,6 +59,9 @@ public double AttainedRequiredRatio { get } } - public required Dictionary Boundaries { get; set; } + /// + /// The VectorBoundaries for this ship/year + /// + public required ShipDdVectorBoundaries VectorBoundariesForYear { get; set; } } } diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/MeasurementModels/ShipDdVectorBoundaries.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/MeasurementModels/ShipDdVectorBoundaries.cs index 153d797..35b26ae 100644 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/MeasurementModels/ShipDdVectorBoundaries.cs +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/MeasurementModels/ShipDdVectorBoundaries.cs @@ -4,23 +4,68 @@ namespace EtiveMor.OpenImoCiiCalculator.Core.Models.MeasurementModels { public class ShipDdVectorBoundaries { + /// + /// IMO MEPC.354(78) ddvectors for a given year for the specified ship type + /// + /// + /// The type of ship to generate ddvector boundaries for + /// + /// + /// The weight classification of the ship to generate ddvector boundaries for. + /// + /// If these ddvectors have a min/max weight boundary in MEPC.354(78), this object describes the + /// lower and upper bound of that classification. For example, Gas Carriers below + /// 65000 DWT have different ddvectors to those at or above 65000 DWT + /// + /// If the ddvectors do not have a weight classification, this object will contain + /// a range between 0 and int.MaxValue + /// + /// + /// Indicates the capacity unit these ddvectors are calculated against. For example + /// GT for passenger/ro-ro ships and DWT for cargo carriers + /// + /// + /// The ddvectors for the specified ship type, weight classification and capacity unit in the given year + /// + /// + /// The year these ddvectors apply to. Note that the ddvectors are only valid for the specified + /// calendar year + /// public ShipDdVectorBoundaries( ShipType shipType, WeightClassification weightClassification, CapacityUnit capacityUnit, - Dictionary boundaryDdVectors2019) + Dictionary boundaryDdVectors, + int year) { ShipType = shipType; WeightClassification = weightClassification; CapacityUnit = capacityUnit; - BoundaryDdVectors2019 = boundaryDdVectors2019; + BoundaryDdVectors = boundaryDdVectors; + Year = year; } + /// + /// The year these DDVector boundaries apply to + /// + public int Year { get; private set; } + + /// + /// The shipType these vector boundaries apply to + /// public ShipType ShipType { get; set; } + + /// + /// The weight classification these vector boundaries apply to + /// public WeightClassification WeightClassification { get; set; } + + /// + /// The capacity unit these vector boundaries apply to + /// public CapacityUnit CapacityUnit { get; set; } - public Dictionary BoundaryDdVectors2019 { get; set; } + public Dictionary BoundaryDdVectors { get; set; } } public class WeightClassification From 6f9c8fa33593bda5ae12d2bbc313dafa6ea118f8 Mon Sep 17 00:00:00 2001 From: LFLaverty Date: Fri, 29 Mar 2024 09:26:21 +0000 Subject: [PATCH 09/10] corrects build errors and unit tests --- .../RatingBoundariesServiceTests.cs | 42 +++++++++++++---- .../Calculator.cs | 2 +- .../Services/IRatingBoundariesService.cs | 2 +- .../Services/Impl/RatingBoundariesService.cs | 46 ++++++++++++------- 4 files changed, 64 insertions(+), 28 deletions(-) diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/RatingBoundariesServiceTests.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/RatingBoundariesServiceTests.cs index 07c3b29..cb0cab0 100644 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/RatingBoundariesServiceTests.cs +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/RatingBoundariesServiceTests.cs @@ -27,8 +27,8 @@ public void TestShipWithCapacityDifferencesHasDifferentBoundariesReturnedBeforeA var service = new RatingBoundariesService(); - var smallBoundaries = service.GetBoundaries(smallShip, 0.5); - var largeBoundaries = service.GetBoundaries(largeShip, 0.5); + var smallBoundaries = service.GetBoundaries(smallShip, 0.5, 2023); + var largeBoundaries = service.GetBoundaries(largeShip, 0.5, 2023); Assert.AreNotEqual(smallBoundaries.BoundaryDdVectors, largeBoundaries.BoundaryDdVectors); @@ -51,14 +51,25 @@ public void TestShipWithCapacityDifferencesHasDifferentBoundariesReturnedBeforeA } - + [DataRow(2019)] + [DataRow(2020)] + [DataRow(2021)] + [DataRow(2022)] + [DataRow(2023)] + [DataRow(2024)] + [DataRow(2025)] + [DataRow(2026)] + [DataRow(2027)] + [DataRow(2028)] + [DataRow(2029)] + [DataRow(2030)] /// /// This method tests that ShipType enum values are considered by the /// GetBoundaries method. If a new value is added to the Enum, this method /// will fail until the RatingsBoundariesService is updated to handle the new value. /// [TestMethod] - public void TestGetBoundariesProcessesAllEnumValues() + public void TestGetBoundariesProcessesAllEnumValues(int year) { ShipType[] possibleShipTypeEnums = (ShipType[])Enum.GetValues(typeof(ShipType)); @@ -71,24 +82,35 @@ public void TestGetBoundariesProcessesAllEnumValues() } var ship = new Ship(possibleShipTypeEnums[i], 250000, 250000); var service = new RatingBoundariesService(); - var boundaries = service.GetBoundaries(ship, 0.5); + var boundaries = service.GetBoundaries(ship, 0.5, year); Assert.IsNotNull(boundaries); } } - + [DataRow(2019)] + [DataRow(2020)] + [DataRow(2021)] + [DataRow(2022)] + [DataRow(2023)] + [DataRow(2024)] + [DataRow(2025)] + [DataRow(2026)] + [DataRow(2027)] + [DataRow(2028)] + [DataRow(2029)] + [DataRow(2030)] /// /// Method checks that an exception is thrown when an unknown ShipType /// is passed to the GetBoundaries method. /// [TestMethod] - public void TestGetBoundariesFailsOnUnknownShipType() + public void TestGetBoundariesFailsOnUnknownShipType(int year) { var ship = new Ship(ShipType.UNKNOWN, 250000, 0); var service = new RatingBoundariesService(); - Assert.ThrowsException(() => service.GetBoundaries(ship, 0.5)); + Assert.ThrowsException(() => service.GetBoundaries(ship, 0.5, year)); } @@ -116,7 +138,7 @@ public void TestGrossTonnageCapacityShipTypesAreHandledCorrectly(ShipType shipTy expectedCapacityUnit == CapacityUnit.DWT ? 250000 : 0, expectedCapacityUnit == CapacityUnit.GT ? 250000 : 0); var service = new RatingBoundariesService(); - var boundaries = service.GetBoundaries(ship, 0.5); + var boundaries = service.GetBoundaries(ship, 0.5, 2019); Assert.AreEqual(expectedCapacityUnit, boundaries.CapacityUnit); } @@ -151,7 +173,7 @@ public void TestValidateShipTonnageValid(ShipType shipType, int deadweightTonnag { var ship = new Ship(shipType, deadweightTonnage, grossTonnage); var service = new RatingBoundariesService(); - var boundaries = service.GetBoundaries(ship, 0.5); + var boundaries = service.GetBoundaries(ship, 0.5, 2030); } } } diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Calculator.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Calculator.cs index 46053f8..7c8b2f6 100644 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Calculator.cs +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Calculator.cs @@ -58,7 +58,7 @@ public CalculationResult CalculateAttainedCiiRating( AttainedCii = attainedCiiInYear, RequiredCii = requiredCiiInYear, Rating = GetImoCiiRatingInYear(attainedCiiInYear, requiredCiiInYear, year), - VectorBoundariesForYear = _ratingBoundariesService.GetBoundaries(new Models.ShipModels.Ship(shipType, deadweightTonnage, grossTonnage), requiredCiiInYear) + VectorBoundariesForYear = _ratingBoundariesService.GetBoundaries(new Models.ShipModels.Ship(shipType, deadweightTonnage, grossTonnage), requiredCiiInYear, year) // Boundaries = _ratingBoundariesService.GetBoundaries(new Models.ShipModels.Ship(shipType, deadweightTonnage, grossTonnage), requiredCiiInYear). }); } diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/IRatingBoundariesService.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/IRatingBoundariesService.cs index e287fed..3756167 100644 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/IRatingBoundariesService.cs +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/IRatingBoundariesService.cs @@ -5,7 +5,7 @@ namespace EtiveMor.OpenImoCiiCalculator.Core.Services { public interface IRatingBoundariesService { - ShipDdVectorBoundaries GetBoundaries(Ship ship, double requiredCiiInYear); + ShipDdVectorBoundaries GetBoundaries(Ship ship, double requiredCiiInYear, int year); } } \ No newline at end of file diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/RatingBoundariesService.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/RatingBoundariesService.cs index 216e429..60a1771 100644 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/RatingBoundariesService.cs +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/RatingBoundariesService.cs @@ -14,7 +14,7 @@ public class RatingBoundariesService : IRatingBoundariesService /// /// /// - public ShipDdVectorBoundaries GetBoundaries(Ship ship, double requiredCiiInYear) + public ShipDdVectorBoundaries GetBoundaries(Ship ship, double requiredCiiInYear, int year) { ValidateShipTonnageValid(ship); @@ -34,7 +34,8 @@ public ShipDdVectorBoundaries GetBoundaries(Ship ship, double requiredCiiInYear) { ImoCiiBoundary.Lower, 0.94 * requiredCiiInYear }, { ImoCiiBoundary.Upper, 1.06 * requiredCiiInYear }, { ImoCiiBoundary.Inferior, 1.18 * requiredCiiInYear } - }); + }, + year); } case ShipType.GasCarrier: { @@ -50,7 +51,8 @@ public ShipDdVectorBoundaries GetBoundaries(Ship ship, double requiredCiiInYear) { ImoCiiBoundary.Lower, 0.91 * requiredCiiInYear }, { ImoCiiBoundary.Upper, 1.12 * requiredCiiInYear }, { ImoCiiBoundary.Inferior, 1.44 * requiredCiiInYear } - }); + }, + year); } else { @@ -64,7 +66,8 @@ public ShipDdVectorBoundaries GetBoundaries(Ship ship, double requiredCiiInYear) { ImoCiiBoundary.Lower, 0.95 * requiredCiiInYear }, { ImoCiiBoundary.Upper, 1.06 * requiredCiiInYear }, { ImoCiiBoundary.Inferior, 1.25 * requiredCiiInYear } - }); + }, + year); } } case ShipType.Tanker: @@ -79,7 +82,7 @@ public ShipDdVectorBoundaries GetBoundaries(Ship ship, double requiredCiiInYear) { ImoCiiBoundary.Lower, 0.93 * requiredCiiInYear }, { ImoCiiBoundary.Upper, 1.08 * requiredCiiInYear }, { ImoCiiBoundary.Inferior, 1.28 * requiredCiiInYear } - }); + }, year); } case ShipType.ContainerShip: { @@ -93,7 +96,8 @@ public ShipDdVectorBoundaries GetBoundaries(Ship ship, double requiredCiiInYear) { ImoCiiBoundary.Lower, 0.94 * requiredCiiInYear }, { ImoCiiBoundary.Upper, 1.07 * requiredCiiInYear }, { ImoCiiBoundary.Inferior, 1.19 * requiredCiiInYear } - }); + }, + year); } case ShipType.GeneralCargoShip: { @@ -107,7 +111,8 @@ public ShipDdVectorBoundaries GetBoundaries(Ship ship, double requiredCiiInYear) { ImoCiiBoundary.Lower, 0.94 * requiredCiiInYear }, { ImoCiiBoundary.Upper, 1.06 * requiredCiiInYear }, { ImoCiiBoundary.Inferior, 1.19 * requiredCiiInYear } - }); + }, + year); } case ShipType.RefrigeratedCargoCarrier: { @@ -121,7 +126,8 @@ public ShipDdVectorBoundaries GetBoundaries(Ship ship, double requiredCiiInYear) { ImoCiiBoundary.Lower, 0.91 * requiredCiiInYear }, { ImoCiiBoundary.Upper, 1.07 * requiredCiiInYear }, { ImoCiiBoundary.Inferior, 1.20 * requiredCiiInYear } - }); + }, + year); } case ShipType.CombinationCarrier: { @@ -135,7 +141,8 @@ public ShipDdVectorBoundaries GetBoundaries(Ship ship, double requiredCiiInYear) { ImoCiiBoundary.Lower, 0.96 * requiredCiiInYear }, { ImoCiiBoundary.Upper, 1.06 * requiredCiiInYear }, { ImoCiiBoundary.Inferior, 1.14 * requiredCiiInYear } - }); + }, + year); } case ShipType.LngCarrier: { @@ -151,7 +158,8 @@ public ShipDdVectorBoundaries GetBoundaries(Ship ship, double requiredCiiInYear) { ImoCiiBoundary.Lower, 0.98 * requiredCiiInYear }, { ImoCiiBoundary.Upper, 1.06 * requiredCiiInYear }, { ImoCiiBoundary.Inferior, 1.13 * requiredCiiInYear } - }); + }, + year); } else { @@ -165,7 +173,8 @@ public ShipDdVectorBoundaries GetBoundaries(Ship ship, double requiredCiiInYear) { ImoCiiBoundary.Lower, 0.92 * requiredCiiInYear }, { ImoCiiBoundary.Upper, 1.10 * requiredCiiInYear }, { ImoCiiBoundary.Inferior, 1.37 * requiredCiiInYear } - }); + }, + year); } } case ShipType.RoRoCargoShipVehicleCarrier: @@ -180,7 +189,8 @@ public ShipDdVectorBoundaries GetBoundaries(Ship ship, double requiredCiiInYear) { ImoCiiBoundary.Lower, 0.94 * requiredCiiInYear }, { ImoCiiBoundary.Upper, 1.06 * requiredCiiInYear }, { ImoCiiBoundary.Inferior, 1.16 * requiredCiiInYear } - }); + }, + year); } case ShipType.RoRoCargoShip: { @@ -194,7 +204,8 @@ public ShipDdVectorBoundaries GetBoundaries(Ship ship, double requiredCiiInYear) { ImoCiiBoundary.Lower, 0.89 * requiredCiiInYear }, { ImoCiiBoundary.Upper, 1.08 * requiredCiiInYear }, { ImoCiiBoundary.Inferior, 1.27 * requiredCiiInYear } - }); + }, + year); } case ShipType.RoRoPassengerShip: { @@ -208,7 +219,8 @@ public ShipDdVectorBoundaries GetBoundaries(Ship ship, double requiredCiiInYear) { ImoCiiBoundary.Lower, 0.92 * requiredCiiInYear }, { ImoCiiBoundary.Upper, 1.14 * requiredCiiInYear }, { ImoCiiBoundary.Inferior, 1.30 * requiredCiiInYear } - }); + }, + year); } case ShipType.RoRoPassengerShip_HighSpeedSOLAS: { @@ -222,7 +234,8 @@ public ShipDdVectorBoundaries GetBoundaries(Ship ship, double requiredCiiInYear) { ImoCiiBoundary.Lower, 0.92 * requiredCiiInYear }, { ImoCiiBoundary.Upper, 1.14 * requiredCiiInYear }, { ImoCiiBoundary.Inferior, 1.30 * requiredCiiInYear } - }); + }, + year); } case ShipType.CruisePassengerShip: { @@ -236,7 +249,8 @@ public ShipDdVectorBoundaries GetBoundaries(Ship ship, double requiredCiiInYear) { ImoCiiBoundary.Lower, 0.95 * requiredCiiInYear }, { ImoCiiBoundary.Upper, 1.06 * requiredCiiInYear }, { ImoCiiBoundary.Inferior, 1.16 * requiredCiiInYear } - }); + }, + year); } case ShipType.UNKNOWN: From 63009b9b0309ab4eee2603206f4883fd4e3cb379 Mon Sep 17 00:00:00 2001 From: LFLaverty Date: Fri, 29 Mar 2024 10:05:07 +0000 Subject: [PATCH 10/10] adds an extensive test for the Ship Calculator --- .../CalculatorTests.cs | 55 ++++++++++++++++++- .../Calculator.cs | 47 +++++++--------- 2 files changed, 75 insertions(+), 27 deletions(-) diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/CalculatorTests.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/CalculatorTests.cs index ee51d92..7c8c817 100644 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/CalculatorTests.cs +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/CalculatorTests.cs @@ -26,7 +26,7 @@ public void TestCalculator() 2019 ); - System.Diagnostics.Debug.WriteLine("result is"); + System.Diagnostics.Debug.WriteLine("Basic result is:"); string json = JsonConvert.SerializeObject(result, Formatting.Indented); System.Diagnostics.Debug.WriteLine(json); @@ -38,5 +38,58 @@ public void TestCalculator() Assert.IsTrue(result.Results.Count(result => result.IsEstimatedYear) == 11); } + + + [DataRow(ShipType.RoRoPassengerShip, 0, 25000, TypeOfFuel.DIESEL_OR_GASOIL, 1.9e+10, 2019, ImoCiiRating.B, 19.184190519387734, 16.243733333333335, 0.8467249799733408)] + [DataRow(ShipType.RoRoPassengerShip, 0, 25000, TypeOfFuel.DIESEL_OR_GASOIL, 1.9e+10, 2020, ImoCiiRating.B, 18.992348614193855, 16.243733333333335, 0.8552777575488293)] + [DataRow(ShipType.RoRoPassengerShip, 0, 25000, TypeOfFuel.DIESEL_OR_GASOIL, 1.9e+10, 2021, ImoCiiRating.B, 18.80050670899998, 16.243733333333335, 0.8640050816054499)] + [DataRow(ShipType.RoRoPassengerShip, 0, 25000, TypeOfFuel.DIESEL_OR_GASOIL, 1.9e+10, 2022, ImoCiiRating.B, 18.6086648038061, 16.243733333333335, 0.8729123504879803)] + [DataRow(ShipType.RoRoPassengerShip, 0, 25000, TypeOfFuel.DIESEL_OR_GASOIL, 1.9e+10, 2023, ImoCiiRating.B, 18.224980993418345, 16.243733333333335, 0.8912894526035168)] + [DataRow(ShipType.RoRoPassengerShip, 0, 25000, TypeOfFuel.DIESEL_OR_GASOIL, 1.9e+10, 2024, ImoCiiRating.B, 17.84129718303059, 16.243733333333335, 0.9104569677132699)] + [DataRow(ShipType.RoRoPassengerShip, 0, 25000, TypeOfFuel.DIESEL_OR_GASOIL, 1.9e+10, 2025, ImoCiiRating.C, 17.45761337264284, 16.243733333333335, 0.9304670109597152)] + [DataRow(ShipType.RoRoPassengerShip, 0, 25000, TypeOfFuel.DIESEL_OR_GASOIL, 1.9e+10, 2026, ImoCiiRating.C, 17.073929562255085, 16.243733333333335, 0.9513763819925177)] + [DataRow(ShipType.RoRoPassengerShip, 0, 25000, TypeOfFuel.DIESEL_OR_GASOIL, 1.9e+10, 2027, ImoCiiRating.C, 16.690245751867327, 16.243733333333335, 0.9732471034176333)] + [DataRow(ShipType.RoRoPassengerShip, 0, 25000, TypeOfFuel.DIESEL_OR_GASOIL, 1.9e+10, 2028, ImoCiiRating.C, 16.306561941479572, 16.243733333333335, 0.996147035262754)] + [DataRow(ShipType.RoRoPassengerShip, 0, 25000, TypeOfFuel.DIESEL_OR_GASOIL, 1.9e+10, 2029, ImoCiiRating.C, 15.922878131091819, 16.243733333333335, 1.0201505782811335)] + [DataRow(ShipType.RoRoPassengerShip, 0, 25000, TypeOfFuel.DIESEL_OR_GASOIL, 1.9e+10, 2030, ImoCiiRating.C, 15.539194320704066, 16.243733333333335, 1.0453394814485688)] + [TestMethod] + public void TestRoRoPassengerShipReturnsExpectedValues( + ShipType shipType, + double deadweightTonnage, + double grossTonnage, + TypeOfFuel typeOfFuel, + double fuelConsumption, + int year, + ImoCiiRating expectedRating, + double expectedRequiredCii, + double expectedAttainedCii, + double expectedArRatio) + { + var _calc = new Calculator(); + + var result = _calc.CalculateAttainedCiiRating( + shipType, + grossTonnage: grossTonnage, + deadweightTonnage: deadweightTonnage, + distanceTravelled: 150000, + fuelType: typeOfFuel, + fuelConsumption: fuelConsumption, + year + ); + + Assert.IsNotNull(result); + Assert.AreEqual(result.Results.Count(), 12); + + Assert.IsTrue(result.Results.Count(result => result.IsMeasuredYear) == 1); + Assert.IsTrue(result.Results.Count(result => result.IsEstimatedYear) == 11); + + Assert.AreEqual(result.Results.First(c => c.Year == year).Year, year); + Assert.AreEqual(result.Results.First(c => c.Year == year).VectorBoundariesForYear.ShipType, shipType); + Assert.AreEqual(result.Results.First(c => c.Year == year).RequiredCii, expectedRequiredCii); + Assert.AreEqual(result.Results.First(c => c.Year == year).AttainedRequiredRatio, expectedArRatio); + Assert.AreEqual(result.Results.First(c => c.Year == year).AttainedCii, expectedAttainedCii); + Assert.AreEqual(result.Results.First(c => c.Year == year).Rating, expectedRating); + Assert.AreNotEqual(result.Results.First(c => c.Year == year).IsMeasuredYear, result.Results.First(c => c.Year == year).IsEstimatedYear); + } } } diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Calculator.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Calculator.cs index 7c8b2f6..873898b 100644 --- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Calculator.cs +++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Calculator.cs @@ -1,5 +1,7 @@ using EtiveMor.OpenImoCiiCalculator.Core.Models; +using EtiveMor.OpenImoCiiCalculator.Core.Models.ShipModels; using EtiveMor.OpenImoCiiCalculator.Core.Models.Enums; +using EtiveMor.OpenImoCiiCalculator.Core.Models.MeasurementModels; using EtiveMor.OpenImoCiiCalculator.Core.Services; using EtiveMor.OpenImoCiiCalculator.Core.Services.Impl; @@ -12,6 +14,8 @@ public class Calculator IShipTransportWorkCalculatorService _shipTransportWorkService; ICarbonIntensityIndicatorCalculatorService _carbonIntensityIndicatorService; IRatingBoundariesService _ratingBoundariesService; + + public Calculator() { _shipMassOfCo2EmissionsService = new ShipMassOfCo2EmissionsCalculatorService(); @@ -32,16 +36,16 @@ public Calculator() /// quantity of fuel consumed in grams /// public CalculationResult CalculateAttainedCiiRating( - ShipType shipType, - double grossTonnage, - double deadweightTonnage, - double distanceTravelled, - TypeOfFuel fuelType, - double fuelConsumption, + ShipType shipType, + double grossTonnage, + double deadweightTonnage, + double distanceTravelled, + TypeOfFuel fuelType, + double fuelConsumption, int targetYear) { var shipCo2Emissions = _shipMassOfCo2EmissionsService.GetMassOfCo2Emissions(fuelType, fuelConsumption); - var shipCapacity = _shipCapacityService.GetShipCapacity(shipType, deadweightTonnage, grossTonnage); + var shipCapacity = _shipCapacityService.GetShipCapacity(shipType, deadweightTonnage, grossTonnage); var transportWork = _shipTransportWorkService.GetShipTransportWork(shipCapacity, distanceTravelled); List results = new List(); @@ -50,6 +54,8 @@ public CalculationResult CalculateAttainedCiiRating( var attainedCiiInYear = _carbonIntensityIndicatorService.GetAttainedCarbonIntensity(shipCo2Emissions, transportWork); var requiredCiiInYear = _carbonIntensityIndicatorService.GetRequiredCarbonIntensity(shipType, shipCapacity, year); + var vectors = _ratingBoundariesService.GetBoundaries(new Ship(shipType, deadweightTonnage, grossTonnage), requiredCiiInYear, year); + var rating = GetImoCiiRatingFromVectors(vectors, attainedCiiInYear, year); results.Add(new ResultYear { @@ -57,35 +63,33 @@ public CalculationResult CalculateAttainedCiiRating( Year = year, AttainedCii = attainedCiiInYear, RequiredCii = requiredCiiInYear, - Rating = GetImoCiiRatingInYear(attainedCiiInYear, requiredCiiInYear, year), - VectorBoundariesForYear = _ratingBoundariesService.GetBoundaries(new Models.ShipModels.Ship(shipType, deadweightTonnage, grossTonnage), requiredCiiInYear, year) - // Boundaries = _ratingBoundariesService.GetBoundaries(new Models.ShipModels.Ship(shipType, deadweightTonnage, grossTonnage), requiredCiiInYear). + Rating = rating, + VectorBoundariesForYear = vectors }); } return new CalculationResult(results); } - private ImoCiiRating GetImoCiiRatingInYear(double attainedCiiInYear, double requiredCiiInYear, int year) - { - var gradeLowerBoundaries = GetBoundaries(ShipType.CruisePassengerShip, requiredCiiInYear); - if (attainedCiiInYear < gradeLowerBoundaries[ImoCiiBoundary.Superior]) + private ImoCiiRating GetImoCiiRatingFromVectors(ShipDdVectorBoundaries boundaries, double attainedCiiInYear, int year) + { + if (attainedCiiInYear < boundaries.BoundaryDdVectors[ImoCiiBoundary.Superior]) { // lower than the "superior" boundary return ImoCiiRating.A; } - else if (attainedCiiInYear < gradeLowerBoundaries[ImoCiiBoundary.Lower]) + else if (attainedCiiInYear < boundaries.BoundaryDdVectors[ImoCiiBoundary.Lower]) { // lower than the "lower" boundary return ImoCiiRating.B; } - else if (attainedCiiInYear < gradeLowerBoundaries[ImoCiiBoundary.Upper]) + else if (attainedCiiInYear < boundaries.BoundaryDdVectors[ImoCiiBoundary.Upper]) { // lower than the "upper" boundary return ImoCiiRating.C; } - else if (attainedCiiInYear < gradeLowerBoundaries[ImoCiiBoundary.Inferior]) + else if (attainedCiiInYear < boundaries.BoundaryDdVectors[ImoCiiBoundary.Inferior]) { // lower than the "inferior" boundary return ImoCiiRating.D; @@ -97,15 +101,6 @@ private ImoCiiRating GetImoCiiRatingInYear(double attainedCiiInYear, double requ } } - private Dictionary GetBoundaries(ShipType shipType, double requiredCiiInYear) - { - return new Dictionary { - { ImoCiiBoundary.Superior, 0.72 * requiredCiiInYear }, - { ImoCiiBoundary.Lower, 0.90 * requiredCiiInYear}, - { ImoCiiBoundary.Upper, 1.12 * requiredCiiInYear}, - { ImoCiiBoundary.Inferior, 1.41 * requiredCiiInYear} - }; - } } }