diff --git a/docs/releases/Version 12.0.0.md b/docs/releases/Version 12.0.0.md new file mode 100644 index 00000000..6b63118a --- /dev/null +++ b/docs/releases/Version 12.0.0.md @@ -0,0 +1,11 @@ +# v12.0.0 +Download here: [![PyPI version shields.io](https://img.shields.io/pypi/v/signal-ocean.svg)](https://pypi.python.org/pypi/signal-ocean/) + +## Vessels API + +- Added missing fields `BowChainStopperDetailsAsStr, BowChainStoppersFitted,NumberOfBowChainStoppers, NumberOfCranes, NumberOfGrabs, NumberOfHatches` +- Fix mapping of fields `IMOType1, IMOType2, IMOType3` so they can load data +- Rename `te_u14` to `teu14` (Breaking Change) + +## Installation and Upgrade Notes +Update your package with: `pip install signal-ocean -U` diff --git a/mkdocs.yml b/mkdocs.yml index 9b3f5379..f264a178 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -106,6 +106,7 @@ nav: - Historical Tonnage List (deprecated): - 'historical_tonnage_list.md' - Release notes: + - 'releases/Version 12.0.0.md' - 'releases/Version 11.1.0.md' - 'releases/Version 11.0.0.md' - 'releases/Version 10.7.0.md' diff --git a/signal_ocean/vessels/models.py b/signal_ocean/vessels/models.py index 5b9a6488..177882e7 100644 --- a/signal_ocean/vessels/models.py +++ b/signal_ocean/vessels/models.py @@ -175,7 +175,7 @@ class Vessel: teu: Numeric, measured in TEU (Twenty-Foot Equivalent Unit), denotes a volumetric measure of a container's cargo carrying capacity. Used for Containers, that is vessels with VesselType=4. - te_u14: Numeric, denotes the capacity of the vessel measured in twenty- + teu14: Numeric, denotes the capacity of the vessel measured in twenty- foot equivalent units (TEU) loaded at 14 tons. reefers: Numeric, denotes the capacity of the vessel measured in refrigerated twenty-foot equivalent units (TEU), i.e., the maximum @@ -205,6 +205,17 @@ class Vessel: vessel's classification certificate. Default value: Not set. number_of_holds: Numeric, the number of separate enclosed spaces within a ship designed for storing cargo. + number_of_hatches: Numeric, the number of cargo hatches on the vessel + that cover the opening to the cargo hold to protect the cargo. Most + cargo holds have a cargo hatch. + number_of_grabs: Numeric, the number of separate grabs a vessel is + equipped with for handling and lifting the cargo. + number_of_cranes: Numeric, the number of separate cranes a vessel is + equipped with for handling and lifting the cargo. + number_of_bow_chain_stoppers: Numeric denotes the + number of bow chain stoppers the vessel is equipped + with. Ships likely to trade to Single Point + Moorings should be equipped with bow chain stoppers. grain_capacity: This is the space available for a liquid-type cargo, like bulk grain, which can flow into every corner. bale_capacity: This is the space available for solid cargo. Bale space @@ -245,9 +256,6 @@ class Vessel: with a heating coils system. Tanker vessels may be fitted with heating coils in order to maintain the required temperature of the cargo for pumping. - crane_details_as_str: String, SWL (Safe Working Load) in mt for - each crane, collected in one single string with the format - commonly used across market reports. Example: "4 x 30 MT". cranes_max_outreach: Numeric, in meters (m). The maximum outreach range across all the cranes that the vessel has. This range is measured as the distance from the boom tip to the crane hook. @@ -267,6 +275,13 @@ class Vessel: mt of each single grab, collected in one single string with the format commonly used across market reports. Example: "4 x 12 CBM". + crane_details_as_str: String, SWL (Safe Working Load) in mt for + each crane, collected in one single string with the format + commonly used across market reports. Example: "4 x 30 MT". + bow_chain_stopper_details_as_str: String, number of Bow + Chain Stoppers with their maximum load capacity for + each type of bow chain stopper in a string format. + Example: `""2 x 200 MT, 1 x 100 MT""`. box_shaped_holds: Boolean, denotes whether the vessel has any hold with box shape. neo_panama_locks: Boolean, denotes whether the vessel is fitted to @@ -381,6 +396,10 @@ class Vessel: manifold is the point on the ship where cargo hoses or arms are connected for the loading or unloading of liquid cargoes. + bow_chain_stoppers_fitted: Boolean, indicates whether + the vessel is equipped with bow chain stoppers. + Ships likely to trade to Single Point Moorings + should be equipped with bow chain stoppers. """ imo: int @@ -420,7 +439,7 @@ class Vessel: shipyard_built_name: Optional[str] = None ice_class: Optional[str] = None teu: Optional[int] = None - te_u14: Optional[int] = None + teu14: Optional[int] = None reefers: Optional[int] = None panama_canal_net_tonnage: Optional[int] = None cubic_size: Optional[int] = None @@ -431,6 +450,10 @@ class Vessel: delivery_date: Optional[datetime] = None classification_register: Optional[str] = None number_of_holds: Optional[int] = None + number_of_hatches: Optional[int] = None + number_of_grabs: Optional[int] = None + number_of_cranes: Optional[int] = None + number_of_bow_chain_stoppers: Optional[int] = None grain_capacity: Optional[int] = None bale_capacity: Optional[int] = None main_engine_kw: Optional[int] = None @@ -454,12 +477,13 @@ class Vessel: beneficial_owner: Optional[str] = None parallel_body_length: Optional[float] = None heating_coils_fitted: Optional[bool] = None - crane_details_as_str: Optional[str] = None cranes_max_outreach: Optional[float] = None cranes_max_lifting_capacity: Optional[float] = None hold_details_as_str: Optional[str] = None hatch_details_as_str: Optional[str] = None grab_details_as_str: Optional[str] = None + crane_details_as_str: Optional[str] = None + bow_chain_stopper_details_as_str: Optional[str] = None box_shaped_holds: Optional[bool] = None neo_panama_locks: Optional[bool] = None australian_hold_ladder: Optional[bool] = None @@ -491,6 +515,7 @@ class Vessel: water_line_to_manifold: Optional[float] = None deck_to_center_manifold: Optional[float] = None rail_to_center_manifold: Optional[float] = None + bow_chain_stoppers_fitted: Optional[bool] = None @dataclass(frozen=True) diff --git a/signal_ocean/vessels/vessels_api.py b/signal_ocean/vessels/vessels_api.py index 5e51c892..763b9f62 100644 --- a/signal_ocean/vessels/vessels_api.py +++ b/signal_ocean/vessels/vessels_api.py @@ -17,6 +17,14 @@ class VesselsAPI: relative_url = "vessels-api/v2/" default_pit = str(date.today()) + rename_keys = {"STSTCoating": "stst_coating", + "BWTS": "bwts", + "GHG": "ghg", + "VCM": "vcm", + "IMOType1": "imo_type_1", + "IMOType2": "imo_type_2", + "IMOType3": "imo_type_3"} + def __init__(self, connection: Optional[Connection] = None): """Initializes VesselsAPI. @@ -56,10 +64,7 @@ def get_vessel(self, imo: int) -> Optional[Vessel]: """ url = urljoin(VesselsAPI.relative_url, f"vessels/{imo}") return get_single(self.__connection, url, Vessel, - rename_keys={"STSTCoating": "stst_coating", - "BWTS": "bwts", - "GHG": "ghg", - "VCM": "vcm"}) + rename_keys=VesselsAPI.rename_keys) def get_vessels(self, name: Optional[str] = None) -> Tuple[Vessel, ...]: """Retrieves all available vessels. @@ -77,10 +82,7 @@ def get_vessels(self, name: Optional[str] = None) -> Tuple[Vessel, ...]: ) url = urljoin(VesselsAPI.relative_url, endpoint) return get_multiple(self.__connection, url, Vessel, - rename_keys={"STSTCoating": "stst_coating", - "BWTS": "bwts", - "GHG": "ghg", - "VCM": "vcm"}) + rename_keys=VesselsAPI.rename_keys) def get_vessels_by_vessel_class( self, vesselClass: int @@ -96,10 +98,7 @@ def get_vessels_by_vessel_class( endpoint = f"vessels?vesselClass={vesselClass}" url = urljoin(VesselsAPI.relative_url, endpoint) response = get_single(self.__connection, url, VesselPagedResponse, - rename_keys={"STSTCoating": "stst_coating", - "BWTS": "bwts", - "GHG": "ghg", - "VCM": "vcm"}) + rename_keys=VesselsAPI.rename_keys) return response if response is None else response.items def get_vessels_name_history( diff --git a/tests/vessels/test_vessels_api.py b/tests/vessels/test_vessels_api.py index 55398f53..4dfaaed8 100644 --- a/tests/vessels/test_vessels_api.py +++ b/tests/vessels/test_vessels_api.py @@ -2,6 +2,7 @@ from typing import Tuple from unittest.mock import MagicMock from urllib.parse import urljoin +from signal_ocean.util.parsing_helpers import _to_snake_case import requests @@ -257,3 +258,130 @@ def test_requests_search_vessels(): mocked_make_request.assert_called_with( urljoin(VesselsAPI.relative_url, 'vessels/searchByName/signal'), query_string=None) + +def test_vessel_field_names(): + api_fields = [ + "IMO", + "VesselName", + "CallSign", + "VesselTypeID", + "VesselType", + "BuiltForTradeID", + "BuiltForTrade", + "TradeID", + "Trade", + "CleanDirtyWilling", + "VesselClassID", + "VesselClass", + "FlagCode", + "Flag", + "CommercialOperatorID", + "CommercialOperator", + "Deadweight", + "BreadthExtreme", + "GrossRatedTonnage", + "ReducedGrossTonnage", + "NetRatedTonnage", + "Draught", + "LengthOverall", + "MouldedDepth", + "BowToCenterManifold", + "WaterLineToManifold", + "DeckToCenterManifold", + "RailToCenterManifold", + "YearBuilt", + "BuiltCountryCode", + "BuiltCountryName", + "ScrappedDate", + "ShipyardBuiltID", + "YardNumber", + "DesignModel", + "ShipyardBuiltName", + "IceClass", + "TEU", + "TEU14", + "Reefers", + "Geared", + "PanamaCanalNetTonnage", + "CubicSize", + "ScrubbersDate", + "SummerTPC", + "LightshipTonnes", + "MainEngineManufacturer", + "MainEngineManufacturerID", + "DeliveryDate", + "ClassificationRegisterID", + "ClassificationRegister", + "NumberOfHolds", + "NumberOfCranes", + "NumberOfGrabs", + "NumberOfHatches", + "GrainCapacity", + "BaleCapacity", + "MainEngineKW", + "MainEngineRPM", + "AirDraught", + "DeckTeu", + "UnderDeckTeu", + "SuezCanalNetTonnage", + "ClassRenewalDate", + "MewisDuct", + "InertGasSystem", + "IMOType1", + "IMOType2", + "IMOType3", + "STSTCoating", + "EpoxyCoating", + "ZincCoating", + "MarinelineCoating", + "InterlineCoating", + "CrudeOilWashing", + "NumberOfBowChainStoppers", + "BowChainStoppersFitted", + "BowChainStopperDetailsAsStr", + "BeneficialOwnerID", + "BeneficialOwner", + "ParallelBodyLength", + "BallastParallelBodyLength", + "EmptyParallelBodyLength", + "HeatingCoilsFitted", + "CraneDetailsAsStr", + "CranesMaxOutreach", + "CranesMaxLiftingCapacity", + "HoldDetailsAsStr", + "HatchDetailsAsStr", + "GrabDetailsAsStr", + "BoxShapedHolds", + "NeoPanamaLocks", + "AustralianHoldLadder", + "CO2Fitted", + "A60Bulkhead", + "LogFitted", + "SternLine", + "OpenHatch", + "BWTS", + "GrabsFitted", + "GHG", + "OrderBookStatusID", + "OrderBookStatus", + "OrderDate", + "ConstructionStartDate", + "LaunchDate", + "ScheduledDeliveryDate", + "CancelledDate", + "MinimumTemperature", + "MaximumPressure", + "Ammonia", + "VCM", + "Ethylene", + "UpdatedDate", + ] + + for k, r, in VesselsAPI.rename_keys.items(): + api_fields[api_fields.index(k)] = r + + snake_case_api_fields = list(map(_to_snake_case, api_fields)) + vessels_model_fields = list(Vessel.__dataclass_fields__) + snake_case_api_fields.sort() + vessels_model_fields.sort() + assert snake_case_api_fields == vessels_model_fields \ No newline at end of file diff --git a/version.txt b/version.txt index 68d8f15e..b0d36450 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -11.1.0 +12.0.0 \ No newline at end of file