geostats 0.1.0

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 (49) hide show
  1. data/LICENSE +13 -0
  2. data/README.md +58 -0
  3. data/bin/geostats +33 -0
  4. data/lib/geostats.rb +36 -0
  5. data/lib/geostats/commands/base.rb +73 -0
  6. data/lib/geostats/commands/console.rb +17 -0
  7. data/lib/geostats/commands/generate.rb +21 -0
  8. data/lib/geostats/commands/help.rb +20 -0
  9. data/lib/geostats/commands/init.rb +31 -0
  10. data/lib/geostats/commands/migrate.rb +21 -0
  11. data/lib/geostats/commands/push.rb +41 -0
  12. data/lib/geostats/commands/set.rb +35 -0
  13. data/lib/geostats/commands/update.rb +175 -0
  14. data/lib/geostats/console.rb +2 -0
  15. data/lib/geostats/core_ext.rb +5 -0
  16. data/lib/geostats/database.rb +15 -0
  17. data/lib/geostats/generator.rb +43 -0
  18. data/lib/geostats/grabber/cache.rb +95 -0
  19. data/lib/geostats/grabber/log.rb +27 -0
  20. data/lib/geostats/grabber/logs.rb +37 -0
  21. data/lib/geostats/grabber/user.rb +15 -0
  22. data/lib/geostats/http.rb +97 -0
  23. data/lib/geostats/migrations/001_create_caches.rb +27 -0
  24. data/lib/geostats/migrations/002_create_cache_types.rb +16 -0
  25. data/lib/geostats/migrations/003_create_logs.rb +13 -0
  26. data/lib/geostats/migrations/004_create_log_types.rb +15 -0
  27. data/lib/geostats/migrations/005_create_settings.rb +9 -0
  28. data/lib/geostats/migrations/006_add_cito_cache_type.rb +5 -0
  29. data/lib/geostats/models/cache.rb +47 -0
  30. data/lib/geostats/models/cache_type.rb +9 -0
  31. data/lib/geostats/models/log.rb +33 -0
  32. data/lib/geostats/models/log_type.rb +9 -0
  33. data/lib/geostats/models/setting.rb +16 -0
  34. data/lib/geostats/stats.rb +220 -0
  35. data/lib/geostats/templates/default.mustache +10 -0
  36. data/lib/geostats/templates/difficulty_terrain_matrix.mustache +40 -0
  37. data/lib/geostats/templates/founds_by_cache_type.mustache +17 -0
  38. data/lib/geostats/templates/milestones.mustache +19 -0
  39. data/lib/geostats/templates/monthly_founds.mustache +18 -0
  40. data/lib/geostats/templates/stylesheet.css +160 -0
  41. data/lib/geostats/utils.rb +42 -0
  42. data/lib/geostats/version.rb +3 -0
  43. data/lib/geostats/views/base.rb +19 -0
  44. data/lib/geostats/views/default.rb +13 -0
  45. data/lib/geostats/views/difficulty_terrain_matrix.rb +34 -0
  46. data/lib/geostats/views/founds_by_cache_type.rb +24 -0
  47. data/lib/geostats/views/milestones.rb +23 -0
  48. data/lib/geostats/views/monthly_founds.rb +24 -0
  49. metadata +165 -0
