2025-12-24 17:40:52 +08:00
|
|
|
package aviation
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"caatsm/internal/domain"
|
|
|
|
|
"fmt"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// ParseContext carries the raw body and tokens for field parsers.
|
|
|
|
|
type ParseContext struct {
|
|
|
|
|
Body string
|
|
|
|
|
Tokens []Token
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// CategoryParser maps a category to patterns and output parsing.
|
|
|
|
|
type CategoryParser interface {
|
|
|
|
|
Category() string
|
|
|
|
|
Patterns() []PatternConfig
|
|
|
|
|
Parse(ctx ParseContext, data map[string]string) (interface{}, error)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type arrParser struct{}
|
|
|
|
|
|
|
|
|
|
func (arrParser) Category() string { return CategoryArrival }
|
|
|
|
|
|
|
|
|
|
func (arrParser) Patterns() []PatternConfig {
|
|
|
|
|
return []PatternConfig{
|
|
|
|
|
{
|
|
|
|
|
Pattern: ArrPatternString,
|
|
|
|
|
Comments: "Pattern for ARR message",
|
|
|
|
|
Expression: ArrPatternExpression,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (arrParser) Parse(_ ParseContext, data map[string]string) (interface{}, error) {
|
2025-12-26 17:55:25 +08:00
|
|
|
// Validate and extract required fields
|
|
|
|
|
category, err := GetRequiredField(data, Category)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
aircraftID, err := GetRequiredField(data, FlightNumber)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
depAirport, err := GetRequiredField(data, DepartureCode)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
arrAirport, err := GetRequiredField(data, ArrivalCode)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
arrTime, err := GetRequiredField(data, ArrivalTime)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-24 17:40:52 +08:00
|
|
|
return &domain.ARR{
|
2025-12-26 17:55:25 +08:00
|
|
|
Category: category,
|
|
|
|
|
AircraftID: aircraftID,
|
|
|
|
|
SSRModeAndCode: GetOptionalField(data, SSR),
|
|
|
|
|
DepartureAirport: depAirport,
|
|
|
|
|
ArrivalAirport: arrAirport,
|
|
|
|
|
ArrivalTime: arrTime,
|
2025-12-24 17:40:52 +08:00
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type depParser struct{}
|
|
|
|
|
|
|
|
|
|
func (depParser) Category() string { return CategoryDeparture }
|
|
|
|
|
|
|
|
|
|
func (depParser) Patterns() []PatternConfig {
|
|
|
|
|
return []PatternConfig{
|
|
|
|
|
{
|
|
|
|
|
Pattern: DepPatternString,
|
|
|
|
|
Comments: "Pattern for DEP message",
|
|
|
|
|
Expression: DepPatternExpression,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (depParser) Parse(_ ParseContext, data map[string]string) (interface{}, error) {
|
2025-12-26 17:55:25 +08:00
|
|
|
// Validate and extract required fields
|
|
|
|
|
category, err := GetRequiredField(data, Category)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
aircraftID, err := GetRequiredField(data, FlightNumber)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
depAirport, err := GetRequiredField(data, DepartureCode)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
depTime, err := GetRequiredField(data, DepartureTime)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
destination, err := GetRequiredField(data, ArrivalCode)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-24 17:40:52 +08:00
|
|
|
return &domain.DEP{
|
2025-12-26 17:55:25 +08:00
|
|
|
Category: category,
|
|
|
|
|
AircraftID: aircraftID,
|
|
|
|
|
SSRModeAndCode: GetOptionalField(data, SSR),
|
|
|
|
|
DepartureAirport: depAirport,
|
|
|
|
|
DepartureTime: depTime,
|
|
|
|
|
Destination: destination,
|
2025-12-24 17:40:52 +08:00
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type cnlParser struct{}
|
|
|
|
|
|
|
|
|
|
func (cnlParser) Category() string { return CategoryCancellation }
|
|
|
|
|
|
|
|
|
|
func (cnlParser) Patterns() []PatternConfig {
|
|
|
|
|
return []PatternConfig{
|
|
|
|
|
{
|
|
|
|
|
Pattern: CnlPatternString,
|
|
|
|
|
Comments: "Pattern for CNL message",
|
|
|
|
|
Expression: CnlPatternExpression,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (cnlParser) Parse(_ ParseContext, data map[string]string) (interface{}, error) {
|
2025-12-26 17:55:25 +08:00
|
|
|
// Validate and extract required fields
|
|
|
|
|
category, err := GetRequiredField(data, Category)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
aircraftID, err := GetRequiredField(data, FlightNumber)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
depAirport, err := GetRequiredField(data, DepartureCode)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
destAirport, err := GetRequiredField(data, ArrivalCode)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-24 17:40:52 +08:00
|
|
|
return &domain.CNL{
|
2025-12-26 17:55:25 +08:00
|
|
|
Category: category,
|
|
|
|
|
AircraftID: aircraftID,
|
|
|
|
|
DepartureAirport: depAirport,
|
|
|
|
|
DestinationAirport: destAirport,
|
2025-12-24 17:40:52 +08:00
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type dlaParser struct{}
|
|
|
|
|
|
|
|
|
|
func (dlaParser) Category() string { return CategoryDelay }
|
|
|
|
|
|
|
|
|
|
func (dlaParser) Patterns() []PatternConfig {
|
|
|
|
|
return []PatternConfig{
|
|
|
|
|
{
|
|
|
|
|
Pattern: DlaPatternString,
|
|
|
|
|
Comments: "Pattern for DLA message",
|
|
|
|
|
Expression: DlaPatternExpression,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (dlaParser) Parse(_ ParseContext, data map[string]string) (interface{}, error) {
|
2025-12-26 17:55:25 +08:00
|
|
|
// Validate and extract required fields
|
|
|
|
|
category, err := GetRequiredField(data, Category)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
aircraftID, err := GetRequiredField(data, FlightNumber)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
depAirport, err := GetRequiredField(data, DepartureCode)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
arrAirport, err := GetRequiredField(data, ArrivalCode)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-24 17:40:52 +08:00
|
|
|
return &domain.DLA{
|
2025-12-26 17:55:25 +08:00
|
|
|
Category: category,
|
|
|
|
|
AircraftID: aircraftID,
|
|
|
|
|
DepartureAirport: depAirport,
|
|
|
|
|
NewDepartureTime: GetOptionalField(data, DepartureTime),
|
|
|
|
|
ArrivalAirport: arrAirport,
|
|
|
|
|
ArrivalTime: GetOptionalField(data, ArrivalTime),
|
2025-12-24 17:40:52 +08:00
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type fplParser struct{}
|
|
|
|
|
|
|
|
|
|
func (fplParser) Category() string { return CategoryFlightPlan }
|
|
|
|
|
|
|
|
|
|
func (fplParser) Patterns() []PatternConfig {
|
|
|
|
|
return []PatternConfig{
|
|
|
|
|
{
|
|
|
|
|
Pattern: FplPatternString,
|
|
|
|
|
Comments: "Pattern for FPL message",
|
|
|
|
|
Expression: FplPatternExpression,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (fplParser) Parse(_ ParseContext, data map[string]string) (interface{}, error) {
|
2025-12-26 17:55:25 +08:00
|
|
|
// Validate and extract required fields
|
|
|
|
|
category, err := GetRequiredField(data, Category)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
flightNumber, err := GetRequiredField(data, FlightNumber)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
aircraftID, err := GetRequiredField(data, AircraftID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
indicator, err := GetRequiredField(data, Indicator)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
speed, err := GetRequiredField(data, Speed)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
level, err := GetRequiredField(data, Level)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
depAirport, err := GetRequiredField(data, DepartureCode)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
depTime, err := GetRequiredField(data, DepartureTime)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
route, err := GetRequiredField(data, Route)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
destCode, err := GetRequiredField(data, DestinationCode)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
estTime, err := GetRequiredField(data, EstimatedTime)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Parse optional "other" fields
|
|
|
|
|
otherInfo := GetOptionalField(data, OtherInfo)
|
|
|
|
|
otherData := parseOther(otherInfo)
|
|
|
|
|
|
2025-12-24 17:40:52 +08:00
|
|
|
return &domain.FPL{
|
2025-12-26 17:55:25 +08:00
|
|
|
Category: category,
|
|
|
|
|
FlightNumber: flightNumber,
|
|
|
|
|
ReferenceData: GetOptionalField(data, ReferenceData),
|
|
|
|
|
AircraftID: aircraftID,
|
|
|
|
|
SSRModeAndCode: GetOptionalField(data, Surveillance),
|
|
|
|
|
FlightRulesAndType: indicator,
|
|
|
|
|
CruisingSpeedAndLevel: speed + level,
|
|
|
|
|
DepartureAirport: depAirport,
|
|
|
|
|
DepartureTime: depTime,
|
|
|
|
|
Route: route,
|
|
|
|
|
DestinationAndTotalTime: destCode + estTime,
|
|
|
|
|
AlternateAirport: GetOptionalField(data, AlternateAirport),
|
|
|
|
|
OtherInfo: otherInfo,
|
2025-12-24 17:40:52 +08:00
|
|
|
Register: otherData[Register],
|
2025-12-26 17:55:25 +08:00
|
|
|
EstimatedArrivalTime: estTime,
|
2025-12-24 17:40:52 +08:00
|
|
|
PBN: otherData[PBN],
|
|
|
|
|
NavigationEquipment: otherData[NavigationEquipment],
|
|
|
|
|
EstimatedElapsedTime: otherData[EstimatedElapsedTime],
|
|
|
|
|
SELCALCode: otherData[SELCALCode],
|
|
|
|
|
PerformanceCategory: otherData[PerformanceCategory],
|
|
|
|
|
RerouteInformation: otherData[RerouteInformation],
|
|
|
|
|
Remarks: otherData[Remarks],
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var categoryRegistry = map[string]CategoryParser{
|
|
|
|
|
CategoryArrival: arrParser{},
|
|
|
|
|
CategoryDeparture: depParser{},
|
|
|
|
|
CategoryCancellation: cnlParser{},
|
|
|
|
|
CategoryDelay: dlaParser{},
|
|
|
|
|
CategoryFlightPlan: fplParser{},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func lookupCategoryParser(category string) (CategoryParser, bool) {
|
|
|
|
|
parser, ok := categoryRegistry[category]
|
|
|
|
|
return parser, ok
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func buildBodyPatterns() map[string]BodyConfig {
|
|
|
|
|
patterns := make(map[string]BodyConfig, len(categoryRegistry))
|
|
|
|
|
for category, parser := range categoryRegistry {
|
|
|
|
|
patterns[category] = BodyConfig{Patterns: parser.Patterns()}
|
|
|
|
|
}
|
|
|
|
|
return patterns
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func parseCategory(category string, ctx ParseContext, data map[string]string) (interface{}, error) {
|
|
|
|
|
parser, ok := lookupCategoryParser(category)
|
|
|
|
|
if !ok {
|
|
|
|
|
return nil, fmt.Errorf("invalid message type: %s", category)
|
|
|
|
|
}
|
|
|
|
|
return parser.Parse(ctx, data)
|
|
|
|
|
}
|