openstudio-workflow 1.3.4 → 1.3.5

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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +89 -77
  3. data/README.md +67 -93
  4. data/Rakefile +36 -36
  5. data/lib/openstudio-workflow.rb +65 -65
  6. data/lib/openstudio/workflow/adapters/input/local.rb +311 -324
  7. data/lib/openstudio/workflow/adapters/output/local.rb +158 -161
  8. data/lib/openstudio/workflow/adapters/output/socket.rb +106 -107
  9. data/lib/openstudio/workflow/adapters/output/web.rb +82 -82
  10. data/lib/openstudio/workflow/adapters/output_adapter.rb +163 -163
  11. data/lib/openstudio/workflow/job.rb +57 -57
  12. data/lib/openstudio/workflow/jobs/resources/monthly_report.idf +222 -222
  13. data/lib/openstudio/workflow/jobs/run_energyplus.rb +70 -70
  14. data/lib/openstudio/workflow/jobs/run_ep_measures.rb +73 -73
  15. data/lib/openstudio/workflow/jobs/run_initialization.rb +203 -203
  16. data/lib/openstudio/workflow/jobs/run_os_measures.rb +89 -89
  17. data/lib/openstudio/workflow/jobs/run_postprocess.rb +73 -73
  18. data/lib/openstudio/workflow/jobs/run_preprocess.rb +104 -104
  19. data/lib/openstudio/workflow/jobs/run_reporting_measures.rb +118 -118
  20. data/lib/openstudio/workflow/jobs/run_translation.rb +84 -84
  21. data/lib/openstudio/workflow/multi_delegator.rb +62 -62
  22. data/lib/openstudio/workflow/registry.rb +172 -172
  23. data/lib/openstudio/workflow/run.rb +342 -328
  24. data/lib/openstudio/workflow/time_logger.rb +96 -96
  25. data/lib/openstudio/workflow/util.rb +49 -49
  26. data/lib/openstudio/workflow/util/energyplus.rb +575 -605
  27. data/lib/openstudio/workflow/util/io.rb +68 -68
  28. data/lib/openstudio/workflow/util/measure.rb +658 -650
  29. data/lib/openstudio/workflow/util/model.rb +151 -151
  30. data/lib/openstudio/workflow/util/post_process.rb +235 -238
  31. data/lib/openstudio/workflow/util/weather_file.rb +143 -143
  32. data/lib/openstudio/workflow/version.rb +40 -40
  33. data/lib/openstudio/workflow_json.rb +475 -476
  34. data/lib/openstudio/workflow_runner.rb +263 -268
  35. metadata +24 -24