@@ -0,0 +1,9 @@
1
+ module Geostats
2
+ class CacheType < ActiveRecord::Base
3
+ has_many :caches, :class_name => "Geostats::Cache"
4
+
5
+ MAPPING = [2, 3, 8, 5, 1858, 137, 4, 11, 12, 6, 453, 3653, 13]
6
+
7
+ validates :name, :presence => true
8
+ end
9
+ end
@@ -0,0 +1,33 @@
1
+ module Geostats
2
+ class Log < ActiveRecord::Base
3
+ belongs_to :cache, :class_name => "Geostats::Cache"
4
+ belongs_to :log_type, :class_name => "Geostats::LogType"
5
+
6
+ scope :found_or_attended, lambda { where("logs.log_type_id IN (?)", [1, 12]) }
7
+
8
+ def self.create_from_website(uuid)
9
+ log = Log.new(:uuid => uuid)
10
+ log.update_from_website
11
+ log
12
+ end
13
+
14
+ def update_from_website
15
+ info = Grabber::Log.new(self.uuid)
16
+
17
+ [:logged_at, :message].each do |attribute|
18
+ if value = info.send(attribute)
19
+ self.attributes = { attribute => value }
20
+ end
21
+ end
22
+
23
+ set_type_from_icon(info.icon)
24
+ self.synced_at = Time.now
25
+ end
26
+
27
+ def set_type_from_icon(icon)
28
+ if icon and index = LogType::ICON_MAPPING.index(icon)
29
+ self.log_type = LogType.where(:id => index + 1).first
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,9 @@
1
+ module Geostats
2
+ class LogType < ActiveRecord::Base
3
+ has_many :logs, :class_name => "Geostats::Log"
4
+
5
+ ICON_MAPPING = %w(icon_smile icon_sad icon_note icon_XXX icon_needsmaint
6
+ traffic_cone icon_enabled icon_disabled coord_update XXX icon_rsvp
7
+ icon_attended)
8
+ end
9
+ end
@@ -0,0 +1,16 @@
1
+ module Geostats
2
+ class Setting < ActiveRecord::Base
3
+ validates :key, :presence => true, :uniqueness => true
4
+
5
+ def self.get(key)
6
+ setting = where(:key => key.to_s).first
7
+ setting ? setting.value : nil
8
+ end
9
+
10
+ def self.set(key, value)
11
+ setting = where(:key => key.to_s).first || new(:key => key.to_s)
12
+ setting.update_attribute(:value, value)
13
+ setting
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,220 @@
1
+ module Geostats
2
+ class Stats
3
+ class << self
4
+ def milestones(step=nil)
5
+ logs = Log
6
+ .joins(:cache)
7
+ .found_or_attended
8
+ .order("logs.logged_at ASC")
9
+ .all
10
+ total = logs.length
11
+ milestones = []
12
+
13
+ step ||= case total
14
+ when 0 then 0
15
+ when 1..100 then 50
16
+ when 101..1000 then 100
17
+ else 500
18
+ end
19
+
20
+ milestones << { :milestone => 1, :log => logs.first }
21
+
22
+ 1.upto(total / step) do |i|
23
+ i *= step
24
+ log = logs[i - 1]
25
+
26
+ milestones << {
27
+ :milestone => i,
28
+ :log => logs[i - 1],
29
+ :distance => (log.logged_at - milestones.last[:log].logged_at) / 60 / 60 / 24
30
+ }
31
+ end
32
+
33
+ milestones << {
34
+ :milestone => total,
35
+ :log => logs.last,
36
+ :distance => (logs.last.logged_at - milestones.last[:log].logged_at) / 60 / 60 / 24
37
+ }
38
+ end
39
+
40
+ def total_founds
41
+ Cache
42
+ .joins(:logs)
43
+ .found_or_attended
44
+ .count
45
+ end
46
+
47
+ def caching_days
48
+ Cache
49
+ .joins(:logs)
50
+ .found_or_attended
51
+ .group("logs.logged_at")
52
+ .count
53
+ .length
54
+ end
55
+
56
+ def avg_founds_per_caching_day
57
+ Cache
58
+ .joins(:logs)
59
+ .found_or_attended
60
+ .group("logs.logged_at")
61
+ .count
62
+ .to_a
63
+ .map { |a| a[1] }
64
+ .avg
65
+ end
66
+
67
+ def monthly_founds(months=6)
68
+ beginning_of_current_month = Time.parse("#{Time.now.strftime("%Y-%m")}-01")
69
+
70
+ 0.upto(months - 1).map do |x|
71
+ start = beginning_of_current_month - x.months
72
+ count = Cache
73
+ .joins(:logs)
74
+ .found_or_attended
75
+ .where("logs.logged_at >= ? AND logs.logged_at < ?", start, start + 1.month)
76
+ .count
77
+
78
+ [start, count]
79
+ end
80
+ end
81
+
82
+ def daily_founds(days=30)
83
+ today = Time.parse(Time.now.strftime("%Y-%m-%d"))
84
+
85
+ 1.upto(days).map do |x|
86
+ day = today - x.days
87
+
88
+ Cache
89
+ .joins(:logs)
90
+ .found_or_attended
91
+ .where("logs.logged_at >= ? AND logs.logged_at < ?", day, day + 1.day)
92
+ .count
93
+ end
94
+ end
95
+
96
+ def day_with_most_founds
97
+ @day_with_most_founds ||= Cache
98
+ .joins(:logs)
99
+ .found_or_attended
100
+ .group("logs.logged_at")
101
+ .count
102
+ .to_a
103
+ .sort { |a, b| a[1] <=> b[1] }
104
+ .last
105
+ end
106
+
107
+ def founds_by_cache_type
108
+ CacheType.all.map do |type|
109
+ count = Cache
110
+ .joins(:logs)
111
+ .found_or_attended
112
+ .where("caches.cache_type_id = ?", type.id)
113
+ .count
114
+
115
+ [type, count]
116
+ end
117
+ end
118
+
119
+ def newest_cache
120
+ @newest_cache ||= Cache
121
+ .joins(:logs)
122
+ .found_or_attended
123
+ .order("caches.hidden_at DESC")
124
+ .first
125
+ end
126
+
127
+ def oldest_cache
128
+ @oldest_cache ||= Cache
129
+ .joins(:logs)
130
+ .found_or_attended
131
+ .order("caches.hidden_at ASC")
132
+ .first
133
+ end
134
+
135
+ def cache_with_most_founds
136
+ @cache_with_most_founds ||= Cache
137
+ .joins(:logs)
138
+ .found_or_attended
139
+ .order("caches.founds_count DESC")
140
+ .first
141
+ end
142
+
143
+ def cache_with_most_dnf
144
+ @cache_with_most_dnf ||= Cache
145
+ .joins(:logs)
146
+ .found_or_attended
147
+ .order("caches.dnf_count DESC")
148
+ .first
149
+ end
150
+
151
+ def archived_total_ratio
152
+ @archived_total_ratio ||= begin
153
+ archived = Cache
154
+ .joins(:logs)
155
+ .found_or_attended
156
+ .where("caches.is_archived = ?", true)
157
+ .count
158
+
159
+ total = Cache
160
+ .joins(:logs)
161
+ .found_or_attended
162
+ .count
163
+
164
+ {
165
+ :archived => archived,
166
+ :total => total,
167
+ :percentage => (archived.to_f / total.to_f * 100.0).round
168
+ }
169
+ end
170
+ end
171
+
172
+ def most_northerly_cache
173
+ @most_northerly_cache ||= Cache
174
+ .joins(:logs)
175
+ .found_or_attended
176
+ .order("caches.latitude DESC")
177
+ .first
178
+ end
179
+
180
+ def most_southerly_cache
181
+ @most_southerly_cache ||= Cache
182
+ .joins(:logs)
183
+ .found_or_attended
184
+ .order("caches.latitude ASC")
185
+ .first
186
+ end
187
+
188
+ def most_westerly_cache
189
+ @most_westerly_cache ||= Cache
190
+ .joins(:logs)
191
+ .found_or_attended
192
+ .order("caches.longitude ASC")
193
+ .first
194
+ end
195
+
196
+ def most_easterly_cache
197
+ @most_easterly_cache ||= Cache
198
+ .joins(:logs)
199
+ .found_or_attended
200
+ .order("caches.longitude DESC")
201
+ .first
202
+ end
203
+
204
+ def difficulty_terrain_matrix
205
+ [1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5].map do |difficulty|
206
+ result = Cache
207
+ .joins(:logs)
208
+ .found_or_attended
209
+ .where("caches.difficulty = ?", difficulty)
210
+ .group(:terrain)
211
+ .count
212
+
213
+ [1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5, 5].map do |terrain|
214
+ result[terrain.to_f] || 0
215
+ end
216
+ end
217
+ end
218
+ end
219
+ end
220
+ end
@@ -0,0 +1,10 @@
1
+ <div id="geostats">
2
+ {{{monthly_founds}}}
3
+ {{{founds_by_cache_type}}}
4
+ {{{milestones}}}
5
+ {{{difficulty_terrain_matrix}}}
6
+ <div id="about" class="box shadow">
7
+ Statistics generated on {{generation_date}} using
8
+ <a href="http://nano.github.com/geostats">geostats {{geostats_version}}</a>
9
+ </div>
10
+ </div>
@@ -0,0 +1,40 @@
1
+ <div id="difficulty-terrain-matrix" class="box">
2
+ <h2>Difficulty Terrain Matrix</h2>
3
+ <table>
4
+ <tr>
5
+ <td width="9%"></td>
6
+ <td width="9%" class="label">T1</td>
7
+ <td width="9%" class="label">T1.5</td>
8
+ <td width="9%" class="label">T2</td>
9
+ <td width="9%" class="label">T2.5</td>
10
+ <td width="9%" class="label">T3</td>
11
+ <td width="9%" class="label">T3.5</td>
12
+ <td width="9%" class="label">T4</td>
13
+ <td width="9%" class="label">T4.5</td>
14
+ <td width="9%" class="label">T5</td>
15
+ <td width="9%"></td>
16
+ </tr>
17
+ {{#matrix}}
18
+ <tr>
19
+ <td class="label">D{{difficulty}}</td>
20
+ <td class="number">{{t1}}</td>
21
+ <td class="number">{{t15}}</td>
22
+ <td class="number">{{t2}}</td>
23
+ <td class="number">{{t25}}</td>
24
+ <td class="number">{{t3}}</td>
25
+ <td class="number">{{t35}}</td>
26
+ <td class="number">{{t4}}</td>
27
+ <td class="number">{{t45}}</td>
28
+ <td class="number">{{t5}}</td>
29
+ <td class="number sum">{{dsum}}</td>
30
+ </tr>
31
+ {{/matrix}}
32
+ <tr>
33
+ <td></td>
34
+ {{#tsums}}
35
+ <td class="number sum">{{sum}}</td>
36
+ {{/tsums}}
37
+ <td></td>
38
+ </tr>
39
+ </table>
40
+ </div>
@@ -0,0 +1,17 @@
1
+ <div id="founds-by-cache-type" class="box">
2
+ <h2>Founds By Cache Type</h2>
3
+ <div class="charts">
4
+ {{#founds_by_cache_type}}
5
+ <div class="section">
6
+ <div class="name">
7
+ {{name}}
8
+ </div>
9
+ <div class="chartbar" style="width: {{height}}px;"></div>
10
+ <div class="founds">
11
+ {{founds}}
12
+ </div>
13
+ <br style="clear: both;">
14
+ </div>
15
+ {{/founds_by_cache_type}}
16
+ </div>
17
+ </div>
@@ -0,0 +1,19 @@
1
+ <div id="milestones" class="box">
2
+ <h2>Milestones</h2>
3
+ <table>
4
+ <tr>
5
+ <th></th>
6
+ <th align="left">Cache</th>
7
+ <th align="left">Date</th>
8
+ <th align="right">Distance in days</th>
9
+ </tr>
10
+ {{#milestones}}
11
+ <tr>
12
+ <td align="right" width="6%">{{milestone}}</td>
13
+ <td><a href="{{cache_url}}">{{name}}</a> ({{type}})</td>
14
+ <td width="16%"><a href="{{log_url}}">{{date}}</a></td>
15
+ <td align="right" width="16%">{{distance}}</td>
16
+ </tr>
17
+ {{/milestones}}
18
+ </table>
19
+ </div>
@@ -0,0 +1,18 @@
1
+ <div id="monthly-founds" class="box">
2
+ <h2>Founds By Month</h2>
3
+ <div class="charts">
4
+ {{#monthly_founds}}
5
+ <div class="section">
6
+ <div class="chartbox">
7
+ {{founds}}
8
+ <div class="chartbar" style="height: {{height}}px;"></div>
9
+ </div>
10
+ <div class="date">
11
+ {{month}}<br>
12
+ {{year}}
13
+ </div>
14
+ </div>
15
+ {{/monthly_founds}}
16
+ <br style="clear: both;">
17
+ </div>
18
+ </div>
@@ -0,0 +1,160 @@
1
+ #geostats {
2
+ font: normal 13px Helvetica;
3
+ }
4
+
5
+ #geostats a {
6
+ color: rgb(19, 94, 196);
7
+ text-decoration: none;
8
+ }
9
+
10
+ #geostats .box {
11
+ width: 700px;
12
+ background-color: #eee;
13
+ background-image: -webkit-gradient(linear, left top, left bottom, from(#f1f1f1), to(#e0e0e0));
14
+ -webkit-border-radius: 15px;
15
+ padding: 20px;
16
+ margin: 30px auto;
17
+ }
18
+
19
+ #geostats .box h2 {
20
+ color: rgb(19, 94, 196);
21
+ text-align: center;
22
+ text-shadow: #fff 1px 1px;
23
+ margin: 0 0 10px 0;
24
+ }
25
+
26
+ #geostats .shadow {
27
+ text-shadow: #fff 1px 1px;
28
+ color: #888;
29
+ }
30
+
31
+ #about {
32
+ text-align: center;
33
+ }
34
+
35
+ #milestones {
36
+ color: #777;
37
+ text-shadow: #fff 1px 1px;
38
+ }
39
+
40
+ #milestones table {
41
+ width: 100%;
42
+ border-spacing: 0;
43
+ padding: 0 20px;
44
+ }
45
+
46
+ #milestones td, #milestones th {
47
+ padding: 6px 10px;
48
+ }
49
+
50
+ #milestones th {
51
+ color: #999;
52
+ font-size: 11px;
53
+ font-weight: normal;
54
+ }
55
+
56
+ #founds-by-cache-type h2 {
57
+ color: rgb(210, 14, 14);
58
+ margin-bottom: 20px;
59
+ }
60
+
61
+ #founds-by-cache-type .charts {
62
+ width: 650px;
63
+ margin: auto;
64
+ }
65
+
66
+ #founds-by-cache-type .section {
67
+ margin: 10px 0;
68
+ }
69
+
70
+ #founds-by-cache-type .chartbar {
71
+ float: left;
72
+ height: 20px;
73
+ background-color: rgb(210, 14, 14);
74
+ -webkit-border-top-right-radius: 4px;
75
+ -webkit-border-bottom-right-radius: 4px;
76
+ }
77
+ #founds-by-cache-type .founds {
78
+ float: left;
79
+ margin-left: 10px;
80
+ line-height: 20px;
81
+ color: rgb(210, 14, 14);
82
+ font-weight: bold;
83
+ }
84
+
85
+ #founds-by-cache-type .name {
86
+ float: left;
87
+ width: 180px;
88
+ margin-right: 20px;
89
+ color: #999;
90
+ text-align: right;
91
+ text-shadow: #fff 1px 1px;
92
+ line-height: 20px;
93
+ }
94
+
95
+ #monthly-founds .charts {
96
+ width: 672px;
97
+ margin: auto;
98
+ }
99
+
100
+ #monthly-founds .section {
101
+ float: left;
102
+ margin: 0 3px;
103
+ }
104
+
105
+ #monthly-founds .chartbox {
106
+ display: table-cell;
107
+ vertical-align: bottom;
108
+ height: 160px;
109
+ text-align: center;
110
+ color: rgb(19, 94, 196);
111
+ font-weight: bold;
112
+ }
113
+
114
+ #monthly-founds .chartbar {
115
+ background-color: rgb(19, 94, 196);
116
+ width: 50px;
117
+ -webkit-border-top-right-radius: 6px 6px;
118
+ -webkit-border-top-left-radius: 6px 6px;
119
+ margin-top: 3px;
120
+ border-top: 1px solid rgba(255, 255, 255, 0.2);
121
+ }
122
+
123
+ #monthly-founds .date {
124
+ color: #AAA;
125
+ text-align: center;
126
+ margin-top: 5px;
127
+ text-shadow: #FFF 1px 1px;
128
+ }
129
+
130
+ #difficulty-terrain-matrix h2 {
131
+ color: rgb(36, 191, 19);
132
+ margin-bottom: 20px;
133
+ }
134
+
135
+ #difficulty-terrain-matrix table {
136
+ width: 100%;
137
+ padding: 0 20px;
138
+ border-spacing: 2px;
139
+ font-size: 11px;
140
+ text-align: center;
141
+ }
142
+
143
+ #difficulty-terrain-matrix td.number,
144
+ #difficulty-terrain-matrix td.label,
145
+ #difficulty-terrain-matrix td.sum {
146
+ color: #fff;
147
+ text-shadow: #888 0px 1px;
148
+ -webkit-border-radius: 3px;
149
+ padding: 4px;
150
+ }
151
+
152
+ #difficulty-terrain-matrix td.number {
153
+ background-color: rgba(36, 191, 19, 0.75);
154
+ }
155
+
156
+ #difficulty-terrain-matrix td.label,
157
+ #difficulty-terrain-matrix td.sum {
158
+ font-weight: bold;
159
+ background-color: rgb(36, 191, 19);
160
+ }