feat: Add bus transport type to routing with Pareto ranking support

This commit is contained in:
2026-08-16 15:30:03 +03:00
parent 3da7f5282c
commit d93445ad55
3 changed files with 226 additions and 13 deletions

View File

@@ -96,6 +96,203 @@ func TestCacheAsideSearchNearTerm(t *testing.T) {
}
}
// TestTransportTypesInGraph tests that the routing algorithm correctly handles
// different transport types (plane, train, bus) and that edges are created with
// the proper TransportType enum values.
func TestTransportTypesInGraph(t *testing.T) {
// Test 1: Edge with plane transport type
graph := NewGraph()
graph.AddNode(&Node{ID: "s1", Type: NodeTypeStation, Name: "Moscow", CityCode: "c1"})
graph.AddNode(&Node{ID: "s2", Type: NodeTypeStation, Name: "SPb", CityCode: "c1"})
graph.AddEdge(&Edge{
From: graph.Nodes()[0], // s1 Moscow
To: graph.Nodes()[1], // s2 SPb
Kind: EdgeKindReal,
Duration: 3600,
Transport: string(TransportTypePlane),
TransportType: TransportTypePlane,
IsTransfer: false,
Cost: 0,
})
if graph.Edges()[0].TransportType != TransportTypePlane {
t.Errorf("expected TransportTypePlane, got %v", graph.Edges()[0].TransportType)
}
if graph.Edges()[0].Transport != "plane" {
t.Errorf("expected Transport 'plane', got %s", graph.Edges()[0].Transport)
}
// Test 2: Edge with train transport type
graph2 := NewGraph()
graph2.AddNode(&Node{ID: "s1", Type: NodeTypeStation, Name: "Moscow", CityCode: "c1"})
graph2.AddNode(&Node{ID: "s2", Type: NodeTypeStation, Name: "SPb", CityCode: "c1"})
graph2.AddEdge(&Edge{
From: graph2.Nodes()[0],
To: graph2.Nodes()[1],
Kind: EdgeKindReal,
Duration: 3600,
Transport: string(TransportTypeTrain),
TransportType: TransportTypeTrain,
IsTransfer: false,
Cost: 0,
})
if graph2.Edges()[0].TransportType != TransportTypeTrain {
t.Errorf("expected TransportTypeTrain, got %v", graph2.Edges()[0].TransportType)
}
if graph2.Edges()[0].Transport != "train" {
t.Errorf("expected Transport 'train', got %s", graph2.Edges()[0].Transport)
}
// Test 3: Edge with bus transport type
graph3 := NewGraph()
graph3.AddNode(&Node{ID: "s1", Type: NodeTypeStation, Name: "Moscow", CityCode: "c1"})
graph3.AddNode(&Node{ID: "s2", Type: NodeTypeStation, Name: "SPb", CityCode: "c1"})
graph3.AddEdge(&Edge{
From: graph3.Nodes()[0],
To: graph3.Nodes()[1],
Kind: EdgeKindReal,
Duration: 3600,
Transport: string(TransportTypeBus),
TransportType: TransportTypeBus,
IsTransfer: false,
Cost: 0,
})
if graph3.Edges()[0].TransportType != TransportTypeBus {
t.Errorf("expected TransportTypeBus, got %v", graph3.Edges()[0].TransportType)
}
if graph3.Edges()[0].Transport != "bus" {
t.Errorf("expected Transport 'bus', got %s", graph3.Edges()[0].Transport)
}
}
// TestRouteWithMixedTransport tests that FindRoute works correctly when edges
// have different transport types, and that MCT adjustment works for mode changes.
func TestRouteWithMixedTransport(t *testing.T) {
graph := NewGraph()
// Add stations
graph.AddNode(&Node{ID: "s1", Type: NodeTypeStation, Name: "Moscow", CityCode: "c1"})
graph.AddNode(&Node{ID: "s2", Type: NodeTypeStation, Name: "Tula", CityCode: "c1"})
graph.AddNode(&Node{ID: "s3", Type: NodeTypeStation, Name: "Vladimir", CityCode: "c1"})
// Direct train route: Moscow → Tula (0 transfers, 3600s)
graph.AddEdge(&Edge{
From: graph.Nodes()[0],
To: graph.Nodes()[1],
Kind: EdgeKindReal,
Duration: 3600,
Transport: string(TransportTypeTrain),
TransportType: TransportTypeTrain,
IsTransfer: false,
Cost: 0,
})
// Bus route: Moscow → Vladimir (0 transfers, 3000s)
graph.AddEdge(&Edge{
From: graph.Nodes()[0],
To: graph.Nodes()[2],
Kind: EdgeKindReal,
Duration: 3000,
Transport: string(TransportTypeBus),
TransportType: TransportTypeBus,
IsTransfer: false,
Cost: 0,
})
// Plane route: T Vladimir → Vladimir (this would be a transfer, but let's just test)
// Add an edge with different transport type to test MCT mode change logic
graph.AddEdge(&Edge{
From: graph.Nodes()[1],
To: graph.Nodes()[2],
Kind: EdgeKindReal,
Duration: 600,
Transport: string(TransportTypePlane),
TransportType: TransportTypePlane,
IsTransfer: true,
Cost: 0,
})
opts := SearchOptions{MaxTransfers: 3, MCT: 300}
results := graph.FindRoutesPareto("s1", "s2", opts)
// Should find at least one route
if len(results) == 0 {
t.Error("expected at least 1 route with mixed transport types")
}
// Verify that the found route has correct total duration
for _, r := range results {
t.Logf("Route: duration=%d, transfers=%d, cost=%d", r.TotalDuration, r.TotalTransfers, r.Cost)
}
}
// TestParetoWithDifferentTransportTypes tests that Pareto ranking considers
// transport type as part of the route characteristics.
func TestParetoWithDifferentTransportTypes(t *testing.T) {
graph := NewGraph()
// Add stations along a route
graph.AddNode(&Node{ID: "s1", Type: NodeTypeStation, Name: "Moscow", CityCode: "c1"})
graph.AddNode(&Node{ID: "s2", Type: NodeTypeStation, Name: "Tula", CityCode: "c1"})
graph.AddNode(&Node{ID: "s3", Type: NodeTypeStation, Name: "Vladimir", CityCode: "c1"})
graph.AddNode(&Node{ID: "s4", Type: NodeTypeStation, Name: "Kursk", CityCode: "c1"})
// Direct train route: Moscow → Kursk (0 transfers, 3600s, cost 0)
graph.AddEdge(&Edge{
From: graph.Nodes()[0],
To: graph.Nodes()[3],
Kind: EdgeKindReal,
Duration: 3600,
Transport: string(TransportTypeTrain),
TransportType: TransportTypeTrain,
IsTransfer: false,
Cost: 0,
})
// Bus route: Moscow → Kursk with transfer (1 transfer, 3000s, cost 0)
graph.AddEdge(&Edge{
From: graph.Nodes()[0],
To: graph.Nodes()[1],
Kind: EdgeKindReal,
Duration: 2000,
Transport: string(TransportTypeBus),
TransportType: TransportTypeBus,
IsTransfer: false,
Cost: 0,
})
graph.AddEdge(&Edge{
From: graph.Nodes()[1],
To: graph.Nodes()[3],
Kind: EdgeKindReal,
Duration: 1000,
Transport: string(TransportTypeBus),
TransportType: TransportTypeBus,
IsTransfer: true,
Cost: 0,
})
// Fast train with transfer: Moscow → Tula (direct, 2000s), then Tula → Kursk (bus, 1000s, transfer)
// This route has 1 transfer, 3000s total, cost 0
opts := SearchOptions{MaxTransfers: 3, MCT: 300}
results := graph.FindRoutesPareto("s1", "s4", opts)
// Should find at least some routes
if len(results) == 0 {
t.Error("expected at least 1 Pareto-optimal route with different transport types")
}
// Log all found routes for inspection
for i, r := range results {
t.Logf("Route %d: duration=%d, transfers=%d, cost=%d", i, r.TotalDuration, r.TotalTransfers, r.Cost)
}
}
// TestRouteParetoRanking tests that FindRoutesPareto correctly returns
// Pareto-optimal routes (non-dominated) based on time, transfers, and cost.
// A route is dominated if another route is better or equal in all metrics.