sniff 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. data/README.markdown +73 -0
  2. data/lib/sniff/active_record_ext.rb +12 -0
  3. data/lib/sniff/conversions_ext.rb +45 -0
  4. data/lib/sniff/database.rb +157 -0
  5. data/lib/sniff/emitter.rb +65 -0
  6. data/lib/sniff/tasks.rb +14 -0
  7. data/lib/sniff/timeframe.rb +251 -0
  8. data/lib/sniff.rb +31 -0
  9. data/lib/test_support/data_models/census_division.rb +9 -0
  10. data/lib/test_support/data_models/census_region.rb +9 -0
  11. data/lib/test_support/data_models/climate_division.rb +8 -0
  12. data/lib/test_support/data_models/country.rb +9 -0
  13. data/lib/test_support/data_models/egrid_region.rb +7 -0
  14. data/lib/test_support/data_models/egrid_subregion.rb +8 -0
  15. data/lib/test_support/data_models/gender.rb +6 -0
  16. data/lib/test_support/data_models/petroleum_administration_for_defense_district.rb +9 -0
  17. data/lib/test_support/data_models/state.rb +13 -0
  18. data/lib/test_support/data_models/urbanity.rb +6 -0
  19. data/lib/test_support/data_models/zip_code.rb +14 -0
  20. data/lib/test_support/db/fixtures/census_divisions.csv +10 -0
  21. data/lib/test_support/db/fixtures/census_regions.csv +5 -0
  22. data/lib/test_support/db/fixtures/climate_divisions.csv +360 -0
  23. data/lib/test_support/db/fixtures/countries.csv +247 -0
  24. data/lib/test_support/db/fixtures/egrid_regions.csv +6 -0
  25. data/lib/test_support/db/fixtures/egrid_subregions.csv +27 -0
  26. data/lib/test_support/db/fixtures/genders.csv +3 -0
  27. data/lib/test_support/db/fixtures/petroleum_administration_for_defense_districts.csv +8 -0
  28. data/lib/test_support/db/fixtures/states.csv +58 -0
  29. data/lib/test_support/db/fixtures/urbanities.csv +5 -0
  30. data/lib/test_support/db/fixtures/yearly_anonymous_emissions.csv +0 -0
  31. data/lib/test_support/db/fixtures/yearly_typical_emissions.csv +0 -0
  32. data/lib/test_support/db/fixtures/zip_codes.csv +3390 -0
  33. data/lib/test_support/db/schema.rb +117 -0
  34. data/spec/lib/sniff/database_spec.rb +22 -0
  35. data/spec/spec.opts +2 -0
  36. data/spec/spec_helper.rb +5 -0
  37. data/vendor/geokit-rails/CHANGELOG.rdoc +46 -0
  38. data/vendor/geokit-rails/MIT-LICENSE +20 -0
  39. data/vendor/geokit-rails/README.markdown +561 -0
  40. data/vendor/geokit-rails/Rakefile +18 -0
  41. data/vendor/geokit-rails/about.yml +9 -0
  42. data/vendor/geokit-rails/assets/api_keys_template +61 -0
  43. data/vendor/geokit-rails/init.rb +1 -0
  44. data/vendor/geokit-rails/install.rb +14 -0
  45. data/vendor/geokit-rails/lib/geokit-rails/acts_as_mappable.rb +456 -0
  46. data/vendor/geokit-rails/lib/geokit-rails/adapters/abstract.rb +31 -0
  47. data/vendor/geokit-rails/lib/geokit-rails/adapters/mysql.rb +22 -0
  48. data/vendor/geokit-rails/lib/geokit-rails/adapters/postgresql.rb +22 -0
  49. data/vendor/geokit-rails/lib/geokit-rails/adapters/sqlserver.rb +43 -0
  50. data/vendor/geokit-rails/lib/geokit-rails/defaults.rb +22 -0
  51. data/vendor/geokit-rails/lib/geokit-rails/geocoder_control.rb +16 -0
  52. data/vendor/geokit-rails/lib/geokit-rails/ip_geocode_lookup.rb +46 -0
  53. data/vendor/geokit-rails/lib/geokit-rails.rb +24 -0
  54. data/vendor/geokit-rails/test/acts_as_mappable_test.rb +474 -0
  55. data/vendor/geokit-rails/test/boot.rb +25 -0
  56. data/vendor/geokit-rails/test/database.yml +20 -0
  57. data/vendor/geokit-rails/test/fixtures/companies.yml +7 -0
  58. data/vendor/geokit-rails/test/fixtures/custom_locations.yml +54 -0
  59. data/vendor/geokit-rails/test/fixtures/locations.yml +54 -0
  60. data/vendor/geokit-rails/test/fixtures/mock_addresses.yml +17 -0
  61. data/vendor/geokit-rails/test/fixtures/mock_families.yml +2 -0
  62. data/vendor/geokit-rails/test/fixtures/mock_houses.yml +9 -0
  63. data/vendor/geokit-rails/test/fixtures/mock_organizations.yml +5 -0
  64. data/vendor/geokit-rails/test/fixtures/mock_people.yml +5 -0
  65. data/vendor/geokit-rails/test/fixtures/stores.yml +0 -0
  66. data/vendor/geokit-rails/test/ip_geocode_lookup_test.rb +77 -0
  67. data/vendor/geokit-rails/test/models/company.rb +3 -0
  68. data/vendor/geokit-rails/test/models/custom_location.rb +12 -0
  69. data/vendor/geokit-rails/test/models/location.rb +4 -0
  70. data/vendor/geokit-rails/test/models/mock_address.rb +4 -0
  71. data/vendor/geokit-rails/test/models/mock_family.rb +3 -0
  72. data/vendor/geokit-rails/test/models/mock_house.rb +3 -0
  73. data/vendor/geokit-rails/test/models/mock_organization.rb +4 -0
  74. data/vendor/geokit-rails/test/models/mock_person.rb +4 -0
  75. data/vendor/geokit-rails/test/models/store.rb +3 -0
  76. data/vendor/geokit-rails/test/schema.rb +60 -0
  77. data/vendor/geokit-rails/test/tasks.rake +31 -0
  78. data/vendor/geokit-rails/test/test_helper.rb +23 -0
  79. metadata +492 -0