@@ -1,324 +1,311 @@
1
- # *******************************************************************************
2
- # OpenStudio(R), Copyright (c) 2008-2018, Alliance for Sustainable Energy, LLC.
3
- # All rights reserved.
4
- # Redistribution and use in source and binary forms, with or without
5
- # modification, are permitted provided that the following conditions are met:
6
- #
7
- # (1) Redistributions of source code must retain the above copyright notice,
8
- # this list of conditions and the following disclaimer.
9
- #
10
- # (2) Redistributions in binary form must reproduce the above copyright notice,
11
- # this list of conditions and the following disclaimer in the documentation
12
- # and/or other materials provided with the distribution.
13
- #
14
- # (3) Neither the name of the copyright holder nor the names of any contributors
15
- # may be used to endorse or promote products derived from this software without
16
- # specific prior written permission from the respective party.
17
- #
18
- # (4) Other than as required in clauses (1) and (2), distributions in any form
19
- # of modifications or other derivative works may not use the "OpenStudio"
20
- # trademark, "OS", "os", or any other confusingly similar designation without
21
- # specific prior written permission from Alliance for Sustainable Energy, LLC.
22
- #
23
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24
- # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25
- # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26
- # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER, THE UNITED STATES
27
- # GOVERNMENT, OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28
- # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
29
- # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30
- # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31
- # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32
- # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
33
- # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
- # *******************************************************************************
35
-
36
- require 'openstudio/workflow_json'
37
-
38
- # Local file based workflow
39
- module OpenStudio
40
- module Workflow
41
- module InputAdapter
42
- class Local
43
- def initialize(osw_path = './workflow.osw')
44
- @osw_abs_path = File.absolute_path(osw_path, Dir.pwd)
45
-
46
- @workflow = nil
47
- if File.exist? @osw_abs_path
48
- @workflow = ::JSON.parse(File.read(@osw_abs_path), symbolize_names: true)
49
- end
50
-
51
- begin
52
- # configure the OSW with paths for loaded extension gems
53
- # Bundler.require is called in the CLI to load extension gems
54
- @workflow = OpenStudio::Extension::configure_osw(@workflow)
55
- rescue NameError => e
56
- end
57
-
58
- @workflow_json = nil
59
- @run_options = nil
60
- if @workflow
61
- begin
62
- # Create a temporary WorkflowJSON, will not be same one used in registry during simulation
63
- @workflow_json = OpenStudio::WorkflowJSON.new(JSON.fast_generate(workflow))
64
- @workflow_json.setOswDir(osw_dir)
65
- rescue NameError => e
66
- @workflow_json = WorkflowJSON_Shim.new(workflow, osw_dir)
67
- end
68
-
69
- begin
70
- @run_options = @workflow_json.runOptions
71
- rescue
72
- end
73
- end
74
- end
75
-
76
- # Get the OSW file from the local filesystem
77
- #
78
- def workflow
79
- raise "Could not read workflow from #{@osw_abs_path}" if @workflow.nil?
80
- @workflow
81
- end
82
-
83
- # Get the OSW path
84
- #
85
- def osw_path
86
- @osw_abs_path
87
- end
88
-
89
- # Get the OSW dir
90
- #
91
- def osw_dir
92
- File.dirname(@osw_abs_path)
93
- end
94
-
95
- # Get the run dir
96
- #
97
- def run_dir
98
- result = File.join(osw_dir, 'run')
99
- if @workflow_json
100
- begin
101
- result = @workflow_json.absoluteRunDir.to_s
102
- rescue
103
- end
104
- end
105
- result
106
- end
107
-
108
- def output_adapter(user_options, default, logger)
109
-
110
- # user option trumps all others
111
- return user_options[:output_adapter] if user_options[:output_adapter]
112
-
113
- # try to read from OSW
114
- if @run_options && !@run_options.empty?
115
- custom_adapter = @run_options.get.customOutputAdapter
116
- if !custom_adapter.empty?
117
- begin
118
- custom_file_name = custom_adapter.get.customFileName
119
- class_name = custom_adapter.get.className
120
- options = ::JSON.parse(custom_adapter.get.options, :symbolize_names => true)
121
-
122
- # merge with user options, user options will replace options loaded from OSW
123
- options.merge!(user_options)
124
-
125
- # stick output_directory in options
126
- options[:output_directory] = run_dir
127
-
128
- p = @workflow_json.findFile(custom_file_name)
129
- if !p.empty?
130
- load(p.get.to_s)
131
- output_adapter = eval("#{class_name}.new(options)")
132
- return output_adapter
133
- else
134
- log_message = "Failed to load custom adapter file '#{custom_file_name}'"
135
- logger.error log_message
136
- raise log_message
137
- end
138
- rescue
139
- log_message = "Failed to load custom adapter '#{class_name}' from file '#{custom_file_name}'"
140
- logger.error log_message
141
- raise log_message
142
- end
143
- end
144
- end
145
-
146
- return default
147
- end
148
-
149
- def jobs(user_options, default, logger)
150
-
151
- # user option trumps all others
152
- return user_options[:jobs] if user_options[:jobs]
153
-
154
- # try to read from OSW
155
- begin
156
- #log_message = "Reading custom job states from OSW is not currently supported'"
157
- #logger.info log_message
158
- rescue
159
- end
160
-
161
- return default
162
- end
163
-
164
- def debug(user_options, default)
165
-
166
- # user option trumps all others
167
- return user_options[:debug] if user_options[:debug]
168
-
169
- # try to read from OSW
170
- if @run_options && !@run_options.empty?
171
- return @run_options.get.debug
172
- end
173
-
174
- return default
175
- end
176
-
177
- def fast(user_options, default)
178
-
179
- # user option trumps all others
180
- return user_options[:fast] if user_options[:fast]
181
-
182
- # try to read from OSW
183
- if @run_options && !@run_options.empty?
184
- if @run_options.get.respond_to?(:fast)
185
- return @run_options.get.fast
186
- else
187
- if @workflow[:run_options]
188
- return @workflow[:run_options][:fast]
189
- end
190
- end
191
- end
192
-
193
- return default
194
- end
195
-
196
- def preserve_run_dir(user_options, default)
197
-
198
- # user option trumps all others
199
- return user_options[:preserve_run_dir] if user_options[:preserve_run_dir]
200
-
201
- # try to read from OSW
202
- if @run_options && !@run_options.empty?
203
- return @run_options.get.preserveRunDir
204
- end
205
-
206
- return default
207
- end
208
-
209
- def skip_expand_objects(user_options, default)
210
-
211
- # user option trumps all others
212
- return user_options[:skip_expand_objects] if user_options[:skip_expand_objects]
213
-
214
- # try to read from OSW
215
- if @run_options && !@run_options.empty?
216
- if @run_options.get.respond_to?(:skipExpandObjects)
217
- return @run_options.get.skipExpandObjects
218
- else
219
- if @workflow[:run_options]
220
- return @workflow[:run_options][:skip_expand_objects]
221
- end
222
- end
223
- end
224
-
225
- return default
226
- end
227
-
228
- def skip_energyplus_preprocess(user_options, default)
229
-
230
- # user option trumps all others
231
- return user_options[:skip_energyplus_preprocess] if user_options[:skip_energyplus_preprocess]
232
-
233
- # try to read from OSW
234
- if @run_options && !@run_options.empty?
235
- if @run_options.get.respond_to?(:skipEnergyPlusPreprocess)
236
- return @run_options.get.skipEnergyPlusPreprocess
237
- else
238
- if @workflow[:run_options]
239
- return @workflow[:run_options][:skip_energyplus_preprocess]
240
- end
241
- end
242
- end
243
-
244
- return default
245
- end
246
-
247
- def cleanup(user_options, default)
248
-
249
- # user option trumps all others
250
- return user_options[:cleanup] if user_options[:cleanup]
251
-
252
- # try to read from OSW
253
- if @run_options && !@run_options.empty?
254
- return @run_options.get.cleanup
255
- end
256
-
257
- return default
258
- end
259
-
260
- def energyplus_path(user_options, default)
261
-
262
- # user option trumps all others
263
- return user_options[:energyplus_path] if user_options[:energyplus_path]
264
-
265
- return default
266
- end
267
-
268
- def profile(user_options, default)
269
-
270
- # user option trumps all others
271
- return user_options[:profile] if user_options[:profile]
272
-
273
- return default
274
- end
275
-
276
- def verify_osw(user_options, default)
277
-
278
- # user option trumps all others
279
- return user_options[:verify_osw] if user_options[:verify_osw]
280
-
281
- return default
282
- end
283
-
284
- def weather_file(user_options, default)
285
-
286
- # user option trumps all others
287
- return user_options[:weather_file] if user_options[:weather_file]
288
-
289
- # try to read from OSW
290
- if !@workflow_json.weatherFile.empty?
291
- return @workflow_json.weatherFile.get.to_s
292
- end
293
-
294
- return default
295
- end
296
-
297
- # Get the associated OSD (datapoint) file from the local filesystem
298
- #
299
- def datapoint
300
- # DLM: should this come from the OSW? the osd id and checksum are specified there.
301
- osd_abs_path = File.join(osw_dir, 'datapoint.osd')
302
- result = nil
303
- if File.exist? osd_abs_path
304
- result = ::JSON.parse(File.read(osd_abs_path), symbolize_names: true)
305
- end
306
- return result
307
- end
308
-
309
- # Get the associated OSA (analysis) definition from the local filesystem
310
- #
311
- def analysis
312
- # DLM: should this come from the OSW? the osd id and checksum are specified there.
313
- osa_abs_path = File.join(osw_dir, '../analysis.json')
314
- result = nil
315
- if File.exist? osa_abs_path
316
- result = ::JSON.parse(File.read(osa_abs_path), symbolize_names: true)
317
- end
318
- return result
319
- end
320
-
321
- end
322
- end
323
- end
324
- end
1
+ # *******************************************************************************
2
+ # OpenStudio(R), Copyright (c) 2008-2020, Alliance for Sustainable Energy, LLC.
3
+ # All rights reserved.
4
+ # Redistribution and use in source and binary forms, with or without
5
+ # modification, are permitted provided that the following conditions are met:
6
+ #
7
+ # (1) Redistributions of source code must retain the above copyright notice,
8
+ # this list of conditions and the following disclaimer.
9
+ #
10
+ # (2) Redistributions in binary form must reproduce the above copyright notice,
11
+ # this list of conditions and the following disclaimer in the documentation
12
+ # and/or other materials provided with the distribution.
13
+ #
14
+ # (3) Neither the name of the copyright holder nor the names of any contributors
15
+ # may be used to endorse or promote products derived from this software without
16
+ # specific prior written permission from the respective party.
17
+ #
18
+ # (4) Other than as required in clauses (1) and (2), distributions in any form
19
+ # of modifications or other derivative works may not use the "OpenStudio"
20
+ # trademark, "OS", "os", or any other confusingly similar designation without
21
+ # specific prior written permission from Alliance for Sustainable Energy, LLC.
22
+ #
23
+ # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24
+ # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25
+ # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26
+ # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER, THE UNITED STATES
27
+ # GOVERNMENT, OR ANY CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28
+ # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
29
+ # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
30
+ # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
31
+ # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
32
+ # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
33
+ # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34
+ # *******************************************************************************
35
+
36
+ require 'openstudio/workflow_json'
37
+
38
+ # Local file based workflow
39
+ module OpenStudio
40
+ module Workflow
41
+ module InputAdapter
42
+ class Local
43
+ def initialize(osw_path = './workflow.osw')
44
+ @osw_abs_path = File.absolute_path(osw_path, Dir.pwd)
45
+
46
+ @workflow = nil
47
+ if File.exist? @osw_abs_path
48
+ @workflow = ::JSON.parse(File.read(@osw_abs_path), symbolize_names: true)
49
+ end
50
+
51
+ begin
52
+ # configure the OSW with paths for loaded extension gems
53
+ # Bundler.require is called in the CLI to load extension gems
54
+ @workflow = OpenStudio::Extension.configure_osw(@workflow)
55
+ rescue NameError => e
56
+ end
57
+
58
+ @workflow_json = nil
59
+ @run_options = nil
60
+ if @workflow
61
+ begin
62
+ # Create a temporary WorkflowJSON, will not be same one used in registry during simulation
63
+ @workflow_json = OpenStudio::WorkflowJSON.new(JSON.fast_generate(workflow))
64
+ @workflow_json.setOswDir(osw_dir)
65
+ rescue NameError => e
66
+ @workflow_json = WorkflowJSON_Shim.new(workflow, osw_dir)
67
+ end
68
+
69
+ begin
70
+ @run_options = @workflow_json.runOptions
71
+ rescue StandardError
72
+ end
73
+ end
74
+ end
75
+
76
+ # Get the OSW file from the local filesystem
77
+ #
78
+ def workflow
79
+ raise "Could not read workflow from #{@osw_abs_path}" if @workflow.nil?
80
+ @workflow
81
+ end
82
+
83
+ # Get the OSW path
84
+ #
85
+ def osw_path
86
+ @osw_abs_path
87
+ end
88
+
89
+ # Get the OSW dir
90
+ #
91
+ def osw_dir
92
+ File.dirname(@osw_abs_path)
93
+ end
94
+
95
+ # Get the run dir
96
+ #
97
+ def run_dir
98
+ result = File.join(osw_dir, 'run')
99
+ if @workflow_json
100
+ begin
101
+ result = @workflow_json.absoluteRunDir.to_s
102
+ rescue StandardError
103
+ end
104
+ end
105
+ result
106
+ end
107
+
108
+ def output_adapter(user_options, default, logger)
109
+ # user option trumps all others
110
+ return user_options[:output_adapter] if user_options[:output_adapter]
111
+
112
+ # try to read from OSW
113
+ if @run_options && !@run_options.empty?
114
+ custom_adapter = @run_options.get.customOutputAdapter
115
+ if !custom_adapter.empty?
116
+ begin
117
+ custom_file_name = custom_adapter.get.customFileName
118
+ class_name = custom_adapter.get.className
119
+ options = ::JSON.parse(custom_adapter.get.options, symbolize_names: true)
120
+
121
+ # merge with user options, user options will replace options loaded from OSW
122
+ options.merge!(user_options)
123
+
124
+ # stick output_directory in options
125
+ options[:output_directory] = run_dir
126
+
127
+ p = @workflow_json.findFile(custom_file_name)
128
+ if !p.empty?
129
+ load(p.get.to_s)
130
+ output_adapter = eval("#{class_name}.new(options)")
131
+ return output_adapter
132
+ else
133
+ log_message = "Failed to load custom adapter file '#{custom_file_name}'"
134
+ logger.error log_message
135
+ raise log_message
136
+ end
137
+ rescue StandardError
138
+ log_message = "Failed to load custom adapter '#{class_name}' from file '#{custom_file_name}'"
139
+ logger.error log_message
140
+ raise log_message
141
+ end
142
+ end
143
+ end
144
+
145
+ return default
146
+ end
147
+
148
+ def jobs(user_options, default, logger)
149
+ # user option trumps all others
150
+ return user_options[:jobs] if user_options[:jobs]
151
+
152
+ # try to read from OSW
153
+ begin
154
+ # log_message = "Reading custom job states from OSW is not currently supported'"
155
+ # logger.info log_message
156
+ rescue StandardError
157
+ end
158
+
159
+ return default
160
+ end
161
+
162
+ def debug(user_options, default)
163
+ # user option trumps all others
164
+ return user_options[:debug] if user_options[:debug]
165
+
166
+ # try to read from OSW
167
+ if @run_options && !@run_options.empty?
168
+ return @run_options.get.debug
169
+ end
170
+
171
+ return default
172
+ end
173
+
174
+ def fast(user_options, default)
175
+ # user option trumps all others
176
+ return user_options[:fast] if user_options[:fast]
177
+
178
+ # try to read from OSW
179
+ if @run_options && !@run_options.empty?
180
+ if @run_options.get.respond_to?(:fast)
181
+ return @run_options.get.fast
182
+ else
183
+ if @workflow[:run_options]
184
+ return @workflow[:run_options][:fast]
185
+ end
186
+ end
187
+ end
188
+
189
+ return default
190
+ end
191
+
192
+ def preserve_run_dir(user_options, default)
193
+ # user option trumps all others
194
+ return user_options[:preserve_run_dir] if user_options[:preserve_run_dir]
195
+
196
+ # try to read from OSW
197
+ if @run_options && !@run_options.empty?
198
+ return @run_options.get.preserveRunDir
199
+ end
200
+
201
+ return default
202
+ end
203
+
204
+ def skip_expand_objects(user_options, default)
205
+ # user option trumps all others
206
+ return user_options[:skip_expand_objects] if user_options[:skip_expand_objects]
207
+
208
+ # try to read from OSW
209
+ if @run_options && !@run_options.empty?
210
+ if @run_options.get.respond_to?(:skipExpandObjects)
211
+ return @run_options.get.skipExpandObjects
212
+ else
213
+ if @workflow[:run_options]
214
+ return @workflow[:run_options][:skip_expand_objects]
215
+ end
216
+ end
217
+ end
218
+
219
+ return default
220
+ end
221
+
222
+ def skip_energyplus_preprocess(user_options, default)
223
+ # user option trumps all others
224
+ return user_options[:skip_energyplus_preprocess] if user_options[:skip_energyplus_preprocess]
225
+
226
+ # try to read from OSW
227
+ if @run_options && !@run_options.empty?
228
+ if @run_options.get.respond_to?(:skipEnergyPlusPreprocess)
229
+ return @run_options.get.skipEnergyPlusPreprocess
230
+ else
231
+ if @workflow[:run_options]
232
+ return @workflow[:run_options][:skip_energyplus_preprocess]
233
+ end
234
+ end
235
+ end
236
+
237
+ return default
238
+ end
239
+
240
+ def cleanup(user_options, default)
241
+ # user option trumps all others
242
+ return user_options[:cleanup] if user_options[:cleanup]
243
+
244
+ # try to read from OSW
245
+ if @run_options && !@run_options.empty?
246
+ return @run_options.get.cleanup
247
+ end
248
+
249
+ return default
250
+ end
251
+
252
+ def energyplus_path(user_options, default)
253
+ # user option trumps all others
254
+ return user_options[:energyplus_path] if user_options[:energyplus_path]
255
+
256
+ return default
257
+ end
258
+
259
+ def profile(user_options, default)
260
+ # user option trumps all others
261
+ return user_options[:profile] if user_options[:profile]
262
+
263
+ return default
264
+ end
265
+
266
+ def verify_osw(user_options, default)
267
+ # user option trumps all others
268
+ return user_options[:verify_osw] if user_options[:verify_osw]
269
+
270
+ return default
271
+ end
272
+
273
+ def weather_file(user_options, default)
274
+ # user option trumps all others
275
+ return user_options[:weather_file] if user_options[:weather_file]
276
+
277
+ # try to read from OSW
278
+ if !@workflow_json.weatherFile.empty?
279
+ return @workflow_json.weatherFile.get.to_s
280
+ end
281
+
282
+ return default
283
+ end
284
+
285
+ # Get the associated OSD (datapoint) file from the local filesystem
286
+ #
287
+ def datapoint
288
+ # DLM: should this come from the OSW? the osd id and checksum are specified there.
289
+ osd_abs_path = File.join(osw_dir, 'datapoint.osd')
290
+ result = nil
291
+ if File.exist? osd_abs_path
292
+ result = ::JSON.parse(File.read(osd_abs_path), symbolize_names: true)
293
+ end
294
+ return result
295
+ end
296
+
297
+ # Get the associated OSA (analysis) definition from the local filesystem
298
+ #
299
+ def analysis
300
+ # DLM: should this come from the OSW? the osd id and checksum are specified there.
301
+ osa_abs_path = File.join(osw_dir, '../analysis.json')
302
+ result = nil
303
+ if File.exist? osa_abs_path
304
+ result = ::JSON.parse(File.read(osa_abs_path), symbolize_names: true)
305
+ end
306
+ return result
307
+ end
308
+ end
309
+ end
310
+ end
311
+ end