Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions tests/api/test_fixture_flattening.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
from unittest import TestCase

from vortexasdk.api.entity_flattening import (
convert_to_flat_dict,
convert_fixture_to_flat_dict,
)


class TestFixtureFlattening(TestCase):
"""Test that convert_fixture_to_flat_dict groups corporate_entities by layer."""

FIXTURE = {
"id": "abc123",
"vessel": {
"id": "v1",
"name": "ALPINE EAGLE",
"imo": 9480980,
"mmsi": 255804460,
"dwt": 300000,
"cubic_capacity": 330000,
"vessel_class": "vlcc",
"classes": [],
"corporate_entities": [
{
"id": "ec1",
"layer": "effective_controller",
"label": "Frontline Ltd",
"probability": 1,
"source": "external",
},
{
"id": "tc1",
"layer": "time_charterer",
"label": "Shell",
"probability": 0.8,
"source": "model",
"start_timestamp": "2023-06-01",
"end_timestamp": "2024-06-01",
},
],
"tags": [],
"flag": [],
"scrubber": [],
},
"tonnes": 270000,
"laycan_from": "2024-01-01T00:00:00+0000",
"laycan_to": "2024-01-03T00:00:00+0000",
"fixing_timestamp": "2023-12-28T14:30:00+0000",
"vtx_fulfilled": True,
"origin": {"id": "org1", "label": "Arabian Gulf"},
"destination": {"id": "dst1", "label": "South Korea"},
"product": {"id": "prd1", "label": "Crude"},
"charterer": {"id": "c1", "label": "Trafigura"},
}

def test_generic_flattener_uses_numeric_indices(self):
flat = convert_to_flat_dict(self.FIXTURE, columns="all")
assert "vessel.corporate_entities.0.label" in flat
assert (
"vessel.corporate_entities.effective_controller.label" not in flat
)

def test_fixture_flattener_groups_by_layer(self):
flat = convert_fixture_to_flat_dict(self.FIXTURE, columns="all")
assert (
flat["vessel.corporate_entities.effective_controller.label"]
== "Frontline Ltd"
)
assert (
flat["vessel.corporate_entities.time_charterer.label"] == "Shell"
)

def test_fixture_flattener_preserves_flat_fields(self):
flat = convert_fixture_to_flat_dict(self.FIXTURE, columns="all")
assert flat["vessel.name"] == "ALPINE EAGLE"
assert flat["vessel.imo"] == 9480980
assert flat["tonnes"] == 270000
assert flat["charterer.label"] == "Trafigura"

def test_fixture_flattener_column_filter(self):
cols = [
"vessel.name",
"vessel.corporate_entities.effective_controller.label",
]
flat = convert_fixture_to_flat_dict(self.FIXTURE, columns=cols)
assert set(flat.keys()) == set(cols)
23 changes: 23 additions & 0 deletions vortexasdk/api/entity_flattening.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,5 +84,28 @@ def _flatten_attributes(dictionary: Dict, key: str) -> Dict:
return copied_dict


def convert_fixture_to_flat_dict(
fixture: Dict, columns: Union[Literal["all"], List[str]] = "all"
) -> Dict:
"""Convert nested `Fixture` object to flat dictionary, keeping *cols*."""
as_dict = _group_fixture_attributes_by_layer(fixture)

formatted = flatten_dictionary(as_dict)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would appreciate some input on this. I think because changing to tonnes could be a breaking change it is doing this to make sure that people don't have to modify anything but idk if that's a good practice


if columns == "all":
return formatted
else:
return {k: v for k, v in formatted.items() if k in columns}


def _group_fixture_attributes_by_layer(fixture: Dict) -> Dict:
"""Group relevant `Fixture` attributes by `Entity.layer`."""
copied = dict(fixture)
vessel = copied.get("vessel")
if isinstance(vessel, dict) and isinstance(vessel.get("corporate_entities"), list):
copied["vessel"] = _flatten_vessel_entity(vessel)
return copied


