Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -1,16 +1,20 @@
|
|
1 |
import pandas as pd
|
2 |
-
from itertools import permutations
|
3 |
from fastapi import FastAPI, File, UploadFile, Form, HTTPException, Request, Response
|
4 |
from fastapi.middleware.cors import CORSMiddleware
|
5 |
from pydantic import BaseModel
|
6 |
-
from typing import List
|
7 |
-
|
|
|
|
|
8 |
|
9 |
app = FastAPI()
|
|
|
|
|
10 |
|
11 |
app.add_middleware(
|
12 |
CORSMiddleware,
|
13 |
-
allow_origins=["*"], #
|
14 |
allow_credentials=True,
|
15 |
allow_methods=["*"],
|
16 |
allow_headers=["*"],
|
@@ -19,21 +23,22 @@ app.add_middleware(
|
|
19 |
@app.options("/{rest_of_path:path}")
|
20 |
async def preflight_handler(request: Request, rest_of_path: str) -> Response:
|
21 |
return Response(headers={
|
22 |
-
"Access-Control-Allow-Origin": "*", #
|
23 |
-
"Access-Control-Allow-Methods": "POST, GET, OPTIONS, PUT, DELETE",
|
24 |
-
"Access-Control-Allow-Headers": "*",
|
25 |
})
|
26 |
|
|
|
|
|
27 |
class Box:
|
28 |
def __init__(self, length, width, height, quantity, box_type):
|
29 |
self.length = length
|
30 |
self.width = width
|
31 |
self.height = height
|
32 |
-
self.quantity = quantity if quantity > 0 else 1
|
33 |
self.type = box_type
|
34 |
|
35 |
def rotated_dimensions(self):
|
36 |
-
# Return rotations that maximize base area and minimize height
|
37 |
possible_rotations = [
|
38 |
(self.length, self.width, self.height),
|
39 |
(self.length, self.height, self.width),
|
@@ -42,12 +47,12 @@ class Box:
|
|
42 |
(self.height, self.length, self.width),
|
43 |
(self.height, self.width, self.length)
|
44 |
]
|
45 |
-
# Sort by volume and base area to prioritize the best fit
|
46 |
return sorted(possible_rotations, key=lambda x: (x[0] * x[1], x[2]), reverse=True)
|
47 |
|
48 |
def volume(self):
|
49 |
return self.length * self.width * self.height
|
50 |
|
|
|
51 |
class Truck:
|
52 |
def __init__(self, length, width, height):
|
53 |
self.length = length
|
@@ -55,30 +60,50 @@ class Truck:
|
|
55 |
self.height = height
|
56 |
self.volume = length * width * height
|
57 |
self.placed_boxes = []
|
58 |
-
self.available_space = [(0, 0, 0, length, width, height)]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
59 |
|
60 |
-
|
|
|
|
|
|
|
|
|
|
|
61 |
best_fit = None
|
62 |
best_fit_space_index = -1
|
|
|
63 |
|
64 |
for rotation in box.rotated_dimensions():
|
65 |
for i, space in enumerate(self.available_space):
|
66 |
x, y, z, l, w, h = space
|
67 |
|
68 |
-
# Check if the box can fit in the current space
|
69 |
if rotation[0] <= l and rotation[1] <= w and rotation[2] <= h:
|
70 |
-
if not
|
|
|
|
|
|
|
|
|
|
|
71 |
best_fit = rotation
|
72 |
best_fit_space_index = i
|
|
|
73 |
|
74 |
if best_fit:
|
75 |
x, y, z, l, w, h = self.available_space[best_fit_space_index]
|
76 |
box_position = (x, y, z)
|
77 |
|
78 |
-
# Place the box in the truck
|
79 |
self.placed_boxes.append((best_fit, box_position))
|
80 |
|
81 |
-
# Update available space after placing the box
|
82 |
self.available_space.pop(best_fit_space_index)
|
83 |
self.update_space(best_fit, box_position, l, w, h)
|
84 |
return box_position
|
@@ -89,39 +114,84 @@ class Truck:
|
|
89 |
x, y, z = position
|
90 |
bl, bw, bh = box
|
91 |
|
92 |
-
# Create new spaces based on the placement of the new box
|
93 |
new_spaces = [
|
94 |
-
(x + bl, y, z, l - bl, w, h),
|
95 |
-
(x, y + bw, z, bl, w - bw, h),
|
96 |
-
(x, y, z + bh, bl, bw, h - bh),
|
97 |
]
|
98 |
|
99 |
-
# Filter out invalid spaces
|
100 |
new_spaces = [space for space in new_spaces if space[3] > 0 and space[4] > 0 and space[5] > 0]
|
101 |
|
102 |
-
# Add new valid spaces to the available_space list
|
103 |
self.available_space.extend(new_spaces)
|
104 |
-
|
105 |
-
# Sort available spaces to prioritize lower and more central spaces for stacking
|
106 |
self.available_space.sort(key=lambda space: (space[2], space[1], space[0]))
|
107 |
|
108 |
-
|
|
|
|
|
109 |
packed_positions = []
|
110 |
|
111 |
-
|
112 |
-
boxes.sort(key=lambda b: b.volume(), reverse=True)
|
113 |
|
114 |
-
|
115 |
-
|
116 |
-
for
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
121 |
|
122 |
return packed_positions
|
123 |
|
124 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
125 |
trucks = {
|
126 |
"TATA ACE": {"length": 7, "width": 4.8, "height": 4.8},
|
127 |
"ASHOK LEYLAND DOST": {"length": 7, "width": 4.8, "height": 4.8},
|
@@ -150,72 +220,179 @@ def suggest_truck(boxes):
|
|
150 |
"MCV": {"length": 19, "width": 7, "height": 7}
|
151 |
}
|
152 |
|
153 |
-
# Sort trucks by volume (smallest first)
|
154 |
sorted_trucks = sorted(trucks.items(), key=lambda t: t[1]['length'] * t[1]['width'] * t[1]['height'])
|
155 |
-
|
156 |
-
# Try packing the boxes in each truck, return the first one that fits all boxes
|
157 |
for truck_name, dimensions in sorted_trucks:
|
158 |
-
truck = Truck(dimensions['length'] * 12, dimensions['width'] * 12, dimensions['height'] * 12)
|
159 |
-
packed_positions =
|
160 |
-
|
161 |
-
|
162 |
-
|
|
|
163 |
return {"name": truck_name, "dimensions": dimensions}
|
164 |
-
|
165 |
return None
|
166 |
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
199 |
if autoSuggest:
|
200 |
-
|
201 |
-
suggested_truck = suggest_truck(boxes)
|
202 |
if suggested_truck:
|
|
|
203 |
length = suggested_truck['dimensions']['length']
|
204 |
width = suggested_truck['dimensions']['width']
|
205 |
height = suggested_truck['dimensions']['height']
|
206 |
else:
|
207 |
raise HTTPException(status_code=400, detail="No suitable truck found")
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
213 |
|
214 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
215 |
|
216 |
-
|
217 |
-
|
218 |
-
|
|
|
|
|
219 |
box_data = [
|
220 |
{
|
221 |
'length': box.length,
|
@@ -228,10 +405,12 @@ async def upload_file(
|
|
228 |
for box, pos in packed_positions
|
229 |
]
|
230 |
|
231 |
-
|
|
|
|
|
232 |
"boxes": box_data,
|
233 |
"truck": {
|
234 |
-
"name":
|
235 |
"dimensions": {
|
236 |
"length": length,
|
237 |
"width": width,
|
@@ -239,3 +418,76 @@ async def upload_file(
|
|
239 |
}
|
240 |
}
|
241 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import pandas as pd
|
2 |
+
from itertools import permutations, combinations
|
3 |
from fastapi import FastAPI, File, UploadFile, Form, HTTPException, Request, Response
|
4 |
from fastapi.middleware.cors import CORSMiddleware
|
5 |
from pydantic import BaseModel
|
6 |
+
from typing import List, Dict, Any
|
7 |
+
import uuid
|
8 |
+
import requests
|
9 |
+
import json
|
10 |
|
11 |
app = FastAPI()
|
12 |
+
stored_combinations = {}
|
13 |
+
processed_data_store: Dict[str, Dict[str, Any]] = {} # Store processed data with session_id
|
14 |
|
15 |
app.add_middleware(
|
16 |
CORSMiddleware,
|
17 |
+
allow_origins=["*"], # Adjust as needed for security
|
18 |
allow_credentials=True,
|
19 |
allow_methods=["*"],
|
20 |
allow_headers=["*"],
|
|
|
23 |
@app.options("/{rest_of_path:path}")
|
24 |
async def preflight_handler(request: Request, rest_of_path: str) -> Response:
|
25 |
return Response(headers={
|
26 |
+
"Access-Control-Allow-Origin": "*", # Adjust as needed for security
|
27 |
+
"Access-Control-Allow-Methods": "POST, GET, OPTIONS, PUT, DELETE",
|
28 |
+
"Access-Control-Allow-Headers": "*",
|
29 |
})
|
30 |
|
31 |
+
|
32 |
+
# Box and Truck classes
|
33 |
class Box:
|
34 |
def __init__(self, length, width, height, quantity, box_type):
|
35 |
self.length = length
|
36 |
self.width = width
|
37 |
self.height = height
|
38 |
+
self.quantity = quantity if quantity > 0 else 1
|
39 |
self.type = box_type
|
40 |
|
41 |
def rotated_dimensions(self):
|
|
|
42 |
possible_rotations = [
|
43 |
(self.length, self.width, self.height),
|
44 |
(self.length, self.height, self.width),
|
|
|
47 |
(self.height, self.length, self.width),
|
48 |
(self.height, self.width, self.length)
|
49 |
]
|
|
|
50 |
return sorted(possible_rotations, key=lambda x: (x[0] * x[1], x[2]), reverse=True)
|
51 |
|
52 |
def volume(self):
|
53 |
return self.length * self.width * self.height
|
54 |
|
55 |
+
|
56 |
class Truck:
|
57 |
def __init__(self, length, width, height):
|
58 |
self.length = length
|
|
|
60 |
self.height = height
|
61 |
self.volume = length * width * height
|
62 |
self.placed_boxes = []
|
63 |
+
self.available_space = [(0, 0, 0, length, width, height)]
|
64 |
+
|
65 |
+
def is_space_supported(self, position, box):
|
66 |
+
x, y, z = position
|
67 |
+
bl, bw, bh = box
|
68 |
+
|
69 |
+
if z == 0:
|
70 |
+
return True
|
71 |
+
|
72 |
+
for placed_box, placed_position in self.placed_boxes:
|
73 |
+
px, py, pz = placed_position
|
74 |
+
pl, pw, ph = placed_box
|
75 |
|
76 |
+
if (x >= px and x + bl <= px + pl) and (y >= py and y + bw <= py + pw) and (z == pz + ph):
|
77 |
+
return True
|
78 |
+
|
79 |
+
return False
|
80 |
+
|
81 |
+
def add_box_ffd(self, box):
|
82 |
best_fit = None
|
83 |
best_fit_space_index = -1
|
84 |
+
best_fit_volume_waste = float('inf')
|
85 |
|
86 |
for rotation in box.rotated_dimensions():
|
87 |
for i, space in enumerate(self.available_space):
|
88 |
x, y, z, l, w, h = space
|
89 |
|
|
|
90 |
if rotation[0] <= l and rotation[1] <= w and rotation[2] <= h:
|
91 |
+
if not self.is_space_supported((x, y, z), rotation):
|
92 |
+
continue
|
93 |
+
|
94 |
+
leftover_volume = (l * w * h) - (rotation[0] * rotation[1] * rotation[2])
|
95 |
+
|
96 |
+
if leftover_volume < best_fit_volume_waste:
|
97 |
best_fit = rotation
|
98 |
best_fit_space_index = i
|
99 |
+
best_fit_volume_waste = leftover_volume
|
100 |
|
101 |
if best_fit:
|
102 |
x, y, z, l, w, h = self.available_space[best_fit_space_index]
|
103 |
box_position = (x, y, z)
|
104 |
|
|
|
105 |
self.placed_boxes.append((best_fit, box_position))
|
106 |
|
|
|
107 |
self.available_space.pop(best_fit_space_index)
|
108 |
self.update_space(best_fit, box_position, l, w, h)
|
109 |
return box_position
|
|
|
114 |
x, y, z = position
|
115 |
bl, bw, bh = box
|
116 |
|
|
|
117 |
new_spaces = [
|
118 |
+
(x + bl, y, z, l - bl, w, h),
|
119 |
+
(x, y + bw, z, bl, w - bw, h),
|
120 |
+
(x, y, z + bh, bl, bw, h - bh),
|
121 |
]
|
122 |
|
|
|
123 |
new_spaces = [space for space in new_spaces if space[3] > 0 and space[4] > 0 and space[5] > 0]
|
124 |
|
|
|
125 |
self.available_space.extend(new_spaces)
|
|
|
|
|
126 |
self.available_space.sort(key=lambda space: (space[2], space[1], space[0]))
|
127 |
|
128 |
+
|
129 |
+
# Helper functions for box packing
|
130 |
+
def pack_boxes_ffd(truck, consignments):
|
131 |
packed_positions = []
|
132 |
|
133 |
+
consignments = sorted(consignments, key=lambda c: (not c[1], -sum(box.volume() * box.quantity for box in c[0])))
|
|
|
134 |
|
135 |
+
for consignment in consignments:
|
136 |
+
consignment_packed = []
|
137 |
+
for box in consignment[0]:
|
138 |
+
for _ in range(box.quantity):
|
139 |
+
position = truck.add_box_ffd(box)
|
140 |
+
if position is None:
|
141 |
+
break
|
142 |
+
consignment_packed.append((box, position))
|
143 |
+
|
144 |
+
if len(consignment_packed) == sum(box.quantity for box in consignment[0]):
|
145 |
+
packed_positions.extend(consignment_packed)
|
146 |
+
else:
|
147 |
+
break
|
148 |
|
149 |
return packed_positions
|
150 |
|
151 |
+
|
152 |
+
def find_max_consignments_combinations(truck, consignments, combination_limit=100):
|
153 |
+
combinations_that_fit = []
|
154 |
+
consignments_sorted = sorted(consignments, key=lambda c: (not c[1], -sum(box.volume() * box.quantity for box in c[0])))
|
155 |
+
|
156 |
+
combo_count = 0
|
157 |
+
max_consignments_fitted = 0
|
158 |
+
|
159 |
+
for r in range(1, len(consignments_sorted) + 1):
|
160 |
+
for combo in combinations(consignments_sorted, r):
|
161 |
+
combo_count += 1
|
162 |
+
print(f"Checking combination {combo_count}: {[f'Consignment {boxes[0].type}' for boxes, _ in combo]}")
|
163 |
+
|
164 |
+
if combo_count > combination_limit:
|
165 |
+
print(f"Reached combination limit of {combination_limit}. Stopping further checks.")
|
166 |
+
break
|
167 |
+
|
168 |
+
temp_truck = Truck(truck.length, truck.width, truck.height)
|
169 |
+
|
170 |
+
total_volume = sum(sum(box.volume() * box.quantity for box in consignment[0]) for consignment in combo)
|
171 |
+
|
172 |
+
if total_volume > temp_truck.volume:
|
173 |
+
continue
|
174 |
+
|
175 |
+
try:
|
176 |
+
packed_positions = pack_boxes_ffd(temp_truck, combo)
|
177 |
+
if packed_positions:
|
178 |
+
print(f"Combination {combo_count} fits with {len(combo)} consignments")
|
179 |
+
combinations_that_fit.append(combo)
|
180 |
+
max_consignments_fitted = max(max_consignments_fitted, len(combo))
|
181 |
+
|
182 |
+
if len(combinations_that_fit) >= 5:
|
183 |
+
break
|
184 |
+
except HTTPException:
|
185 |
+
print(f"Combination {combo_count} does not fit")
|
186 |
+
continue
|
187 |
+
|
188 |
+
if combo_count > combination_limit:
|
189 |
+
break
|
190 |
+
|
191 |
+
return combinations_that_fit[-5:]
|
192 |
+
|
193 |
+
|
194 |
+
def suggest_truck(consignments):
|
195 |
trucks = {
|
196 |
"TATA ACE": {"length": 7, "width": 4.8, "height": 4.8},
|
197 |
"ASHOK LEYLAND DOST": {"length": 7, "width": 4.8, "height": 4.8},
|
|
|
220 |
"MCV": {"length": 19, "width": 7, "height": 7}
|
221 |
}
|
222 |
|
|
|
223 |
sorted_trucks = sorted(trucks.items(), key=lambda t: t[1]['length'] * t[1]['width'] * t[1]['height'])
|
224 |
+
|
|
|
225 |
for truck_name, dimensions in sorted_trucks:
|
226 |
+
truck = Truck(dimensions['length'] * 12, dimensions['width'] * 12, dimensions['height'] * 12)
|
227 |
+
packed_positions = pack_boxes_ffd(truck, consignments)
|
228 |
+
|
229 |
+
total_boxes = sum(box.quantity for consignment in consignments for box in consignment[0])
|
230 |
+
|
231 |
+
if len(packed_positions) == total_boxes:
|
232 |
return {"name": truck_name, "dimensions": dimensions}
|
233 |
+
|
234 |
return None
|
235 |
|
236 |
+
|
237 |
+
# Models
|
238 |
+
class BoxData(BaseModel):
|
239 |
+
PieceLength: float
|
240 |
+
PieceBreadth: float
|
241 |
+
PieceHeight: float
|
242 |
+
Priority: int
|
243 |
+
|
244 |
+
|
245 |
+
class ConsignmentData(BaseModel):
|
246 |
+
ConsignmentNo: str
|
247 |
+
Priority: int
|
248 |
+
boxes: List[BoxData]
|
249 |
+
|
250 |
+
|
251 |
+
class SubmitDataRequest(BaseModel):
|
252 |
+
truck_name: str
|
253 |
+
autoSuggest: bool
|
254 |
+
consignments_data: List[ConsignmentData]
|
255 |
+
|
256 |
+
from fastapi.responses import RedirectResponse
|
257 |
+
|
258 |
+
@app.post("/submit_data/")
|
259 |
+
async def submit_data(request: SubmitDataRequest):
|
260 |
+
"""
|
261 |
+
Endpoint to receive data from the client via API.
|
262 |
+
"""
|
263 |
+
truck_name = request.truck_name
|
264 |
+
autoSuggest = request.autoSuggest
|
265 |
+
consignments_data = request.consignments_data # This is already parsed from JSON
|
266 |
+
|
267 |
+
global stored_combinations, processed_data_store
|
268 |
+
|
269 |
+
# Parse consignments and calculate total quantity based on box dimensions
|
270 |
+
consignments = []
|
271 |
+
for consignment_data in consignments_data:
|
272 |
+
consignment_no = consignment_data.ConsignmentNo # ConsignmentNo is now accessible as a string
|
273 |
+
is_priority = consignment_data.Priority
|
274 |
+
|
275 |
+
# Aggregate the boxes with same dimensions
|
276 |
+
box_aggregation = {}
|
277 |
+
for box_data in consignment_data.boxes:
|
278 |
+
dimensions = (box_data.PieceLength, box_data.PieceBreadth, box_data.PieceHeight)
|
279 |
+
|
280 |
+
if dimensions in box_aggregation:
|
281 |
+
box_aggregation[dimensions]['Quantity'] += 1 # Increment the count if dimensions are the same
|
282 |
+
else:
|
283 |
+
box_aggregation[dimensions] = {
|
284 |
+
'PieceLength': box_data.PieceLength,
|
285 |
+
'PieceBreadth': box_data.PieceBreadth,
|
286 |
+
'PieceHeight': box_data.PieceHeight,
|
287 |
+
'Quantity': 1 # Initialize count to 1
|
288 |
+
}
|
289 |
+
|
290 |
+
# Convert aggregated boxes back to the expected list format
|
291 |
+
consignment_boxes = [
|
292 |
+
Box(
|
293 |
+
box['PieceLength'] / 2.54, # Convert cm to inches
|
294 |
+
box['PieceBreadth'] / 2.54,
|
295 |
+
box['PieceHeight'] / 2.54,
|
296 |
+
box['Quantity'],
|
297 |
+
f"Consignment {consignment_no} ({'Priority' if is_priority else 'Non-Priority'})"
|
298 |
+
)
|
299 |
+
for box in box_aggregation.values()
|
300 |
+
]
|
301 |
+
consignments.append((consignment_boxes, is_priority))
|
302 |
+
|
303 |
+
# Handle autoSuggest logic and other functionality here
|
304 |
if autoSuggest:
|
305 |
+
suggested_truck = suggest_truck(consignments)
|
|
|
306 |
if suggested_truck:
|
307 |
+
truck_name = suggested_truck["name"]
|
308 |
length = suggested_truck['dimensions']['length']
|
309 |
width = suggested_truck['dimensions']['width']
|
310 |
height = suggested_truck['dimensions']['height']
|
311 |
else:
|
312 |
raise HTTPException(status_code=400, detail="No suitable truck found")
|
313 |
+
else:
|
314 |
+
# Assuming truck dimensions are known based on truck_name
|
315 |
+
trucks = {
|
316 |
+
"TATA ACE": {"length": 7, "width": 4.8, "height": 4.8},
|
317 |
+
"ASHOK LEYLAND DOST": {"length": 7, "width": 4.8, "height": 4.8},
|
318 |
+
"MAHINDRA BOLERO PICK UP": {"length": 8, "width": 5, "height": 4.8},
|
319 |
+
"ASHOK LEYLAND BADA DOST": {"length": 9.5, "width": 5.5, "height": 5},
|
320 |
+
"TATA 407": {"length": 9, "width": 5.5, "height": 5},
|
321 |
+
"EICHER 14 FEET": {"length": 14, "width": 6, "height": 6.5},
|
322 |
+
"EICHER 17 FEET": {"length": 17, "width": 6, "height": 7},
|
323 |
+
"EICHER 19 FEET": {"length": 19, "width": 7, "height": 7},
|
324 |
+
"TATA 22 FEET": {"length": 22, "width": 7.5, "height": 7},
|
325 |
+
"TATA TRUCK (6 TYRE)": {"length": 17.5, "width": 7, "height": 7},
|
326 |
+
"TAURUS 16 T (10 TYRE)": {"length": 21, "width": 7.2, "height": 7},
|
327 |
+
"TAURUS 21 T (12 TYRE)": {"length": 24, "width": 7.3, "height": 7},
|
328 |
+
"TAURUS 25 T (14 TYRE)": {"length": 28, "width": 7.8, "height": 7},
|
329 |
+
"CONTAINER 20 FT": {"length": 20, "width": 8, "height": 8},
|
330 |
+
"CONTAINER 32 FT SXL": {"length": 32, "width": 8, "height": 8},
|
331 |
+
"CONTAINER 32 FT MXL": {"length": 32, "width": 8, "height": 8},
|
332 |
+
"CONTAINER 32 FT SXL / MXL HQ": {"length": 32, "width": 8, "height": 10},
|
333 |
+
"20 FEET OPEN ALL SIDE (ODC)": {"length": 20, "width": 8, "height": 8},
|
334 |
+
"28-32 FEET OPEN-TRAILOR JCB ODC": {"length": 28, "width": 8, "height": 8},
|
335 |
+
"32 FEET OPEN-TRAILOR ODC": {"length": 32, "width": 8, "height": 8},
|
336 |
+
"40 FEET OPEN-TRAILOR ODC": {"length": 40, "width": 8, "height": 8},
|
337 |
+
"SCV": {"length": 5, "width": 12, "height": 6},
|
338 |
+
"LCV": {"length": 11, "width": 5, "height": 5},
|
339 |
+
"ICV": {"length": 16, "width": 6.5, "height": 6.5},
|
340 |
+
"MCV": {"length": 19, "width": 7, "height": 7}
|
341 |
+
}
|
342 |
+
|
343 |
+
if truck_name not in trucks:
|
344 |
+
raise HTTPException(status_code=400, detail="Invalid truck name provided")
|
345 |
+
|
346 |
+
truck_dimensions = trucks[truck_name]
|
347 |
+
length = truck_dimensions['length']
|
348 |
+
width = truck_dimensions['width']
|
349 |
+
height = truck_dimensions['height']
|
350 |
|
351 |
+
# Initialize the Truck object and pack boxes
|
352 |
+
truck = Truck(length * 12, width * 12, height * 12) # Convert feet to inches
|
353 |
+
packed_positions = pack_boxes_ffd(truck, consignments)
|
354 |
+
|
355 |
+
total_boxes = sum(box.quantity for consignment in consignments for box in consignment[0])
|
356 |
+
|
357 |
+
if len(packed_positions) < total_boxes:
|
358 |
+
stored_combinations = find_max_consignments_combinations(truck, consignments)
|
359 |
+
if stored_combinations:
|
360 |
+
combinations_info = []
|
361 |
+
for index, combo in enumerate(stored_combinations):
|
362 |
+
total_quantity = sum(box.quantity for boxes, _ in combo for box in boxes)
|
363 |
+
combination_info = {
|
364 |
+
"combination_number": index + 1,
|
365 |
+
"consignments": [
|
366 |
+
{
|
367 |
+
"consignment_number": f"{boxes[0].type.split(' ')[1]}",
|
368 |
+
"priority_status": boxes[0].type.split('(')[1].strip(')')
|
369 |
+
}
|
370 |
+
for boxes, _ in combo
|
371 |
+
],
|
372 |
+
"total_quantity": total_quantity # Add total quantity for this combination
|
373 |
+
}
|
374 |
+
combinations_info.append(combination_info)
|
375 |
+
|
376 |
+
# Generate a unique session_id
|
377 |
+
session_id = str(uuid.uuid4())
|
378 |
+
processed_data_store[session_id] = {
|
379 |
+
"error": "Not all consignments fit.",
|
380 |
+
"combinations_that_fit": combinations_info,
|
381 |
+
"truck": {
|
382 |
+
"name": truck_name,
|
383 |
+
"dimensions": {
|
384 |
+
"length": length,
|
385 |
+
"width": width,
|
386 |
+
"height": height
|
387 |
+
}
|
388 |
+
}
|
389 |
+
}
|
390 |
|
391 |
+
# Redirect to the visualization page
|
392 |
+
return RedirectResponse(url=f"https://66e6a59e6eaa79674a3c7c47--stellar-jelly-eac527.netlify.app?session_id={session_id}", status_code=302)
|
393 |
+
|
394 |
+
|
395 |
+
# Prepare box data for visualization
|
396 |
box_data = [
|
397 |
{
|
398 |
'length': box.length,
|
|
|
405 |
for box, pos in packed_positions
|
406 |
]
|
407 |
|
408 |
+
# Generate a unique session_id
|
409 |
+
session_id = str(uuid.uuid4())
|
410 |
+
processed_data_store[session_id] = {
|
411 |
"boxes": box_data,
|
412 |
"truck": {
|
413 |
+
"name": truck_name,
|
414 |
"dimensions": {
|
415 |
"length": length,
|
416 |
"width": width,
|
|
|
418 |
}
|
419 |
}
|
420 |
}
|
421 |
+
|
422 |
+
# Redirect to the visualization page
|
423 |
+
return RedirectResponse(url=f"https://66e6a59e6eaa79674a3c7c47--stellar-jelly-eac527.netlify.app?session_id={session_id}", status_code=302)
|
424 |
+
|
425 |
+
|
426 |
+
|
427 |
+
@app.post("/load_combination/")
|
428 |
+
async def load_combination(
|
429 |
+
index: int = Form(...),
|
430 |
+
truck_length: float = Form(...),
|
431 |
+
truck_width: float = Form(...),
|
432 |
+
truck_height: float = Form(...),
|
433 |
+
truck_name: str = Form(...)
|
434 |
+
):
|
435 |
+
"""
|
436 |
+
Endpoint to visualize the selected combination.
|
437 |
+
"""
|
438 |
+
print(f"Received index: {index}")
|
439 |
+
print(f"Received truck details: {truck_length}L x {truck_width}W x {truck_height}H, Name: {truck_name}")
|
440 |
+
|
441 |
+
# Ensure the index is within the valid range
|
442 |
+
if index >= len(stored_combinations):
|
443 |
+
raise HTTPException(status_code=400, detail="Invalid combination index")
|
444 |
+
|
445 |
+
selected_combination = stored_combinations[index]
|
446 |
+
|
447 |
+
# Create a Truck object using the received truck dimensions
|
448 |
+
truck = Truck(truck_length * 12, truck_width * 12, truck_height * 12) # Convert feet to inches
|
449 |
+
|
450 |
+
# Unpack and structure the combination data for the pack_boxes_ffd function
|
451 |
+
consignments = [
|
452 |
+
(boxes, priority) for boxes, priority in selected_combination # Unpack the combination as expected
|
453 |
+
]
|
454 |
+
|
455 |
+
# Generate the box data for this combination
|
456 |
+
packed_positions = pack_boxes_ffd(truck, consignments)
|
457 |
+
|
458 |
+
# Prepare the box data
|
459 |
+
box_data = [
|
460 |
+
{
|
461 |
+
'length': box.length,
|
462 |
+
'width': box.width,
|
463 |
+
'height': box.height,
|
464 |
+
'type': box.type,
|
465 |
+
'quantity': box.quantity,
|
466 |
+
'position': {'x': pos[0], 'y': pos[1], 'z': pos[2]}
|
467 |
+
}
|
468 |
+
for box, pos in packed_positions
|
469 |
+
]
|
470 |
+
|
471 |
+
# Return the box data and truck information to the frontend
|
472 |
+
return {
|
473 |
+
"boxes": box_data,
|
474 |
+
"truck": {
|
475 |
+
"name": truck_name,
|
476 |
+
"dimensions": {
|
477 |
+
"length": truck_length,
|
478 |
+
"width": truck_width,
|
479 |
+
"height": truck_height
|
480 |
+
}
|
481 |
+
}
|
482 |
+
}
|
483 |
+
|
484 |
+
|
485 |
+
@app.get("/get_visualization/{session_id}/")
|
486 |
+
async def get_visualization(session_id: str):
|
487 |
+
"""
|
488 |
+
Endpoint for the frontend to fetch processed data using session_id.
|
489 |
+
"""
|
490 |
+
if session_id not in processed_data_store:
|
491 |
+
raise HTTPException(status_code=404, detail="Session ID not found")
|
492 |
+
|
493 |
+
return processed_data_store[session_id]
|