Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LocationLevel retrieve constant values in time window #887

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 19 additions & 14 deletions cwms-data-api/src/main/java/cwms/cda/api/LevelsController.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
import static cwms.cda.api.Controllers.PAGE_SIZE;
import static cwms.cda.api.Controllers.RESULTS;
import static cwms.cda.api.Controllers.SIZE;
import static cwms.cda.api.Controllers.START;
import static cwms.cda.api.Controllers.STATUS_200;
import static cwms.cda.api.Controllers.TIMEZONE;
import static cwms.cda.api.Controllers.UNIT;
Expand Down Expand Up @@ -89,11 +90,12 @@
import io.javalin.plugin.openapi.annotations.OpenApiParam;
import io.javalin.plugin.openapi.annotations.OpenApiRequestBody;
import io.javalin.plugin.openapi.annotations.OpenApiResponse;
import javax.servlet.http.HttpServletResponse;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import org.jetbrains.annotations.NotNull;
import org.jooq.DSLContext;

Expand Down Expand Up @@ -330,17 +332,14 @@ public void getAll(@NotNull Context ctx) {
+ "specified), as well as the time zone of any times in the response."
+ " If this field is not specified, the default time zone of UTC "
+ "shall be used."),
@OpenApiParam(name = UNIT, description = "Specifies the unit or unit system"
+ " of the response. Valid values for the unit field are:"
+ "\n* `EN` "
+ "Specifies English unit system. Location level values will be in"
+ " the default English units for their parameters."
+ "\n* `SI` "
+ "Specifies the SI unit system. Location level values will be in "
+ "the default SI units for their parameters."
+ "\n* `Other` "
+ "Any unit returned in the response to the units URI request that is "
+ "appropriate for the requested parameters. "),
@OpenApiParam(name = START, type = Instant.class, description = "Specifies the start of the time "
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding a start and end date for getOne does not make sense to me as it conflicts with the effective date parameter.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fair, but because there are multiple effective dates there does need to be a way to retrieve them all without it necessarily being a "timeseries" as that's a different concept (seasonal, etc) of location level.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see how that's different than the behavior of the getAll endpoint.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We'll see how Daniel responds.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+ "window for data to be included in the response. Both this field and the end field must be "
+ " specified, or no time window will be used."),
@OpenApiParam(name = END, type = Instant.class, description = "Specifies the end of the time "
+ "window for data to be included in the response. Both this field and the start field must be"
+ " specified, or no time window will be used."),
@OpenApiParam(name = UNIT, description = "Desired unit for "
+ "the values retrieved.")
},
responses = {
@OpenApiResponse(status = STATUS_200,content = {
Expand All @@ -357,16 +356,22 @@ public void getOne(@NotNull Context ctx, @NotNull String levelId) {
String dateString = queryParamAsClass(ctx, new String[]{EFFECTIVE_DATE, DATE},
String.class, null, metrics, name(LevelsController.class.getName(),
GET_ONE));
Instant start = ctx.queryParamAsClass(START, Long.class).getOrDefault(null) == null ? null
: Instant.ofEpochMilli(ctx.queryParamAsClass(START, Long.class).get());
Instant end = ctx.queryParamAsClass(END, Long.class).getOrDefault(null) == null ? null
: Instant.ofEpochMilli(ctx.queryParamAsClass(END, Long.class).getOrDefault(null));
String timezone = ctx.queryParamAsClass(TIMEZONE, String.class)
.getOrDefault("UTC");

try (final Timer.Context ignored = markAndTime(GET_ONE)) {
DSLContext dsl = getDslContext(ctx);
ZonedDateTime unmarshalledDateTime = DateUtils.parseUserDate(dateString, timezone);
ZonedDateTime startZdt = start == null ? null : ZonedDateTime.ofInstant(start, ZoneId.of(timezone));
ZonedDateTime endZdt = end == null ? null : ZonedDateTime.ofInstant(end, ZoneId.of(timezone));

LocationLevelsDao levelsDao = getLevelsDao(dsl);
LocationLevel locationLevel = levelsDao.retrieveLocationLevel(levelId,
units, unmarshalledDateTime, office);
units, unmarshalledDateTime, office, startZdt, endZdt);
ctx.json(locationLevel);
ctx.status(HttpServletResponse.SC_OK);
}
Expand Down Expand Up @@ -424,7 +429,7 @@ public void update(@NotNull Context ctx, @NotNull String oldLevelId) {
ZoneId.systemDefault().getId());
//retrieveLocationLevel will throw an error if level does not exist
LocationLevel existingLevelLevel = levelsDao.retrieveLocationLevel(oldLevelId,
UnitSystem.EN.getValue(), unmarshalledDateTime, officeId);
UnitSystem.EN.getValue(), unmarshalledDateTime, officeId, null, null);
existingLevelLevel = updatedClearedFields(ctx.body(), contentType.getType(),
existingLevelLevel);
//only store (update) if level does exist
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
import mil.army.usace.hec.metadata.Interval;

import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;

public interface LocationLevelsDao {
Expand All @@ -43,7 +42,7 @@ void deleteLocationLevel(String locationLevelName, ZonedDateTime date, String of
void renameLocationLevel(String oldLocationLevelName, String newLocationLevelName, String officeId);

LocationLevel retrieveLocationLevel(String locationLevelName, String unitSystem,
ZonedDateTime effectiveDate, String officeId);
ZonedDateTime effectiveDate, String officeId, ZonedDateTime start, ZonedDateTime end);

String getLocationLevels(String format, String names, String office, String unit,
String datum, String begin,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
Expand All @@ -74,6 +75,7 @@
import org.jooq.DSLContext;
import org.jooq.Record;
import org.jooq.Record1;
import org.jooq.Result;
import org.jooq.SelectLimitPercentAfterOffsetStep;
import org.jooq.TableField;
import org.jooq.conf.ParamType;
Expand Down Expand Up @@ -249,7 +251,7 @@ private static SEASONAL_VALUE_TAB_T getSeasonalValues(LocationLevel locationLeve
SEASONAL_VALUE_TAB_T pSeasonalValues = null;
if (seasonalValues != null && !seasonalValues.isEmpty()) {
pSeasonalValues = new SEASONAL_VALUE_TAB_T();
for(SeasonalValueBean seasonalValue : seasonalValues) {
for (SeasonalValueBean seasonalValue : seasonalValues) {
SEASONAL_VALUE_T seasonalValueT = new SEASONAL_VALUE_T();
seasonalValueT.setOFFSET_MINUTES(toBigDecimal(seasonalValue.getOffsetMinutes()));
if (seasonalValue.getOffsetMonths() != null) {
Expand Down Expand Up @@ -329,50 +331,101 @@ public void renameLocationLevel(String oldLocationLevelName, String newLocationL

@Override
public LocationLevel retrieveLocationLevel(String locationLevelName, String pUnits,
ZonedDateTime effectiveDate, String officeId) {
ZonedDateTime effectiveDate, String officeId, ZonedDateTime start, ZonedDateTime end) {
Timestamp date = Timestamp.from(effectiveDate.toInstant());
String[] levelIdParts = locationLevelIdParsingPattern.split(locationLevelName);
if (levelIdParts.length <= 2) {
throw new IllegalArgumentException("Location level name is in an invalid format, must be separated by '.'");
}
String parameter = levelIdParts[1];
Timestamp startDate = start == null ? null : Timestamp.from(start.toInstant());
Timestamp endDate = end == null ? null : Timestamp.from(end.toInstant());
return connectionResult(dsl, c -> {
String units = pUnits;
Configuration configuration = getDslContext(c, officeId).configuration();
if (units != null && (units.equalsIgnoreCase("SI")
|| units.equalsIgnoreCase("EN"))) {
units = CWMS_UTIL_PACKAGE.call_GET_DEFAULT_UNITS(configuration,
parameter, units);
}
RETRIEVE_LOCATION_LEVEL3 level = CWMS_LEVEL_PACKAGE.call_RETRIEVE_LOCATION_LEVEL3(
configuration, locationLevelName, units, date,
"UTC", null, null, units,
"F", officeId);
List<SeasonalValueBean> seasonalValues = buildSeasonalValues(level);
if (units == null) {
logger.info("Getting default units for " + parameter);
String defaultUnits = CWMS_UTIL_PACKAGE.call_GET_DEFAULT_UNITS(
configuration, parameter, UnitSystem.SI.getValue());
logger.info("Default units are " + defaultUnits);
units = defaultUnits;

if (start != null && end != null) {

ZoneId tz = start.getZone();
if (units == null) {
String parameter = locationLevelName.split("\\.")[1];
logger.info("Getting default units for " + parameter);
String defaultUnits = CWMS_UTIL_PACKAGE.call_GET_DEFAULT_UNITS(
configuration, parameter, UnitSystem.SI.getValue());
logger.info("Default units are " + defaultUnits);
units = defaultUnits;
}

Result<?> result = dsl.select(AV_LOCATION_LEVEL.LOCATION_LEVEL_ID,
AV_LOCATION_LEVEL.OFFICE_ID, AV_LOCATION_LEVEL.LEVEL_DATE,
AV_LOCATION_LEVEL.CONSTANT_LEVEL, AV_LOCATION_LEVEL.LEVEL_UNIT)
.from(AV_LOCATION_LEVEL)
.where(AV_LOCATION_LEVEL.LOCATION_LEVEL_ID.eq(locationLevelName))
.and(AV_LOCATION_LEVEL.OFFICE_ID.eq(officeId))
.and(AV_LOCATION_LEVEL.LEVEL_DATE.ge(startDate))
.and(AV_LOCATION_LEVEL.LEVEL_DATE.le(endDate))
.and(AV_LOCATION_LEVEL.LEVEL_UNIT.eq(units))
.fetch();

if (result.isEmpty()) {
throw new NotFoundException("No location level found for " + locationLevelName + " at " + date);
}

boolean parentData = false;
String locLevelId = null;
Timestamp recentDate = null;
Double constantValue = null;
List<LocationLevel.ConstantValue> constantList = new ArrayList<>();
for (Record r : result) {
if (!parentData)
{
locLevelId = r.get(AV_LOCATION_LEVEL.LOCATION_LEVEL_ID);
}

if (recentDate == null || recentDate.before(r.get(AV_LOCATION_LEVEL.LEVEL_DATE))) {
recentDate = r.get(AV_LOCATION_LEVEL.LEVEL_DATE);
constantValue = r.get(AV_LOCATION_LEVEL.CONSTANT_LEVEL);
}
constantList.add(new LocationLevel.ConstantValue.Builder()
.withConstantValue(r.get(AV_LOCATION_LEVEL.CONSTANT_LEVEL))
.withLevelDate(ZonedDateTime.of(r.get(AV_LOCATION_LEVEL.LEVEL_DATE).toLocalDateTime(),
tz))
.build());
}

return new LocationLevel.Builder(locLevelId,
ZonedDateTime.of(recentDate.toLocalDateTime(), tz))
.withConstantValueList(constantList).withConstantValue(constantValue)
.withOfficeId(officeId).withAttributeUnitsId(units)
.build();

} else {
RETRIEVE_LOCATION_LEVEL3 level = CWMS_LEVEL_PACKAGE.call_RETRIEVE_LOCATION_LEVEL3(
configuration, locationLevelName, units, date,
"UTC", null, null, units,
"F", officeId);
List<SeasonalValueBean> seasonalValues = buildSeasonalValues(level);
if (units == null) {
String parameter = locationLevelName.split("\\.")[1];
logger.info("Getting default units for " + parameter);
String defaultUnits = CWMS_UTIL_PACKAGE.call_GET_DEFAULT_UNITS(
configuration, parameter, UnitSystem.SI.getValue());
logger.info("Default units are " + defaultUnits);
units = defaultUnits;
}
return new LocationLevel.Builder(locationLevelName, effectiveDate)
.withLevelUnitsId(units)
.withAttributeUnitsId(units)
.withInterpolateString(level.getP_INTERPOLATE())
.withIntervalMinutes(Optional.ofNullable(level.getP_INTERVAL_MINUTES())
.map(BigInteger::intValue).orElse(null))
.withIntervalMonths(Optional.ofNullable(level.getP_INTERVAL_MONTHS())
.map(BigInteger::intValue).orElse(null))
.withIntervalOrigin(level.getP_INTERVAL_ORIGIN(), effectiveDate)
.withLevelComment(level.getP_LEVEL_COMMENT())
.withOfficeId(officeId)
.withAttributeParameterId(level.get(RETRIEVE_LOCATION_LEVEL3.P_ATTRIBUTE_ID))
.withSeasonalTimeSeriesId(level.get(RETRIEVE_LOCATION_LEVEL3.P_TSID))
.withSeasonalValues(seasonalValues)
.withConstantValue(Optional.ofNullable(level.getP_LEVEL_VALUE())
.map(BigDecimal::doubleValue).orElse(null))
.build();
}
return new LocationLevel.Builder(locationLevelName, effectiveDate)
.withLevelUnitsId(units)
.withAttributeUnitsId(units)
.withInterpolateString(level.getP_INTERPOLATE())
.withIntervalMinutes(Optional.ofNullable(level.getP_INTERVAL_MINUTES())
.map(BigInteger::intValue).orElse(null))
.withIntervalMonths(Optional.ofNullable(level.getP_INTERVAL_MONTHS())
.map(BigInteger::intValue).orElse(null))
.withIntervalOrigin(level.getP_INTERVAL_ORIGIN(), effectiveDate)
.withLevelComment(level.getP_LEVEL_COMMENT())
.withOfficeId(officeId)
.withAttributeParameterId(level.get(RETRIEVE_LOCATION_LEVEL3.P_ATTRIBUTE_ID))
.withSeasonalTimeSeriesId(level.get(RETRIEVE_LOCATION_LEVEL3.P_TSID))
.withSeasonalValues(seasonalValues)
.withConstantValue(Optional.ofNullable(level.getP_LEVEL_VALUE())
.map(BigDecimal::doubleValue).orElse(null))
.build();
});
}

Expand Down
63 changes: 59 additions & 4 deletions cwms-data-api/src/main/java/cwms/cda/data/dto/LocationLevel.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,10 @@ public final class LocationLevel extends CwmsDTO {
+ "seasonableTimeSeriesId and seasonValues.")

private final Double constantValue;
@Schema(description = "List of constant values for this location level.")
@JsonFormat(shape = JsonFormat.Shape.ARRAY)

private final List<ConstantValue> constantValueList;
@Schema(description = "Units the provided levels are in")

private final String levelUnitsId;
Expand Down Expand Up @@ -121,6 +125,7 @@ private LocationLevel(Builder builder) {
parameterTypeId = builder.parameterTypeId;
parameterId = builder.parameterId;
constantValue = builder.constantValue;
constantValueList = builder.constantValueList;
levelUnitsId = builder.levelUnitsId;
levelDate = builder.levelDate;
levelComment = builder.levelComment;
Expand Down Expand Up @@ -162,6 +167,10 @@ public Double getConstantValue() {
return constantValue;
}

public List<ConstantValue> getConstantValueList() {
return constantValueList;
}

public String getLevelUnitsId() {
return levelUnitsId;
}
Expand Down Expand Up @@ -231,6 +240,7 @@ public static class Builder {
private String parameterTypeId;
private String parameterId;
private Double constantValue;
private List<ConstantValue> constantValueList;
private String levelUnitsId;
private ZonedDateTime levelDate;
private String levelComment;
Expand Down Expand Up @@ -470,6 +480,11 @@ public Builder withConstantValue(Double value) {
return this;
}

public Builder withConstantValueList(List<ConstantValue> constantValueList) {
this.constantValueList = constantValueList;
return this;
}

public Builder withLevelUnitsId(String levelUnitsId) {
this.levelUnitsId = levelUnitsId;
return this;
Expand Down Expand Up @@ -576,14 +591,54 @@ protected void validateInternal(CwmsDTOValidator validator) {
super.validateInternal(validator);
validator.required(getOfficeId(), "office-id");
validator.required(getLocationLevelId(), "location-level-id");
if(getConstantValue() == null && getSeasonalTimeSeriesId() == null) {
if (getConstantValue() == null && getSeasonalTimeSeriesId() == null) {
validator.required(getSeasonalValues(), "seasonal-values");
} else if(getSeasonalValues() == null && getSeasonalTimeSeriesId() == null) {
} else if (getSeasonalValues() == null && getSeasonalTimeSeriesId() == null) {
validator.required(getConstantValue(), "constant-value");
} else if(getConstantValue() == null && getSeasonalValues() == null) {
} else if (getConstantValue() == null && getSeasonalValues() == null) {
validator.required(getSeasonalTimeSeriesId(), "seasonable-time-series-id");
}
validator.mutuallyExclusive("Only one of the following can be defined for location levels: constant-value, seasonal-values, seasonable-time-series-id",
validator.mutuallyExclusive("Only one of the following can be defined for location levels: "
+ "constant-value, seasonal-values, seasonable-time-series-id",
getConstantValue(), getSeasonalValues(), getSeasonalTimeSeriesId());
}

@JsonFormat(shape = JsonFormat.Shape.ARRAY)
public static class ConstantValue {
@JsonFormat(shape = JsonFormat.Shape.STRING)
private final ZonedDateTime levelDate;
private final double value;

private ConstantValue(Builder builder) {
levelDate = builder.levelDate;
value = builder.constantValue;
}

public ZonedDateTime getLevelDate() {
return levelDate;
}

public double getValue() {
return value;
}

public static class Builder {
private ZonedDateTime levelDate;
private double constantValue;

public Builder withConstantValue(double constantValue) {
this.constantValue = constantValue;
return this;
}

public Builder withLevelDate(ZonedDateTime levelDate) {
this.levelDate = levelDate;
return this;
}

public ConstantValue build() {
return new ConstantValue(this);
}
}
}
}
Loading
Loading