cdss-ruby 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. checksums.yaml +7 -0
  2. data/.rubocop.yml +86 -0
  3. data/CHANGELOG.md +5 -0
  4. data/CODE_OF_CONDUCT.md +84 -0
  5. data/README.md +101 -0
  6. data/Rakefile +26 -0
  7. data/docs/Cdss/AdminCalls.html +399 -0
  8. data/docs/Cdss/Analysis.html +972 -0
  9. data/docs/Cdss/Client.html +581 -0
  10. data/docs/Cdss/Climate.html +1257 -0
  11. data/docs/Cdss/Concerns/LogReadingAttributes.html +406 -0
  12. data/docs/Cdss/Concerns/WellReadingAttributes.html +414 -0
  13. data/docs/Cdss/Concerns.html +117 -0
  14. data/docs/Cdss/GroundWater.html +945 -0
  15. data/docs/Cdss/Models/AdminCall.html +252 -0
  16. data/docs/Cdss/Models/Analysis.html +397 -0
  17. data/docs/Cdss/Models/CallAnalysis.html +140 -0
  18. data/docs/Cdss/Models/ClimateStation.html +249 -0
  19. data/docs/Cdss/Models/DiversionRecord.html +248 -0
  20. data/docs/Cdss/Models/Reading.html +301 -0
  21. data/docs/Cdss/Models/ReferenceTable.html +339 -0
  22. data/docs/Cdss/Models/RouteAnalysis.html +140 -0
  23. data/docs/Cdss/Models/SourceRoute.html +140 -0
  24. data/docs/Cdss/Models/Station.html +248 -0
  25. data/docs/Cdss/Models/Structure.html +259 -0
  26. data/docs/Cdss/Models/WaterClass.html +249 -0
  27. data/docs/Cdss/Models/WaterRight.html +255 -0
  28. data/docs/Cdss/Models/Well.html +251 -0
  29. data/docs/Cdss/Models.html +117 -0
  30. data/docs/Cdss/Parser.html +2155 -0
  31. data/docs/Cdss/Parsers/AdminCallsParser.html +201 -0
  32. data/docs/Cdss/Parsers/AnalysisParser.html +296 -0
  33. data/docs/Cdss/Parsers/BaseParser.html +207 -0
  34. data/docs/Cdss/Parsers/ClimateParser.html +253 -0
  35. data/docs/Cdss/Parsers/ReadingParser.html +201 -0
  36. data/docs/Cdss/Parsers/ReferenceTablesParser.html +201 -0
  37. data/docs/Cdss/Parsers/StationParser.html +201 -0
  38. data/docs/Cdss/Parsers/StructuresParser.html +305 -0
  39. data/docs/Cdss/Parsers/WaterRightsParser.html +219 -0
  40. data/docs/Cdss/Parsers/WellParser.html +357 -0
  41. data/docs/Cdss/Parsers.html +117 -0
  42. data/docs/Cdss/ReferenceTables.html +332 -0
  43. data/docs/Cdss/Structures.html +1132 -0
  44. data/docs/Cdss/SurfaceWater.html +798 -0
  45. data/docs/Cdss/Telemetry.html +763 -0
  46. data/docs/Cdss/Utils.html +1276 -0
  47. data/docs/Cdss/WaterRights.html +634 -0
  48. data/docs/Cdss.html +292 -0
  49. data/docs/_index.html +493 -0
  50. data/docs/class_list.html +54 -0
  51. data/docs/css/common.css +1 -0
  52. data/docs/css/full_list.css +58 -0
  53. data/docs/css/style.css +503 -0
  54. data/docs/file.README.html +108 -0
  55. data/docs/file_list.html +59 -0
  56. data/docs/frames.html +22 -0
  57. data/docs/index.html +108 -0
  58. data/docs/js/app.js +344 -0
  59. data/docs/js/full_list.js +242 -0
  60. data/docs/js/jquery.js +4 -0
  61. data/docs/method_list.html +790 -0
  62. data/docs/top-level-namespace.html +110 -0
  63. data/lib/cdss/admin_calls.rb +44 -0
  64. data/lib/cdss/analysis.rb +183 -0
  65. data/lib/cdss/client.rb +121 -0
  66. data/lib/cdss/climate.rb +155 -0
  67. data/lib/cdss/concerns/log_reading_attributes.rb +48 -0
  68. data/lib/cdss/concerns/well_reading_attributes.rb +56 -0
  69. data/lib/cdss/ground_water.rb +112 -0
  70. data/lib/cdss/models/admin_call.rb +45 -0
  71. data/lib/cdss/models/analysis.rb +77 -0
  72. data/lib/cdss/models/climate_station.rb +40 -0
  73. data/lib/cdss/models/reading.rb +54 -0
  74. data/lib/cdss/models/reference_table.rb +56 -0
  75. data/lib/cdss/models/station.rb +40 -0
  76. data/lib/cdss/models/structure.rb +101 -0
  77. data/lib/cdss/models/water_right.rb +47 -0
  78. data/lib/cdss/models/well.rb +43 -0
  79. data/lib/cdss/parser.rb +172 -0
  80. data/lib/cdss/parsers/admin_calls_parser.rb +47 -0
  81. data/lib/cdss/parsers/analysis_parser.rb +124 -0
  82. data/lib/cdss/parsers/base_parser.rb +18 -0
  83. data/lib/cdss/parsers/climate_parser.rb +86 -0
  84. data/lib/cdss/parsers/reading_parser.rb +90 -0
  85. data/lib/cdss/parsers/reference_tables_parser.rb +55 -0
  86. data/lib/cdss/parsers/station_parser.rb +42 -0
  87. data/lib/cdss/parsers/structures_parser.rb +96 -0
  88. data/lib/cdss/parsers/water_rights_parser.rb +77 -0
  89. data/lib/cdss/parsers/well_parser.rb +107 -0
  90. data/lib/cdss/reference_tables.rb +147 -0
  91. data/lib/cdss/structures.rb +235 -0
  92. data/lib/cdss/surface_water.rb +186 -0
  93. data/lib/cdss/telemetry.rb +98 -0
  94. data/lib/cdss/utils.rb +152 -0
  95. data/lib/cdss/version.rb +5 -0
  96. data/lib/cdss/water_rights.rb +95 -0
  97. data/lib/cdss.rb +27 -0
  98. data/sig/cdss/ruby.rbs +6 -0
  99. metadata +272 -0