def _group_by_layer(entity_list: List[Dict]) -> Dict:
return {e["layer"]: e for e in entity_list}
2 changes: 1 addition & 1 deletion vortexasdk/api/fixture.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class Fixture(BaseModel):
vessel: Optional[VesselEntity] = None
laycan_from: Optional[str] = None
laycan_to: Optional[str] = None
tones: Optional[int] = None
tonnes: Optional[int] = None
fixing_timestamp: Optional[str] = None
vtx_fulfilled: Optional[bool] = None
origin: Optional[Entity] = None
Expand Down
2 changes: 1 addition & 1 deletion vortexasdk/endpoints/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ def search(

returns

| vessel.name | tones | origin.label | product.label |
| vessel.name | tonnes | origin.label | product.label |
| ------------------------ | ---------------- | ------------ | ------------- |
| ALPINE EAGLE | 454.96048964485 | UK | Crude |

Expand Down
74 changes: 38 additions & 36 deletions vortexasdk/endpoints/fixtures_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import pandas as pd

from vortexasdk.api.fixture import Fixture
from vortexasdk.api.entity_flattening import convert_to_flat_dict
from vortexasdk.api.entity_flattening import convert_fixture_to_flat_dict
from vortexasdk.api.search_result import Result
from vortexasdk.logger import get_logger
from vortexasdk.result_conversions import create_dataframe, create_list
Expand All @@ -20,7 +20,7 @@
"vessel.name",
"laycan_from",
"laycan_to",
"tones",
"tonnes",
"fixing_timestamp",
"vtx_fulfilled",
"destination.label",
Expand Down Expand Up @@ -49,40 +49,40 @@ def to_df(
columns: The Fixtures columns we want in the dataframe.
Defaults to `columns = [
"id",
'vessels.corporate_entities.charterer.id',
'vessels.corporate_entities.charterer.label',
'vessels.corporate_entities.charterer.layer',
'vessels.corporate_entities.charterer.probability',
'vessels.corporate_entities.charterer.source',
'vessels.corporate_entities.effective_controller.id',
'vessels.corporate_entities.effective_controller.label',
'vessels.corporate_entities.effective_controller.layer',
'vessels.corporate_entities.effective_controller.probability',
'vessels.corporate_entities.effective_controller.source',
'vessels.corporate_entities.time_charterer.end_timestamp',
'vessels.corporate_entities.time_charterer.id',
'vessels.corporate_entities.time_charterer.label',
'vessels.corporate_entities.time_charterer.layer',
'vessels.corporate_entities.time_charterer.probability',
'vessels.corporate_entities.time_charterer.source',
'vessels.corporate_entities.time_charterer.start_timestamp',
'vessels.cubic_capacity',
'vessels.dwt',
'vessels.end_timestamp',
'vessels.id',
'vessels.imo',
'vessels.mmsi',
'vessels.name',
'vessels.start_timestamp',
'vessels.status',
'vessels.tags.end_timestamp',
'vessels.tags.start_timestamp',
'vessels.tags.tag',
'vessels.vessel_class',
'vessels.voyage_id',
'vessel.corporate_entities.charterer.id',
Comment thread
hansenfan marked this conversation as resolved.
'vessel.corporate_entities.charterer.label',
'vessel.corporate_entities.charterer.layer',
'vessel.corporate_entities.charterer.probability',
'vessel.corporate_entities.charterer.source',
'vessel.corporate_entities.effective_controller.id',
'vessel.corporate_entities.effective_controller.label',
'vessel.corporate_entities.effective_controller.layer',
'vessel.corporate_entities.effective_controller.probability',
'vessel.corporate_entities.effective_controller.source',
'vessel.corporate_entities.time_charterer.end_timestamp',
'vessel.corporate_entities.time_charterer.id',
'vessel.corporate_entities.time_charterer.label',
'vessel.corporate_entities.time_charterer.layer',
'vessel.corporate_entities.time_charterer.probability',
'vessel.corporate_entities.time_charterer.source',
'vessel.corporate_entities.time_charterer.start_timestamp',
'vessel.cubic_capacity',
'vessel.dwt',
'vessel.end_timestamp',
'vessel.id',
'vessel.imo',
'vessel.mmsi',
'vessel.name',
'vessel.start_timestamp',
'vessel.status',
'vessel.tags.end_timestamp',
'vessel.tags.start_timestamp',
'vessel.tags.tag',
'vessel.vessel_class',
'vessel.voyage_id',
"laycan_from",
"laycan_to",
"tones",
"tonnes",
"fixing_timestamp",
"vtx_fulfilled",
"destination.label",
Expand All @@ -103,7 +103,7 @@ def to_df(
"vessel.name",
"laycan_from",
"laycan_to",
"tones",
"tonnes",
"fixing_timestamp",
"vtx_fulfilled",
"destination.label",
Expand All @@ -117,7 +117,9 @@ def to_df(
`pd.DataFrame` of Fixtures.
"""

flatten = functools.partial(convert_to_flat_dict, columns=columns)
flatten = functools.partial(
convert_fixture_to_flat_dict, columns=columns
)

with Pool(os.cpu_count()) as pool:
records = pool.map(flatten, super().to_list())
Expand Down
2 changes: 1 addition & 1 deletion vortexasdk/version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "1.0.29"
__version__ = "1.0.30a1"
Loading