File size: 5,088 Bytes
e40294e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
from dataclasses import dataclass
from enum import Enum
from typing import TYPE_CHECKING, ClassVar

if TYPE_CHECKING:
    from .domain import Trolley


class Side(Enum):
    """Available shelving sides where products can be located."""
    LEFT = "LEFT"
    RIGHT = "RIGHT"


class Column(Enum):
    """Defines the warehouse columns."""
    COL_A = 'A'
    COL_B = 'B'
    COL_C = 'C'
    COL_D = 'D'
    COL_E = 'E'


class Row(Enum):
    """Defines the warehouse rows."""
    ROW_1 = 1
    ROW_2 = 2
    ROW_3 = 3


@dataclass
class Shelving:
    """
    Represents a products container. Each shelving has two sides where
    products can be stored, and a number of rows.
    """
    id: str
    x: int  # Absolute x position of shelving's left bottom corner
    y: int  # Absolute y position of shelving's left bottom corner

    ROWS_SIZE: ClassVar[int] = 10


@dataclass
class WarehouseLocation:
    """
    Represents a location in the warehouse where a product can be stored.
    """
    shelving_id: str
    side: Side
    row: int

    def __str__(self) -> str:
        return f"WarehouseLocation(shelving={self.shelving_id}, side={self.side.name}, row={self.row})"


def new_shelving_id(column: Column, row: Row) -> str:
    """Create a shelving ID from column and row."""
    return f"({column.value},{row.value})"


# Warehouse constants
SHELVING_WIDTH = 2   # meters
SHELVING_HEIGHT = 10  # meters
SHELVING_PADDING = 3  # spacing between shelvings in meters

# Initialize static shelving map
SHELVING_MAP: dict[str, Shelving] = {}

def _init_shelving_map():
    """Initialize the warehouse shelving grid."""
    shelving_x = 0
    for col in Column:
        shelving_y = 0
        for row in Row:
            shelving_id = new_shelving_id(col, row)
            SHELVING_MAP[shelving_id] = Shelving(
                id=shelving_id,
                x=shelving_x,
                y=shelving_y
            )
            shelving_y += SHELVING_HEIGHT + SHELVING_PADDING
        shelving_x += SHELVING_WIDTH + SHELVING_PADDING

_init_shelving_map()


def get_absolute_x(shelving: Shelving, location: WarehouseLocation) -> int:
    """Calculate absolute X position of a location."""
    if location.side == Side.LEFT:
        return shelving.x
    else:
        return shelving.x + SHELVING_WIDTH


def get_absolute_y(shelving: Shelving, location: WarehouseLocation) -> int:
    """Calculate absolute Y position of a location."""
    return shelving.y + location.row


def calculate_best_y_distance_in_shelving_row(start_row: int, end_row: int) -> int:
    """Calculate the best Y distance when crossing a shelving."""
    north_direction = start_row + end_row
    south_direction = (SHELVING_HEIGHT - start_row) + (SHELVING_HEIGHT - end_row)
    return min(north_direction, south_direction)


def calculate_distance(start: WarehouseLocation, end: WarehouseLocation) -> int:
    """
    Calculate distance in meters between two locations considering warehouse structure.
    """
    start_shelving = SHELVING_MAP.get(start.shelving_id)
    if start_shelving is None:
        raise IndexError(f"Shelving: {start.shelving_id} was not found in current Warehouse structure.")

    end_shelving = SHELVING_MAP.get(end.shelving_id)
    if end_shelving is None:
        raise IndexError(f"Shelving: {end.shelving_id} was not found in current Warehouse structure.")

    delta_x = 0

    start_x = get_absolute_x(start_shelving, start)
    start_y = get_absolute_y(start_shelving, start)
    end_x = get_absolute_x(end_shelving, end)
    end_y = get_absolute_y(end_shelving, end)

    if start_shelving == end_shelving:
        # Same shelving
        if start.side == end.side:
            # Same side - just vertical distance
            delta_y = abs(start_y - end_y)
        else:
            # Different side - calculate shortest walk around
            delta_x = SHELVING_WIDTH
            delta_y = calculate_best_y_distance_in_shelving_row(start.row, end.row)
    elif start_shelving.y == end_shelving.y:
        # Different shelvings but on same warehouse row
        if abs(start_x - end_x) == SHELVING_PADDING:
            # Neighbor shelvings with contiguous sides
            delta_x = SHELVING_PADDING
            delta_y = abs(start_y - end_y)
        else:
            # Other combinations in same warehouse row
            delta_x = abs(start_x - end_x)
            delta_y = calculate_best_y_distance_in_shelving_row(start.row, end.row)
    else:
        # Shelvings on different warehouse rows
        delta_x = abs(start_x - end_x)
        delta_y = abs(start_y - end_y)

    return delta_x + delta_y


def calculate_distance_to_travel(trolley: "Trolley") -> int:
    """Calculate total distance a trolley needs to travel through its steps."""
    distance = 0
    previous_location = trolley.location

    for step in trolley.steps:
        distance += calculate_distance(previous_location, step.location)
        previous_location = step.location

    # Return trip to origin
    distance += calculate_distance(previous_location, trolley.location)
    return distance