@@ -0,0 +1,110 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>
7
+ Top Level Namespace
8
+
9
+ &mdash; Documentation by YARD 0.9.37
10
+
11
+ </title>
12
+
13
+ <link rel="stylesheet" href="css/style.css" type="text/css" />
14
+
15
+ <link rel="stylesheet" href="css/common.css" type="text/css" />
16
+
17
+ <script type="text/javascript">
18
+ pathId = "";
19
+ relpath = '';
20
+ </script>
21
+
22
+
23
+ <script type="text/javascript" charset="utf-8" src="js/jquery.js"></script>
24
+
25
+ <script type="text/javascript" charset="utf-8" src="js/app.js"></script>
26
+
27
+
28
+ </head>
29
+ <body>
30
+ <div class="nav_wrap">
31
+ <iframe id="nav" src="class_list.html?1"></iframe>
32
+ <div id="resizer"></div>
33
+ </div>
34
+
35
+ <div id="main" tabindex="-1">
36
+ <div id="header">
37
+ <div id="menu">
38
+
39
+ <a href="_index.html">Index</a> &raquo;
40
+
41
+
42
+ <span class="title">Top Level Namespace</span>
43
+
44
+ </div>
45
+
46
+ <div id="search">
47
+
48
+ <a class="full_list_link" id="class_list_link"
49
+ href="class_list.html">
50
+
51
+ <svg width="24" height="24">
52
+ <rect x="0" y="4" width="24" height="4" rx="1" ry="1"></rect>
53
+ <rect x="0" y="12" width="24" height="4" rx="1" ry="1"></rect>
54
+ <rect x="0" y="20" width="24" height="4" rx="1" ry="1"></rect>
55
+ </svg>
56
+ </a>
57
+
58
+ </div>
59
+ <div class="clear"></div>
60
+ </div>
61
+
62
+ <div id="content"><h1>Top Level Namespace
63
+
64
+
65
+
66
+ </h1>
67
+ <div class="box_info">
68
+
69
+
70
+
71
+
72
+
73
+
74
+
75
+
76
+
77
+
78
+
79
+ </div>
80
+
81
+ <h2>Defined Under Namespace</h2>
82
+ <p class="children">
83
+
84
+
85
+ <strong class="modules">Modules:</strong> <span class='object_link'><a href="Cdss.html" title="Cdss (module)">Cdss</a></span>
86
+
87
+
88
+
89
+
90
+ </p>
91
+
92
+
93
+
94
+
95
+
96
+
97
+
98
+
99
+
100
+ </div>
101
+
102
+ <div id="footer">
103
+ Generated on Thu Feb 20 21:19:08 2025 by
104
+ <a href="https://yardoc.org" title="Yay! A Ruby Documentation Tool" target="_parent">yard</a>
105
+ 0.9.37 (ruby-3.2.2).
106
+ </div>
107
+
108
+ </div>
109
+ </body>
110
+ </html>
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cdss
4
+ # Provides methods for accessing administrative calls data from the CDSS API.
5
+ #
6
+ # This module includes functionality for retrieving active and historical administrative
7
+ # calls based on various criteria such as division, location, and date ranges.
8
+ module AdminCalls
9
+ include Utils
10
+
11
+ # Fetches administrative calls based on various filtering criteria.
12
+ #
13
+ # @param [Integer, nil] division Water division to filter calls.
14
+ # @param [String, nil] location_wdid WDID of the call location structure.
15
+ # @param [Integer, nil] call_number Unique call identifier to query.
16
+ # @param [Date, nil] start_date Start date for calls data.
17
+ # @param [Date, nil] end_date End date for calls data.
18
+ # @param [Boolean] active Whether to fetch active (true) or historical (false) calls. Defaults to true.
19
+ # @return [Array<AdminCall>] Array of matching administrative call objects.
20
+ # @example Fetch active calls for a division
21
+ # get_admin_calls(division: 1, active: true)
22
+ def get_admin_calls(division: nil, location_wdid: nil, call_number: nil, start_date: nil, end_date: nil,
23
+ active: true)
24
+ query = build_query(
25
+ {
26
+ dateFormat: "spaceSepToSeconds",
27
+ division: division,
28
+ callNumber: call_number,
29
+ "min-dateTimeSet": format_date(start_date),
30
+ "max-dateTimeSet": format_date(end_date),
31
+ locationWdid: location_wdid
32
+ },
33
+ encode: true
34
+ )
35
+
36
+ endpoint = active ? "administrativecalls/active/" : "administrativecalls/historical/"
37
+
38
+ fetch_paginated_data(
39
+ endpoint: "/#{endpoint}",
40
+ query: query
41
+ ) { |data| Parser.parse_admin_calls(data) }
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cdss
4
+ # Provides methods for accessing analysis services from the CDSS API including
5
+ # call analysis and water source route frameworks.
6
+ module Analysis
7
+ include Utils
8
+
9
+ # Performs call analysis by WDID, showing daily priority percentages.
10
+ #
11
+ # @param [String] wdid DWR WDID unique structure identifier code.
12
+ # @param [String, Integer] admin_no Water Right Administration Number.
13
+ # @param [Date, nil] start_date Start date for analysis data.
14
+ # @param [Date, nil] end_date End date for analysis data.
15
+ # @param [Boolean] batch Whether to break date range into yearly batches. Defaults to false.
16
+ # @return [Array<CallAnalysis>] Array of call analysis records.
17
+ # @example Analyze calls for a specific WDID
18
+ # get_call_analysis_wdid(wdid: "0301234", admin_no: "12345.00000")
19
+ def get_call_analysis_wdid(wdid:, admin_no:, start_date: nil, end_date: nil, batch: false)
20
+ admin_no = admin_no.to_s
21
+
22
+ if batch
23
+ results = []
24
+ date_ranges = batch_dates(start_date, end_date)
25
+
26
+ date_ranges.each_with_index do |range, _index|
27
+ results.concat(
28
+ fetch_call_analysis_wdid(
29
+ wdid: wdid,
30
+ admin_no: admin_no,
31
+ start_date: range[0],
32
+ end_date: range[1]
33
+ )
34
+ )
35
+ end
36
+
37
+ results
38
+ else
39
+ fetch_call_analysis_wdid(
40
+ wdid: wdid,
41
+ admin_no: admin_no,
42
+ start_date: start_date,
43
+ end_date: end_date
44
+ )
45
+ end
46
+ end
47
+
48
+ # Performs call analysis by GNIS ID, showing daily priority percentages.
49
+ #
50
+ # @param [String] gnis_id GNIS ID to analyze.
51
+ # @param [String, Integer] admin_no Water Right Administration Number.
52
+ # @param [Float] stream_mile Stream mile for the analysis point.
53
+ # @param [Date, nil] start_date Start date for analysis data.
54
+ # @param [Date, nil] end_date End date for analysis data.
55
+ # @param [Boolean] batch Whether to break date range into yearly batches. Defaults to false.
56
+ # @return [Array<CallAnalysis>] Array of call analysis records.
57
+ def get_call_analysis_gnisid(gnis_id:, admin_no:, stream_mile:, start_date: nil, end_date: nil, batch: false)
58
+ admin_no = admin_no.to_s
59
+
60
+ if batch
61
+ results = []
62
+ date_ranges = batch_dates(start_date, end_date)
63
+
64
+ date_ranges.each_with_index do |range, _index|
65
+ results.concat(
66
+ fetch_call_analysis_gnisid(
67
+ gnis_id: gnis_id,
68
+ admin_no: admin_no,
69
+ stream_mile: stream_mile,
70
+ start_date: range[0],
71
+ end_date: range[1]
72
+ )
73
+ )
74
+ end
75
+
76
+ results
77
+ else
78
+ fetch_call_analysis_gnisid(
79
+ gnis_id: gnis_id,
80
+ admin_no: admin_no,
81
+ stream_mile: stream_mile,
82
+ start_date: start_date,
83
+ end_date: end_date
84
+ )
85
+ end
86
+ end
87
+
88
+ # Retrieves the DWR source route framework reference data.
89
+ #
90
+ # @param [Integer, nil] division Water division to filter by.
91
+ # @param [String, nil] gnis_name GNIS Name to filter by.
92
+ # @param [Integer, nil] water_district Water district to filter by.
93
+ # @return [Array<SourceRoute>] Array of source route framework records.
94
+ def get_source_route_framework(division: nil, gnis_name: nil, water_district: nil)
95
+ query = build_query(
96
+ {
97
+ dateFormat: "spaceSepToSeconds",
98
+ division: division,
99
+ gnisName: gnis_name,
100
+ waterDistrict: water_district
101
+ }
102
+ )
103
+
104
+ fetch_paginated_data(
105
+ endpoint: "/analysisservices/watersourcerouteframework/",
106
+ query: query
107
+ ) { |data| Parser.parse_source_routes(data) }
108
+ end
109
+
110
+ # Analyzes water source routes between two points.
111
+ #
112
+ # @param [String] lt_gnis_id Lower terminus GNIS ID.
113
+ # @param [Float] lt_stream_mile Lower terminus stream mile.
114
+ # @param [String] ut_gnis_id Upper terminus GNIS ID.
115
+ # @param [Float] ut_stream_mile Upper terminus stream mile.
116
+ # @return [Array<RouteAnalysis>] Array of route analysis records.
117
+ def get_source_route_analysis(lt_gnis_id:, lt_stream_mile:, ut_gnis_id:, ut_stream_mile:)
118
+ query = build_query(
119
+ {
120
+ ltGnisId: lt_gnis_id,
121
+ ltStreamMile: lt_stream_mile,
122
+ utGnisId: ut_gnis_id,
123
+ utStreamMile: ut_stream_mile
124
+ }
125
+ )
126
+
127
+ fetch_paginated_data(
128
+ endpoint: "/analysisservices/watersourcerouteanalysis/",
129
+ query: query
130
+ ) { |data| Parser.parse_route_analyses(data) }
131
+ end
132
+
133
+ private
134
+
135
+ # Fetches call analysis data for a specific WDID.
136
+ #
137
+ # @param [String] wdid WDID to analyze
138
+ # @param [String] admin_no Water Right Administration Number
139
+ # @param [Date, nil] start_date Start date for analysis data
140
+ # @param [Date, nil] end_date End date for analysis data
141
+ # @return [Array<CallAnalysis>] Array of call analysis records
142
+ def fetch_call_analysis_wdid(wdid:, admin_no:, start_date:, end_date:)
143
+ query = build_query(
144
+ {
145
+ wdid: wdid,
146
+ adminNo: admin_no,
147
+ startDate: format_date(start_date),
148
+ endDate: format_date(end_date)
149
+ }
150
+ )
151
+
152
+ fetch_paginated_data(
153
+ endpoint: "/analysisservices/callanalysisbywdid/",
154
+ query: query
155
+ ) { |data| Parser.parse_call_analyses(data, type: :wdid) }
156
+ end
157
+
158
+ # Fetches call analysis data for a specific GNIS ID and stream mile.
159
+ #
160
+ # @param [String] gnis_id GNIS ID to analyze
161
+ # @param [String] admin_no Water Right Administration Number
162
+ # @param [Float] stream_mile Stream mile for the analysis point
163
+ # @param [Date, nil] start_date Start date for analysis data
164
+ # @param [Date, nil] end_date End date for analysis data
165
+ # @return [Array<CallAnalysis>] Array of call analysis records
166
+ def fetch_call_analysis_gnisid(gnis_id:, admin_no:, stream_mile:, start_date:, end_date:)
167
+ query = build_query(
168
+ {
169
+ gnisId: gnis_id,
170
+ adminNo: admin_no,
171
+ streamMile: stream_mile,
172
+ startDate: format_date(start_date),
173
+ endDate: format_date(end_date)
174
+ }
175
+ )
176
+
177
+ fetch_paginated_data(
178
+ endpoint: "/analysisservices/callanalysisbygnisid/",
179
+ query: query
180
+ ) { |data| Parser.parse_call_analyses(data, type: :gnis) }
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cdss
4
+ # Main client class for interacting with the CDSS API.
5
+ #
6
+ # The Client class provides access to all CDSS API endpoints and handles authentication,
7
+ # request configuration, and response processing.
8
+ #
9
+ # @example Create a new client
10
+ # client = Cdss::Client.new(api_key: "your-api-key")
11
+ # client.get_climate_stations(county: "Denver")
12
+ class Client
13
+ include HTTParty
14
+ base_uri Cdss.config.base_url
15
+
16
+ include AdminCalls
17
+ include Analysis
18
+ include Climate
19
+ include GroundWater
20
+ include Structures
21
+ include SurfaceWater
22
+ include Telemetry
23
+ include WaterRights
24
+ include ReferenceTables
25
+
26
+ # @return [Hash] Additional options passed to the client
27
+ attr_reader :options
28
+
29
+ # @return [String, nil] API key for authentication
30
+ attr_reader :api_key
31
+
32
+ # Initialize a new CDSS API client.
33
+ #
34
+ # @param [String, nil] api_key API key for authentication
35
+ # @param [Hash] options Additional options for configuring the client
36
+ # @return [Client] New client instance
37
+ def initialize(api_key: nil, **options)
38
+ @options = options
39
+ @api_key = api_key
40
+ setup_client
41
+ end
42
+
43
+ # Update the client's API key.
44
+ #
45
+ # @param [String] key New API key
46
+ # @return [String] The new API key
47
+ def api_key=(key)
48
+ @api_key = key
49
+ setup_client
50
+ end
51
+
52
+ private
53
+
54
+ # Configures the HTTP client with timeout and authorization headers.
55
+ # Sets the User-Agent and API token if provided.
56
+ #
57
+ # @return [void]
58
+ def setup_client
59
+ self.class.default_timeout(Cdss.config.timeout)
60
+ self.class.headers({
61
+ "User-Agent" => Cdss.config.user_agent,
62
+ "Token" => api_key
63
+ }.compact)
64
+ end
65
+
66
+ # Processes API responses and handles error conditions.
67
+ #
68
+ # @param [HTTParty::Response] response The raw API response
69
+ # @return [Hash] Parsed JSON response body
70
+ # @raise [RuntimeError] When the API request fails
71
+ def handle_response(response)
72
+ return JSON.parse(response.body) if response.success?
73
+
74
+ raise "API request failed with status #{response.code}: #{response.message}"
75
+ end
76
+
77
+ # Makes GET requests with debug logging support.
78
+ #
79
+ # @param [Array] args The request arguments including path and options
80
+ # @return [HTTParty::Response] The API response
81
+ def self.get(*args)
82
+ options = args.last.is_a?(Hash) ? args.pop : {}
83
+ debug_request(args.first, options)
84
+ super(*args, options)
85
+ end
86
+
87
+ # Makes authenticated GET requests to the API.
88
+ #
89
+ # @param [String] path The API endpoint path
90
+ # @param [Hash] options Request options including query parameters
91
+ # @return [HTTParty::Response] The API response
92
+ def get(path, options = {})
93
+ options[:query] ||= {}
94
+ options[:query][:apiKey] = api_key if api_key
95
+ self.class.get(path, options)
96
+ end
97
+
98
+ # Logs detailed request information when debug mode is enabled.
99
+ #
100
+ # @param [String] endpoint The API endpoint being called
101
+ # @param [Hash] options Request options including query parameters and headers
102
+ # @return [void]
103
+ def self.debug_request(endpoint, options)
104
+ return unless Cdss.config.debug
105
+
106
+ query_string = options[:query]&.map { |k, v| "#{k}=#{v}" }&.join("&")
107
+ full_url = [base_uri, endpoint].join
108
+ full_url += "?#{query_string}" if query_string
109
+
110
+ puts "\n=== CDSS API Request ==="
111
+ puts "URL: #{full_url}"
112
+ puts "\nHeaders:"
113
+ headers = default_options[:headers] || {}
114
+ headers.merge!(options[:headers] || {})
115
+ headers.each do |key, value|
116
+ puts " #{key}: #{value}"
117
+ end
118
+ puts "=====================\n"
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cdss
4
+ # Provides methods for accessing climate data from the CDSS API.
5
+ #
6
+ # This module includes functionality for retrieving climate stations,
7
+ # frost dates, and time series data at various time scales.
8
+ module Climate
9
+ include Utils
10
+
11
+ # Fetches climate stations based on various filtering criteria.
12
+ #
13
+ # @param [Hash, Array, nil] aoi Area of interest for spatial searches. If hash, must contain :latitude and :longitude keys.
14
+ # If array, must contain [longitude, latitude].
15
+ # @param [Integer, nil] radius Radius in miles for spatial search around aoi. Defaults to 20 if aoi is provided.
16
+ # @param [String, nil] county County name to filter stations.
17
+ # @param [Integer, nil] division Water division number to filter stations.
18
+ # @param [String, nil] station_name Name of the station to filter by.
19
+ # @param [String, nil] site_id Station site ID to filter by.
20
+ # @param [Integer, nil] water_district Water district number to filter stations.
21
+ # @return [Array<Station>] Array of matching climate station objects.
22
+ # @raise [ArgumentError] If aoi parameter is provided but invalid.
23
+ # @example Fetch stations in Denver county
24
+ # get_climate_stations(county: 'Denver')
25
+ def get_climate_stations(aoi: nil, radius: nil, county: nil, division: nil, station_name: nil, site_id: nil,
26
+ water_district: nil)
27
+ query_params = {
28
+ dateFormat: "spaceSepToSeconds",
29
+ units: "miles",
30
+ county: county,
31
+ division: division,
32
+ stationName: station_name,
33
+ siteId: site_id,
34
+ waterDistrict: water_district
35
+ }
36
+
37
+ if aoi
38
+ if aoi.is_a?(Hash) && aoi[:latitude] && aoi[:longitude]
39
+ query_params.merge!(longitude: aoi[:longitude], latitude: aoi[:latitude])
40
+ elsif aoi.is_a?(Array) && aoi.count == 2
41
+ query_params.merge!(longitude: aoi[0], latitude: aoi[1])
42
+ else
43
+ raise ArgumentError, "Invalid 'aoi' parameter"
44
+ end
45
+ query_params[:radius] = radius || 20
46
+ end
47
+
48
+ fetch_paginated_data(
49
+ endpoint: "/climatedata/climatestations/",
50
+ query: query_params
51
+ ) { |data| Parser.parse_climate_stations(data) }
52
+ end
53
+
54
+ # Fetches frost dates for a specific climate station.
55
+ #
56
+ # @param [String] station_number Station number to fetch frost dates for.
57
+ # @param [Date, nil] start_date Start date for frost dates data.
58
+ # @param [Date, nil] end_date End date for frost dates data.
59
+ # @return [Array<Reading>] Array of frost date readings.
60
+ def get_climate_frost_dates(station_number:, start_date: nil, end_date: nil)
61
+ query_params = {
62
+ dateFormat: "spaceSepToSeconds",
63
+ stationNum: station_number
64
+ }
65
+
66
+ query_params[:"min-calYear"] = start_date.strftime("%Y") if start_date
67
+ query_params[:"max-calYear"] = end_date.strftime("%Y") if end_date
68
+
69
+ fetch_paginated_data(
70
+ endpoint: "/climatedata/climatestationfrostdates/",
71
+ query: query_params
72
+ ) { |data| Parser.parse_climate_readings(data, type: :frost_dates) }
73
+ end
74
+
75
+ # Fetches daily climate time series data.
76
+ #
77
+ # @param [String] param Climate parameter type to retrieve
78
+ # @param [String, nil] station_number Station number
79
+ # @param [String, nil] site_id Site ID
80
+ # @param [Date, nil] start_date Start date for time series data
81
+ # @param [Date, nil] end_date End date for time series data
82
+ # @return [Array<Reading>] Array of daily climate readings
83
+ def get_climate_ts_day(param:, station_number: nil, site_id: nil, start_date: nil, end_date: nil)
84
+ query_params = {
85
+ dateFormat: "spaceSepToSeconds",
86
+ measType: param,
87
+ stationNum: station_number,
88
+ siteId: site_id
89
+ }
90
+
91
+ query_params[:"min-measDate"] = start_date.strftime("%m-%d-%Y") if start_date
92
+ query_params[:"max-measDate"] = end_date.strftime("%m-%d-%Y") if end_date
93
+
94
+ fetch_paginated_data(
95
+ endpoint: "/climatedata/climatestationtsday/",
96
+ query: query_params
97
+ ) { |data| Parser.parse_climate_readings(data, type: :daily) }
98
+ end
99
+
100
+ # Fetches monthly climate time series data.
101
+ #
102
+ # @param [String] param Climate parameter type to retrieve
103
+ # @param [String, nil] station_number Station number
104
+ # @param [String, nil] site_id Site ID
105
+ # @param [Date, nil] start_date Start date for time series data
106
+ # @param [Date, nil] end_date End date for time series data
107
+ # @return [Array<Reading>] Array of monthly climate readings
108
+ def get_climate_ts_month(param:, station_number: nil, site_id: nil, start_date: nil, end_date: nil)
109
+ query_params = {
110
+ dateFormat: "spaceSepToSeconds",
111
+ measType: param,
112
+ stationNum: station_number,
113
+ siteId: site_id
114
+ }
115
+
116
+ query_params[:"min-calYear"] = start_date.strftime("%Y") if start_date
117
+ query_params[:"max-calYear"] = end_date.strftime("%Y") if end_date
118
+
119
+ fetch_paginated_data(
120
+ endpoint: "/climatedata/climatestationtsmonth/",
121
+ query: query_params
122
+ ) { |data| Parser.parse_climate_readings(data, type: :monthly) }
123
+ end
124
+
125
+ # Fetches climate time series data for specified stations.
126
+ #
127
+ # @param [String, nil] station_number Station number
128
+ # @param [String, nil] site_id Site ID
129
+ # @param [String] param Climate parameter to retrieve (Evap, FrostDate, MaxTemp, etc.)
130
+ # @param [Date, nil] start_date Start date for time series data
131
+ # @param [Date, nil] end_date End date for time series data
132
+ # @param [String] timescale Time interval for data aggregation ('day' or 'month'). Defaults to 'day'
133
+ # @return [Array<Reading>] Array of time series reading objects
134
+ # @raise [ArgumentError] If an invalid parameter or timescale is provided
135
+ def get_climate_ts(station_number: nil, site_id: nil, param: nil, start_date: nil, end_date: nil, timescale: "day")
136
+ valid_params = %w[Evap FrostDate MaxTemp MeanTemp MinTemp Precip Snow SnowDepth SnowSWE Solar VP Wind]
137
+ raise ArgumentError, "Invalid parameter: '#{param}'. Valid values are: #{valid_params.join(', ')}" unless valid_params.include?(param)
138
+
139
+ day_formats = %w[day days daily d]
140
+ month_formats = %w[month months monthly mon m]
141
+ timescale = timescale.to_s.downcase
142
+
143
+ case timescale
144
+ when *day_formats
145
+ get_climate_ts_day(station_number: station_number, site_id: site_id, param: param,
146
+ start_date: start_date, end_date: end_date)
147
+ when *month_formats
148
+ get_climate_ts_month(station_number: station_number, site_id: site_id, param: param,
149
+ start_date: start_date, end_date: end_date)
150
+ else
151
+ raise ArgumentError, "Invalid timescale: '#{timescale}'. Use 'day' or 'month'."
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Cdss
4
+ module Concerns
5
+ # A concern module that adds log reading-related attributes to classes.
6
+ #
7
+ # This module provides a set of standardized attributes for log reading data,
8
+ # including aquifer information, depth measurements, elevation, and comments.
9
+ # When included in a class, it dynamically adds accessor methods for these attributes.
10
+ #
11
+ # @example Adding log reading attributes to a class
12
+ # class WellLog
13
+ # include Cdss::Concerns::LogReadingAttributes
14
+ # end
15
+ module LogReadingAttributes
16
+ # Predefined list of log reading attributes
17
+ #
18
+ # @return [Array<Symbol>] List of attributes related to log readings
19
+ LOG_ATTRIBUTES = %i[
20
+ aquifer
21
+ g_log_top_depth
22
+ g_log_base_depth
23
+ g_log_top_elev
24
+ g_log_base_elev
25
+ g_log_thickness
26
+ comment
27
+ ].freeze
28
+
29
+ # Dynamically adds accessor methods when the module is included in a class
30
+ #
31
+ # @param [Class] base The class including this module
32
+ def self.included(base)
33
+ base.class_eval do
34
+ attr_accessor(*LOG_ATTRIBUTES)
35
+ end
36
+ end
37
+
38
+ # Checks if the instance has log reading data
39
+ #
40
+ # @return [Boolean] true if aquifer or log thickness is present, false otherwise
41
+ # @example Check if a well log has reading data
42
+ # well_log.log_reading? # => true or false
43
+ def log_reading?
44
+ !aquifer.nil? || !g_log_thickness.nil?
45
+ end
46
+ end
47
+ end
48
+ end