data/README.markdown ADDED
@@ -0,0 +1,73 @@
1
+ # sniff
2
+ Testing environment for Brighter Planet Climate Middleware emission calculation gems.
3
+
4
+ This gem provides:
5
+ * Sample climate data, representative of data found on http://data.brighterplanet.com
6
+ * References to gems needed by each emitter gem
7
+
8
+ # Usage
9
+ Within an emitter gem's test setup, you can:
10
+ require 'sniff'
11
+
12
+ Sniff.init '/path/to/emitter_project'
13
+
14
+ # How to contribute
15
+ Typical contributions will include updates to test data.
16
+
17
+ 1. Fork the project.
18
+ 1. Make your feature addition or bug fix.
19
+ 1. Commit, do not mess with rakefile, version, or history. (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
20
+ 1. Send me a pull request. Bonus points for topic branches.
21
+
22
+ # Local Gems
23
+ Sniff depends on several gems, some of which are developed by Brighter Planet. You can tell Sniff or any of the carbon gems to use your local repos in lieu of installed rubygems through the following steps:
24
+
25
+
26
+ Paste the following functions into your ~/.bash_profile
27
+ function mod_devgem() {
28
+ var="LOCAL_`echo $2 | tr 'a-z' 'A-Z'`"
29
+
30
+ if [ "$1" == "disable" ]
31
+ then
32
+ echo "unset $var"
33
+ unset $var
34
+ else
35
+ dir=${3:-"~/$2"}
36
+ echo "export $var=$dir"
37
+ export $var=$dir
38
+ fi
39
+ }
40
+
41
+ function devgems () {
42
+ # Usage: devgems [enable|disable] [gemname]
43
+ cmd=${1:-"enable"}
44
+ if [ -z $2 ]
45
+ then
46
+ mod_devgem $cmd characterizable
47
+ mod_devgem $cmd cohort_scope
48
+ mod_devgem $cmd falls_back_on
49
+ mod_devgem $cmd leap
50
+ mod_devgem $cmd loose_tight_dictionary
51
+ mod_devgem $cmd sniff
52
+ mod_devgem $cmd data_miner
53
+ else
54
+ mod_devgem $cmd $2
55
+ fi
56
+ }
57
+
58
+ To enable all local gems, run `devgems enable`
59
+ To turn off devgems, run `devgems disable`
60
+ To turn off a specific gem, run `devgems disable leap`
61
+ To turn on a specific gem, run `devgems enable leap`
62
+
63
+ Typical development process:
64
+ cd ~
65
+ git clone http://github.com/rossmeissl/leap.git
66
+ cd leap
67
+ <do some development on leap>
68
+ cd ~/sniff
69
+ devgems enable leap
70
+ rake gemspec
71
+ rm -f Gemfile.lock
72
+ bundle install
73
+ <run tests, e.g. `spec spec`>
@@ -0,0 +1,12 @@
1
+ require 'active_record'
2
+ require 'common_name'
3
+ require 'falls_back_on'
4
+
5
+ ActiveRecord::Base.class_eval do
6
+ extend FallsBackOn
7
+
8
+ def self._common_name
9
+ name.underscore
10
+ end
11
+ include CommonName
12
+ end
@@ -0,0 +1,45 @@
1
+ require 'conversions'
2
+ Conversions.register(:miles, :nautical_miles, 0.868976242)
3
+ Conversions.register(:kilometres, :nautical_miles, 0.539956803)
4
+ Conversions.register(:pounds_per_gallon, :kilograms_per_litre, 0.119826427)
5
+ Conversions.register(:inches, :meters, 0.0254)
6
+ Conversions.register(:kilowatt_hours, :watt_hours, 1_000.0)
7
+ Conversions.register(:kilowatt_hours, :btus, 3_412.14163) # obsolete?
8
+ Conversions.register(:watt_hours, :btus, 3.4121414799) # obsolete?
9
+ Conversions.register(:watt_hours, :joules, 3_600.0)
10
+ Conversions.register(:kilowatt_hours, :joules, 3_600_000.0)
11
+
12
+ # Conversions.register(:btus, :gallons_of_kerosene, 0.000007419521097)
13
+ Conversions.register(:joules, :litres_of_kerosene, 1.0 / (135_000.0 * 3.78541178 * 1_055.05585)) # should only be used for RECS 2005
14
+
15
+ # Conversions.register(:btus, :gallons_of_propane, 0.000010471204188)
16
+ Conversions.register(:joules, :litres_of_propane, 1.0 / (91_333.0 * 3.78541178 * 1_055.05585)) # should only be used for RECS 2005
17
+
18
+ # Conversions.register(:btus, :therms, 0.000010002388121)
19
+ Conversions.register(:therms, :joules, 105_505_585.0) # should only be used for RECS 2005
20
+
21
+ # Conversions.register(:btus, :gallons_of_fuel_oil, 0.0000072007637183)
22
+ Conversions.register(:joules, :litres_of_fuel_oil, 1.0 / (138_690.0 * 3.78541178 * 1_055.05585)) # should only be used for RECS 2005
23
+
24
+ # Conversions.register(:tons_of_coal, :btus, 20_169_000.0)
25
+ Conversions.register(:joules, :kilograms_of_coal, 1.0 / (22_342_000.0 * 0.00110231131 * 1_055.05585)) # should only be used for RECS 2005
26
+
27
+ Conversions.register(:kilograms, :lbs, 2.20462262)
28
+ Conversions.register(:kbtus, :btus, 1_000.0)
29
+ Conversions.register(:square_feet, :square_metres, 0.09290304)
30
+ Conversions.register(:pounds_per_square_foot, :kilograms_per_square_metre, 4.88242764)
31
+ Conversions.register(:kilograms_per_kilowatt_hour, :kilograms_per_megawatt_hour, 1_000.0)
32
+ Conversions.register(:btus, :joules, 1_055.05585)
33
+ Conversions.register(:kbtus, :joules, 1_000.0 * 1_055.05585)
34
+ Conversions.register(:cords, :joules, 2.11011171e10)
35
+
36
+ Conversions.register(:gallons_per_mile, :litres_per_kilometre, 2.35214583)
37
+ Conversions.register(:pounds_per_mile, :kilograms_per_kilometre, 0.281849232)
38
+ Conversions.register(:dollars, :cents, 100)
39
+ Conversions.register(:cubic_feet, :cubic_metres, 0.0283168466)
40
+ # 1 (kilocalories per pound) = 9 224.14105 joules per kilogram
41
+ Conversions.register :kilocalories_per_pound, :joules_per_kilogram, 9_224.14105
42
+ # 1 (grams per kilocalories) = 2.39005736 × 10-7 kilograms per joules
43
+ Conversions.register :grams_per_kilocalorie, :kilograms_per_joule, 2.39005736e-7
44
+ # 1 joule = 0.000239005736 kilocalories
45
+ Conversions.register :joules, :kilocalories, 0.000239005736
@@ -0,0 +1,157 @@
1
+ require 'fileutils'
2
+ require 'logger'
3
+
4
+ module Sniff
5
+ class Database
6
+ class << self
7
+ def init(local_root, options = {})
8
+ environments = []
9
+ environments << really_init(local_root, options)
10
+
11
+ unless local_root == Sniff.root
12
+ environments << really_init(Sniff.root)
13
+ end
14
+
15
+ db_init local_root
16
+ environments.each { |e| e.populate_fixtures }
17
+ end
18
+
19
+ def really_init(root, options = {})
20
+ db = new root, options
21
+ db.init
22
+ db
23
+ end
24
+
25
+ def db_init(local_root)
26
+ db_path = File.join(local_root, 'db')
27
+ db_file_path = File.join(db_path, 'emitter_data.sqlite3')
28
+ FileUtils.mkdir_p db_path
29
+ db_drop db_file_path
30
+ connect db_file_path
31
+ db_create
32
+ load_all_schemas
33
+ end
34
+
35
+ def connect(db_file_path)
36
+ ActiveRecord::Base.establish_connection :adapter => 'sqlite3',
37
+ :database => db_file_path,
38
+ :pool => 5,
39
+ :timeout => 5000
40
+ end
41
+
42
+ def db_drop(db_file_path)
43
+ FileUtils.rm db_file_path if File.exists?(db_file_path)
44
+ end
45
+
46
+ def db_create
47
+ ActiveRecord::Base.connection
48
+ end
49
+
50
+ def define_schema(&blk)
51
+ @schemas = [] unless defined?(@schemas)
52
+ @schemas << blk
53
+ end
54
+
55
+ def schemas
56
+ @schemas
57
+ end
58
+
59
+ def load_all_schemas
60
+ orig_std_out = STDOUT.clone
61
+ STDOUT.reopen File.open(File.join('/tmp', 'schema_output'), 'w')
62
+
63
+ ActiveRecord::Schema.define(:version => 1) do
64
+ ar_schema = self
65
+ Sniff::Database.schemas.each do |s|
66
+ ar_schema.instance_eval &s
67
+ end
68
+ end
69
+ ensure
70
+ STDOUT.reopen(orig_std_out)
71
+ end
72
+ end
73
+
74
+ attr_accessor :root, :lib_path, :schema_path, :fixtures_path,
75
+ :load_data, :fixtures, :logger
76
+
77
+ def initialize(root, options)
78
+ self.root = root
79
+ self.lib_path = File.join(root, 'lib', 'test_support')
80
+ self.load_data = options[:load_data]
81
+ self.schema_path = options[:schema_path]
82
+ self.fixtures_path = options[:fixtures_path]
83
+ self.logger = Logger.new options[:logdev]
84
+ end
85
+
86
+ def log(str)
87
+ logger.log str
88
+ end
89
+
90
+ def load_data?
91
+ @load_data = true if @load_data.nil?
92
+ @load_data
93
+ end
94
+
95
+ def schema_path
96
+ @schema_path ||= File.join(lib_path, 'db', 'schema.rb')
97
+ end
98
+
99
+ def fixtures_path
100
+ @fixtures_path ||= File.join(lib_path, 'db', 'fixtures')
101
+ end
102
+
103
+ def fixtures
104
+ @fixtures ||= []
105
+ end
106
+
107
+ def init
108
+ load_models
109
+ load_supporting_libs
110
+ read_schema
111
+ read_fixtures if load_data?
112
+ end
113
+
114
+ def read_schema
115
+ log "Reading schema #{schema_path}"
116
+ load(schema_path)
117
+ end
118
+
119
+ def read_fixtures
120
+ require 'active_record/fixtures'
121
+ log "Reading fixtures from #{fixtures_path}/**/*.{yml,csv}"
122
+
123
+ Dir["#{fixtures_path}/**/*.{yml,csv}"].each do |fixture_file|
124
+ fixtures << fixture_file
125
+ end
126
+ end
127
+
128
+ def populate_fixtures
129
+ fixtures.each do |fixture_file|
130
+ log "Loading fixture #{fixture_file}"
131
+ Fixtures.create_fixtures(fixtures_path, fixture_file[(fixtures_path.size + 1)..-5])
132
+ end
133
+ end
134
+
135
+ def load_supporting_libs
136
+ $:.unshift File.join(root, 'lib')
137
+ Dir[File.join(root, 'lib', 'test_support', '*.rb')].each do |lib|
138
+ log "Loading #{lib}"
139
+ require lib
140
+ end
141
+ end
142
+
143
+ def load_models
144
+ require 'falls_back_on'
145
+ require 'falls_back_on/active_record_ext'
146
+
147
+ require 'cohort_scope'
148
+ require 'leap'
149
+ # require 'data_miner'
150
+
151
+ Dir["#{lib_path}/data_models/**/*.rb"].each do |lib|
152
+ log "Loading model #{lib}"
153
+ require lib
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,65 @@
1
+ require 'summary_judgement'
2
+ require 'fast_timestamp'
3
+ require 'common_name'
4
+ require 'falls_back_on'
5
+
6
+ module Sniff
7
+ module Emitter
8
+ def self.included(target)
9
+ target.send :extend, ClassMethods
10
+ target.send :extend, SummaryJudgement
11
+ target.send :extend, FastTimestamp
12
+ target.send :include, Characterizable
13
+ end
14
+
15
+ def parent_class
16
+ Emitter
17
+ end
18
+
19
+ def visible_effective_characteristics
20
+ characteristics.effective.reject { |_, c| c.hidden? }
21
+ end
22
+
23
+ def retired?
24
+ has_attribute?(:retirement) and retirement
25
+ end
26
+
27
+ def pattern?
28
+ self.class.pattern?
29
+ end
30
+
31
+ module ClassMethods
32
+ def from_params_hash(params = Hash.new)
33
+ resolved_params = Hash.new
34
+ instance = new
35
+ associations = reflect_on_all_associations
36
+ params.each do |k, v|
37
+ next if v.blank?
38
+ c = characteristics[k.to_sym]
39
+ next if c.nil?
40
+ if associations.map(&:name).include?(c.name.to_sym)
41
+ association = associations.find { |a| a.name == c.name.to_sym }
42
+ klass = association.options[:class_name] || association.name.to_s.pluralize.classify
43
+ klass = klass.constantize
44
+ if v.is_a?(Hash)
45
+ # h[:origin_airport][:iata_code] => 'MIA'
46
+ attr_name, attr_value = v.to_a.flatten[0, 2]
47
+ resolved_params[k] = klass.send "find_by_#{attr_name}", attr_value
48
+ else
49
+ # h[:origin_airport] => 'MIA'
50
+ resolved_params[k] = klass.send "find_by_#{k}", v
51
+ end
52
+ else
53
+ resolved_params[k] = v
54
+ end
55
+ end
56
+ new resolved_params
57
+ end
58
+
59
+ def pattern?
60
+ common_name.ends_with? 'pattern'
61
+ end
62
+ end
63
+ end
64
+ end
65
+
@@ -0,0 +1,14 @@
1
+ task :environment do
2
+ ENV['SCHEMA'] = File.join Sniff.root, 'db', 'schema.rb'
3
+ ENV['FIXTURES_PATH'] = File.join Sniff.root, 'test', 'fixtures'
4
+ end
5
+
6
+ task :console do
7
+ require 'sniff'
8
+ cwd = Dir.pwd
9
+ Sniff.init cwd
10
+
11
+ require 'irb'
12
+ ARGV.clear
13
+ IRB.start
14
+ end
@@ -0,0 +1,251 @@
1
+ # Encapsulates a timeframe between two dates. The dates provided to the class are always until the last date. That means
2
+ # that the last date is excluded.
3
+ #
4
+ # # from 2007-10-01 00:00:00.000 to 2007-10-31 23:59:59.999
5
+ # Timeframe.new(Date(2007,10,1), Date(2007,11,1))
6
+ # # and holds 31 days
7
+ # Timeframe.new(Date(2007,10,1), Date(2007,11,1)).days #=> 31
8
+
9
+ module Sniff
10
+ class Timeframe
11
+ attr_accessor :from, :to
12
+
13
+ # Creates a new instance of Timeframe. You can either pass a start and end Date or a Hash with named arguments,
14
+ # with the following options:
15
+ #
16
+ # <tt>:month</tt>: Start date becomes the first day of this month, and the end date becomes the first day of
17
+ # the next month. If no <tt>:year</tt> is specified, the current year is used.
18
+ # <tt>:year</tt>: Start date becomes the first day of this year, and the end date becomes the first day of the
19
+ # next year.
20
+ #
21
+ # Examples:
22
+ #
23
+ # Timeframe.new Date.new(2007, 2, 1), Date.new(2007, 4, 1) # February and March
24
+ # Timeframe.new :year => 2004 # The year 2004
25
+ # Timeframe.new :month => 4 # April
26
+ # Timeframe.new :year => 2004, :month => 2 # Feburary 2004
27
+ def initialize(*args)
28
+ options = args.extract_options!
29
+
30
+ if month = options[:month]
31
+ month = Date.parse(month).month if month.is_a? String
32
+ year = options[:year] || Time.zone.today.year
33
+ from = Date.new(year, month, 1)
34
+ to = from.next_month
35
+ elsif year = options[:year]
36
+ from = Date.new(year, 1, 1)
37
+ to = Date.new(year+1, 1, 1)
38
+ end
39
+
40
+ from ||= args.shift.andand.to_date
41
+ to ||= args.shift.andand.to_date
42
+
43
+ raise ArgumentError, "Please supply a start and end date, `#{args.map(&:inspect).to_sentence}' is not enough" if from.nil? or to.nil?
44
+ raise ArgumentError, "Start date #{from} should be earlier than end date #{to}" if from > to
45
+ raise ArgumentError, 'Timeframes that cross year boundaries are dangerous' unless options[:skip_year_boundary_crossing_check] or from.year == to.yesterday.year or from == to
46
+
47
+ @from, @to = from, to
48
+ end
49
+
50
+ def inspect
51
+ "<Timeframe(#{object_id}) #{days} days starting #{from} ending #{to}>"
52
+ end
53
+
54
+ # The number of days in the timeframe
55
+ #
56
+ # Timeframe.new(Date.new(2007, 11, 1), Date.new(2007, 12, 1)).days #=> 30
57
+ # Timeframe.new(:month => 1).days #=> 31
58
+ # Timeframe.new(:year => 2004).days #=> 366
59
+ def days
60
+ (to - from).to_i
61
+ end
62
+
63
+ # Returns a string representation of the timeframe
64
+ def to_s
65
+ if [from.day, from.month, to.day, to.month].uniq == [1]
66
+ from.year.to_s
67
+ elsif from.day == 1 and to.day == 1 and to.month - from.month == 1
68
+ "#{Date::MONTHNAMES[from.month]} #{from.year}"
69
+ else
70
+ "the period from #{from.strftime('%d %B')} to #{to.yesterday.strftime('%d %B %Y')}"
71
+ end
72
+ end
73
+
74
+ # Returns true when the date is included in this Timeframe
75
+ def include?(obj)
76
+ # puts "checking to see if #{date} is between #{from} and #{to}" if Emitter::DEBUG
77
+ case obj
78
+ when Date
79
+ (from...to).include?(obj)
80
+ when Time
81
+ # (from...to).include?(obj.to_date)
82
+ raise "this wasn't previously supported, but it could be"
83
+ when Timeframe
84
+ from <= obj.from and to >= obj.to
85
+ end
86
+ end
87
+
88
+ def proper_include?(other_timeframe)
89
+ raise ArgumentError, 'Proper inclusion only makes sense when testing other Timeframes' unless other_timeframe.is_a? Timeframe
90
+ (from < other_timeframe.from) and (to > other_timeframe.to)
91
+ end
92
+
93
+ # Returns true when this timeframe is equal to the other timeframe
94
+ def ==(other)
95
+ # puts "checking to see if #{self} is equal to #{other}" if Emitter::DEBUG
96
+ return false unless other.is_a?(Timeframe)
97
+ from == other.from and to == other.to
98
+ end
99
+ alias :eql? :==
100
+
101
+ # Calculates a hash value for the Timeframe, used for equality checking and Hash lookups.
102
+ # This needs to be an integer or else it won't use #eql?
103
+ def hash
104
+ from.hash + to.hash
105
+ end
106
+ alias :to_param :hash
107
+
108
+ # Returns an array of month-long subtimeframes
109
+ # TODO: rename to month_subtimeframes
110
+ def months
111
+ raise ArgumentError, "Please only provide whole-month timeframes to Timeframe#months" unless from.day == 1 and to.day == 1
112
+ raise ArgumentError, 'Timeframes that cross year boundaries are dangerous during Timeframe#months' unless from.year == to.yesterday.year
113
+ year = from.year # therefore this only works in the from year
114
+ (from.month..to.yesterday.month).map { |m| Timeframe.new :month => m, :year => year }
115
+ end
116
+
117
+ # Returns the relevant year as a Timeframe
118
+ def year
119
+ raise ArgumentError, 'Timeframes that cross year boundaries are dangerous during Timeframe#year' unless from.year == to.yesterday.year
120
+ Timeframe.new :year => from.year
121
+ end
122
+
123
+ # multiyear safe
124
+ def month_subtimeframes
125
+ (from.year..to.yesterday.year).map do |year|
126
+ (1..12).map do |month|
127
+ Timeframe.new(:year => year, :month => month) & self
128
+ end
129
+ end.flatten.compact
130
+ end
131
+
132
+ # multiyear safe
133
+ def full_month_subtimeframes
134
+ month_subtimeframes.map { |st| Timeframe.new(:year => st.from.year, :month => st.from.month) }
135
+ end
136
+
137
+ # multiyear safe
138
+ def year_subtimeframes
139
+ (from.year..to.yesterday.year).map do |year|
140
+ Timeframe.new(:year => year) & self
141
+ end
142
+ end
143
+
144
+ # multiyear safe
145
+ def full_year_subtimeframes
146
+ (from.year..to.yesterday.year).map do |year|
147
+ Timeframe.new :year => year
148
+ end
149
+ end
150
+
151
+ # multiyear safe
152
+ def ending_no_later_than(date)
153
+ if to < date
154
+ self
155
+ elsif from >= date
156
+ nil
157
+ else
158
+ Timeframe.multiyear from, date
159
+ end
160
+ end
161
+
162
+ # Returns a timeframe representing the intersection of the timeframes
163
+ def &(other_timeframe)
164
+ this_timeframe = self
165
+ if other_timeframe == this_timeframe
166
+ this_timeframe
167
+ elsif this_timeframe.from > other_timeframe.from and this_timeframe.to < other_timeframe.to
168
+ this_timeframe
169
+ elsif other_timeframe.from > this_timeframe.from and other_timeframe.to < this_timeframe.to
170
+ other_timeframe
171
+ elsif this_timeframe.from >= other_timeframe.to or this_timeframe.to <= other_timeframe.from
172
+ nil
173
+ else
174
+ Timeframe.new [this_timeframe.from, other_timeframe.from].max, [this_timeframe.to, other_timeframe.to].min, :skip_year_boundary_crossing_check => true
175
+ end
176
+ end
177
+
178
+ # Returns a fraction of another Timeframe
179
+ def /(other_timeframe)
180
+ raise ArgumentError, 'You can only divide a Timeframe by another Timeframe' unless other_timeframe.is_a? Timeframe
181
+ self.days.to_f / other_timeframe.days.to_f
182
+ end
183
+
184
+ def crop(container)
185
+ raise ArgumentError, 'You can only crop a timeframe by another timeframe' unless container.is_a? Timeframe
186
+ self.class.new [from, container.from].max, [to, container.to].min
187
+ end
188
+
189
+ def gaps_left_by(*timeframes)
190
+ # remove extraneous timeframes
191
+ timeframes.reject! { |t| t.to <= from }
192
+ timeframes.reject! { |t| t.from >= to }
193
+
194
+ # crop timeframes
195
+ timeframes.map! { |t| t.crop self }
196
+
197
+ # remove proper subtimeframes
198
+ timeframes.reject! { |t| timeframes.detect { |u| u.proper_include? t } }
199
+
200
+ # escape
201
+ return [self] if timeframes.empty?
202
+
203
+ timeframes.sort! { |x, y| x.from <=> y.from }
204
+
205
+ timeframes.collect(&:to).unshift(from).ykk(timeframes.collect(&:from).push(to)) do |gap|
206
+ Timeframe.new(*gap) if gap[1] > gap[0]
207
+ end.compact
208
+ end
209
+
210
+ def covered_by?(*timeframes)
211
+ gaps_left_by(*timeframes).empty?
212
+ end
213
+
214
+ def last_year
215
+ self.class.new((from - 1.year), (to - 1.year))
216
+ end
217
+
218
+ class << self
219
+ def this_year
220
+ new :year => Time.now.year
221
+ end
222
+
223
+ def constrained_new(from, to, constraint)
224
+ raise ArgumentError, 'Need Date, Date, Timeframe as args' unless from.is_a? Date and to.is_a? Date and constraint.is_a? Timeframe
225
+ raise ArgumentError, "Start date #{from} should be earlier than end date #{to}" if from > to
226
+ if to <= constraint.from or from >= constraint.to
227
+ new constraint.from, constraint.from
228
+ elsif from.year == to.yesterday.year
229
+ new(from, to) & constraint
230
+ elsif from.year < constraint.from.year and constraint.from.year < to.yesterday.year
231
+ constraint
232
+ else
233
+ new [constraint.from, from].max, [constraint.to, to].min
234
+ end
235
+ end
236
+
237
+ def multiyear(from, to)
238
+ from = Date.parse(from) if from.is_a?(String)
239
+ to = Date.parse(to) if to.is_a?(String)
240
+ new from, to, :skip_year_boundary_crossing_check => true
241
+ end
242
+
243
+ # create a multiyear timeframe +/- number of years around today
244
+ def mid(number)
245
+ from = Time.zone.today - number.years
246
+ to = Time.zone.today + number.years
247
+ multiyear from, to
248
+ end
249
+ end
250
+ end
251
+ end
data/lib/sniff.rb ADDED
@@ -0,0 +1,31 @@
1
+ module Sniff
2
+ extend self
3
+
4
+ def root
5
+ File.join(File.dirname(__FILE__), '..')
6
+ end
7
+
8
+ def init(local_root, options = {})
9
+ require 'active_record'
10
+ require 'sqlite3'
11
+
12
+ load_plugins
13
+
14
+ Sniff::Database.init local_root, options
15
+ end
16
+
17
+ def load_plugins
18
+ Dir[File.join(Sniff.root, 'vendor', '**', 'init.rb')].each do |pluginit|
19
+ $:.unshift File.join(File.dirname(pluginit), 'lib')
20
+ load pluginit
21
+ end
22
+ end
23
+ end
24
+
25
+ require 'characterizable'
26
+ $:.unshift File.dirname(__FILE__)
27
+ require 'sniff/active_record_ext'
28
+ require 'sniff/database'
29
+ require 'sniff/emitter'
30
+ require 'sniff/conversions_ext'
31
+ require 'sniff/timeframe'
@@ -0,0 +1,9 @@
1
+ class CensusDivision < ActiveRecord::Base
2
+ set_primary_key :number
3
+
4
+ belongs_to :census_region, :foreign_key => 'census_region_number'
5
+ has_many :states, :foreign_key => 'census_division_number'
6
+ has_many :zip_codes, :through => :states
7
+ has_many :climate_divisions, :through => :states
8
+ has_many :residential_energy_consumption_survey_responses, :foreign_key => 'census_division_number'
9
+ end
@@ -0,0 +1,9 @@
1
+ class CensusRegion < ActiveRecord::Base
2
+ set_primary_key :number
3
+
4
+ has_many :census_divisions, :foreign_key => 'census_region_number'
5
+ has_many :states, :through => :census_divisions
6
+ # has_many :climate_divisions, :through => :census_divisions
7
+ # has_many :zip_codes, :through => :census_divisions
8
+ has_many :residential_energy_consumption_survey_responses, :foreign_key => 'census_region_number'
9
+ end
@@ -0,0 +1,8 @@
1
+ class ClimateDivision < ActiveRecord::Base
2
+ set_primary_key :name
3
+
4
+ has_many :zip_codes, :foreign_key => 'climate_division_name'
5
+ belongs_to :state, :foreign_key => 'state_postal_abbreviation'
6
+
7
+ RADIUS = 750
8
+ end
@@ -0,0 +1,9 @@
1
+ class Country < ActiveRecord::Base
2
+ set_primary_key :iso_3166_code
3
+
4
+ class << self
5
+ def united_states
6
+ find_by_iso_3166_code('US')
7
+ end
8
+ end
9
+ end