diff --git a/internal/server/postgres/dataserverimpl.go b/internal/server/postgres/dataserverimpl.go index 5226a64..86673ff 100644 --- a/internal/server/postgres/dataserverimpl.go +++ b/internal/server/postgres/dataserverimpl.go @@ -245,23 +245,39 @@ func (s *DataPlatformDataServiceServerImpl) DeleteForecast( querier := db.New(ix.GetTxFromContext(ctx)) - dfprms := db.DeleteForecastParams{ - ForecastUuid: uuid.MustParse(req.ForecastUuid), + // Check the forecaster exists + pctprms := db.GetForecasterElseLatestParams{ + ForecasterName: req.Forecaster.ForecasterName, + ForecasterVersion: req.Forecaster.ForecasterVersion, } - err := querier.DeleteForecast(ctx, dfprms) + dbForecaster, err := querier.GetForecasterElseLatest(ctx, pctprms) if err != nil { - l.Err(err).Msgf("querier.DeleteForecast(%+v)", dfprms) + l.Err(err).Msgf("querier.GetForecasterElseLatest(%+v)", pctprms) return nil, status.Error( - codes.Internal, - "Backend communication error.", + codes.NotFound, "No such forecaster. "+ + "Create the forecaster before submitting a forecast.", ) } - l.Debug(). - Str("dp.forecast.uuid", req.ForecastUuid). - Msg("deleted forecast") + // Delete the forecast + dfcprms := db.DeleteForecastParams{ + ForecasterID: dbForecaster.ForecasterID, + GeometryUuid: uuid.MustParse(req.LocationUuid), + SourceTypeID: int16(req.EnergySource.Number()), + InitTimestamp: timeptrToPgTimestamp(req.InitTimeUtc), + } + + err = querier.DeleteForecast(ctx, dfcprms) + if err != nil { + l.Err(err).Msgf("querier.DeleteForecast(%+v)", dfcprms) + + return nil, status.Error( + codes.Internal, + "Could not delete forecast. Ensure the forecast exists.", + ) + } return &pb.DeleteForecastResponse{}, nil } diff --git a/internal/server/postgres/dataserverimpl_test.go b/internal/server/postgres/dataserverimpl_test.go index 01ad434..4ad7cfb 100644 --- a/internal/server/postgres/dataserverimpl_test.go +++ b/internal/server/postgres/dataserverimpl_test.go @@ -1630,7 +1630,7 @@ func TestCreateForecast(t *testing.T) { for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { - resp, err := dc.CreateForecast(t.Context(), tc.req) + _, err := dc.CreateForecast(t.Context(), tc.req) if strings.Contains(tc.name, "Shouldn't") { require.Error(t, err) } else { @@ -1649,7 +1649,10 @@ func TestCreateForecast(t *testing.T) { require.NoError(t, err) require.Equal(t, len(tc.req.Values), len(fResp.Values)) _, err = dc.DeleteForecast(t.Context(), &pb.DeleteForecastRequest{ - ForecastUuid: resp.ForecastUuid, + Forecaster: fcResp.Forecaster, + LocationUuid: siteResp.LocationUuid, + EnergySource: pb.EnergySource_ENERGY_SOURCE_SOLAR, + InitTimeUtc: tc.req.InitTimeUtc, }) require.NoError(t, err) @@ -1886,7 +1889,7 @@ func BenchmarkPostgresClient(b *testing.B) { }) b.Run(fmt.Sprintf("%d/CreateForecast", output.NumPgvs), func(b *testing.B) { for b.Loop() { - resp, err := dc.CreateForecast(b.Context(), &pb.CreateForecastRequest{ + _, err := dc.CreateForecast(b.Context(), &pb.CreateForecastRequest{ Forecaster: &pb.Forecaster{ ForecasterName: tc.NamePrefix + "_forecaster_1", ForecasterVersion: "v1", @@ -1902,7 +1905,13 @@ func BenchmarkPostgresClient(b *testing.B) { }) require.NoError(b, err) _, err = dc.DeleteForecast(b.Context(), &pb.DeleteForecastRequest{ - ForecastUuid: resp.ForecastUuid, + Forecaster: &pb.Forecaster{ + ForecasterName: tc.NamePrefix + "_forecaster_1", + ForecasterVersion: "v1", + }, + LocationUuid: output.LocationUuids[0], + EnergySource: pb.EnergySource_ENERGY_SOURCE_SOLAR, + InitTimeUtc: timestamppb.New(pivotTime.Add(time.Duration(12+2) * time.Hour)), }) require.NoError(b, err) } diff --git a/internal/server/postgres/sql/queries/predictions.sql b/internal/server/postgres/sql/queries/predictions.sql index f4c5498..53815a0 100644 --- a/internal/server/postgres/sql/queries/predictions.sql +++ b/internal/server/postgres/sql/queries/predictions.sql @@ -95,10 +95,28 @@ INSERT INTO pred.forecasts ( target_period, metadata; --- name: DeleteForecast :exec +-- name: DeleteForecastByUUID :exec DELETE FROM pred.forecasts WHERE forecast_uuid = $1; +-- name: DeleteForecast :exec +WITH forecasts_to_delete AS ( + SELECT forecast_uuid FROM pred.forecasts AS f + WHERE f.forecast_uuid >= UUIDV7_BOUNDARY(sqlc.arg(init_timestamp)::TIMESTAMP) + AND f.forecast_uuid < UUIDV7_BOUNDARY(sqlc.arg(init_timestamp)::TIMESTAMP + INTERVAL '1 second') + AND f.geometry_uuid = $1 + AND f.source_type_id = $2 + AND f.forecaster_id = $3 +), +deleted_values AS ( + DELETE FROM pred.predicted_generation_values + WHERE target_time_utc >= sqlc.arg(init_timestamp)::TIMESTAMP + AND target_time_utc < sqlc.arg(init_timestamp)::TIMESTAMP + INTERVAL '3 days' + AND forecast_uuid IN (SELECT forecast_uuid FROM forecasts_to_delete) +) +DELETE FROM pred.forecasts +WHERE forecast_uuid IN (SELECT forecast_uuid FROM forecasts_to_delete); + -- name: CreatePredictedValues :copyfrom /* CreatePredictedValues inserts predicted generation values using * postgres COPY protocol, making it the fastest way to perform large inserts of predictions. diff --git a/proto/ocf/dp/dp-data.messages.proto b/proto/ocf/dp/dp-data.messages.proto index 9a749bb..59be3c8 100644 --- a/proto/ocf/dp/dp-data.messages.proto +++ b/proto/ocf/dp/dp-data.messages.proto @@ -308,10 +308,20 @@ message CreateForecastResponse { message DeleteForecastRequest { - string forecast_uuid = 1 [ + string location_uuid = 1 [ (buf.validate.field).required = true, (buf.validate.field).string.uuid = true ]; + EnergySource energy_source = 2 [ + (buf.validate.field).required = true + ]; + Forecaster forecaster = 3 [ + (buf.validate.field).required = true + ]; + google.protobuf.Timestamp init_time_utc = 4 [ + (buf.validate.field).required = true, + (buf.validate.field).timestamp = { gt: { seconds: 112000000}, lt_now: true } + ]; } message DeleteForecastResponse {}