lewt 0.5.12

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 (45) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.md +22 -0
  3. data/README.md +238 -0
  4. data/bin/lewt +10 -0
  5. data/lib/config/customers.yml +33 -0
  6. data/lib/config/enterprise.yml +54 -0
  7. data/lib/config/settings.yml +10 -0
  8. data/lib/config/templates/invoice.html.liquid +63 -0
  9. data/lib/config/templates/invoice.text.liquid +40 -0
  10. data/lib/config/templates/meta.html.liquid +0 -0
  11. data/lib/config/templates/meta.text.liquid +1 -0
  12. data/lib/config/templates/metastat.html.liquid +2 -0
  13. data/lib/config/templates/metastat.text.liquid +8 -0
  14. data/lib/config/templates/report.html.liquid +29 -0
  15. data/lib/config/templates/report.text.liquid +15 -0
  16. data/lib/config/templates/style.css +461 -0
  17. data/lib/extension.rb +158 -0
  18. data/lib/extensions/calendar-timekeeping/apple_extractor.rb +63 -0
  19. data/lib/extensions/calendar-timekeeping/calendar-timekeeping.rb +65 -0
  20. data/lib/extensions/calendar-timekeeping/extractor.rb +62 -0
  21. data/lib/extensions/calendar-timekeeping/gcal_extractor.rb +61 -0
  22. data/lib/extensions/calendar-timekeeping/ical_extractor.rb +52 -0
  23. data/lib/extensions/liquid-renderer.rb +106 -0
  24. data/lib/extensions/metastat/metamath.rb +108 -0
  25. data/lib/extensions/metastat/metastat.rb +161 -0
  26. data/lib/extensions/simple-expenses.rb +112 -0
  27. data/lib/extensions/simple-invoices.rb +93 -0
  28. data/lib/extensions/simple-milestones.rb +102 -0
  29. data/lib/extensions/simple-reports.rb +81 -0
  30. data/lib/extensions/store.rb +81 -0
  31. data/lib/lewt.rb +233 -0
  32. data/lib/lewt_book.rb +29 -0
  33. data/lib/lewt_ledger.rb +149 -0
  34. data/lib/lewtopts.rb +170 -0
  35. data/tests/LEWT Schedule.ics +614 -0
  36. data/tests/expenses.csv +1 -0
  37. data/tests/milestones.csv +1 -0
  38. data/tests/run_tests.rb +14 -0
  39. data/tests/tc_Billing.rb +29 -0
  40. data/tests/tc_CalExt.rb +44 -0
  41. data/tests/tc_Lewt.rb +37 -0
  42. data/tests/tc_LewtExtension.rb +31 -0
  43. data/tests/tc_LewtLedger.rb +38 -0
  44. data/tests/tc_LewtOpts.rb +26 -0
  45. metadata +158 -0
