Skip to content

Fixes #634 - Implemented data entry date option for TS data retrieval #927

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

Open
wants to merge 21 commits into
base: develop
Choose a base branch
from

Conversation

zack-rma
Copy link
Collaborator

Fixes #634 - Implements data entry date as option for TimeSeries data retrieval - Serialization bug in progress

@@ -248,6 +256,11 @@ public static class Record {
@JsonProperty(value = "quality-code", index = 2)
int qualityCode;

Copy link
Contributor

Choose a reason for hiding this comment

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

This might be better as a sub class. I know that adds some complex but eventually we may also want to include the version date and text notes that may be attached and that would be a lot of logic in this one class.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Changed to a subclass. I added a custom deserializer to handle the different classes.

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay, so I said this, Adam said this, but after reading the code and our other discussions above timeseries does it make more sense to just make TimeSeries more generic?

e.g.

A builder where you manually add column names, index, and type, and and functions in the row builder to set such?
something like

withColumn(int index, String name, String description, Class<T> type) {
"logic"
}

... Record:

<T> setColumn(int index, T value, Class<T> type) {
  "logic"
}

Or something like that, it would prevent the need in TimeSeriesDaoImpl have two different loops doing almost 90% the same work. just a check for "I have this column requested, let's also add it."

Basically instead of hard coding the columns at all (okay, maybe time... is a time series) , the user of the given TimeSeries object (after set by builder) can define them at run-time.

Sorry, I know you did a lot here, that just came to me now looking through the current PR.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't want Zach to do it in this PR, already been too long, but does anyone think the above idea is crazy?

@zack-rma zack-rma marked this pull request as ready for review October 25, 2024 18:53
@zack-rma zack-rma requested a review from adamkorynta October 25, 2024 21:04
@zack-rma zack-rma changed the title Fixes #634 - Implemented data entry data option for TS data retrieval Fixes #634 - Implemented data entry date option for TS data retrieval Oct 31, 2024
@MikeNeilson
Copy link
Contributor

@jbkolze what are your thoughts on this? At least how it's done. Some appears it may be a breaking change which we want to avoid.

@jbkolze
Copy link

jbkolze commented Nov 4, 2024

@jbkolze what are your thoughts on this? At least how it's done. Some appears it may be a breaking change which we want to avoid.

Conceptually, I don't personally have any qualms with it. You had mentioned in the typing discussion that you all were trying to leave flexibility for dynamically adjusting the time series values array, and this seems like a prime use case for that.

That being said, I am a little concerned about the part you marked as a breaking change. I don't know the CDA source that well, but is this indicating that the response would include a fourth null value even if the include_entry_date is false? Because that definitely would not be ideal -- we've written a lot of CDA code already (and I think other districts have as well) that would have to be updated. Not as big of a deal if an API version were included in the path (.../v3/...), but somewhat cumbersome in the current setup.

My understanding from previous conversations is that you'd get the normal 3-value array if this were set to false, but receive a 4-value array if include_entry_date is true. And the "value-columns" object would be updated to match. That'd be my "ideal" implementation.

@zack-rma
Copy link
Collaborator Author

zack-rma commented Nov 4, 2024

You are correct that setting the include_entry_date parameter to true would result in a four-value array, whereas setting it to false would return a three-value array. Your "ideal" implementation is what I was aiming for to retain backwards compatibility and avoid breaking any of the other endpoints that rely on the TimeSeries implementation.

@MikeNeilson
Copy link
Contributor

My understanding from previous conversations is that you'd get the normal 3-value array if this were set to false, but receive a 4-value array if include_entry_date is true. And the "value-columns" object would be updated to match. That'd be my "ideal" implementation.

That is correct for the location indicated, which is location levels backed by a time series. I don't know how much it would affect what you're currently using, but it's definitely not ideal. At the least it definitely shouldn't be null, may as well provide the data, but seems like a parameter should be added to match on the location level end point.

But it sounds like your on the same page with Adam about the shape being already explicitly flexible so I'm okay with that section now; not "ideal", but what is, definitely something to better document though.

});
logger.fine(() -> query2.getSQL(ParamType.INLINED));
final TimeSeriesWithDate timeSeries = new TimeSeriesWithDate(timeseries);
query2.forEach(tsRecord -> timeSeries.addValue(
Copy link
Contributor

Choose a reason for hiding this comment

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

we don't need to solve this now, but definitely before we add requesting any text notes as well. There has to be a better way to handle this with the builders.

Copy link
Contributor

Choose a reason for hiding this comment

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

Given the previous logic above, does this need a new or just a cast so you can get the desired methods?

if (pageSize != 0) {
if (versionDate != null) {
whereCond = whereCond.and(AV_TSV_DQU.AV_TSV_DQU.VERSION_DATE.eq(versionDate == null ? null :
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this logic handle max version? or will AV_TSV_DQU always return every version? or only the specifically requested version?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Updated logic to handle max version in line with previous implementation. If the version date is not specified, the max version will be set to true and the most recent version date will be used for retrieval. This does prevent retrieval of multiple versions in one call, so that could be a problem if that is a desired feature.

@@ -248,6 +256,11 @@ public static class Record {
@JsonProperty(value = "quality-code", index = 2)
int qualityCode;

Copy link
Contributor

Choose a reason for hiding this comment

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

Okay, so I said this, Adam said this, but after reading the code and our other discussions above timeseries does it make more sense to just make TimeSeries more generic?

e.g.

A builder where you manually add column names, index, and type, and and functions in the row builder to set such?
something like

withColumn(int index, String name, String description, Class<T> type) {
"logic"
}

... Record:

<T> setColumn(int index, T value, Class<T> type) {
  "logic"
}

Or something like that, it would prevent the need in TimeSeriesDaoImpl have two different loops doing almost 90% the same work. just a check for "I have this column requested, let's also add it."

Basically instead of hard coding the columns at all (okay, maybe time... is a time series) , the user of the given TimeSeries object (after set by builder) can define them at run-time.

Sorry, I know you did a lot here, that just came to me now looking through the current PR.

@zack-rma zack-rma requested a review from adamkorynta December 2, 2024 22:12
@zack-rma
Copy link
Collaborator Author

zack-rma commented Dec 7, 2024

Build errors appear to be unrelated to these changes

@FormattableWith(contentType = Formats.XMLV2, formatter = XMLv2.class, aliases = {Formats.XML})
public final class TimeSeriesWithDate extends TimeSeries {

private List<TimeSeriesWithDate.Record> values;
Copy link
Collaborator

Choose a reason for hiding this comment

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

should be able to reuse the parent's values since there is inheritance

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Needed to add the local variables to get the typing correct. Removing it causes loss of type within the List, which drops the data entry date value.

Copy link
Collaborator

Choose a reason for hiding this comment

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

then you shouldn't inherit from TimeSeries as the class structure then has two sets of data

Copy link
Contributor

@MikeNeilson MikeNeilson May 20, 2025

Choose a reason for hiding this comment

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

This lends itself to the other suggestion I made with the more generic structure. Though I think that would be too much for this PR.

The Time Series class Daniel created is already generic enough (in concept at least) to handle adding additional columns.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Cleaned up the duplicate value list data from the subclass. Required adding custom deserializer back to properly handle the subtypes of TimeSeries.Record.

@zack-rma zack-rma requested a review from adamkorynta January 21, 2025 21:51
@zack-rma
Copy link
Collaborator Author

Build will fail until database schema changes are merged and a new release is available. Changes can be found here: https://bitbucket.hecdev.net/projects/CWMS/repos/cwms_database/pull-requests/412/overview

@MikeNeilson
Copy link
Contributor

Tests are still failing, actions output is still showing schema of "latest-dev"

@zack-rma zack-rma force-pushed the bugfix/ts_data_entry_issue_634 branch from 7ff531a to 67bcd17 Compare April 23, 2025 21:20
@inguyen314
Copy link

what is status?

@zack-rma
Copy link
Collaborator Author

what is status?

The necessary changes to the database were just merged in, we'll need an updated schema to be available before this feature can be finalized.

@zack-rma zack-rma force-pushed the bugfix/ts_data_entry_issue_634 branch from 0ac074b to c0b2a47 Compare May 19, 2025 22:18
@zack-rma zack-rma requested a review from MikeNeilson May 19, 2025 23:34
Copy link
Contributor

@MikeNeilson MikeNeilson left a comment

Choose a reason for hiding this comment

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

I have some minor nitpicks on formatting.

});

if (includeEntryDate) {
Copy link
Contributor

Choose a reason for hiding this comment

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

why are we doing this check if the above would already have returned a TimeSeriesWithDataEntryDate?

Alternatively why have the login within the lambda for data_entry_date if we're goign to change it here?

(I'm not seeing a difference other than the type after new.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Remnant from before the above line, perhaps. I've removed it and tested that all works as intended without it.

});
logger.fine(() -> query2.getSQL(ParamType.INLINED));
final TimeSeriesWithDate timeSeries = new TimeSeriesWithDate(timeseries);
query2.forEach(tsRecord -> timeSeries.addValue(
Copy link
Contributor

Choose a reason for hiding this comment

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

Given the previous logic above, does this need a new or just a cast so you can get the desired methods?

}
tsvArray.add(new ZTSV_TYPE(value.getDateTime(), dataValue, BigDecimal.valueOf(value.getQualityCode())));
}
for(TimeSeries.Record value : values)
Copy link
Contributor

Choose a reason for hiding this comment

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

incorrect curlies, but otherwise thank you for cleaning up the loop structures and formatting.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Fixed brackets

@@ -248,6 +256,11 @@ public static class Record {
@JsonProperty(value = "quality-code", index = 2)
int qualityCode;

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't want Zach to do it in this PR, already been too long, but does anyone think the above idea is crazy?


public Record(Timestamp dateTime, Double value, int qualityCode) {
super();
Copy link
Contributor

Choose a reason for hiding this comment

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

... what super?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Leftover from when I had created a shared parent class to resolve some serialization issues. Removed

* The data entry date is the date that the data was entered into the database.
*/
@JsonDeserialize(using = JsonDeserializer.None.class)
public final class TimeSeriesRecordWithDate extends TimeSeries.Record {
Copy link
Contributor

Choose a reason for hiding this comment

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

This confused me. Should be TimeSeriesRecordWithEntryDate

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Renamed as suggested

.header("Authorization",user.toHeaderValue())
.queryParam("office",officeId)
.queryParam("units","cfs")
.queryParam("unit","cfs")
Copy link
Contributor

Choose a reason for hiding this comment

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

It would seem we should really start using the constants we've defined. (Not a ding, pretty sure we're all guilty of that.)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Replaced with Controllers constant where applicable

.accept(Formats.JSONV2)
.header("Authorization", user.toHeaderValue())
.queryParam(Controllers.OFFICE, officeId)
.queryParam(Controllers.UNIT, "CFS")
Copy link
Contributor

Choose a reason for hiding this comment

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

Yes, this is definitely the superior way to set those parameters.

Copy link
Contributor

Choose a reason for hiding this comment

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

thank you for updating the formatting within this file while you were at it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Need Data Entry Date for Time Series Data
5 participants