feat: Implement lazy hub expansion depth limiting with transfer depth limit of 5 (Task 14)
This commit is contained in:
77
internal/storage/neighbors.go
Normal file
77
internal/storage/neighbors.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package storage
|
||||
|
||||
// StationNeighbor represents a neighboring station that can be used as a fallback
|
||||
// when the main station is closed. The Source field indicates how the neighbor was discovered:
|
||||
// "geo" for geographic proximity-based discovery, "manual" for human-defined overrides.
|
||||
type StationNeighbor struct {
|
||||
// StationID is the ID of the neighboring station
|
||||
StationID string `json:"station_id"`
|
||||
// Name is the display name of the neighboring station
|
||||
Name string `json:"name"`
|
||||
// CityCode is the city the station belongs to
|
||||
CityCode string `json:"city_code"`
|
||||
// Source indicates how this neighbor was discovered: "geo" or "manual"
|
||||
Source string `json:"source"`
|
||||
// IsExcluded indicates whether this neighbor has been excluded from routing
|
||||
// (e.g., due to closure, maintenance, or other reasons)
|
||||
IsExcluded bool `json:"is_excluded"`
|
||||
}
|
||||
|
||||
// StationNeighborsTable manages station neighbor records in the database.
|
||||
// This is a mock implementation for when Postgres integration is available.
|
||||
type StationNeighborsTable struct {
|
||||
// In a full implementation, this would be a database connection/pool
|
||||
// For now, we use in-memory maps per city code
|
||||
neighbors map[string][]StationNeighbor
|
||||
}
|
||||
|
||||
// NewStationNeighborsTable creates a new StationNeighborsTable instance.
|
||||
func NewStationNeighborsTable() *StationNeighborsTable {
|
||||
return &StationNeighborsTable{
|
||||
neighbors: make(map[string][]StationNeighbor),
|
||||
}
|
||||
}
|
||||
|
||||
// Add adds a station neighbor to the table for the given city code.
|
||||
func (snt *StationNeighborsTable) Add(cityCode, stationID, name, source string) {
|
||||
snt.neighbors[cityCode] = append(snt.neighbors[cityCode], StationNeighbor{
|
||||
StationID: stationID,
|
||||
Name: name,
|
||||
CityCode: cityCode,
|
||||
Source: source,
|
||||
})
|
||||
}
|
||||
|
||||
// GetByCity returns all neighbors for a given city code.
|
||||
func (snt *StationNeighborsTable) GetByCity(cityCode string) []StationNeighbor {
|
||||
if neighbors, ok := snt.neighbors[cityCode]; ok {
|
||||
return neighbors
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarkExcluded marks a neighbor as excluded for the given station ID and city code.
|
||||
func (snt *StationNeighborsTable) MarkExcluded(cityCode, stationID string) {
|
||||
if neighbors, ok := snt.neighbors[cityCode]; ok {
|
||||
for i := range neighbors {
|
||||
if neighbors[i].StationID == stationID {
|
||||
neighbors[i].IsExcluded = true
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetNonExcluded returns non-excluded neighbors for a given city code.
|
||||
func (snt *StationNeighborsTable) GetNonExcluded(cityCode string) []StationNeighbor {
|
||||
if neighbors, ok := snt.neighbors[cityCode]; ok {
|
||||
var result []StationNeighbor
|
||||
for _, n := range neighbors {
|
||||
if !n.IsExcluded {
|
||||
result = append(result, n)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
return nil
|
||||
}
|
||||
45
internal/storage/transfer_rules.go
Normal file
45
internal/storage/transfer_rules.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package storage
|
||||
|
||||
// TransferRule represents a minimum connection time rule.
|
||||
type TransferRule struct {
|
||||
RuleKey string `json:"rule_key"`
|
||||
MinTransferTimeMinutes int `json:"min_transfer_time_minutes"`
|
||||
}
|
||||
|
||||
// TransferRuleMap is a lookup map for MCT values.
|
||||
type TransferRuleMap map[string]int
|
||||
|
||||
// MinTransferTime returns the minimum connection time in seconds for a given rule key.
|
||||
// It looks up the rule from the provided rules map, or returns a default value.
|
||||
func MinTransferTime(ruleKey string, rules TransferRuleMap, defaultMCT int) int {
|
||||
// Try exact match first
|
||||
if minutes, ok := rules[ruleKey]; ok {
|
||||
return minutes * 60 // convert minutes to seconds
|
||||
}
|
||||
|
||||
// Try base key matches (e.g., "airport_internal" matches "airport_internal_through")
|
||||
baseKey := ExtractBaseKey(ruleKey)
|
||||
if minutes, ok := rules[baseKey]; ok {
|
||||
return minutes * 60
|
||||
}
|
||||
|
||||
// Return default MCT
|
||||
return defaultMCT
|
||||
}
|
||||
|
||||
// ExtractBaseKey extracts the base rule key from a full rule key.
|
||||
// e.g., "airport_internal_through" -> "airport_internal"
|
||||
func ExtractBaseKey(ruleKey string) string {
|
||||
// Remove the suffix: through, separate, small, million_plus
|
||||
switch ruleKey {
|
||||
case "airport_internal_through", "airport_internal_separate":
|
||||
return "airport_internal"
|
||||
case "airport_to_city_small", "airport_to_city_million_plus":
|
||||
return "airport_to_city"
|
||||
default:
|
||||
return ruleKey
|
||||
}
|
||||
}
|
||||
|
||||
// DefaultMCT is the default minimum connection time in seconds (30 minutes).
|
||||
const DefaultMCT = 1800 // 30 minutes
|
||||
Reference in New Issue
Block a user