diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/CalculatorTests.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/CalculatorTests.cs
index d35d5a2..7c8c817 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,11 +22,74 @@ 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("Basic 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);
}
+
+
+ [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.Tests/RatingBoundariesServiceTests.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/RatingBoundariesServiceTests.cs
new file mode 100644
index 0000000..cb0cab0
--- /dev/null
+++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/RatingBoundariesServiceTests.cs
@@ -0,0 +1,179 @@
+using EtiveMor.OpenImoCiiCalculator.Core.Models.Enums;
+using EtiveMor.OpenImoCiiCalculator.Core.Models.ShipModels;
+using EtiveMor.OpenImoCiiCalculator.Core.Services.Impl;
+
+namespace EtiveMor.OpenImoCiiCalculator.Core.Tests
+{
+ [TestClass]
+ 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, 2023);
+ var largeBoundaries = service.GetBoundaries(largeShip, 0.5, 2023);
+
+ Assert.AreNotEqual(smallBoundaries.BoundaryDdVectors, largeBoundaries.BoundaryDdVectors);
+
+ Assert.AreNotEqual(
+ smallBoundaries.BoundaryDdVectors[ImoCiiBoundary.Inferior],
+ largeBoundaries.BoundaryDdVectors[ImoCiiBoundary.Inferior]);
+
+
+ Assert.AreNotEqual(
+ smallBoundaries.BoundaryDdVectors[ImoCiiBoundary.Upper],
+ largeBoundaries.BoundaryDdVectors[ImoCiiBoundary.Upper]);
+
+ Assert.AreNotEqual(
+ smallBoundaries.BoundaryDdVectors[ImoCiiBoundary.Lower],
+ largeBoundaries.BoundaryDdVectors[ImoCiiBoundary.Lower]);
+
+ Assert.AreNotEqual(
+ smallBoundaries.BoundaryDdVectors[ImoCiiBoundary.Superior],
+ largeBoundaries.BoundaryDdVectors[ImoCiiBoundary.Superior]);
+ }
+
+
+ [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(int year)
+ {
+ 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, 250000);
+ var service = new RatingBoundariesService();
+ 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(int year)
+ {
+ var ship = new Ship(ShipType.UNKNOWN, 250000, 0);
+ var service = new RatingBoundariesService();
+
+ Assert.ThrowsException(() => service.GetBoundaries(ship, 0.5, year));
+ }
+
+
+ ///
+ /// 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)]
+ [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,
+ expectedCapacityUnit == CapacityUnit.DWT ? 250000 : 0,
+ expectedCapacityUnit == CapacityUnit.GT ? 250000 : 0);
+ var service = new RatingBoundariesService();
+ var boundaries = service.GetBoundaries(ship, 0.5, 2019);
+
+ 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, 2030);
+ }
+ }
+}
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.Tests/ShipCapacityTests.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core.Tests/ShipCapacityTests.cs
index bd599f7..0bdbec6 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,9 @@ public class ShipCapacityTests
[TestMethod]
public void TestCalculateCapacity_BulkCarrier()
{
- var ship = new Ship
- {
- ShipType = ShipType.BulkCarrier,
- DeadweightTonnage = 250000,
- GrossTonnage = 0
- };
+ var ship = new Ship(
+ ShipType.BulkCarrier,
+ deadweightTonnage: 250000, grossTonnage: 0);
var capacity = new ShipCapacityCalculatorService().GetShipCapacity(ship);
@@ -55,15 +53,13 @@ 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
- {
- ShipType = shipType,
- DeadweightTonnage = deadweightTonnage,
- GrossTonnage = grossTonnage
- };
+ var ship = new Ship(
+ shipType,
+ deadweightTonnage,
+ grossTonnage);
new ShipCapacityCalculatorService().GetShipCapacity(ship);
}
@@ -90,15 +86,15 @@ 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
- {
- ShipType = shipType,
- DeadweightTonnage = deadweightTonnage,
- GrossTonnage = grossTonnage
- };
+ var ship = new Ship(
+ shipType,
+ deadweightTonnage,
+ grossTonnage);
+
+
var capacity = new ShipCapacityCalculatorService().GetShipCapacity(ship);
diff --git a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Calculator.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Calculator.cs
index 99dd5d0..873898b 100644
--- a/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Calculator.cs
+++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Calculator.cs
@@ -1,4 +1,7 @@
-using EtiveMor.OpenImoCiiCalculator.Core.Models.Enums;
+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;
@@ -10,12 +13,16 @@ 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 +35,72 @@ 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);
+ var vectors = _ratingBoundariesService.GetBoundaries(new Ship(shipType, deadweightTonnage, grossTonnage), requiredCiiInYear, year);
+ var rating = GetImoCiiRatingFromVectors(vectors, attainedCiiInYear, 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 = rating,
+ VectorBoundariesForYear = vectors
+ });
+ }
- 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 GetImoCiiRatingFromVectors(ShipDdVectorBoundaries boundaries, double attainedCiiInYear, int year)
{
- double cii = (double)(massOfCo2Emissions / transportWork);
-
- if (cii < 0.0001)
+ if (attainedCiiInYear < boundaries.BoundaryDdVectors[ImoCiiBoundary.Superior])
{
+ // lower than the "superior" boundary
return ImoCiiRating.A;
}
- else if (cii >= 0.0001 && cii < 0.0002)
+ else if (attainedCiiInYear < boundaries.BoundaryDdVectors[ImoCiiBoundary.Lower])
{
+ // lower than the "lower" boundary
return ImoCiiRating.B;
}
- else if (cii >= 0.0002 && cii < 0.0003)
+ else if (attainedCiiInYear < boundaries.BoundaryDdVectors[ImoCiiBoundary.Upper])
{
+ // lower than the "upper" boundary
return ImoCiiRating.C;
}
- else if (cii >= 0.0003 && cii < 0.0004)
+ else if (attainedCiiInYear < boundaries.BoundaryDdVectors[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)
- {
- return deadweightTonnage * distanceSailedCalendarYear;
- }
-
-
-
}
+
}
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..cf311bb
--- /dev/null
+++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/CalculationResult.cs
@@ -0,0 +1,67 @@
+using EtiveMor.OpenImoCiiCalculator.Core.Models.Enums;
+using EtiveMor.OpenImoCiiCalculator.Core.Models.MeasurementModels;
+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;
+ }
+ }
+
+ ///
+ /// The VectorBoundaries for this ship/year
+ ///
+ public required ShipDdVectorBoundaries VectorBoundariesForYear { get; set; }
+ }
+}
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/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/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/MeasurementModels/ShipDdVectorBoundaries.cs b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/MeasurementModels/ShipDdVectorBoundaries.cs
new file mode 100644
index 0000000..35b26ae
--- /dev/null
+++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/MeasurementModels/ShipDdVectorBoundaries.cs
@@ -0,0 +1,82 @@
+using EtiveMor.OpenImoCiiCalculator.Core.Models.Enums;
+
+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 boundaryDdVectors,
+ int year)
+ {
+ ShipType = shipType;
+ WeightClassification = weightClassification;
+ CapacityUnit = capacityUnit;
+ 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 BoundaryDdVectors { 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/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..f5655e5
--- /dev/null
+++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Models/ShipModels/Ship.cs
@@ -0,0 +1,62 @@
+using EtiveMor.OpenImoCiiCalculator.Core.Models.Enums;
+
+namespace EtiveMor.OpenImoCiiCalculator.Core.Models.ShipModels
+{
+
+
+ public class Ship
+ {
+ public Ship(
+ ShipType shipType,
+ double deadweightTonnage,
+ double grossTonnage
+ )
+ {
+ ShipType = shipType;
+ DeadweightTonnage = deadweightTonnage;
+ GrossTonnage = grossTonnage;
+ }
+
+
+
+ ///
+ /// The type of the ship (e.g., BulkCarrier, RoRoCargoShip, CruisePassengerShip, etc.)
+ ///
+ 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; private 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; 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; }
+ }
+
+
+
+
+
+
+}
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..3756167
--- /dev/null
+++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/IRatingBoundariesService.cs
@@ -0,0 +1,11 @@
+using EtiveMor.OpenImoCiiCalculator.Core.Models.MeasurementModels;
+using EtiveMor.OpenImoCiiCalculator.Core.Models.ShipModels;
+
+namespace EtiveMor.OpenImoCiiCalculator.Core.Services
+{
+ public interface IRatingBoundariesService
+ {
+ ShipDdVectorBoundaries GetBoundaries(Ship ship, double requiredCiiInYear, int year);
+
+ }
+}
\ No newline at end of file
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..d64171d 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
@@ -159,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}'")
};
@@ -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..60a1771
--- /dev/null
+++ b/EtiveMor.OpenImoCiiCalculator/EtiveMor.OpenImoCiiCalculator.Core/Services/Impl/RatingBoundariesService.cs
@@ -0,0 +1,312 @@
+using EtiveMor.OpenImoCiiCalculator.Core.Models.Enums;
+using EtiveMor.OpenImoCiiCalculator.Core.Models.MeasurementModels;
+using EtiveMor.OpenImoCiiCalculator.Core.Models.ShipModels;
+
+namespace EtiveMor.OpenImoCiiCalculator.Core.Services.Impl
+{
+ public class RatingBoundariesService : IRatingBoundariesService
+ {
+ ///
+ /// Returns the ship grading boundaries ouelines in MEPC354(78) for a given
+ /// ship and required CII in a year.
+ ///
+ ///
+ ///
+ ///
+ ///
+ public ShipDdVectorBoundaries GetBoundaries(Ship ship, double requiredCiiInYear, int year)
+ {
+ 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),
+ CapacityUnit.DWT,
+ new Dictionary
+ {
+ { ImoCiiBoundary.Superior, 0.86 * requiredCiiInYear },
+ { ImoCiiBoundary.Lower, 0.94 * requiredCiiInYear },
+ { ImoCiiBoundary.Upper, 1.06 * requiredCiiInYear },
+ { ImoCiiBoundary.Inferior, 1.18 * requiredCiiInYear }
+ },
+ year);
+ }
+ case ShipType.GasCarrier:
+ {
+ if (ship.DeadweightTonnage >= 65000)
+ {
+ return new ShipDdVectorBoundaries(
+ 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 }
+ },
+ year);
+ }
+ else
+ {
+ return new ShipDdVectorBoundaries(
+ 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 }
+ },
+ year);
+ }
+ }
+ case ShipType.Tanker:
+ {
+ return new ShipDdVectorBoundaries(
+ 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 }
+ }, year);
+ }
+ case ShipType.ContainerShip:
+ {
+ return new ShipDdVectorBoundaries(
+ 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 }
+ },
+ year);
+ }
+ case ShipType.GeneralCargoShip:
+ {
+ return new ShipDdVectorBoundaries(
+ 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 }
+ },
+ year);
+ }
+ case ShipType.RefrigeratedCargoCarrier:
+ {
+ return new ShipDdVectorBoundaries(
+ 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 }
+ },
+ year);
+ }
+ case ShipType.CombinationCarrier:
+ {
+ return new ShipDdVectorBoundaries(
+ 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 }
+ },
+ year);
+ }
+ case ShipType.LngCarrier:
+ {
+ if (ship.DeadweightTonnage >= 100000)
+ {
+ return new ShipDdVectorBoundaries(
+ 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 }
+ },
+ year);
+ }
+ else
+ {
+ return new ShipDdVectorBoundaries(
+ 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 }
+ },
+ year);
+ }
+ }
+ case ShipType.RoRoCargoShipVehicleCarrier:
+ {
+ return new ShipDdVectorBoundaries(
+ 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 }
+ },
+ year);
+ }
+ case ShipType.RoRoCargoShip:
+ {
+ return new ShipDdVectorBoundaries(
+ 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 }
+ },
+ year);
+ }
+ case ShipType.RoRoPassengerShip:
+ {
+ return new ShipDdVectorBoundaries(
+ 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 }
+ },
+ year);
+ }
+ case ShipType.RoRoPassengerShip_HighSpeedSOLAS:
+ {
+ return new ShipDdVectorBoundaries(
+ 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 }
+ },
+ year);
+ }
+ case ShipType.CruisePassengerShip:
+ {
+ return new ShipDdVectorBoundaries(
+ 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 }
+ },
+ year);
+ }
+
+ case ShipType.UNKNOWN:
+ throw new NotSupportedException($"Ship type '{ship.ShipType}' not supported");
+ default:
+ throw new NotSupportedException($"Ship type '{ship.ShipType}' not supported");
+ }
+
+
+ ///
+ /// 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");
+ }
+ }
+ }
+ }
+
+
+
+
+
+
+
+}
\ 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 446e39f..b11e409 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
{
@@ -63,7 +63,7 @@ public double GetShipCapacity(ShipType shipType, double deadweightTonnage, doubl
return deadweightTonnage;
case ShipType.LngCarrier:
return deadweightTonnage < 65000 ? 65000 : deadweightTonnage;
- case ShipType.RoRoCargoShipVehicleCarrier:
+ case ShipType.RoRoCargoShipVehicleCarrier:
return deadweightTonnage >= 57700 ? 57700 : grossTonnage;
case ShipType.RoRoCargoShip:
return grossTonnage;
@@ -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(),