@@ -0,0 +1,65 @@
1
+ load File.expand_path('../extractor.rb', __FILE__)
2
+ load File.expand_path('../gcal_extractor.rb', __FILE__)
3
+ load File.expand_path('../ical_extractor.rb', __FILE__)
4
+ load File.expand_path('../apple_extractor.rb', __FILE__)
5
+
6
+
7
+ # Author:: Jason Wijegooneratne (mailto:code@jwije.com)
8
+ # Copyright:: Copyright (c) 2014 Jason Wijegooneratne
9
+ # License:: MIT. See LICENSE.md distributed with the source code for more information.
10
+
11
+
12
+
13
+ module LEWT
14
+
15
+ # The Calander Timekeeping LEWT Extensions lets you extract timesheet data from your iCal, Google Calender, or OSX Calender
16
+ # sources. In the process it transforms the data into a LEWTBook ready for processing.
17
+ #
18
+ # In order for your calender events to be recognised as billable timesheet entries there are some naming conventions you must
19
+ # observe.
20
+ #
21
+ # ===Conventions:
22
+ # - The <tt>title</tt> of your event must contain a client name or alias reference in it.
23
+ # - The <tt>description</tt> of your event will be pulled into the LEWTBook description column.
24
+ include CalendarExtractors
25
+
26
+ class CalendarTimekeeping < LEWT::Extension
27
+
28
+ # Sets up this extension and registers its options.
29
+ def initialize
30
+ # set extension options
31
+ options = {
32
+ :calendar => {
33
+ :default => "ical",
34
+ :definition => "The calender extraction method to use, supports 'gcal', 'ical', 'osx' calender extraction. Defaults to ical.",
35
+ :type => String,
36
+ },
37
+ :suppress => {
38
+ :definition => "Suppresses the cost calculation for the specified targets when calulating the hourly rates on extracted calender data.",
39
+ :type => String
40
+ }
41
+ }
42
+ super({:cmd => "calendar", :options => options})
43
+ end
44
+
45
+ # Extracts data from a given calender source based on what was passed in the <tt>options['ext_method']</tt> parameter.
46
+ # options [Hash]:: The options hash passed to this function by the Lewt program.
47
+ # returns:: LEWTBook
48
+ def extract( options )
49
+ targetCustomers = self.get_matched_customers( options[:target], options[:suppress] )
50
+ dStart = options[:start]
51
+ dEnd = options[:end]
52
+ suppressTargets = options[:suppress] == nil ? nil : self.get_matched_customers(options[:suppress])
53
+ if options[:calendar] == "ical"
54
+ extract = CalendarExtractors::ICalExtractor.new( dStart, dEnd, targetCustomers, lewt_settings, suppressTargets )
55
+ elsif options[:calendar] == "gcal"
56
+ extract = CalendarExtractors::GCalExtractor.new(dStart, dEnd, targetCustomers, lewt_settings, suppressTargets )
57
+ elsif options[:calendar] == "osx"
58
+ extract = CalendarExtractors::AppleExtractor.new(dStart, dEnd, targetCustomers, lewt_settings, suppressTargets )
59
+ end
60
+ return extract.data
61
+ end
62
+
63
+ end
64
+
65
+ end
@@ -0,0 +1,62 @@
1
+ # Author:: Jason Wijegooneratne (mailto:code@jwije.com)
2
+ # Copyright:: Copyright (c) 2014 Jason Wijegooneratne
3
+ # License:: MIT. See LICENSE.md distributed with the source code for more information.
4
+
5
+
6
+
7
+ module CalendarExtractors
8
+
9
+ # The CalExtractor class acts as base class for various calender extraction interfaces such as GCalExtractor, ICalExtractor
10
+ # AppleExtractor. It provides some convenience methods that are useful across the various implementations.
11
+ class CalExtractor < LEWT::Extension
12
+
13
+ attr_reader :data
14
+
15
+ # Initialises this class. This method should be invoked by sub-classes with <tt>super()</tt>. It invokes
16
+ # <tt>extractCalenderData</tt> on the sub-classes behalf when called...
17
+ # dateStart [String]:: a human readable date as a string for the start time period
18
+ # dateEnd [String]:: a human readable date as a string for the end time period
19
+ # targets [Hash]:: a hash containing all the targets returned by the LewtExtension.get_matched_customers() method
20
+ def initialize( dateStart, dateEnd, targets )
21
+ @data = LEWT::LEWTBook.new
22
+ @dateStart = DateTime.parse dateStart.to_s
23
+ @dateEnd = DateTime.parse dateEnd.to_s
24
+ @targets = targets
25
+ @category = "Hourly Income"
26
+ self.extractCalendarData
27
+ end
28
+
29
+ # Returns the extracted calendar data. Must be implimented by subclasses.
30
+ def extractCalendarData
31
+
32
+ end
33
+
34
+ # Matches a search string against customer names/aliases
35
+ # evtSearch [String]:: a string to search against such as the title of an event
36
+ # returns:: false when no match found or the target customer details (as a hash) when matched
37
+ def isTargetCustomer? ( evtSearch )
38
+ match = false
39
+ @targets.each do |t|
40
+ reg = [ t['alias'], t['name'] ]
41
+ regex = Regexp.new( reg.join("|"), Regexp::IGNORECASE )
42
+ match = regex.match(evtSearch) != nil ? t : false;
43
+ break if match != false
44
+ end
45
+ return match
46
+ end
47
+
48
+ # Checks whether an event date is within target range
49
+ # date [Date]:: the date to check against
50
+ # return:: Boolean true/false operation status
51
+ def isTargetDate? ( date )
52
+ d = DateTime.parse(date.to_s)
53
+ check = false
54
+ if d >= @dateStart && d <= @dateEnd
55
+ check = true
56
+ end
57
+ return check
58
+ end
59
+
60
+ end
61
+
62
+ end
@@ -0,0 +1,61 @@
1
+ require 'google_calendar'
2
+
3
+ # Author:: Jason Wijegooneratne (mailto:code@jwije.com)
4
+ # Copyright:: Copyright (c) 2014 Jason Wijegooneratne
5
+ # License:: MIT. See LICENSE.md distributed with the source code for more information.
6
+
7
+
8
+ module CalendarExtractors
9
+
10
+ # Extracts data from a Google Calender source.
11
+ #
12
+ # === Setup
13
+ # It is required that you have a Google App setup with your account to allow for API access to your data. Then
14
+ # add the following keys to your settings file.
15
+ #
16
+ # gmail_username:: The username for your google application.
17
+ # gmail_password:: The password associated with this username.
18
+ # gmail_app_name:: The name of the application you created.
19
+ #
20
+ class GCalExtractor < CalExtractor
21
+
22
+ # Sets up this extension
23
+ def initialize ( dStart, dEnd, targetCustomers, lewt_settings, suppressTargets )
24
+ uname = lewt_settings["gmail_username"]
25
+ pass = lewt_settings["gmail_password"]
26
+ app = lewt_settings["google_app_name"]
27
+ @googleCalender = Google::Calendar.new(
28
+ :username => uname,
29
+ :password => pass,
30
+ :app_name => app
31
+ )
32
+ super( dStart, dEnd, targetCustomers )
33
+ end
34
+
35
+ # This method does the actual google calender extract, comparing events to the requested paramters.
36
+ # It manipulates the @data property of this object which is used by LEWT to gather the extracted data.
37
+ def extractCalendarData
38
+ gConf = { :max_results => 2500, :order_by => 'starttime', :single_events => true }
39
+ @googleCalender.find_events_in_range(@dateStart, @dateEnd + 1, gConf).each do |e|
40
+ eStart = Time.parse( e.start_time )
41
+ eEnd = Time.parse( e.end_time )
42
+ timeDiff = (eEnd - eStart)/60/60
43
+ target = self.isTargetCustomer?(e.title)
44
+ if self.isTargetDate?( eStart ) == true && target != false
45
+ row = LEWT::LEWTLedger.new({
46
+ :date_start => eStart,
47
+ :date_end => eEnd,
48
+ :category => @category,
49
+ :entity => target["name"],
50
+ :description => e.content,
51
+ :quantity => timeDiff,
52
+ :unit_cost => target["rate"]
53
+ })
54
+ @data.push(row)
55
+ end
56
+ end
57
+ end
58
+
59
+ end
60
+
61
+ end
@@ -0,0 +1,52 @@
1
+ require 'icalendar'
2
+
3
+ # Author:: Jason Wijegooneratne (mailto:code@jwije.com)
4
+ # Copyright:: Copyright (c) 2014 Jason Wijegooneratne
5
+ # License:: MIT. See LICENSE.md distributed with the source code for more information.
6
+
7
+
8
+ module CalendarExtractors
9
+
10
+ # This class handles extraction from iCal sources.
11
+ #
12
+ # ===Usage:
13
+ # - add the key <tt>ical_filepath</tt> to you settings file corresponding to the filepath of the ical file you wish to have parsed.
14
+ #
15
+ class ICalExtractor < CalExtractor
16
+
17
+ # Initialises the object and calls the parent class' super() method.
18
+ def initialize( dateStart, dateEnd, targets, lewt_settings, suppressTargets )
19
+ @calendarPath = lewt_settings["ical_filepath"]
20
+ super( dateStart, dateEnd, targets )
21
+ end
22
+
23
+ # Open iCalender file, parses it, then check events with the regular CalExtractor methods.
24
+ # Sets the data property of this object if match data is found.
25
+ def extractCalendarData
26
+ calendars = Icalendar.parse( File.open( @calendarPath ) )
27
+ calendars.each do |calendar|
28
+ calendar.events.each do |e|
29
+ target = self.isTargetCustomer?( e.summary )
30
+ dstart = Time.parse( e.dtstart.to_s )
31
+ dend = Time.parse( e.dtend.to_s )
32
+ if self.isTargetDate?(dstart) == true && target != false
33
+ timeDiff = (dend - dstart) /60/60
34
+ row = LEWT::LEWTLedger.new({
35
+ :date_start => dstart,
36
+ :date_end => dend,
37
+ :category => @category,
38
+ :entity => target["name"],
39
+ :description => e.description.to_s,
40
+ :quantity => timeDiff,
41
+ :unit_cost => target["rate"]
42
+ })
43
+
44
+ @data.push( row )
45
+ end
46
+ end
47
+ end
48
+ end
49
+
50
+ end
51
+
52
+ end
@@ -0,0 +1,106 @@
1
+ require "liquid"
2
+ require "pdfkit"
3
+
4
+ # Author:: Jason Wijegooneratne (mailto:code@jwije.com)
5
+ # Copyright:: Copyright (c) 2014 Jason Wijegooneratne
6
+ # License:: MIT. See LICENSE.md distributed with the source code for more information.
7
+
8
+
9
+ module LEWT
10
+
11
+ # The Liquid Renderer LEWT Extension handles rendering processed data to TEXT, HTML, and PDF formats using the
12
+ # {liquid templating engine}[http://liquidmarkup.org] at its core. This allows for easy marking up of templates
13
+ # to be used with arbitrary LEWT extensions and processing them into multiple human readable formats on the fly.
14
+
15
+ class LiquidRenderer < LEWT::Extension
16
+
17
+ attr_reader :textTemplate, :htmlTemplate, :pdfTemplate, :stylesheet, :markup
18
+
19
+ # Sets up this extension and registers its run-time options.
20
+ def initialize ()
21
+ options = {
22
+ :method => {
23
+ :definition => "Specify html, text, pdf, or any combination of the three to define output method",
24
+ :default => "text",
25
+ :short_flag => "-m",
26
+ :type => String
27
+ },
28
+ :save_path => {
29
+ :definition => "Specify where to save the output file (required for PDFs)",
30
+ :type => String
31
+ },
32
+ :liquid_template => {
33
+ :definition => "Override the template that liquid render should use. Defaults to the template which matches the processor name but you will want to override this if you are using multiple processors.",
34
+ :type => String
35
+ }
36
+ }
37
+
38
+ super({:cmd => "liquid_render", :options => options })
39
+ end
40
+
41
+
42
+ # Loads the plaint-text, html, & (optionally) pdf template files of the given template name and parses it with the Liquid class
43
+ # template [String]:: The name of the template to load.
44
+ def load_templates ( template )
45
+ @textTemplate = Liquid::Template::parse( File.open( File.expand_path( lewt_stash + "/templates/#{template}.text.liquid", __FILE__) ).read )
46
+ @htmlTemplate = Liquid::Template::parse( File.open( File.expand_path( lewt_stash + "/templates/#{template}.html.liquid", __FILE__) ).read )
47
+ @stylesheet = File.expand_path( lewt_stash + '/templates/style.css', __FILE__)
48
+ end
49
+
50
+ # Called on LEWT render cycle, this method outputs the data as per a pre-formated liquid template.
51
+ # options [Hash]:: The options hash passed to this function by the Lewt program.
52
+ # data [Array]:: An array of hash data to format.
53
+ def render ( options, data )
54
+ output = Array.new
55
+ # template name is always the same as processor name
56
+ template = options[:liquid_template] != nil ? options[:liquid_template] : options[:process]
57
+ load_templates( template )
58
+
59
+ data.each_with_index do |d, i|
60
+
61
+ if options[:method].match "text"
62
+ r = textTemplate.render(d)
63
+ if options[:save_path]
64
+ save_name = format_save_name( options, i )
65
+ File.open( save_name, 'w') {|f| f.write r }
66
+ output << save_name
67
+ else
68
+ output << r
69
+ end
70
+ end
71
+
72
+ if options[:method].match "html"
73
+ r = htmlTemplate.render(d)
74
+ if options[:save_path]
75
+ save_name = format_save_name( options, i )
76
+ File.open( save_name, 'w') {|f| f.write r }
77
+ output << save_name
78
+ else
79
+ output << r
80
+ end
81
+ end
82
+
83
+ if options[:method].match "pdf"
84
+ raise ArgumentError,"--save-file flag must be specified for PDF output in #{self.class.name}" if !options[:save_path]
85
+ save_name = format_save_name( options, i )
86
+ html = htmlTemplate.render(d)
87
+ kit = PDFKit.new(html, :page_size => 'A4')
88
+ kit.stylesheets << @stylesheet
89
+ file = kit.to_file( save_name )
90
+ output << save_name
91
+ end
92
+ end
93
+ # if options[:dump_output] != false
94
+ # output.each do |r|
95
+ # puts r
96
+ # end
97
+ # end
98
+
99
+ return output
100
+ end
101
+
102
+ protected
103
+
104
+ end
105
+
106
+ end
@@ -0,0 +1,108 @@
1
+
2
+ # Author:: Jason Wijegooneratne (mailto:code@jwije.com)
3
+ # Copyright:: Copyright (c) 2014 Jason Wijegooneratne
4
+ # License:: MIT. See LICENSE.md distributed with the source code for more information.
5
+
6
+
7
+ module LEWT
8
+
9
+ # MetaMath is a base class for developing statistics routines for use with the Metastat LEWT extension
10
+ # It provides some convienience methods for developing new routines, as well as acting as a container
11
+ # for common mathematical proceedures.
12
+
13
+ class MetaMath
14
+
15
+ # Takes an array of numeric values and returns there mean.
16
+ # ar:: Array of values
17
+ def mean(ar)
18
+ raise TypeError "Expected an array" if not ar.kind_of?(Array)
19
+ total = ar.reduce(0) { |sum, x| x + sum }
20
+ return Float(total)/Float(ar.length)
21
+ end
22
+
23
+ # Takes an array of values and returns there mode.
24
+ # ar:: Array of values
25
+ def mode(ar)
26
+ raise TypeError "Expected an array" if not ar.kind_of?(Array)
27
+ freq = ar.inject(Hash.new(0)) { |h,v| h[v] += 1; h }
28
+ return ar.max_by { |v| freq[v] }
29
+ end
30
+
31
+ # Takes an array of values and returns the median
32
+ # ar:: Array of values
33
+ def median(ar)
34
+ raise TypeError "Expected an array" if not ar.kind_of?(Array)
35
+ mid = ar.length / 2
36
+ return (mid % 1 != 0) ? mean( [ ar[(mid).floor], ar[(mid).round ]] ) : ar[mid]
37
+ end
38
+
39
+ # Return the descriptive statistics for an array of values [mean, media, mode]
40
+ # ar:: Array of values
41
+ def descriptive_stats(ar)
42
+ raise TypeError "Expected an array" if not ar.kind_of?(Array)
43
+ return {
44
+ :mean => mean(ar),
45
+ :median => median(ar),
46
+ :mode => mode(ar)
47
+ }
48
+ end
49
+
50
+ end
51
+
52
+ # This subclass performs a Pearson Correlation analysis on an x/y dataset.
53
+ class PearsonR < MetaMath
54
+
55
+ def initialize (xs, ys)
56
+ raise Exception "_x & _y datasets must be of equal length" if xs.length != ys.length
57
+ @xs, @ys = xs, ys
58
+ end
59
+
60
+ # Returns a Pearson Correlation R value
61
+ # xs:: The x series array of values
62
+ # ys:: The y series array of values
63
+ def correlate(xs = @xs, ys = @ys)
64
+ raise Exception "_x & _y datasets must be of equal length" if xs.length != ys.length
65
+ x_mean = mean(@xs)
66
+ y_mean = mean(@ys)
67
+ numerator = (0...@xs.length).reduce(0) do |sum, i|
68
+ sum + ((@xs[i] - x_mean) * (@ys[i] - y_mean))
69
+ end
70
+ denominator = @xs.reduce(0) do |sum, x|
71
+ sum + ((x - x_mean) ** 2)
72
+ end
73
+ (numerator / Math.sqrt(denominator))
74
+ end
75
+
76
+ end
77
+
78
+
79
+ # This class performas a simple regression on an x/y dataset
80
+ class SimpleRegression < MetaMath
81
+
82
+ def initialize (xs, ys)
83
+ raise Exception "_x & _y datasets must be of equal length" if xs.length != ys.length
84
+ @xs, @ys = xs, ys
85
+ end
86
+
87
+ def y_intercept
88
+ mean(@ys) - (slope * mean(@xs))
89
+ end
90
+
91
+ def slope
92
+ x_mean = mean(@xs)
93
+ y_mean = mean(@ys)
94
+
95
+ numerator = (0...@xs.length).reduce(0) do |sum, i|
96
+ sum + ((@xs[i] - x_mean) * (@ys[i] - y_mean))
97
+ end
98
+
99
+ denominator = @xs.reduce(0) do |sum, x|
100
+ sum + ((x - x_mean) ** 2)
101
+ end
102
+
103
+ (numerator / denominator)
104
+ end
105
+
106
+
107
+ end
108
+ end
@@ -0,0 +1,161 @@
1
+ require_relative "metamath.rb"
2
+
3
+ # Author:: Jason Wijegooneratne (mailto:code@jwije.com)
4
+ # Copyright:: Copyright (c) 2014 Jason Wijegooneratne
5
+ # License:: MIT. See LICENSE.md distributed with the source code for more information.
6
+
7
+
8
+ module LEWT
9
+
10
+ # The Metastat extension [experimental] handles processing meta-data added to ledger entries. It can compute statistics based of
11
+ # these parsed values
12
+ #
13
+ # ===Usage:
14
+ # Here is an example 'description' field entered into a LEWTLedger which can be parsed with Metastat
15
+ #
16
+ # 'description' => 'Adding some features to the system #happiness=10 #average-pay'
17
+ #
18
+ # Metastat will extract the # tags from the above string and log an indicatior called happinesswhich is equal to 10
19
+ # and a Boolean switch average-pay will be set to true
20
+ #
21
+ # These values will be aggregate over the whole 'extract' dataset then processed in a few ways.
22
+ #
23
+ # 1. Create a graph out of the data
24
+ # 2. Create a summary table out of the data
25
+
26
+ class Metastat < LEWT::Extension
27
+
28
+ attr_reader :client_table, :client_graph
29
+
30
+ # Sets up this extension and registers its options
31
+ def initialize
32
+ options = {
33
+ :tags => {
34
+ :definition => "A comma seperated list of metatags to lookup.",
35
+ :type => String
36
+ },
37
+ :y_series => {
38
+ :definition => "An optional y series to use for a correlation analysis.",
39
+ :type => String
40
+ }
41
+ }
42
+ @boolean_table = Hash.new
43
+ @raw_data = Hash.new
44
+ @dataset = Hash.new
45
+ super({:cmd => 'metastat', :options => options})
46
+ end
47
+
48
+ # Handles the process event for this extension
49
+ # options [Hash]:: The options hash passed to this function by the Lewt program.
50
+ # extract_data [LEWTBook]:: The data in LEWTBook format
51
+ def process (options, extract_data)
52
+ @options = options
53
+ extract_data.each do |row|
54
+ filter_row_values(row, options)
55
+ end
56
+ @dataset["frequency_table"] = @boolean_table
57
+ if options[:y_series]
58
+ @dataset["correlations"] = compute_correlations @raw_data
59
+ end
60
+ return @dataset
61
+ end
62
+
63
+ protected
64
+
65
+ # extracts row data from a LEWTLedger object. Also handles initiating tag lookup.
66
+ # row [LEWTLedger]:: a LEWTLedger object container the row data.
67
+ # options [Hash]:: a Hash containing the options passed to LEWT.
68
+ def filter_row_values (row, options)
69
+ return if row.metatags == nil
70
+ # match tags requested via options
71
+ options[:tags].split(Lewt::OPTION_DELIMITER_REGEX).each do |meta|
72
+ row.metatags.each { |k, v|
73
+ next unless meta.gsub(Lewt::OPTION_SYMBOL_REGEX,"_").to_sym == k
74
+ # found match operate on it
75
+ client = get_matched_customers(row[:entity])[0]
76
+ # case our value
77
+ case v
78
+ when !!v == v
79
+ add_to_tally( client, row, k, v )
80
+ when Rational
81
+ transform_dataset( client, row, k, v)
82
+ end
83
+ }
84
+ end
85
+ end
86
+
87
+ # adds +1 count to the client table for the provided key
88
+ # c [Hash]:: The client hash
89
+ # r [LEWTLedger]:: A LEWTLedger row
90
+ # k:: the metatag hash key
91
+ # v:: the meta tag value, this should be a boolean
92
+ def add_to_tally( c, r, k, v )
93
+ if !@boolean_table.has_key?(c["name"])
94
+ @boolean_table[ c["name"] ] = { k.to_s => 0 }
95
+ end
96
+
97
+ if !@boolean_table[c["name"]].has_key?(k.to_s)
98
+ @boolean_table[c["name"]][k.to_s] = 0;
99
+ end
100
+
101
+ @boolean_table[c["name"]][k.to_s] += 1
102
+ end
103
+
104
+ # Transforms the dataset into something usable for statistical computation
105
+ # c [Hash]:: The client hash
106
+ # r [LEWTLedger]:: A LEWTLedger row
107
+ # k:: the metatag hash key
108
+ # v:: the meta tag value, this should be a boolean
109
+ def transform_dataset( c, r, k, v )
110
+ if !@raw_data.has_key?(c["name"])
111
+ @raw_data[ c["name"] ] = Array.new
112
+ end
113
+ # prep the data
114
+ @raw_data[c["name"]].push(prepare_row_data(r,k,v))
115
+ end
116
+
117
+ # prepares normal row for statistical analysis computing some basic numbers we are going to need
118
+ # r [LEWTLedger]:: A LEWTLedger row
119
+ # v:: the meta tag value, this should be a boolean
120
+ def prepare_row_data ( r, k, v )
121
+ data = {
122
+ k.to_sym => v.to_f,
123
+ :duration => r[:quantity].to_f,
124
+ :value => r[:total].to_f
125
+ }
126
+ return data
127
+ end
128
+
129
+ # performs some statistics using R.
130
+ # d:: the dataset to work with
131
+ def compute_correlations(d)
132
+ result = nil
133
+ d.each { |context, data_array|
134
+ # hash of arrays
135
+ data_hash = Hash.new
136
+ data_array.each do |r|
137
+ r.each { |k,v|
138
+ data_hash[k] = Array.new if !data_hash.has_key? k
139
+ data_hash[k].push v
140
+ }
141
+ end
142
+ result = correlate_y data_hash, @options[:y_series]
143
+ }
144
+ return result
145
+ end
146
+
147
+ def correlate_y(r_dataset, y_key)
148
+ results = Hash.new
149
+ r_dataset.each { |k, v_set|
150
+ next if k == y_key.to_sym
151
+ results[y_key.to_sym] = Hash.new if results[y_key.to_sym] == nil
152
+ results[y_key.to_sym][k] = Hash.new if results[y_key.to_sym][k] == nil
153
+ r = PearsonR.new( v_set, r_dataset[y_key.to_sym] )
154
+ results[y_key.to_sym][k][:pearson_r] = r.correlate
155
+ results[y_key.to_sym][k][:descriptive_stats] = r.descriptive_stats(v_set)
156
+ }
157
+ return results
158
+ end
159
+
160
+ end
161
+ end