summaryrefslogtreecommitdiff
path: root/backend-go/internal/core/loader.go
diff options
context:
space:
mode:
authorkboz <kutayboz@gmail.com>2026-09-27 22:36:34 +0200
committerkboz <kutayboz@gmail.com>2026-09-27 22:36:34 +0200
commit1b59961ce2385252e3346ea6107b15a2c1feafe6 (patch)
treefbe9e0cb968c47cff353e937e4dc835a64058c60 /backend-go/internal/core/loader.go
parent29c4b57db9a34e0f815d93cac6a2cae543778a93 (diff)
downloadhaul-1b59961ce2385252e3346ea6107b15a2c1feafe6.tar.gz
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.
Diffstat (limited to 'backend-go/internal/core/loader.go')
-rw-r--r--backend-go/internal/core/loader.go286
1 files changed, 286 insertions, 0 deletions
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
+}