inat-get 0.8.0.11 → 0.8.0.12
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/inat/app/info.rb +1 -1
- data/lib/inat/app/task/context.rb +2 -0
- data/lib/inat/data/sets/dataset.rb +1 -1
- data/lib/inat/data/sets/listers.rb +1 -0
- data/lib/inat/data/sets/wrappers.rb +99 -74
- data/lib/inat/report/report_dsl.rb +203 -0
- data/lib/inat/report/table.rb +4 -0
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8b6b2164211848152ba97642e69979744046065cc6916b7f64d5f0b6127137e2
|
4
|
+
data.tar.gz: c3578401e4ac8127126da08d124c27834d4ce0ed3c083fe0de61aefcc9b72618
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9cfa13aeecf17554be9d62b41c4ef1cac2018e916e49aa9cc9698260c36598e1f90acfeb2cd89f0320027446fb58f099a4d3f65f8a48f7c358b2db16f8630a05
|
7
|
+
data.tar.gz: ddee5675c3b2b832a9ecfa35e37a0059947aea87e98cce5f67ab7e7179e41750f08fe15c3937756268e2b8ac3c9ba6c137bf2996f5077847ca3a98997c3352c0
|
data/lib/inat/app/info.rb
CHANGED
@@ -6,7 +6,7 @@ require_relative 'list'
|
|
6
6
|
class DataSet
|
7
7
|
|
8
8
|
attr_reader :time
|
9
|
-
|
9
|
+
attr_accessor :object # TODO: переделать select так, чтобы не было необходимости во внешнем присваивании
|
10
10
|
attr_reader :observations
|
11
11
|
|
12
12
|
def initialize object, observations, time: Time::new
|
@@ -2,136 +2,161 @@
|
|
2
2
|
|
3
3
|
require 'date'
|
4
4
|
|
5
|
-
class
|
5
|
+
class Calendarian
|
6
6
|
|
7
7
|
class << self
|
8
8
|
|
9
9
|
private :new
|
10
10
|
|
11
|
-
def []
|
12
|
-
return nil if
|
13
|
-
return
|
14
|
-
|
15
|
-
when Date
|
16
|
-
|
11
|
+
def [] src
|
12
|
+
return nil if src == nil
|
13
|
+
return src if self === src
|
14
|
+
value = case src
|
15
|
+
when Date
|
16
|
+
self.date_to_value src
|
17
|
+
when Time
|
18
|
+
self.date_to_value src.to_date
|
17
19
|
when String
|
18
|
-
date = Date::parse
|
19
|
-
date
|
20
|
+
date = Date::parse src
|
21
|
+
self.date_to_value date
|
20
22
|
when Integer
|
21
|
-
|
23
|
+
src
|
22
24
|
else
|
23
|
-
raise TypeError, "Invalid
|
25
|
+
raise TypeError, "Invalid date: #{ src.inspect }", caller
|
24
26
|
end
|
25
|
-
|
26
|
-
@
|
27
|
-
@
|
27
|
+
return nil if value == nil
|
28
|
+
@values ||= {}
|
29
|
+
@values[value] ||= new value
|
30
|
+
@values[value]
|
28
31
|
end
|
29
32
|
|
30
33
|
end
|
31
34
|
|
32
|
-
attr_reader :
|
35
|
+
attr_reader :value
|
33
36
|
|
34
|
-
def initialize
|
35
|
-
@
|
37
|
+
def initialize value
|
38
|
+
@value = value
|
36
39
|
end
|
37
40
|
|
38
41
|
include Comparable
|
39
42
|
|
40
43
|
def <=> other
|
41
|
-
return nil unless
|
42
|
-
@
|
44
|
+
return nil unless self.class === other
|
45
|
+
@value <=> other.value
|
43
46
|
end
|
44
47
|
|
48
|
+
end
|
49
|
+
|
50
|
+
class Year < Calendarian
|
51
|
+
|
52
|
+
class << self
|
53
|
+
|
54
|
+
protected def date_to_value date
|
55
|
+
date.year
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
alias :year :value
|
61
|
+
|
45
62
|
def - num
|
46
|
-
self.class[@
|
63
|
+
self.class[@value - num]
|
47
64
|
end
|
48
65
|
|
49
66
|
def to_s
|
50
|
-
"<i class=\"glyphicon glyphicon-calendar\"></i> #{ @
|
67
|
+
"<i class=\"glyphicon glyphicon-calendar\"></i> #{ @value } год"
|
68
|
+
end
|
69
|
+
|
70
|
+
def query_params
|
71
|
+
"year=#{ @value }"
|
51
72
|
end
|
52
73
|
|
53
74
|
end
|
54
75
|
|
55
|
-
class Month
|
76
|
+
class Month < Calendarian
|
56
77
|
|
57
78
|
class << self
|
58
79
|
|
59
|
-
|
60
|
-
|
61
|
-
def [] source
|
62
|
-
return nil if source == nil
|
63
|
-
return source if Month === source
|
64
|
-
month = case source
|
65
|
-
when Date, Time
|
66
|
-
source.month
|
67
|
-
when String
|
68
|
-
date = Date::parse source
|
69
|
-
date.month
|
70
|
-
when Integer
|
71
|
-
source
|
72
|
-
else
|
73
|
-
raise TypeError, "Invalid month key: #{ source.inspect }!", caller
|
74
|
-
end
|
75
|
-
@months ||= {}
|
76
|
-
@months[month] ||= new month
|
77
|
-
@months[month]
|
80
|
+
protected def date_to_value date
|
81
|
+
date.month
|
78
82
|
end
|
79
83
|
|
80
84
|
end
|
81
85
|
|
82
|
-
|
86
|
+
alias :month :value
|
87
|
+
|
88
|
+
NAMES = {
|
89
|
+
1 => 'Январь',
|
90
|
+
2 => 'Февраль',
|
91
|
+
3 => 'Март',
|
92
|
+
4 => 'Апрель',
|
93
|
+
5 => 'Май',
|
94
|
+
6 => 'Июнь',
|
95
|
+
7 => 'Июль',
|
96
|
+
8 => 'Август',
|
97
|
+
9 => 'Сентябрь',
|
98
|
+
10 => 'Октябрь',
|
99
|
+
11 => 'Ноябрь',
|
100
|
+
12 => 'Декабрь'
|
101
|
+
}
|
83
102
|
|
84
|
-
def
|
85
|
-
@
|
103
|
+
def to_s
|
104
|
+
"<i class=\"glyphicon glyphicon-calendar\"></i> #{ NAMES[@value] }"
|
86
105
|
end
|
87
106
|
|
88
|
-
|
89
|
-
|
90
|
-
def <=> other
|
91
|
-
return nil unless Month === other
|
92
|
-
@month <=> other.month
|
107
|
+
def query_params
|
108
|
+
"month=#{ @value }"
|
93
109
|
end
|
94
110
|
|
95
111
|
end
|
96
112
|
|
97
|
-
class Day
|
113
|
+
class Day < Calendarian
|
98
114
|
|
99
115
|
class << self
|
100
116
|
|
101
|
-
|
117
|
+
protected def date_to_value date
|
118
|
+
date.day
|
119
|
+
end
|
102
120
|
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
121
|
+
end
|
122
|
+
|
123
|
+
alias :day :value
|
124
|
+
|
125
|
+
def to_s
|
126
|
+
"<i class=\"glyphicon glyphicon-calendar\"></i> #{ @value }"
|
127
|
+
end
|
128
|
+
|
129
|
+
def query_params
|
130
|
+
"day=#{ @value }"
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
class Winter < Calendarian
|
136
|
+
|
137
|
+
class << self
|
138
|
+
|
139
|
+
protected def date_to_value date
|
140
|
+
month = date.month
|
141
|
+
if month <= 4
|
142
|
+
date.year
|
143
|
+
elsif month >= 10
|
144
|
+
date.year + 1
|
114
145
|
else
|
115
|
-
|
146
|
+
nil
|
116
147
|
end
|
117
|
-
@days ||= {}
|
118
|
-
@days[day] ||= new day
|
119
|
-
@days[day]
|
120
148
|
end
|
121
149
|
|
122
150
|
end
|
123
151
|
|
124
|
-
|
152
|
+
alias :winter :value
|
125
153
|
|
126
|
-
def
|
127
|
-
@
|
154
|
+
def to_s
|
155
|
+
"<i class=\"glyphicon glyphicon-calendar\"></i> Зима #{ @value - 1 }–#{ @value }"
|
128
156
|
end
|
129
157
|
|
130
|
-
|
131
|
-
|
132
|
-
def <=> other
|
133
|
-
return nil unless Day === other
|
134
|
-
@day <=> other.day
|
158
|
+
def query_params
|
159
|
+
"d1=#{ @value - 1 }-10-01&d2=#{ @value }-04-30"
|
135
160
|
end
|
136
161
|
|
137
162
|
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'table'
|
4
|
+
|
5
|
+
module ReportDSL
|
6
|
+
|
7
|
+
include TableDSL
|
8
|
+
|
9
|
+
def class_title object, is_observer: true
|
10
|
+
case object
|
11
|
+
when Taxon
|
12
|
+
'Таксон'
|
13
|
+
when Place
|
14
|
+
'Территория'
|
15
|
+
when Project
|
16
|
+
'Проект'
|
17
|
+
when User
|
18
|
+
if is_observer
|
19
|
+
'Наблюдатель'
|
20
|
+
else
|
21
|
+
'Пользователь'
|
22
|
+
end
|
23
|
+
when Calendarian
|
24
|
+
'Период'
|
25
|
+
else
|
26
|
+
'Объект'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def species_table list, observers: false, details: true
|
31
|
+
raise ArgumentError, 'Unconsistent flags!' if observers && !details
|
32
|
+
object_title = class_title list.first.object
|
33
|
+
species = table do
|
34
|
+
column '#', width: 3, align: :right, data: :line_no
|
35
|
+
column object_title, data: :object
|
36
|
+
if details
|
37
|
+
column 'Наблюдения', data: :observations
|
38
|
+
else
|
39
|
+
column 'Наблюдения', data: :observations, width: 6, align: :right
|
40
|
+
end
|
41
|
+
end
|
42
|
+
users = nil
|
43
|
+
user_rows = []
|
44
|
+
if observers
|
45
|
+
@@prefix ||= 0
|
46
|
+
@@prefix += 1
|
47
|
+
users = table do
|
48
|
+
column '#', width: 3, align: :right, data: :line_no
|
49
|
+
column 'Наблюдатель', data: :user
|
50
|
+
column 'Виды', width: 6, align: :right, data: :species
|
51
|
+
column 'Наблюдения', width: 6, align: :right, data: :observations
|
52
|
+
end
|
53
|
+
by_user = list.to_dataset.to_list Listers::USER
|
54
|
+
by_user.each do |ds|
|
55
|
+
ls = ds.to_list Listers::SPECIES
|
56
|
+
user = ds.object
|
57
|
+
user_rows << { user: user, anchor: "#{ @@prefix }-user-#{ user.id }", species: ls.count, observations: ds.count }
|
58
|
+
end
|
59
|
+
user_rows.sort_by! { |row| -row[:species] }
|
60
|
+
users << user_rows
|
61
|
+
end
|
62
|
+
species_rows = []
|
63
|
+
list.each do |ds|
|
64
|
+
observations = if details
|
65
|
+
if observers
|
66
|
+
ds.observations.map { |o| "#{ o }<sup><a href=\"\##{ @@prefix }-user-#{ o.user.id }\">#{ user_rows.index { |row| row[:user] == o.user } }</a></sup>" }
|
67
|
+
else
|
68
|
+
ds.observations.map(&:to_s)
|
69
|
+
end
|
70
|
+
else
|
71
|
+
[ ds.count.to_s ]
|
72
|
+
end
|
73
|
+
species_rows << { object: ds.object, observations: observations.join(', ') }
|
74
|
+
end
|
75
|
+
species << species_rows
|
76
|
+
if observers
|
77
|
+
[ species, users ]
|
78
|
+
else
|
79
|
+
species
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
LAST_STYLE = 'font-size:110%;'
|
84
|
+
SUMMARY_STYLE = 'font-weight:bold;'
|
85
|
+
|
86
|
+
def history_table list, news: true, summary: true, base_link: nil, last: nil, extras: false, last_style: LAST_STYLE, summary_style: SUMMARY_STYLE
|
87
|
+
object_title = class_title list.first.object, is_observer: false
|
88
|
+
history = table do
|
89
|
+
column '#', width: 3, align: :right, data: :line_no
|
90
|
+
column object_title, data: :object
|
91
|
+
column 'Наблюдения', width: 6, align: :right, data: :observations
|
92
|
+
column 'Виды', width: 6, align: :right, data: :species
|
93
|
+
if news
|
94
|
+
column 'Новые', width: 6, align: :right, data: :news
|
95
|
+
end
|
96
|
+
end
|
97
|
+
history_rows = []
|
98
|
+
last_object = nil
|
99
|
+
last_ds = nil
|
100
|
+
last_ls = nil
|
101
|
+
delta = nil
|
102
|
+
if news || summary
|
103
|
+
base_ls = List::zero Listers::SPECIES
|
104
|
+
end
|
105
|
+
list.each do |ds|
|
106
|
+
last_object = ds.object
|
107
|
+
last_ds = ds
|
108
|
+
last_ls = ds.to_list Listers::SPECIES
|
109
|
+
row = { observations: last_ds.count, species: last_ls.count }
|
110
|
+
if base_link && last_object.respond_to?(:query_params)
|
111
|
+
link = base_link + '&' + last_object.query_params
|
112
|
+
row[:object] = "<a href=\"#{ link }\">#{ last_object }</a>"
|
113
|
+
else
|
114
|
+
row[:object] = last_object
|
115
|
+
end
|
116
|
+
if news || summary
|
117
|
+
delta = last_ls - base_ls
|
118
|
+
base_ls += last_ls
|
119
|
+
row[:news] = delta.count
|
120
|
+
end
|
121
|
+
history_rows << row
|
122
|
+
end
|
123
|
+
if last && last != last_object
|
124
|
+
row = { object: last, observations: 0, species: 0 }
|
125
|
+
if news
|
126
|
+
row[:news] = 0
|
127
|
+
delta = List::zero Listers::SPECIES
|
128
|
+
last_object = last
|
129
|
+
last_ds = DataSet::zero
|
130
|
+
last_ls = List::zero Listers::SPECIES
|
131
|
+
end
|
132
|
+
history_rows << row
|
133
|
+
end
|
134
|
+
history_rows.last[:style] = last_style if last_style
|
135
|
+
if summary
|
136
|
+
# Почему base_ls, а не list?
|
137
|
+
# 1. list в некоторых случаях может быть не List, а просто массив DataSet с заполненным object.
|
138
|
+
# 2. Даже в базовом случае list сформирован не по видам.
|
139
|
+
row = { line_no: '', observations: base_ls.observation_count, species: base_ls.count }
|
140
|
+
row[:style] = summary_style if summary_style
|
141
|
+
history_rows << row
|
142
|
+
end
|
143
|
+
history << history_rows
|
144
|
+
if extras
|
145
|
+
[ history, last_ds, delta ]
|
146
|
+
else
|
147
|
+
history
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Вариант history_table с другими предустановками.
|
152
|
+
def summary_table list, extras: false, summary_style: SUMMARY_STYLE
|
153
|
+
history_table list, news: false, summary: true, base_link: nil, last: nil, extras: extras, last_style: nil, summary_style: summary_style
|
154
|
+
end
|
155
|
+
|
156
|
+
# TODO: разобраться с разными count в разных контекстах.
|
157
|
+
def rating_table list, limit: 1, count: 3, details: true, key: :species, summary: false
|
158
|
+
object_title = class_title list.first.object
|
159
|
+
rating = table do
|
160
|
+
column '#', width: 3, align: :right, data: :line_no
|
161
|
+
column object_title, data: :object
|
162
|
+
column 'Виды', width: 6, align: :right, data: :species
|
163
|
+
if details
|
164
|
+
column 'Наблюдения', data: :observations
|
165
|
+
else
|
166
|
+
column 'Наблюдения', width: 6, align: :right, data: :observations
|
167
|
+
end
|
168
|
+
end
|
169
|
+
rating_rows = []
|
170
|
+
list.each do |ds|
|
171
|
+
ls = ds.to_list Listers::SPECIES
|
172
|
+
row = { object: ds.object, species: ls.count, count: ds.count }
|
173
|
+
if details
|
174
|
+
row[:observations] = ds.observations.map(&:to_s).join(', ')
|
175
|
+
else
|
176
|
+
row[:observations] = ds.count
|
177
|
+
end
|
178
|
+
rating_rows << row
|
179
|
+
end
|
180
|
+
size = rating_rows.size
|
181
|
+
key = :count if key == :observations
|
182
|
+
if limit
|
183
|
+
rating_rows.filter! { |row| row[key] >= limit }
|
184
|
+
end
|
185
|
+
rating_rows.sort_by! { |row| -row[key] }
|
186
|
+
if count
|
187
|
+
rating_rows = rating_rows.take(count)
|
188
|
+
end
|
189
|
+
if summary
|
190
|
+
full_ls = List === list ? list : list.reduce(DataSet::zero, :|).to_list
|
191
|
+
rating_rows << { line_no: '', species: full_ls.count, count: full_ls.observation_count }
|
192
|
+
end
|
193
|
+
rating << rating_rows
|
194
|
+
if count
|
195
|
+
[ rating, size ]
|
196
|
+
else
|
197
|
+
rating
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
module_function :species_table, :history_table, :summary_table, :rating_table
|
202
|
+
|
203
|
+
end
|
data/lib/inat/report/table.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: inat-get
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.8.0.
|
4
|
+
version: 0.8.0.12
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ivan Shikhalev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-11-
|
11
|
+
date: 2023-11-26 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sqlite3
|
@@ -107,6 +107,7 @@ files:
|
|
107
107
|
- lib/inat/data/types/extras.rb
|
108
108
|
- lib/inat/data/types/location.rb
|
109
109
|
- lib/inat/data/types/std.rb
|
110
|
+
- lib/inat/report/report_dsl.rb
|
110
111
|
- lib/inat/report/table.rb
|
111
112
|
- lib/inat/utils/deep.rb
|
112
113
|
homepage: https://github.com/shikhalev/inat-get
|