Spaces:
Running
Running
hotfix: replace Google Maps API with local estimation in optimizer
Browse files- Disabled Google Maps `Compute Routes` and `Distance Matrix` APIs in the optimization loop to prevent billing overages.
- Swapped the underlying algorithm with a local Haversine-based distance estimator.
- This is an emergency fix to resolve the high API usage incident.
src/optimization/graph/graph_builder.py
CHANGED
|
@@ -8,7 +8,7 @@ from typing import List, Dict, Tuple, Any
|
|
| 8 |
import numpy as np
|
| 9 |
|
| 10 |
from src.infra.logger import get_logger
|
| 11 |
-
from src.services.
|
| 12 |
|
| 13 |
from ..models.internal_models import _Task, _Location, _Graph
|
| 14 |
|
|
@@ -25,8 +25,8 @@ class GraphBuilder:
|
|
| 25 |
- 計算距離/時間矩陣
|
| 26 |
"""
|
| 27 |
|
| 28 |
-
def __init__(self,
|
| 29 |
-
self.
|
| 30 |
|
| 31 |
def build_graph(
|
| 32 |
self,
|
|
@@ -59,7 +59,7 @@ class GraphBuilder:
|
|
| 59 |
duration_matrix = np.zeros((1, 1), dtype=int)
|
| 60 |
distance_matrix = np.zeros((1, 1), dtype=int)
|
| 61 |
else:
|
| 62 |
-
duration_matrix, distance_matrix = self._calculate_matrices(locations
|
| 63 |
|
| 64 |
# 3. 返回圖
|
| 65 |
return _Graph(
|
|
@@ -122,7 +122,6 @@ class GraphBuilder:
|
|
| 122 |
def _calculate_matrices(
|
| 123 |
self,
|
| 124 |
locations: List[Dict[str, float]],
|
| 125 |
-
travel_mode="DRIVE",
|
| 126 |
) -> Tuple[np.ndarray, np.ndarray]:
|
| 127 |
"""
|
| 128 |
計算距離/時間矩陣
|
|
@@ -134,12 +133,9 @@ class GraphBuilder:
|
|
| 134 |
"""
|
| 135 |
locations_dict = [{"lat": loc["lat"], "lng": loc["lng"]} for loc in locations]
|
| 136 |
|
| 137 |
-
compute_route_result = self.
|
| 138 |
-
locations_dict,
|
| 139 |
-
locations_dict
|
| 140 |
-
travel_mode=travel_mode,
|
| 141 |
-
routing_preference="TRAFFIC_UNAWARE",
|
| 142 |
-
)
|
| 143 |
|
| 144 |
duration_matrix = np.array(compute_route_result["duration_matrix"])
|
| 145 |
distance_matrix = np.array(compute_route_result["distance_matrix"])
|
|
|
|
| 8 |
import numpy as np
|
| 9 |
|
| 10 |
from src.infra.logger import get_logger
|
| 11 |
+
from src.services.local_route_estimator import LocalRouteEstimator
|
| 12 |
|
| 13 |
from ..models.internal_models import _Task, _Location, _Graph
|
| 14 |
|
|
|
|
| 25 |
- 計算距離/時間矩陣
|
| 26 |
"""
|
| 27 |
|
| 28 |
+
def __init__(self, **kwargs):
|
| 29 |
+
self.estimator = LocalRouteEstimator()
|
| 30 |
|
| 31 |
def build_graph(
|
| 32 |
self,
|
|
|
|
| 59 |
duration_matrix = np.zeros((1, 1), dtype=int)
|
| 60 |
distance_matrix = np.zeros((1, 1), dtype=int)
|
| 61 |
else:
|
| 62 |
+
duration_matrix, distance_matrix = self._calculate_matrices(locations)
|
| 63 |
|
| 64 |
# 3. 返回圖
|
| 65 |
return _Graph(
|
|
|
|
| 122 |
def _calculate_matrices(
|
| 123 |
self,
|
| 124 |
locations: List[Dict[str, float]],
|
|
|
|
| 125 |
) -> Tuple[np.ndarray, np.ndarray]:
|
| 126 |
"""
|
| 127 |
計算距離/時間矩陣
|
|
|
|
| 133 |
"""
|
| 134 |
locations_dict = [{"lat": loc["lat"], "lng": loc["lng"]} for loc in locations]
|
| 135 |
|
| 136 |
+
compute_route_result = self.estimator.compute_route_matrix(
|
| 137 |
+
origins=locations_dict,
|
| 138 |
+
destinations=locations_dict)
|
|
|
|
|
|
|
|
|
|
| 139 |
|
| 140 |
duration_matrix = np.array(compute_route_result["duration_matrix"])
|
| 141 |
distance_matrix = np.array(compute_route_result["distance_matrix"])
|
src/optimization/test/_solver_test.py
CHANGED
|
@@ -311,8 +311,7 @@ def main():
|
|
| 311 |
from src.infra.config import get_settings
|
| 312 |
settings = get_settings()
|
| 313 |
# 創建求解器
|
| 314 |
-
solver = TSPTWSolver(
|
| 315 |
-
time_limit_seconds=5, verbose=True)
|
| 316 |
|
| 317 |
# 設置參數
|
| 318 |
start_location = {"lat": 25.0400, "lng": 121.5300}
|
|
|
|
| 311 |
from src.infra.config import get_settings
|
| 312 |
settings = get_settings()
|
| 313 |
# 創建求解器
|
| 314 |
+
solver = TSPTWSolver(time_limit_seconds=5, verbose=True)
|
|
|
|
| 315 |
|
| 316 |
# 設置參數
|
| 317 |
start_location = {"lat": 25.0400, "lng": 121.5300}
|
src/optimization/test/_test_convertes.py
DELETED
|
@@ -1,255 +0,0 @@
|
|
| 1 |
-
"""
|
| 2 |
-
测试時間視窗转换和求解
|
| 3 |
-
|
| 4 |
-
验证修复是否正確
|
| 5 |
-
"""
|
| 6 |
-
from datetime import datetime
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
def test_parse_time_window():
|
| 10 |
-
"""测试時間視窗解析"""
|
| 11 |
-
print("=" * 60)
|
| 12 |
-
print("测试 _parse_time_window()")
|
| 13 |
-
print("=" * 60)
|
| 14 |
-
print()
|
| 15 |
-
|
| 16 |
-
from src.optimization.models.converters import _parse_time_window
|
| 17 |
-
|
| 18 |
-
# 测试 1: Dict 格式(完整)
|
| 19 |
-
print("测试 1: Dict 格式(完整時間視窗)")
|
| 20 |
-
tw_dict = {
|
| 21 |
-
"earliest_time": "2025-11-19T16:00:00",
|
| 22 |
-
"latest_time": "2025-11-19T18:00:00"
|
| 23 |
-
}
|
| 24 |
-
result = _parse_time_window(tw_dict)
|
| 25 |
-
print(f" input: {tw_dict}")
|
| 26 |
-
print(f" output: {result}")
|
| 27 |
-
assert result == (datetime(2025, 11, 19, 16, 0), datetime(2025, 11, 19, 18, 0))
|
| 28 |
-
print(" ✅ passed\n")
|
| 29 |
-
|
| 30 |
-
# 测试 2: Dict 格式(部分時間視窗 - 只有结束)
|
| 31 |
-
print("测试 2: Dict 格式(只有结束时间)")
|
| 32 |
-
tw_dict = {
|
| 33 |
-
"earliest_time": None,
|
| 34 |
-
"latest_time": "2025-11-19T15:00:00"
|
| 35 |
-
}
|
| 36 |
-
result = _parse_time_window(tw_dict)
|
| 37 |
-
print(f" input: {tw_dict}")
|
| 38 |
-
print(f" output: {result}")
|
| 39 |
-
assert result == (None, datetime(2025, 11, 19, 15, 0))
|
| 40 |
-
print(" ✅ passed\n")
|
| 41 |
-
|
| 42 |
-
# 测试 3: Dict 格式(部分時間視窗 - 只有开始)
|
| 43 |
-
print("测试 3: Dict 格式(只有开始时间)")
|
| 44 |
-
tw_dict = {
|
| 45 |
-
"earliest_time": "2025-11-19T10:00:00",
|
| 46 |
-
"latest_time": None
|
| 47 |
-
}
|
| 48 |
-
result = _parse_time_window(tw_dict)
|
| 49 |
-
print(f" input: {tw_dict}")
|
| 50 |
-
print(f" output: {result}")
|
| 51 |
-
assert result == (datetime(2025, 11, 19, 10, 0), None)
|
| 52 |
-
print(" ✅ passed\n")
|
| 53 |
-
|
| 54 |
-
# 测试 4: Dict 格式(全 None)
|
| 55 |
-
print("测试 4: Dict 格式(全 None)")
|
| 56 |
-
tw_dict = {
|
| 57 |
-
"earliest_time": None,
|
| 58 |
-
"latest_time": None
|
| 59 |
-
}
|
| 60 |
-
result = _parse_time_window(tw_dict)
|
| 61 |
-
print(f" input: {tw_dict}")
|
| 62 |
-
print(f" output: {result}")
|
| 63 |
-
assert result is None
|
| 64 |
-
print(" ✅ passed\n")
|
| 65 |
-
|
| 66 |
-
# 测试 5: None
|
| 67 |
-
print("测试 5: None input")
|
| 68 |
-
result = _parse_time_window(None)
|
| 69 |
-
print(f" input: None")
|
| 70 |
-
print(f" output: {result}")
|
| 71 |
-
assert result is None
|
| 72 |
-
print(" ✅ passed\n")
|
| 73 |
-
|
| 74 |
-
# 测试 6: Tuple(兼容)
|
| 75 |
-
print("测试 6: Tuple input(兼容旧代码)")
|
| 76 |
-
tw_tuple = (datetime(2025, 11, 19, 16, 0), datetime(2025, 11, 19, 18, 0))
|
| 77 |
-
result = _parse_time_window(tw_tuple)
|
| 78 |
-
print(f" input: {tw_tuple}")
|
| 79 |
-
print(f" output: {result}")
|
| 80 |
-
assert result == tw_tuple
|
| 81 |
-
print(" ✅ passed\n")
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
def test_convert_tasks():
|
| 85 |
-
"""测试完整的任务转换"""
|
| 86 |
-
print("=" * 60)
|
| 87 |
-
print("测试 convert_tasks_to_internal()")
|
| 88 |
-
print("=" * 60)
|
| 89 |
-
print()
|
| 90 |
-
|
| 91 |
-
from converters import convert_tasks_to_internal
|
| 92 |
-
|
| 93 |
-
# 测试数据:模拟你的实际input
|
| 94 |
-
tasks = [
|
| 95 |
-
{
|
| 96 |
-
"task_id": "task_1",
|
| 97 |
-
"priority": "HIGH",
|
| 98 |
-
"service_duration_min": 60,
|
| 99 |
-
"time_window": {
|
| 100 |
-
"earliest_time": "2025-11-19T16:00:00",
|
| 101 |
-
"latest_time": "2025-11-19T18:00:00"
|
| 102 |
-
},
|
| 103 |
-
"candidates": [
|
| 104 |
-
{
|
| 105 |
-
"poi_id": "hospital_1",
|
| 106 |
-
"lat": 25.040978499999998,
|
| 107 |
-
"lng": 121.51919869999999
|
| 108 |
-
}
|
| 109 |
-
]
|
| 110 |
-
},
|
| 111 |
-
{
|
| 112 |
-
"task_id": "task_2",
|
| 113 |
-
"priority": "MEDIUM",
|
| 114 |
-
"service_duration_min": 45,
|
| 115 |
-
"time_window": None,
|
| 116 |
-
"candidates": [
|
| 117 |
-
{
|
| 118 |
-
"poi_id": "supermarket_1",
|
| 119 |
-
"lat": 25.0376995,
|
| 120 |
-
"lng": 121.50622589999999
|
| 121 |
-
}
|
| 122 |
-
]
|
| 123 |
-
},
|
| 124 |
-
{
|
| 125 |
-
"task_id": "task_3",
|
| 126 |
-
"priority": "HIGH",
|
| 127 |
-
"service_duration_min": 20,
|
| 128 |
-
"time_window": {
|
| 129 |
-
"earliest_time": None,
|
| 130 |
-
"latest_time": "2025-11-19T15:00:00"
|
| 131 |
-
},
|
| 132 |
-
"candidates": [
|
| 133 |
-
{
|
| 134 |
-
"poi_id": "post_office_1",
|
| 135 |
-
"lat": 25.0514598,
|
| 136 |
-
"lng": 121.5481353
|
| 137 |
-
}
|
| 138 |
-
]
|
| 139 |
-
}
|
| 140 |
-
]
|
| 141 |
-
|
| 142 |
-
# 转换
|
| 143 |
-
internal_tasks = convert_tasks_to_internal(tasks)
|
| 144 |
-
|
| 145 |
-
# 验证
|
| 146 |
-
print("测试 1: task_1 (看病 16:00-18:00)")
|
| 147 |
-
print(f" task_id: {internal_tasks[0].task_id}")
|
| 148 |
-
print(f" priority: {internal_tasks[0].priority}")
|
| 149 |
-
print(f" time_window: {internal_tasks[0].time_window}")
|
| 150 |
-
assert internal_tasks[0].time_window == (
|
| 151 |
-
datetime(2025, 11, 19, 16, 0),
|
| 152 |
-
datetime(2025, 11, 19, 18, 0)
|
| 153 |
-
)
|
| 154 |
-
print(" ✅ passed\n")
|
| 155 |
-
|
| 156 |
-
print("测试 2: task_2 (买菜 无限制)")
|
| 157 |
-
print(f" task_id: {internal_tasks[1].task_id}")
|
| 158 |
-
print(f" priority: {internal_tasks[1].priority}")
|
| 159 |
-
print(f" time_window: {internal_tasks[1].time_window}")
|
| 160 |
-
assert internal_tasks[1].time_window is None
|
| 161 |
-
print(" ✅ passed\n")
|
| 162 |
-
|
| 163 |
-
print("测试 3: task_3 (寄包裹 None-15:00)")
|
| 164 |
-
print(f" task_id: {internal_tasks[2].task_id}")
|
| 165 |
-
print(f" priority: {internal_tasks[2].priority}")
|
| 166 |
-
print(f" time_window: {internal_tasks[2].time_window}")
|
| 167 |
-
assert internal_tasks[2].time_window == (
|
| 168 |
-
None,
|
| 169 |
-
datetime(2025, 11, 19, 15, 0)
|
| 170 |
-
)
|
| 171 |
-
print(" ✅ passed\n")
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
def test_time_window_seconds():
|
| 175 |
-
"""测试時間視窗转换为秒"""
|
| 176 |
-
print("=" * 60)
|
| 177 |
-
print("测试時間視窗秒数计算")
|
| 178 |
-
print("=" * 60)
|
| 179 |
-
print()
|
| 180 |
-
|
| 181 |
-
from converters import _parse_time_window
|
| 182 |
-
from datetime import timedelta
|
| 183 |
-
|
| 184 |
-
# 开始时间: 08:00
|
| 185 |
-
start_time = datetime(2025, 11, 19, 8, 0)
|
| 186 |
-
|
| 187 |
-
# task_1: 16:00-18:00
|
| 188 |
-
tw1 = _parse_time_window({
|
| 189 |
-
"earliest_time": "2025-11-19T16:00:00",
|
| 190 |
-
"latest_time": "2025-11-19T18:00:00"
|
| 191 |
-
})
|
| 192 |
-
|
| 193 |
-
start_sec = int((tw1[0] - start_time).total_seconds())
|
| 194 |
-
end_sec = int((tw1[1] - start_time).total_seconds())
|
| 195 |
-
|
| 196 |
-
print("task_1 時間視窗:")
|
| 197 |
-
print(f" 原始: 16:00-18:00")
|
| 198 |
-
print(f" 相对 08:00: {start_sec}秒 - {end_sec}秒")
|
| 199 |
-
print(f" 即: {start_sec / 3600:.1f}小时 - {end_sec / 3600:.1f}小时")
|
| 200 |
-
|
| 201 |
-
assert start_sec == 8 * 3600 # 8小时 = 28800秒
|
| 202 |
-
assert end_sec == 10 * 3600 # 10小时 = 36000秒
|
| 203 |
-
print(" ✅ passed\n")
|
| 204 |
-
|
| 205 |
-
# task_3: None - 15:00
|
| 206 |
-
tw3 = _parse_time_window({
|
| 207 |
-
"earliest_time": None,
|
| 208 |
-
"latest_time": "2025-11-19T15:00:00"
|
| 209 |
-
})
|
| 210 |
-
|
| 211 |
-
print("task_3 時間視窗:")
|
| 212 |
-
print(f" 原始: None - 15:00")
|
| 213 |
-
if tw3[0] is None:
|
| 214 |
-
print(f" start: 0秒 (无限制)")
|
| 215 |
-
if tw3[1] is not None:
|
| 216 |
-
end_sec = int((tw3[1] - start_time).total_seconds())
|
| 217 |
-
print(f" end: {end_sec}秒 = {end_sec / 3600:.1f}小时")
|
| 218 |
-
assert end_sec == 7 * 3600 # 7小时 = 25200秒
|
| 219 |
-
print(" ✅ passed\n")
|
| 220 |
-
|
| 221 |
-
|
| 222 |
-
def main():
|
| 223 |
-
"""运行所有测试"""
|
| 224 |
-
print("\n")
|
| 225 |
-
print("╔" + "=" * 58 + "╗")
|
| 226 |
-
print("║" + " " * 15 + "時間視窗转换测试" + " " * 15 + "║")
|
| 227 |
-
print("╚" + "=" * 58 + "╝")
|
| 228 |
-
print("\n")
|
| 229 |
-
|
| 230 |
-
try:
|
| 231 |
-
test_parse_time_window()
|
| 232 |
-
test_convert_tasks()
|
| 233 |
-
test_time_window_seconds()
|
| 234 |
-
|
| 235 |
-
print("\n")
|
| 236 |
-
print("╔" + "=" * 58 + "╗")
|
| 237 |
-
print("║" + " " * 18 + "✅ 所有测试passed" + " " * 18 + "║")
|
| 238 |
-
print("╚" + "=" * 58 + "╝")
|
| 239 |
-
print("\n")
|
| 240 |
-
|
| 241 |
-
print("总结:")
|
| 242 |
-
print("✅ Dict 格式解析正確")
|
| 243 |
-
print("✅ 部分時間視窗支持")
|
| 244 |
-
print("✅ 完整任务转换正確")
|
| 245 |
-
print("✅ 时间秒数计算正確")
|
| 246 |
-
print("\n")
|
| 247 |
-
|
| 248 |
-
except Exception as e:
|
| 249 |
-
print(f"\n❌ 測試失敗: {e}")
|
| 250 |
-
import traceback
|
| 251 |
-
traceback.print_exc()
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
if __name__ == "__main__":
|
| 255 |
-
main()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
src/optimization/tsptw_solver.py
CHANGED
|
@@ -36,7 +36,6 @@ class TSPTWSolver:
|
|
| 36 |
|
| 37 |
def __init__(
|
| 38 |
self,
|
| 39 |
-
api_key: Optional[str] = None,
|
| 40 |
time_limit_seconds: Optional[int] = None,
|
| 41 |
verbose: bool = False,
|
| 42 |
):
|
|
@@ -54,14 +53,13 @@ class TSPTWSolver:
|
|
| 54 |
or "1"
|
| 55 |
)
|
| 56 |
|
| 57 |
-
self.gmaps = GoogleMapAPIService(api_key=api_key)
|
| 58 |
self.time_limit_seconds = (
|
| 59 |
time_limit_seconds if time_limit_seconds is not None else int(env_limit)
|
| 60 |
)
|
| 61 |
self.verbose = verbose
|
| 62 |
|
| 63 |
# 初始化各模塊
|
| 64 |
-
self.graph_builder = GraphBuilder(
|
| 65 |
self.ortools_solver = ORToolsSolver(
|
| 66 |
time_limit_seconds=self.time_limit_seconds,
|
| 67 |
verbose=verbose,
|
|
|
|
| 36 |
|
| 37 |
def __init__(
|
| 38 |
self,
|
|
|
|
| 39 |
time_limit_seconds: Optional[int] = None,
|
| 40 |
verbose: bool = False,
|
| 41 |
):
|
|
|
|
| 53 |
or "1"
|
| 54 |
)
|
| 55 |
|
|
|
|
| 56 |
self.time_limit_seconds = (
|
| 57 |
time_limit_seconds if time_limit_seconds is not None else int(env_limit)
|
| 58 |
)
|
| 59 |
self.verbose = verbose
|
| 60 |
|
| 61 |
# 初始化各模塊
|
| 62 |
+
self.graph_builder = GraphBuilder()
|
| 63 |
self.ortools_solver = ORToolsSolver(
|
| 64 |
time_limit_seconds=self.time_limit_seconds,
|
| 65 |
verbose=verbose,
|
src/services/googlemap_api_service.py
CHANGED
|
@@ -174,7 +174,6 @@ class GoogleMapAPIService:
|
|
| 174 |
"places.userRatingCount,"
|
| 175 |
"places.businessStatus,"
|
| 176 |
"places.currentOpeningHours,"
|
| 177 |
-
"places.userRatingCount,"
|
| 178 |
)
|
| 179 |
}
|
| 180 |
|
|
|
|
| 174 |
"places.userRatingCount,"
|
| 175 |
"places.businessStatus,"
|
| 176 |
"places.currentOpeningHours,"
|
|
|
|
| 177 |
)
|
| 178 |
}
|
| 179 |
|
src/tools/optimizer_toolkit.py
CHANGED
|
@@ -5,12 +5,15 @@ from datetime import datetime, timedelta
|
|
| 5 |
from agno.tools import Toolkit
|
| 6 |
from src.optimization.tsptw_solver import TSPTWSolver
|
| 7 |
from src.infra.poi_repository import poi_repo
|
|
|
|
|
|
|
|
|
|
| 8 |
|
| 9 |
|
| 10 |
class OptimizationToolkit(Toolkit):
|
| 11 |
-
def __init__(self
|
| 12 |
super().__init__(name="optimization_toolkit")
|
| 13 |
-
self.solver = TSPTWSolver(
|
| 14 |
self.register(self.optimize_from_ref)
|
| 15 |
|
| 16 |
def optimize_from_ref(self, ref_id: str) -> str:
|
|
@@ -74,7 +77,8 @@ class OptimizationToolkit(Toolkit):
|
|
| 74 |
return_to_start=False
|
| 75 |
)
|
| 76 |
except Exception as e:
|
| 77 |
-
|
|
|
|
| 78 |
|
| 79 |
# ✅ [Critical] 將 global_info 繼承下去!
|
| 80 |
# 如果不加這一行,Navigator 就會因為找不到 departure_time 而報錯
|
|
|
|
| 5 |
from agno.tools import Toolkit
|
| 6 |
from src.optimization.tsptw_solver import TSPTWSolver
|
| 7 |
from src.infra.poi_repository import poi_repo
|
| 8 |
+
from src.infra.logger import get_logger
|
| 9 |
+
|
| 10 |
+
logger = get_logger(__name__)
|
| 11 |
|
| 12 |
|
| 13 |
class OptimizationToolkit(Toolkit):
|
| 14 |
+
def __init__(self):
|
| 15 |
super().__init__(name="optimization_toolkit")
|
| 16 |
+
self.solver = TSPTWSolver()
|
| 17 |
self.register(self.optimize_from_ref)
|
| 18 |
|
| 19 |
def optimize_from_ref(self, ref_id: str) -> str:
|
|
|
|
| 77 |
return_to_start=False
|
| 78 |
)
|
| 79 |
except Exception as e:
|
| 80 |
+
logger.warning(f"Solver failed: {e}")
|
| 81 |
+
return f"❌ Solver Failed: {e}, Please check the input data."
|
| 82 |
|
| 83 |
# ✅ [Critical] 將 global_info 繼承下去!
|
| 84 |
# 如果不加這一行,Navigator 就會因為找不到 departure_time 而報錯
|