geostats 0.1.0

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