forked from gsn/predictor
194 lines
5 KiB
Go
194 lines
5 KiB
Go
package handler
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"time"
|
|
|
|
"git.intra.yksa.space/gsn/predictor/internal/pkg/ds"
|
|
"git.intra.yksa.space/gsn/predictor/internal/pkg/errcodes"
|
|
api "git.intra.yksa.space/gsn/predictor/pkg/rest"
|
|
)
|
|
|
|
var (
|
|
_ api.Handler = (*Handler)(nil)
|
|
)
|
|
|
|
type Handler struct {
|
|
svc Service
|
|
}
|
|
|
|
func New(svc Service) *Handler {
|
|
return &Handler{
|
|
svc: svc,
|
|
}
|
|
}
|
|
|
|
func (h *Handler) PerformPrediction(ctx context.Context, params api.PerformPredictionParams) (*api.PredictionResult, error) {
|
|
internalParams := ds.ConvertFlatPredictionParams(params)
|
|
if internalParams == nil {
|
|
return nil, errcodes.New(http.StatusBadRequest, "invalid or missing parameters")
|
|
}
|
|
results, err := h.svc.PerformPrediction(ctx, *internalParams)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(results) == 0 {
|
|
return nil, errcodes.New(http.StatusInternalServerError, "no prediction results")
|
|
}
|
|
|
|
// Group results into stages (ascent and descent)
|
|
stages := h.groupResultsIntoStages(results)
|
|
|
|
// Map to OpenAPI schema
|
|
var predictionItems []api.PredictionResultPredictionItem
|
|
|
|
for _, stage := range stages {
|
|
var trajectory []api.PredictionResultPredictionItemTrajectoryItem
|
|
|
|
for _, result := range stage.Results {
|
|
traj := api.PredictionResultPredictionItemTrajectoryItem{
|
|
Datetime: *result.Timestamp,
|
|
Latitude: *result.Latitude,
|
|
Longitude: *result.Longitude,
|
|
Altitude: *result.Altitude,
|
|
}
|
|
trajectory = append(trajectory, traj)
|
|
}
|
|
|
|
item := api.PredictionResultPredictionItem{
|
|
Stage: stage.Stage,
|
|
Trajectory: trajectory,
|
|
}
|
|
predictionItems = append(predictionItems, item)
|
|
}
|
|
|
|
metadata := api.PredictionResultMetadata{
|
|
StartDatetime: *results[0].Timestamp,
|
|
CompleteDatetime: *results[len(results)-1].Timestamp,
|
|
}
|
|
|
|
resp := &api.PredictionResult{
|
|
Metadata: metadata,
|
|
Prediction: predictionItems,
|
|
}
|
|
return resp, nil
|
|
}
|
|
|
|
// StageResult represents a stage with its results
|
|
type StageResult struct {
|
|
Stage api.PredictionResultPredictionItemStage
|
|
Results []ds.PredicitonResult
|
|
}
|
|
|
|
// groupResultsIntoStages groups the prediction results into ascent and descent stages
|
|
func (h *Handler) groupResultsIntoStages(results []ds.PredicitonResult) []StageResult {
|
|
if len(results) == 0 {
|
|
return nil
|
|
}
|
|
|
|
var stages []StageResult
|
|
var currentStage []ds.PredicitonResult
|
|
var currentStageType api.PredictionResultPredictionItemStage
|
|
|
|
// Determine if we're in ascent or descent based on altitude changes
|
|
prevAlt := *results[0].Altitude
|
|
currentStage = append(currentStage, results[0])
|
|
currentStageType = api.PredictionResultPredictionItemStageAscent
|
|
|
|
for i := 1; i < len(results); i++ {
|
|
result := results[i]
|
|
currentAlt := *result.Altitude
|
|
|
|
// Determine if we're still in the same stage
|
|
var stageType api.PredictionResultPredictionItemStage
|
|
if currentAlt > prevAlt {
|
|
stageType = api.PredictionResultPredictionItemStageAscent
|
|
} else if currentAlt < prevAlt {
|
|
stageType = api.PredictionResultPredictionItemStageDescent
|
|
} else {
|
|
// Same altitude - continue with current stage
|
|
stageType = currentStageType
|
|
}
|
|
|
|
// If stage type changed, finalize current stage and start new one
|
|
if stageType != currentStageType && len(currentStage) > 0 {
|
|
stages = append(stages, StageResult{
|
|
Stage: currentStageType,
|
|
Results: currentStage,
|
|
})
|
|
currentStage = nil
|
|
currentStageType = stageType
|
|
}
|
|
|
|
currentStage = append(currentStage, result)
|
|
prevAlt = currentAlt
|
|
}
|
|
|
|
// Add the final stage
|
|
if len(currentStage) > 0 {
|
|
stages = append(stages, StageResult{
|
|
Stage: currentStageType,
|
|
Results: currentStage,
|
|
})
|
|
}
|
|
|
|
return stages
|
|
}
|
|
|
|
func (h *Handler) NewError(ctx context.Context, err error) *api.ErrorStatusCode {
|
|
if errcode, ok := err.(*errcodes.ErrorCode); ok {
|
|
resp := api.Error{
|
|
Message: errcode.Message,
|
|
}
|
|
|
|
if errcode.Details != "" {
|
|
resp.Details = api.NewOptString(errcode.Details)
|
|
}
|
|
|
|
return &api.ErrorStatusCode{
|
|
StatusCode: errcode.StatusCode,
|
|
Response: resp,
|
|
}
|
|
}
|
|
|
|
return &api.ErrorStatusCode{
|
|
StatusCode: http.StatusInternalServerError,
|
|
Response: api.Error{
|
|
Message: "undefined internal error",
|
|
Details: api.NewOptString(err.Error()),
|
|
},
|
|
}
|
|
}
|
|
|
|
func (h *Handler) ReadinessCheck(ctx context.Context) (*api.ReadinessResponse, error) {
|
|
status := api.ReadinessResponseStatusNotReady
|
|
var lastUpdate time.Time
|
|
var isFresh bool
|
|
var errMsg string
|
|
|
|
if s, ok := h.svc.(interface {
|
|
GetGribStatus(ctx context.Context) (ready bool, lastUpdate time.Time, isFresh bool, errMsg string)
|
|
}); ok {
|
|
ready, lu, fresh, em := s.GetGribStatus(ctx)
|
|
lastUpdate = lu
|
|
isFresh = fresh
|
|
errMsg = em
|
|
if ready {
|
|
status = api.ReadinessResponseStatusOk
|
|
} else if em != "" {
|
|
status = api.ReadinessResponseStatusError
|
|
}
|
|
} else {
|
|
errMsg = "service does not implement GetGribStatus"
|
|
status = api.ReadinessResponseStatusError
|
|
}
|
|
|
|
resp := &api.ReadinessResponse{
|
|
Status: status,
|
|
IsFresh: api.NewOptBool(isFresh),
|
|
LastUpdate: api.NewOptDateTime(lastUpdate),
|
|
ErrorMessage: api.NewOptString(errMsg),
|
|
}
|
|
return resp, nil
|
|
}
|