inat-get 0.8.0.11 → 0.8.0.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.
- 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
|