From 1b59961ce2385252e3346ea6107b15a2c1feafe6 Mon Sep 17 00:00:00 2001 From: kboz Date: Sun, 27 Sep 2026 22:36:34 +0200 Subject: feat(backend): port route-planning backend to Go Add a Go implementation mirroring the Python pipeline (loader, graph, pathfinder, arbitrage, market, route, manager) under internal/core, with ESI client/API integration and setup endpoints. Reuses backend/src as the working directory so existing data/cache paths keep working. --- backend-go/internal/core/loader.go | 286 +++++++++++++++++++++++++++++++++++++ 1 file changed, 286 insertions(+) create mode 100644 backend-go/internal/core/loader.go (limited to 'backend-go/internal/core/loader.go') diff --git a/backend-go/internal/core/loader.go b/backend-go/internal/core/loader.go new file mode 100644 index 0000000..8e451ef --- /dev/null +++ b/backend-go/internal/core/loader.go @@ -0,0 +1,286 @@ +package core + +import ( + "encoding/csv" + "os" + "path/filepath" + "strconv" +) + +const ( + TypeIDSolarSystem = 5 + GroupIDStation = 15 + GroupIDStargate = 10 +) + +var AllowedRegionIDs = map[int]bool{ + 10000002: true, // The Forge + 10000016: true, // Lonetrek + 10000030: true, // Heimatar + 10000033: true, // The Citadel + 10000032: true, // Sinq Laison + 10000042: true, // Metropolis + 10000064: true, // Essence + 10000037: true, // Everyshore + 10000043: true, // Domain + 10000036: true, // Devoid + 10000052: true, // Kador + 10000067: true, // Genesis + 10000068: true, // Verge Vendor + 10000001: true, // Derelik + 10000020: true, // Tash-Murkon +} + +type Position struct { + X float64 `json:"x"` + Y float64 `json:"y"` + Z float64 `json:"z"` +} + +type Station struct { + IsStation bool `json:"is_station"` + ItemName string `json:"item_name"` + Security float64 `json:"security"` + Position Position `json:"position"` + SolarSystemID int `json:"solar_system_id"` + RegionID int `json:"region_id"` + RegionName string `json:"region_name"` + SolarSystemName string `json:"solar_system_name"` + SolarSystemPosition Position `json:"solar_system_position"` + Jumps []int `json:"jumps"` +} + +type SolarSystem struct { + ItemName string `json:"item_name"` + Position Position `json:"position"` + RegionID int `json:"region_id"` + RegionName string `json:"region_name"` +} + +type Item struct { + TypeID int `json:"type_id"` + GroupID int `json:"group_id"` + TypeName string `json:"type_name"` + Description string `json:"description"` + Mass *float64 `json:"mass"` + Volume *float64 `json:"volume"` + Capacity *float64 `json:"capacity"` +} + +// DataLoader handles loading data from CSV files +type DataLoader struct { + BaseDir string +} + +func NewDataLoader(baseDir string) *DataLoader { + return &DataLoader{BaseDir: baseDir} +} + +func (dl *DataLoader) ReadRegions() (map[int]string, error) { + file, err := os.Open(filepath.Join(dl.BaseDir, "mapRegions.csv")) + if err != nil { + return nil, err + } + defer file.Close() + + reader := csv.NewReader(file) + records, err := reader.ReadAll() + if err != nil { + return nil, err + } + + regions := make(map[int]string) + if len(records) > 0 { + records = records[1:] // Skip header + } + + for _, row := range records { + regionID, err := strconv.Atoi(row[0]) + if err != nil { + continue + } + regions[regionID] = row[1] + } + return regions, nil +} + +func (dl *DataLoader) LoadMapData() (map[int]*Station, map[int]*SolarSystem, error) { + regions, err := dl.ReadRegions() + if err != nil { + return nil, nil, err + } + + file, err := os.Open(filepath.Join(dl.BaseDir, "mapDenormalize.csv")) + if err != nil { + return nil, nil, err + } + defer file.Close() + + reader := csv.NewReader(file) + // Some fields might be mismatched or have commas in strings, standard csv covers it + records, err := reader.ReadAll() + if err != nil { + return nil, nil, err + } + + stations := make(map[int]*Station) + solarSystems := make(map[int]*SolarSystem) + + if len(records) > 0 { + records = records[1:] + } + + for _, row := range records { + if len(row) < 15 { + continue + } + + regionIDStr := row[5] + if regionIDStr == "None" || regionIDStr == "" { + continue + } + regionID, err := strconv.Atoi(regionIDStr) + if err != nil || !AllowedRegionIDs[regionID] { + continue + } + + itemID, _ := strconv.Atoi(row[0]) + typeID, _ := strconv.Atoi(row[1]) + groupID, _ := strconv.Atoi(row[2]) + + solarSystemID := 0 + if row[3] != "None" && row[3] != "" { + solarSystemID, _ = strconv.Atoi(row[3]) + } + + x, _ := strconv.ParseFloat(row[7], 64) + y, _ := strconv.ParseFloat(row[8], 64) + z, _ := strconv.ParseFloat(row[9], 64) + + itemName := row[11] + security, _ := strconv.ParseFloat(row[12], 64) + + regionName := regions[regionID] + if regionName == "" { + regionName = "Unknown" + } + + if typeID == TypeIDSolarSystem { + solarSystems[itemID] = &SolarSystem{ + ItemName: itemName, + Position: Position{X: x, Y: y, Z: z}, + RegionID: regionID, + RegionName: regionName, + } + } else if groupID == GroupIDStation || groupID == GroupIDStargate { + stations[itemID] = &Station{ + IsStation: groupID == GroupIDStation, + ItemName: itemName, + Security: security, + Position: Position{X: x, Y: y, Z: z}, + SolarSystemID: solarSystemID, + RegionID: regionID, + Jumps: []int{}, + } + } + } + + // Update station data + for _, station := range stations { + if ss, ok := solarSystems[station.SolarSystemID]; ok { + station.SolarSystemName = ss.ItemName + station.SolarSystemPosition = ss.Position + station.RegionID = ss.RegionID + if name, exists := regions[ss.RegionID]; exists { + station.RegionName = name + } else { + station.RegionName = "Unknown" + } + } + } + + return stations, solarSystems, nil +} + +func parseOptionalFloat(val string) *float64 { + if val == "None" || val == "" { + return nil + } + f, err := strconv.ParseFloat(val, 64) + if err != nil { + return nil + } + return &f +} + +func (dl *DataLoader) LoadItems() (map[int]*Item, error) { + file, err := os.Open(filepath.Join(dl.BaseDir, "invTypes.csv")) + if err != nil { + return nil, err + } + defer file.Close() + + reader := csv.NewReader(file) + records, err := reader.ReadAll() + if err != nil { + return nil, err + } + + items := make(map[int]*Item) + if len(records) > 0 { + records = records[1:] + } + + for _, row := range records { + if len(row) < 7 { + continue + } + typeID, err := strconv.Atoi(row[0]) + if err != nil { + continue + } + groupID, _ := strconv.Atoi(row[1]) + + items[typeID] = &Item{ + TypeID: typeID, + GroupID: groupID, + TypeName: row[2], + Description: row[3], + Mass: parseOptionalFloat(row[4]), + Volume: parseOptionalFloat(row[5]), + Capacity: parseOptionalFloat(row[6]), + } + } + return items, nil +} + +func (dl *DataLoader) LoadStarGateConnections() ([][2]int, error) { + file, err := os.Open(filepath.Join(dl.BaseDir, "mapJumps.csv")) + if err != nil { + return nil, err + } + defer file.Close() + + reader := csv.NewReader(file) + records, err := reader.ReadAll() + if err != nil { + return nil, err + } + + var connections [][2]int + if len(records) > 0 { + records = records[1:] + } + + for _, row := range records { + if len(row) < 2 { + continue + } + from, err1 := strconv.Atoi(row[0]) + to, err2 := strconv.Atoi(row[1]) + if err1 == nil && err2 == nil { + connections = append(connections, [2]int{from, to}) + } + } + return connections, nil +} -- cgit v1.3