amee 2.0.25

Sign up to get free protection for your applications and to get access to all the features.
@@ -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