summaryrefslogtreecommitdiff
path: root/backend/src/pathfinder.py
blob: a08d47c387bcbf6c6ec8342b750bde24d444c758 (plain)
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
import logging
from dataclasses import dataclass
from typing import Optional

from tqdm import tqdm

from arbitrage import Trade
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
    cargo: int
    max_warp_speed: float
    max_subwarp_speed: float
    gankers_areas: list[str]
    player_cost: float
    risk_cost: float


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.

    Args:
        order_matches: List of trade opportunities.
        graph: Navigation graph.
        ship: Ship parameters.

    Returns:
        A list of filtered and annotated trade opportunities.
    """
    order_matches = filter_order_matches_not_in_graph(order_matches, graph)
    logger.info("After filtering order matches not in graph: %d", len(order_matches))

    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))

    return order_matches

def filter_order_matches_not_in_graph(order_matches: list[Trade], graph: Graph) -> list[Trade]:
    """
    Filters out order matches where either the from_station or to_station is not in the graph.

    Args:
        order_matches: List of trade opportunities.
        graph: Navigation graph.

    Returns:
        Filtered list of trade opportunities.
    """
    return [
        order_match for order_match in order_matches
        if order_match.from_station in graph.graph.nodes and order_match.to_station in graph.graph.nodes
    ]

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.

    Args:
        order_matches: List of trade opportunities.
        graph: Navigation graph.
        ship: Ship parameters.

    Returns:
        List of trade opportunities with net_profit calculated.
    """
    logger.info("Calculating estimated profits")
    for order_match in tqdm(order_matches, desc="Scoring trades"):
        try:
            # Calculate transport cost from ship location to 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 = 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:
                logger.warning(f"No path found for order match: {order_match}")
                order_match.net_profit = None
                continue

            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}")
            order_match.net_profit = None

    return order_matches

def filter_order_matches_low_profit(order_matches: list[Trade]) -> list[Trade]:
    """
    Filters out order matches with non-positive net profit.

    Args:
        order_matches: List of trade opportunities with net_profit calculated.

    Returns:
        Filtered list of profitable trade opportunities.
    """
    return [
        order_match for order_match in order_matches
        if order_match.net_profit and order_match.net_profit > 0
    ]