Skip to content
Draft
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
38 changes: 38 additions & 0 deletions schema/src/cwms/cwms_level_pkg.sql
Original file line number Diff line number Diff line change
Expand Up @@ -1448,6 +1448,44 @@ function retrieve_loc_lvl_values3(
p_office_id in varchar2 default null,
p_level_precedence in varchar2 default 'VN')
return ztsv_array;
/**
* Retrieves a time series of location level values for a specified location level (returns times in UTC)
*
* @param p_location_level_id The location level identifier. Format is location.parameter.parameter_type.duration.specified_level
* @param p_specified_times The times to retrieve the location level values (regular interval only, null is for irregular)
* @param p_start_time The start of the time window
* @param p_end_time The end of the time window
* @param p_level_units The value unit to retrieve the level values in
* @param p_attribute_id The attribute identifier, if applicable. Format is parameter.parameter_type.duration
* @param p_attribute_value The value of the attribute, if applicable
* @param p_attribute_units The unit of the attribute, if applicable
* @param p_timezone_id The time zone of the time window. Retrieved dates are also in this time zone
* @param p_office_id The office that owns the location level. If not specified or NULL, the session user's default office is used
* @param p_level_precedence One or two characters that specify the precedence of virtual and non-virtual location levels in the results.
* The default value 'VN' gives virtual location levels higher precedence, allowing them to hide non-virtual location levels
* where the two types overlap in time. Valid value are:
* <ul>
* <li><b>N</b> specifies results from non-virtual (normal) location levels only
* <li><b>V</b> specifies results from virtual location levels only
* <li><b>NV</b> specifies results from non-virtual (normal) location levels where they exist, with virtual location levels allowed where non-virtual levels don't exist
* <li><b>VN</b> (default) specifies results from virtual location levels where they exist, with non-virtual location levels allowed where virtual levels don't exist
* </ul>
*
* @return The location level values as a double_tab_t
*/
function retrieve_loc_lvl_values4(
p_location_level_id in varchar2,
p_specified_times in ztsv_array default null,
p_start_time in date default null,
p_end_time in date default null,
p_level_units in varchar2,
p_attribute_id in varchar2 default null,
p_attribute_value in number default null,
p_attribute_units in varchar2 default null,
p_timezone_id in varchar2 default 'UTC',
p_office_id in varchar2 default null,
p_level_precedence in varchar2 default 'VN')
return ztsv_array;
/**
* Retrieves a time series of location level values for a specified location level
* time series, specified level, and time window. The location level identifier
Expand Down
138 changes: 138 additions & 0 deletions schema/src/cwms/cwms_level_pkg_body.sql
Original file line number Diff line number Diff line change
Expand Up @@ -6395,6 +6395,144 @@ begin
return l_level_values;
end retrieve_loc_lvl_values3;
--------------------------------------------------------------------------------
-- FUNCTION retrieve_loc_lvl_values4
--------------------------------------------------------------------------------
function retrieve_loc_lvl_values4(
p_location_level_id in varchar2,
p_specified_times in ztsv_array default null,
p_start_time in date default null,
p_end_time in date default null,
p_level_units in varchar2,
p_attribute_id in varchar2 default null,
p_attribute_value in number default null,
p_attribute_units in varchar2 default null,
p_timezone_id in varchar2 default 'UTC',
p_office_id in varchar2 default null,
p_level_precedence in varchar2 default 'VN')
return ztsv_array
is
l_level_values ztsv_array;
l_level_values_interp ztsv_array := ztsv_array();
l_min_date_utc date;
l_max_date_utc date;
l_level_id_parts str_tab_t;
l_attr_id_parts str_tab_t;
l_hi_idx pls_integer;
l_lo_idx pls_integer;
l_log_used boolean;
l_ratio number;
l_date_offset number;
l_date_offsets number_tab_t;
l_seq_props cwms_lookup.sequence_properties_t;
l_values double_tab_t;
l_quality number_tab_t;
begin
-- sanity checks
if p_location_level_id is null then
cwms_err.raise(
'ERROR',
'Location Level ID must not be null.');
end if;
if p_level_units is null then
cwms_err.raise(
'ERROR',
'Location Level units must not be null.');
end if;
if p_specified_times is not null then
retrieve_loc_lvl_values3(
p_level_values => l_level_values,
p_specified_times => p_specified_times,
p_location_level_id => p_location_level_id,
p_level_units => p_level_units,
p_attribute_id => p_attribute_id,
p_attribute_value => p_attribute_value,
p_attribute_units => p_attribute_units,
p_timezone_id => p_timezone_id,
p_office_id => p_office_id,
p_level_precedence => p_level_precedence
);
return l_level_values;
end if;
-- do retrieval for irregular level as ts
if p_start_time is null or p_end_time is null then
cwms_err.raise(
'ERROR',
'Location Level start and end timestamps must not be null.');
end if;
---------------------------------------------------------
-- get the location level values the level breakpoints --
---------------------------------------------------------
l_level_id_parts := cwms_util.split_text(p_location_level_id, '.');
if p_attribute_id is null then
l_attr_id_parts := str_tab_t(null, null, null);
else
l_attr_id_parts := cwms_util.split_text(p_attribute_id, '.');
end if;
l_min_date_utc := cwms_util.CHANGE_TIMEZONE(p_start_time, p_timezone_id, 'UTC');
l_max_date_utc := cwms_util.CHANGE_TIMEZONE(p_end_time, p_timezone_id, 'UTC');
retrieve_loc_lvl_values_utc(
p_level_values => l_level_values,
p_location_id => l_level_id_parts(1),
p_parameter_id => l_level_id_parts(2),
p_parameter_type_id => l_level_id_parts(3),
p_duration_id => l_level_id_parts(4),
p_spec_level_id => l_level_id_parts(5),
p_level_units => p_level_units,
p_start_time_utc => l_min_date_utc,
p_end_time_utc => l_max_date_utc,
p_attribute_value => p_attribute_value,
p_attribute_units => p_attribute_units,
p_attribute_parameter_id => l_attr_id_parts(1),
p_attribute_param_type_id => l_attr_id_parts(2),
p_attribute_duration_id => l_attr_id_parts(3),
p_level_precedence => p_level_precedence,
p_office_id => p_office_id);
-----------------------------------------
-- set up variables to do lookups with --
-----------------------------------------
select date_time - l_min_date_utc,
value,
quality_code
bulk collect
into l_date_offsets,
l_values,
l_quality
from table(l_level_values);
l_seq_props := cwms_lookup.analyze_sequence(l_date_offsets);
l_level_values_interp.extend(l_level_values.count);
for i in 1..l_level_values.count loop
l_level_values_interp(i) := ztsv_type(l_level_values(i).date_time, null, 0);
l_date_offset := l_level_values(i).date_time - l_min_date_utc;
l_hi_idx := cwms_lookup.find_high_index(l_date_offset, l_date_offsets, l_seq_props);
l_lo_idx := l_hi_idx -1 ;
l_ratio := cwms_lookup.find_ratio(
p_log_used => l_log_used,
p_value => l_date_offset,
p_sequence => l_date_offsets,
p_high_index => l_hi_idx,
p_increasing => l_seq_props.increasing_range,
p_in_range_behavior => cwms_lookup.method_linear,
p_out_range_low_behavior => cwms_lookup.method_null, -- set values to null before earliest effective date
p_out_range_high_behavior => cwms_lookup.method_linear);
if l_ratio is not null then
if l_level_values(l_lo_idx).quality_code = 0 then
----------------------
-- no interpolation --
----------------------
l_level_values_interp(i).value := l_level_values(l_lo_idx).value;
else
-------------------
-- interpolation --
-------------------
l_level_values_interp(i).value := l_level_values(l_lo_idx).value + l_ratio * (l_level_values(l_hi_idx).value - l_level_values(l_lo_idx).value);
l_level_values_interp(i).quality_code := 1;
end if;
end if;
end loop;
l_level_values := l_level_values_interp;
return l_level_values;
end retrieve_loc_lvl_values4;
--------------------------------------------------------------------------------
-- PROCEDURE retrieve_location_level_values
--
-- Retreives a time series of Location Level values for a specified time window
Expand Down
89 changes: 89 additions & 0 deletions schema/src/test/test_cwms_level.sql
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ procedure test_cwdb_304_null_values_in_av_location_level_curval;
procedure test_issue_61_monthly_seasonal_values_with_origin_day_gt_28;
--%test(Issue 83: level-as-timeseries retrieval bug for seasonal data)
procedure test_issue_83_retrieve_seasonal_as_timeseries;
--%test(CDA-116: irregular level-as-timeseries retrieval)
procedure test_cda_116_irregular_level_as_timeseries;

c_office_id varchar2(16) := '&&office_id';
c_location_id varchar2(57) := 'LocLevelTestLoc';
Expand Down Expand Up @@ -2512,6 +2514,93 @@ begin

end test_issue_83_retrieve_seasonal_as_timeseries;

--------------------------------------------------------------------------------
-- procedure test_cda_116_irregular_level_as_timeseries
--------------------------------------------------------------------------------
procedure test_cda_116_irregular_level_as_timeseries
is
l_result_values ztsv_array := ztsv_array();
l_effective_date date := date '2025-01-01';
l_start_ts timestamp := to_timestamp('2025-01-01 12:00', 'YYYY-MM-DD HH24:MI');
l_end_ts timestamp := to_timestamp('2025-01-03 14:45', 'YYYY-MM-DD HH24:MI');
l_interpolate str_tab_t := str_tab_t('F', 'T');
l_values ztsv_array;
l_elev_tsid varchar2(191) := c_location_id||'.Elev.Inst.1Hour.0.IRRTest';
l_elev_ts_data cwms_t_tsv_array := cwms_t_tsv_array (
cwms_t_tsv (from_tz(cast(cwms_util.change_timezone(l_start_ts, c_timezone_id, 'UTC') + 1/24 as timestamp), 'UTC'), 1000, 0),
cwms_t_tsv (from_tz(cast(cwms_util.change_timezone(l_start_ts, c_timezone_id, 'UTC') + 3/24 as timestamp), 'UTC'), 1010, 0),
cwms_t_tsv (from_tz(cast(cwms_util.change_timezone(l_start_ts, c_timezone_id, 'UTC') + 4/24 as timestamp), 'UTC'), 1020, 0),
cwms_t_tsv (from_tz(cast(cwms_util.change_timezone(l_start_ts, c_timezone_id, 'UTC') + 7/24 as timestamp), 'UTC'), 1050, 0),
cwms_t_tsv (from_tz(cast(cwms_util.change_timezone(l_start_ts, c_timezone_id, 'UTC') + 11/24 as timestamp), 'UTC'), 1100, 0),
cwms_t_tsv (from_tz(cast(cwms_util.change_timezone(l_start_ts, c_timezone_id, 'UTC') + 12/24 as timestamp), 'UTC'), 1110, 0),
cwms_t_tsv (from_tz(cast(cwms_util.change_timezone(l_start_ts, c_timezone_id, 'UTC') + 16/24 as timestamp), 'UTC'), 1150, 0)
);
l_prev binary_integer;
l_next binary_integer;
l_prev_val number;
l_next_val number;
begin
cwms_ts.store_ts(
l_elev_tsid,
c_elev_unit,
l_elev_ts_data,
cwms_util.replace_all,
'F',
cwms_util.non_versioned,
c_office_id,
'T');
for i in 1..l_interpolate.count loop
cwms_level.store_location_level4(
p_location_level_id => c_top_of_normal_elev_id,
p_level_value => null,
p_level_units => c_elev_unit,
p_effective_date => l_effective_date,
p_timezone_id => c_timezone_id,
p_tsid => l_elev_tsid,
p_interpolate => l_interpolate(i),
p_office_id => c_office_id);

l_result_values := cwms_level.retrieve_loc_lvl_values4(
p_location_level_id => c_top_of_normal_elev_id,
p_start_time => l_start_ts,
p_end_time => l_end_ts,
p_level_units => 'm',
p_timezone_id => 'UTC',
p_office_id => c_office_id
);

for j in 1..l_result_values.count loop
if mod(j, 2) = 0 then
------------------------------------------
-- even indices are between value times --
------------------------------------------
l_prev := j/2;
l_next := mod(j/2, l_elev_ts_data.count)+1;
l_prev_val := round(l_elev_ts_data(l_prev).value, 5);
l_next_val := round(l_elev_ts_data(l_next).value, 5);
if l_interpolate(i) = 'T' then
ut.expect(round(l_result_values(j).value, 5)).to_equal((l_prev_val + l_next_val) / 2);
else
ut.expect(round(l_result_values(j).value, 5)).to_equal(l_elev_ts_data(j/2).value);
end if;
else
------------------------------------
-- odd indices are at value times --
------------------------------------
l_prev := mod((j-1)/2, l_elev_ts_data.count)+1;
l_prev_val := round(l_elev_ts_data(l_prev).value, 5);
ut.expect(round(l_result_values(j).value, 5)).to_equal(l_prev_val);
end if;
ut.expect(l_result_values(i).quality_code).to_equal(case l_interpolate(i) when 'T' then 1 else 0 end);
end loop;

cwms_level.delete_location_level(
p_location_level_id => c_top_of_normal_elev_id,
p_office_id => c_office_id
);
end loop;
end test_cda_116_irregular_level_as_timeseries;

end test_cwms_level;
/
show errors;
Loading