amee 2.0.25

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.
@@ -0,0 +1,514 @@
1
+ require 'date'
2
+
3
+ module AMEE
4
+ module Profile
5
+ class Category < AMEE::Profile::Object
6
+
7
+ def initialize(data = {})
8
+ @children = data ? data[:children] : []
9
+ @items = data ? data[:items] : []
10
+ @total_amount = data[:total_amount]
11
+ @total_amount_unit = data[:total_amount_unit]
12
+ @start_date = data[:start_date]
13
+ @end_date = data[:end_date]
14
+ super
15
+ end
16
+
17
+ attr_reader :children
18
+ attr_reader :items
19
+ attr_reader :total_amount
20
+ attr_reader :total_amount_unit
21
+
22
+ def start_date
23
+ @start_date || profile_date
24
+ end
25
+
26
+ def end_date
27
+ @end_date
28
+ end
29
+
30
+ def self.parse_json_profile_item(item)
31
+ item_data = {}
32
+ item_data[:values] = {}
33
+ item.each_pair do |key, value|
34
+ case key
35
+ when 'dataItemLabel', 'dataItemUid', 'name', 'path', 'uid'
36
+ item_data[key.to_sym] = value
37
+ when 'dataItem'
38
+ item_data[:dataItemLabel] = value['Label']
39
+ item_data[:dataItemUid] = value['uid']
40
+ when 'label' # ignore these
41
+ nil
42
+ when 'created'
43
+ item_data[:created] = DateTime.parse(value)
44
+ when 'modified'
45
+ item_data[:modified] = DateTime.parse(value)
46
+ when 'validFrom'
47
+ item_data[:validFrom] = DateTime.strptime(value, "%Y%m%d")
48
+ when 'startDate'
49
+ item_data[:startDate] = DateTime.parse(value)
50
+ when 'endDate'
51
+ item_data[:endDate] = DateTime.parse(value) rescue nil
52
+ when 'end'
53
+ item_data[:end] = (value == "true")
54
+ when 'amountPerMonth'
55
+ item_data[:amountPerMonth] = value.to_f
56
+ when 'amount'
57
+ item_data[:amount] = value['value'].to_f
58
+ item_data[:amount_unit] = value['unit']
59
+ when 'itemValues'
60
+ value.each do |itemval|
61
+ path = itemval['path'].to_sym
62
+ item_data[:values][path.to_sym] = {}
63
+ item_data[:values][path.to_sym][:name] = itemval['name']
64
+ item_data[:values][path.to_sym][:value] = itemval['value']
65
+ item_data[:values][path.to_sym][:unit] = itemval['unit']
66
+ item_data[:values][path.to_sym][:per_unit] = itemval['perUnit']
67
+ end
68
+ else
69
+ item_data[:values][key.to_sym] = value
70
+ end
71
+ end
72
+ item_data[:path] ||= item_data[:uid] # Fill in path if not retrieved from response
73
+ return item_data
74
+ end
75
+
76
+ def self.parse_json_profile_category(category)
77
+ datacat = category['dataCategory'] ? category['dataCategory'] : category
78
+ category_data = {}
79
+ category_data[:name] = datacat['name']
80
+ category_data[:path] = datacat['path']
81
+ category_data[:uid] = datacat['uid']
82
+ category_data[:totalAmountPerMonth] = category['totalAmountPerMonth'].to_f
83
+ category_data[:children] = []
84
+ category_data[:items] = []
85
+ if category['children']
86
+ category['children'].each do |child|
87
+ if child[0] == 'dataCategories'
88
+ child[1].each do |child_cat|
89
+ category_data[:children] << parse_json_profile_category(child_cat)
90
+ end
91
+ end
92
+ if child[0] == 'profileItems' && child[1]['rows']
93
+ child[1]['rows'].each do |child_item|
94
+ category_data[:items] << parse_json_profile_item(child_item)
95
+ end
96
+ end
97
+ end
98
+ end
99
+ return category_data
100
+ end
101
+
102
+ def self.from_json(json, options)
103
+ # Parse json
104
+ doc = JSON.parse(json)
105
+ data = {}
106
+ data[:profile_uid] = doc['profile']['uid']
107
+ data[:profile_date] = DateTime.strptime(doc['profileDate'], "%Y%m") rescue nil
108
+ data[:name] = doc['dataCategory']['name']
109
+ data[:path] = doc['path']
110
+ data[:total_amount] = doc['totalAmountPerMonth']
111
+ data[:total_amount_unit] = "kg/month"
112
+ data[:children] = []
113
+ if doc['children'] && doc['children']['dataCategories']
114
+ doc['children']['dataCategories'].each do |child|
115
+ data[:children] << parse_json_profile_category(child)
116
+ end
117
+ end
118
+ data[:items] = []
119
+ profile_items = []
120
+ profile_items.concat doc['children']['profileItems']['rows'] rescue nil
121
+ profile_items << doc['profileItem'] unless doc['profileItem'].nil?
122
+ profile_items.each do |item|
123
+ data[:items] << parse_json_profile_item(item)
124
+ end
125
+ # Create object
126
+ Category.new(data)
127
+ rescue
128
+ raise AMEE::BadData.new("Couldn't load ProfileCategory from JSON data. Check that your URL is correct.\n#{json}")
129
+ end
130
+
131
+ def self.from_v2_json(json, options)
132
+ # Parse json
133
+ doc = JSON.parse(json)
134
+ data = {}
135
+ data[:profile_uid] = doc['profile']['uid']
136
+ data[:start_date] = options[:start_date]
137
+ data[:end_date] = options[:end_date]
138
+ data[:name] = doc['dataCategory']['name']
139
+ data[:path] = doc['path']
140
+ data[:total_amount] = doc['totalAmount']['value'].to_f rescue nil
141
+ data[:total_amount_unit] = doc['totalAmount']['unit'] rescue nil
142
+ data[:children] = []
143
+ if doc['profileCategories']
144
+ doc['profileCategories'].each do |child|
145
+ data[:children] << parse_json_profile_category(child)
146
+ end
147
+ end
148
+ data[:items] = []
149
+ profile_items = []
150
+ profile_items.concat doc['profileItems'] rescue nil
151
+ profile_items << doc['profileItem'] unless doc['profileItem'].nil?
152
+ profile_items.each do |item|
153
+ data[:items] << parse_json_profile_item(item)
154
+ end
155
+ # Create object
156
+ Category.new(data)
157
+ rescue
158
+ raise AMEE::BadData.new("Couldn't load ProfileCategory from V2 JSON data. Check that your URL is correct.\n#{json}")
159
+ end
160
+
161
+ def self.parse_xml_profile_item(item)
162
+ item_data = {}
163
+ item_data[:values] = {}
164
+ item.elements.each do |element|
165
+ key = element.name
166
+ value = element.text
167
+ case key.downcase
168
+ when 'dataitemlabel', 'dataitemuid', 'name', 'path'
169
+ item_data[key.to_sym] = value
170
+ when 'dataitem'
171
+ item_data[:dataItemUid] = element.attributes['uid']
172
+ when 'validfrom'
173
+ item_data[:validFrom] = DateTime.strptime(value, "%Y%m%d")
174
+ when 'end'
175
+ item_data[:end] = (value == "true")
176
+ when 'amountpermonth'
177
+ item_data[:amountPerMonth] = value.to_f
178
+ else
179
+ item_data[:values][key.to_sym] = value
180
+ end
181
+ end
182
+ item_data[:uid] = item.attributes['uid'].to_s
183
+ item_data[:created] = DateTime.parse(item.attributes['created'].to_s) rescue nil
184
+ item_data[:modified] = DateTime.parse(item.attributes['modified'].to_s) rescue nil
185
+ item_data[:path] ||= item_data[:uid] # Fill in path if not retrieved from response
186
+ return item_data
187
+ end
188
+
189
+ def self.from_v2_batch_json(json)
190
+ # Parse JSON
191
+ doc = JSON.parse(json)
192
+ data = {}
193
+ data[:profileItems] = []
194
+ doc['profileItems'].each do |child|
195
+ profile_item = {}
196
+ profile_item[:uri] = child['uri'].to_s
197
+ profile_item[:uid] = child['uid'].to_s
198
+ data[:profileItems] << profile_item
199
+ end
200
+ return data
201
+ rescue
202
+ raise AMEE::BadData.new("Couldn't load ProfileCategory batch response from V2 JSON data. Check that your URL is correct.\n#{json}")
203
+ end
204
+
205
+ def self.parse_xml_profile_category(category)
206
+ category_data = {}
207
+ category_data[:name] = category.elements['DataCategory'].elements['Name'].text
208
+ category_data[:path] = category.elements['DataCategory'].elements['Path'].text
209
+ category_data[:uid] = category.elements['DataCategory'].attributes['uid'].to_s
210
+ category_data[:totalAmountPerMonth] = category.elements['TotalAmountPerMonth'].text.to_f rescue nil
211
+ category_data[:children] = []
212
+ category_data[:items] = []
213
+ if category.elements['Children']
214
+ category.elements['Children'].each do |child|
215
+ if child.name == 'ProfileCategories'
216
+ child.each do |child_cat|
217
+ category_data[:children] << parse_xml_profile_category(child_cat)
218
+ end
219
+ end
220
+ if child.name == 'ProfileItems'
221
+ child.each do |child_item|
222
+ category_data[:items] << parse_xml_profile_item(child_item)
223
+ end
224
+ end
225
+ end
226
+ end
227
+ return category_data
228
+ end
229
+
230
+ def self.from_xml(xml, options)
231
+ # Parse XML
232
+ doc = REXML::Document.new(xml)
233
+ data = {}
234
+ data[:profile_uid] = REXML::XPath.first(doc, "/Resources/ProfileCategoryResource/Profile/@uid").to_s
235
+ data[:profile_date] = DateTime.strptime(REXML::XPath.first(doc, "/Resources/ProfileCategoryResource/ProfileDate").text, "%Y%m")
236
+ data[:name] = REXML::XPath.first(doc, '/Resources/ProfileCategoryResource/DataCategory/?ame').text
237
+ data[:path] = REXML::XPath.first(doc, '/Resources/ProfileCategoryResource/Path | /Resources/ProfileCategoryResource/DataCategory/path').text || ""
238
+ data[:path] = "/#{data[:path]}" if data[:path].slice(0,1) != '/'
239
+ data[:total_amount] = REXML::XPath.first(doc, '/Resources/ProfileCategoryResource/TotalAmountPerMonth').text.to_f rescue nil
240
+ data[:total_amount_unit] = "kg/month"
241
+ data[:children] = []
242
+ REXML::XPath.each(doc, '/Resources/ProfileCategoryResource/Children/ProfileCategories/DataCategory | /Resources/ProfileCategoryResource/Children/DataCategories/DataCategory') do |child|
243
+ category_data = {}
244
+ category_data[:name] = (child.elements['Name'] || child.elements['name']).text
245
+ category_data[:path] = (child.elements['Path'] || child.elements['path']).text
246
+ category_data[:uid] = child.attributes['uid'].to_s
247
+ data[:children] << category_data
248
+ end
249
+ REXML::XPath.each(doc, '/Resources/ProfileCategoryResource/Children/ProfileCategories/ProfileCategory') do |child|
250
+ data[:children] << parse_xml_profile_category(child)
251
+ end
252
+ data[:items] = []
253
+ REXML::XPath.each(doc, '/Resources/ProfileCategoryResource/Children/ProfileItems/ProfileItem') do |item|
254
+ data[:items] << parse_xml_profile_item(item)
255
+ end
256
+ REXML::XPath.each(doc, '/Resources/ProfileCategoryResource/ProfileItem') do |item|
257
+ data[:items] << parse_xml_profile_item(item)
258
+ end
259
+ REXML::XPath.each(doc, '/Resources/ProfileCategoryResource/ProfileItems/ProfileItem') do |item|
260
+ data[:items] << parse_xml_profile_item(item)
261
+ end
262
+ # Create object
263
+ Category.new(data)
264
+ rescue
265
+ raise AMEE::BadData.new("Couldn't load ProfileCategory from XML data. Check that your URL is correct.\n#{xml}")
266
+ end
267
+
268
+ def self.parse_v2_xml_profile_item(item)
269
+ item_data = {}
270
+ item_data[:values] = {}
271
+ item.elements.each do |element|
272
+ key = element.name
273
+ case key.downcase
274
+ when 'name', 'path'
275
+ item_data[key.downcase.to_sym] = element.text
276
+ when 'dataitem'
277
+ item_data[:dataItemLabel] = element.elements['Label'].text
278
+ item_data[:dataItemUid] = element.attributes['uid'].to_s
279
+ when 'validfrom'
280
+ item_data[:validFrom] = DateTime.strptime(element.text, "%Y%m%d")
281
+ when 'startdate'
282
+ item_data[:startDate] = DateTime.parse(element.text)
283
+ when 'enddate'
284
+ item_data[:endDate] = DateTime.parse(element.text) if element.text
285
+ when 'end'
286
+ item_data[:end] = (element.text == "true")
287
+ when 'amount'
288
+ item_data[:amount] = element.text.to_f
289
+ item_data[:amount_unit] = element.attributes['unit'].to_s
290
+ when 'itemvalues'
291
+ element.elements.each do |itemvalue|
292
+ path = itemvalue.elements['Path'].text
293
+ item_data[:values][path.to_sym] = {}
294
+ item_data[:values][path.to_sym][:name] = itemvalue.elements['Name'].text
295
+ item_data[:values][path.to_sym][:value] = itemvalue.elements['Value'].text || "0"
296
+ item_data[:values][path.to_sym][:unit] = itemvalue.elements['Unit'].text
297
+ item_data[:values][path.to_sym][:per_unit] = itemvalue.elements['PerUnit'].text
298
+ end
299
+ else
300
+ item_data[:values][key.to_sym] = element.text
301
+ end
302
+ end
303
+ item_data[:uid] = item.attributes['uid'].to_s
304
+ item_data[:created] = DateTime.parse(item.attributes['created'].to_s) rescue nil
305
+ item_data[:modified] = DateTime.parse(item.attributes['modified'].to_s) rescue nil
306
+ item_data[:path] ||= item_data[:uid] # Fill in path if not retrieved from response
307
+ return item_data
308
+ end
309
+
310
+ def self.from_v2_xml(xml, options)
311
+ # Parse XML
312
+ doc = REXML::Document.new(xml)
313
+ data = {}
314
+ data[:profile_uid] = REXML::XPath.first(doc, "/Resources/ProfileCategoryResource/Profile/@uid").to_s
315
+ data[:start_date] = options[:start_date]
316
+ data[:end_date] = options[:end_date]
317
+ data[:name] = REXML::XPath.first(doc, '/Resources/ProfileCategoryResource/DataCategory/Name').text
318
+ data[:path] = REXML::XPath.first(doc, '/Resources/ProfileCategoryResource/Path').text || ""
319
+ data[:total_amount] = REXML::XPath.first(doc, '/Resources/ProfileCategoryResource/TotalAmount').text.to_f rescue nil
320
+ data[:total_amount_unit] = REXML::XPath.first(doc, '/Resources/ProfileCategoryResource/TotalAmount/@unit').to_s rescue nil
321
+ data[:children] = []
322
+ REXML::XPath.each(doc, '/Resources/ProfileCategoryResource/ProfileCategories/DataCategory') do |child|
323
+ category_data = {}
324
+ category_data[:name] = child.elements['Name'].text
325
+ category_data[:path] = child.elements['Path'].text
326
+ category_data[:uid] = child.attributes['uid'].to_s
327
+ data[:children] << category_data
328
+ end
329
+ REXML::XPath.each(doc, '/Resources/ProfileCategoryResource/Children/ProfileCategories/ProfileCategory') do |child|
330
+ data[:children] << parse_xml_profile_category(child)
331
+ end
332
+ data[:items] = []
333
+ REXML::XPath.each(doc, '/Resources/ProfileCategoryResource/ProfileItems/ProfileItem') do |item|
334
+ data[:items] << parse_v2_xml_profile_item(item)
335
+ end
336
+ REXML::XPath.each(doc, '/Resources/ProfileCategoryResource/ProfileItem') do |item|
337
+ data[:items] << parse_v2_xml_profile_item(item)
338
+ end
339
+ # Create object
340
+ Category.new(data)
341
+ rescue
342
+ raise AMEE::BadData.new("Couldn't load ProfileCategory from V2 XML data. Check that your URL is correct.\n#{xml}")
343
+ end
344
+
345
+ def self.from_v2_batch_xml(xml)
346
+ # Parse XML
347
+ doc = REXML::Document.new(xml)
348
+ data = {}
349
+ data[:profileItems] = []
350
+ REXML::XPath.each(doc, '/Resources/ProfileItems/ProfileItem') do |child|
351
+ profile_item = {}
352
+ profile_item[:uri] = child.attributes['uri'].to_s
353
+ profile_item[:uid] = child.attributes['uid'].to_s
354
+ data[:profileItems] << profile_item
355
+ end
356
+ return data
357
+ rescue
358
+ raise AMEE::BadData.new("Couldn't load ProfileCategory batch response from V2 XML data. Check that your URL is correct.\n#{xml}")
359
+ end
360
+
361
+ def self.from_v2_atom(response, options)
362
+ # Parse XML
363
+ doc = REXML::Document.new(response)
364
+ data = {}
365
+ data[:profile_uid] = REXML::XPath.first(doc, "/feed/@xml:base").to_s.match("/profiles/(.*?)/")[1]
366
+ data[:start_date] = options[:start_date]
367
+ data[:end_date] = options[:end_date]
368
+ data[:name] = REXML::XPath.first(doc, '/feed/amee:name').text
369
+ data[:path] = REXML::XPath.first(doc, "/feed/@xml:base").to_s.match("/profiles/.*?(/.*)")[1]
370
+ data[:total_amount] = REXML::XPath.first(doc, '/feed/amee:totalAmount').text.to_f rescue nil
371
+ data[:total_amount_unit] = REXML::XPath.first(doc, '/feed/amee:totalAmount/@unit').to_s rescue nil
372
+ data[:children] = []
373
+ REXML::XPath.each(doc, '/feed/amee:categories/amee:category') do |child|
374
+ category_data = {}
375
+ # category_data[:path] = child.text
376
+ category_data[:path] = child.text
377
+ # category_data[:uid] = child.attributes['uid'].to_s
378
+ data[:children] << category_data
379
+ end
380
+ # REXML::XPath.each(doc, '/Resources/ProfileCategoryResource/Children/ProfileCategories/ProfileCategory') do |child|
381
+ # data[:children] << parse_xml_profile_category(child)
382
+ # end
383
+ data[:items] = []
384
+ REXML::XPath.each(doc, '/feed/entry') do |entry|
385
+ item = {}
386
+ item[:uid] = entry.elements['id'].text.match("urn:item:(.*)")[1]
387
+ item[:name] = entry.elements['title'].text
388
+ item[:path] = item[:uid]
389
+ # data[:dataItemLabel].should == "gas"
390
+ # data[:dataItemUid].should == "66056991EE23"
391
+ item[:amount] = entry.elements['amee:amount'].text.to_f rescue nil
392
+ item[:amount_unit] = entry.elements['amee:amount'].attributes['unit'].to_s rescue nil
393
+ item[:startDate] = DateTime.parse(entry.elements['amee:startDate'].text)
394
+ item[:endDate] = DateTime.parse(entry.elements['amee:endDate'].text) rescue nil
395
+ item[:values] = {}
396
+ entry.elements.each do |itemvalue|
397
+ if itemvalue.name == 'itemValue'
398
+ path = itemvalue.elements['link'].attributes['href'].to_s.match(".*/(.*)")[1]
399
+ x = {}
400
+ x[:path] = path
401
+ x[:name] = itemvalue.elements['amee:name'].text
402
+ x[:value] = itemvalue.elements['amee:value'].text unless itemvalue.elements['amee:value'].text == "N/A"
403
+ x[:value] ||= "0"
404
+ x[:unit] = itemvalue.elements['amee:unit'].text rescue nil
405
+ x[:per_unit] = itemvalue.elements['amee:perUnit'].text rescue nil
406
+ item[:values][path.to_sym] = x
407
+ end
408
+ end
409
+ data[:items] << item
410
+ end
411
+ # Create object
412
+ Category.new(data)
413
+ rescue
414
+ raise AMEE::BadData.new("Couldn't load ProfileCategory from V2 Atom data. Check that your URL is correct.\n#{response}")
415
+ end
416
+
417
+ def self.get_history(connection, path, num_months, end_date = Date.today, items_per_page = 10)
418
+ month = end_date.month
419
+ year = end_date.year
420
+ history = []
421
+ num_months.times do
422
+ date = Date.new(year, month)
423
+ data = self.get(connection, path, :start_date => date, :itemsPerPage => items_per_page)
424
+ # If we get no data items back, there is no data at all before this date, so don't bother fetching it
425
+ if data.items.empty?
426
+ (num_months - history.size).times do
427
+ history << Category.new(:children => [], :items => [])
428
+ end
429
+ break
430
+ else
431
+ history << data
432
+ end
433
+ month -= 1
434
+ if (month == 0)
435
+ year -= 1
436
+ month = 12
437
+ end
438
+ end
439
+ return history.reverse
440
+ end
441
+
442
+ def self.parse(connection, response, options)
443
+ # Parse data from response
444
+ if response.is_v2_json?
445
+ cat = Category.from_v2_json(response, options)
446
+ elsif response.is_json?
447
+ cat = Category.from_json(response, options)
448
+ elsif response.is_v2_atom?
449
+ cat = Category.from_v2_atom(response, options)
450
+ elsif response.is_v2_xml?
451
+ cat = Category.from_v2_xml(response, options)
452
+ elsif response.is_xml?
453
+ cat = Category.from_xml(response, options)
454
+ end
455
+ # Store connection in object for future use
456
+ cat.connection = connection
457
+ # Done
458
+ return cat
459
+ end
460
+
461
+ def self.parse_batch(connection, response)
462
+ if response.is_v2_json?
463
+ return Category.from_v2_batch_json(response)
464
+ elsif response.is_v2_xml?
465
+ return Category.from_v2_batch_xml(response)
466
+ else
467
+ return self.parse(connection, response, nil)
468
+ end
469
+ end
470
+
471
+ def self.get(connection, path, orig_options = {})
472
+ unless orig_options.is_a?(Hash)
473
+ raise AMEE::ArgumentError.new("Third argument must be a hash of options!")
474
+ end
475
+ # Convert to AMEE options
476
+ options = orig_options.clone
477
+ if options[:start_date] && connection.version < 2
478
+ options[:profileDate] = options[:start_date].amee1_month
479
+ elsif options[:start_date] && connection.version >= 2
480
+ options[:startDate] = options[:start_date].xmlschema
481
+ end
482
+ options.delete(:start_date)
483
+ if options[:end_date] && connection.version >= 2
484
+ options[:endDate] = options[:end_date].xmlschema
485
+ end
486
+ options.delete(:end_date)
487
+ if options[:duration] && connection.version >= 2
488
+ options[:duration] = "PT#{options[:duration] * 86400}S"
489
+ end
490
+ # Load data from path
491
+ response = connection.get(path, options).body
492
+ return Category.parse(connection, response, orig_options)
493
+ rescue
494
+ raise AMEE::BadData.new("Couldn't load ProfileCategory. Check that your URL is correct.\n#{response}")
495
+ end
496
+
497
+ def child(child_path)
498
+ AMEE::Profile::Category.get(connection, "#{full_path}/#{child_path}")
499
+ end
500
+
501
+ def item(options)
502
+ # Search fields - from most specific to least specific
503
+ item = items.find{ |x| x[:uid] == options[:uid] || x[:name] == options[:name] || x[:dataItemUid] == options[:dataItemUid] || x[:dataItemLabel] == options[:dataItemLabel] }
504
+ # Pass through some options
505
+ new_opts = {}
506
+ new_opts[:returnUnit] = options[:returnUnit] if options[:returnUnit]
507
+ new_opts[:returnPerUnit] = options[:returnPerUnit] if options[:returnPerUnit]
508
+ new_opts[:format] = options[:format] if options[:format]
509
+ item ? AMEE::Profile::Item.get(connection, "#{full_path}/#{item[:path]}", new_opts) : nil
510
+ end
511
+
512
+ end
513
+ end
514
+ end