summaryrefslogtreecommitdiff
path: root/backend/src/pathfinder.py
diff options
context:
space:
mode:
authorkboz <kutayboz@gmail.com>2026-04-24 00:55:26 +0200
committerkboz <kutayboz@gmail.com>2026-04-24 00:55:26 +0200
commite43c98997b0de6a8da989f68d97f1d4f7fb7bb6f (patch)
tree1b9c8073acdef60af2ca3e19ae0c4a1b2c55410f /backend/src/pathfinder.py
parentacbf6d6bd6d63ab0e9f7c4e119827f76e0382aac (diff)
downloadhaul-e43c98997b0de6a8da989f68d97f1d4f7fb7bb6f.tar.gz
feat(core): implement wallet trade tracking and sqlite market caching
- Transitioned primary ESI networking and dataset initialization to `asyncio` and `aiohttp` for non-blocking I/O. - Introduced `aiosqlite` utilizing an in-memory database to significantly speed up arbitrage querying. - Implemented character wallet tracking (added `esi-wallet.read_character_wallet.v1` scope) to automatically deduct filled market volumes internally instead of waiting for full region polling. - Exposed `/settings` endpoint allowing dynamic tweaking of threshold variables (cargo size, limits, tax rate, ship speeds) directly from the React UI. - Decoupled auto-waypoint submission during route planning; users now push routes selectively via a new "Set in Game" frontend action and `/set_waypoints` backend logic. - Optimized order evaluations and triplet generation limits through `LegMetrics` to lower CPU footprint drastically on large dataset passes. - Migrated React frontend completely to Server-Sent Events (SSE), cleanly ditching HTTP polling.
Diffstat (limited to 'backend/src/pathfinder.py')
-rw-r--r--backend/src/pathfinder.py61
1 files changed, 15 insertions, 46 deletions
diff --git a/backend/src/pathfinder.py b/backend/src/pathfinder.py
index 4ce2df3..a08d47c 100644
--- a/backend/src/pathfinder.py
+++ b/backend/src/pathfinder.py
@@ -1,13 +1,17 @@
import logging
from dataclasses import dataclass
+from typing import Optional
+
from tqdm import tqdm
from arbitrage import Trade
-from graph import NavigationGraph as Graph
+from graph import NavigationGraph as Graph, LegMetrics
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
+LegMetricLookup = dict[tuple[int, int], Optional[LegMetrics]]
+
@dataclass
class Ship:
location: int
@@ -19,7 +23,7 @@ class Ship:
risk_cost: float
-def pathfinder(order_matches: list[Trade], graph: Graph, ship: Ship) -> list[Trade]:
+def pathfinder(order_matches: list[Trade], graph: Graph, ship: Ship, leg_metrics: Optional[LegMetricLookup] = None) -> list[Trade]:
"""
Processes order matches by filtering and calculating net profit.
@@ -33,11 +37,11 @@ def pathfinder(order_matches: list[Trade], graph: Graph, ship: Ship) -> list[Tra
"""
order_matches = filter_order_matches_not_in_graph(order_matches, graph)
logger.info("After filtering order matches not in graph: %d", len(order_matches))
-
- order_matches = filter_order_matches_same_region(order_matches, graph)
- logger.info("After filtering order matches to same region: %d", len(order_matches))
- order_matches = calculate_net_profit(order_matches, graph, ship)
+ if leg_metrics is None:
+ leg_metrics = graph.build_trade_leg_metrics(ship["location"], order_matches)
+
+ order_matches = calculate_net_profit(order_matches, ship["location"], leg_metrics)
order_matches = filter_order_matches_low_profit(order_matches)
logger.info("After filtering low profit order matches: %d", len(order_matches))
@@ -60,42 +64,7 @@ def filter_order_matches_not_in_graph(order_matches: list[Trade], graph: Graph)
if order_match.from_station in graph.graph.nodes and order_match.to_station in graph.graph.nodes
]
-def filter_order_matches_same_region(order_matches: list[Trade], graph: Graph) -> list[Trade]:
- """
- Filters out order matches where the from_region and to_region are different.
- Keeps only order matches within the same region.
-
- Args:
- order_matches: List of trade opportunities.
- graph: Navigation graph.
-
- Returns:
- Filtered list of trade opportunities where both stations are in the same region.
- """
- filtered_order_matches = []
-
- for order_match in order_matches:
- from_station = order_match.from_station
- to_station = order_match.to_station
-
- from_station_data = graph.stations.get(from_station)
- to_station_data = graph.stations.get(to_station)
-
- if not from_station_data or not to_station_data:
- continue # Skip if station data is missing
-
- from_solar_system_id = from_station_data["solar_system_id"]
- to_solar_system_id = to_station_data["solar_system_id"]
-
- from_region = graph.solar_systems[from_solar_system_id]["region_id"]
- to_region = graph.solar_systems[to_solar_system_id]["region_id"]
-
- if from_region == to_region:
- filtered_order_matches.append(order_match)
-
- return filtered_order_matches
-
-def calculate_net_profit(order_matches: list[Trade], graph: Graph, ship: Ship) -> list[Trade]:
+def calculate_net_profit(order_matches: list[Trade], ship_location: int, leg_metrics: LegMetricLookup) -> list[Trade]:
"""
Calculates the net profit for each order match by subtracting transport cost from gross profit.
@@ -108,12 +77,12 @@ def calculate_net_profit(order_matches: list[Trade], graph: Graph, ship: Ship) -
List of trade opportunities with net_profit calculated.
"""
logger.info("Calculating estimated profits")
- for order_match in tqdm(order_matches):
+ for order_match in tqdm(order_matches, desc="Scoring trades"):
try:
# Calculate transport cost from ship location to from_station
- cost_to_source = graph.shortest_path_length(ship["location"], order_match.from_station)
+ cost_to_source = leg_metrics.get((ship_location, order_match.from_station))
# Calculate transport cost from from_station to to_station
- cost_to_destination = graph.shortest_path_length(order_match.from_station, order_match.to_station)
+ cost_to_destination = leg_metrics.get((order_match.from_station, order_match.to_station))
# Check if paths exist
if cost_to_source is None or cost_to_destination is None:
@@ -121,7 +90,7 @@ def calculate_net_profit(order_matches: list[Trade], graph: Graph, ship: Ship) -
order_match.net_profit = None
continue
- total_transport_cost = cost_to_source + cost_to_destination
+ total_transport_cost = cost_to_source.weight + cost_to_destination.weight
order_match.net_profit = order_match.gross_profit - total_transport_cost
except Exception as e:
logger.error(f"Error calculating net profit for order match {order_match}: {e}")