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, req api.OptPredictionParameters, params api.PerformPredictionParams) (*api.PredictionResult, error) { internalParams := ds.ConvertOptPredictionParameters(req) 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 }