Skip to content

Commit 0ff2c64

Browse files
authored
Merge pull request #1731 from coderdojo-japan/add-inactivated-at-to-dojo
Cont'd: Add `inactivated_at` to `Dojo` model to replace `is_active` boolean column (#1726)
2 parents 86e6f5e + 0c95b47 commit 0ff2c64

File tree

3 files changed

+250
-12
lines changed

3 files changed

+250
-12
lines changed

app/models/high_charts_builder.rb

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,16 @@ def global_options
99
end
1010

1111
def build_annual_dojos(source, lang = 'ja')
12-
data = annual_chart_data_from(source)
12+
data = annual_dojos_chart_data_from(source)
1313
title_text = lang == 'en' ? 'Number of Dojos' : '道場数の推移'
1414

1515
LazyHighCharts::HighChart.new('graph') do |f|
1616
f.title(text: title_text)
1717
f.xAxis(categories: data[:years])
18-
f.series(type: 'column', name: lang == 'en' ? 'New' : '増加数', yAxis: 0, data: data[:increase_nums])
18+
f.series(type: 'column', name: lang == 'en' ? 'New' : '開設数', yAxis: 0, data: data[:increase_nums])
1919
f.series(type: 'line', name: lang == 'en' ? 'Total' : '累積合計', yAxis: 1, data: data[:cumulative_sums])
2020
f.yAxis [
21-
{ title: { text: lang == 'en' ? 'New' : '増加数' }, tickInterval: 15, max: 75 },
21+
{ title: { text: lang == 'en' ? 'New' : '開設数' }, tickInterval: 15, max: 75 },
2222
{ title: { text: lang == 'en' ? 'Total' : '累積合計' }, tickInterval: 50, max: 250, opposite: true }
2323
]
2424
f.chart(width: HIGH_CHARTS_WIDTH, alignTicks: false)
@@ -71,14 +71,44 @@ def annual_chart_data_from(source)
7171
years = source_array.map(&:first)
7272
counts = source_array.map(&:last)
7373

74-
# 増加数を計算(前年との差分)
75-
increase_nums = counts.each_with_index.map do |count, i|
76-
i == 0 ? count : count - counts[i - 1]
77-
end
74+
# 年間の値として扱う(イベント回数や参加者数用)
75+
increase_nums = counts
7876

79-
# annual_dojos_with_historical_dataからの値は既にその時点での総数
80-
# (累積値として扱う)
81-
cumulative_sums = counts
77+
# 累積合計を計算
78+
cumulative_sums = counts.size.times.map {|i| counts[0..i].sum }
79+
80+
{
81+
years: years,
82+
increase_nums: increase_nums,
83+
cumulative_sums: cumulative_sums
84+
}
85+
end
86+
87+
# 道場数の推移用の特別なデータ処理
88+
# 新規開設数と累積数を表示
89+
def annual_dojos_chart_data_from(source)
90+
# sourceが新しい形式(active_dojosとnew_dojosを含む)の場合
91+
if source.is_a?(Hash) && source.key?(:active_dojos) && source.key?(:new_dojos)
92+
active_array = source[:active_dojos].to_a
93+
new_array = source[:new_dojos].to_a
94+
95+
years = active_array.map(&:first)
96+
cumulative_sums = active_array.map(&:last)
97+
increase_nums = new_array.map(&:last) # 新規開設数を使用
98+
else
99+
# 後方互換性のため、古い形式もサポート
100+
source_array = source.is_a?(Hash) ? source.to_a : source
101+
102+
years = source_array.map(&:first)
103+
counts = source_array.map(&:last)
104+
105+
# 増減数を計算(前年との差分)- 後方互換性のため
106+
increase_nums = counts.each_with_index.map do |count, i|
107+
i == 0 ? count : count - counts[i - 1]
108+
end
109+
110+
cumulative_sums = counts
111+
end
82112

83113
{
84114
years: years,

app/models/stat.rb

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,11 @@ def annual_sum_of_participants
3434
def annual_dojos_chart(lang = 'ja')
3535
# 各年末時点でアクティブだったDojoを集計(過去の非アクティブDojoも含む)
3636
# YAMLマスターデータには既にinactivated_atが含まれているため、常にこの方式を使用
37-
HighChartsBuilder.build_annual_dojos(annual_dojos_with_historical_data, lang)
37+
data = {
38+
active_dojos: annual_dojos_with_historical_data,
39+
new_dojos: annual_new_dojos_count
40+
}
41+
HighChartsBuilder.build_annual_dojos(data, lang)
3842
end
3943

4044
# 各年末時点でアクティブだったDojo数を集計(過去の非アクティブDojoも含む)
@@ -46,6 +50,17 @@ def annual_dojos_with_historical_data
4650
hash[year.to_s] = count
4751
end
4852
end
53+
54+
# 各年に新規開設されたDojo数を集計
55+
def annual_new_dojos_count
56+
(@period.first.year..@period.last.year).each_with_object({}) do |year, hash|
57+
start_of_year = Time.zone.local(year).beginning_of_year
58+
end_of_year = Time.zone.local(year).end_of_year
59+
# その年に作成されたDojoの数を集計
60+
count = Dojo.where(created_at: start_of_year..end_of_year).sum(:counter)
61+
hash[year.to_s] = count
62+
end
63+
end
4964

5065
def annual_event_histories_chart(lang = 'ja')
5166
HighChartsBuilder.build_annual_event_histories(annual_count_of_event_histories, lang)

spec/models/stat_spec.rb

Lines changed: 194 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,197 @@
8080
expect(result).to be_a(LazyHighCharts::HighChart)
8181
end
8282
end
83-
end
83+
84+
describe 'グラフデータの妥当性検証' do
85+
let(:period) { Date.new(2012, 1, 1)..Date.new(2024, 12, 31) }
86+
let(:stat) { Stat.new(period) }
87+
88+
before do
89+
# テスト用のDojoを作成(複数作成して、一部を非アクティブ化)
90+
dojo1 = Dojo.create!(
91+
name: 'CoderDojo テスト1',
92+
93+
description: 'テスト用Dojo1の説明',
94+
tags: ['Scratch'],
95+
url: 'https://test1.coderdojo.jp',
96+
created_at: Time.zone.local(2012, 4, 1),
97+
prefecture_id: 13,
98+
is_active: true
99+
)
100+
101+
# 2022年に非アクティブ化される道場
102+
dojo2 = Dojo.create!(
103+
name: 'CoderDojo テスト2',
104+
105+
description: 'テスト用Dojo2の説明',
106+
tags: ['Python'],
107+
url: 'https://test2.coderdojo.jp',
108+
created_at: Time.zone.local(2019, 1, 1),
109+
prefecture_id: 14,
110+
is_active: false,
111+
inactivated_at: Time.zone.local(2022, 6, 1)
112+
)
113+
114+
# 2023年に非アクティブ化される道場
115+
dojo3 = Dojo.create!(
116+
name: 'CoderDojo テスト3',
117+
118+
description: 'テスト用Dojo3の説明',
119+
tags: ['Ruby'],
120+
url: 'https://test3.coderdojo.jp',
121+
created_at: Time.zone.local(2020, 1, 1),
122+
prefecture_id: 27,
123+
is_active: false,
124+
inactivated_at: Time.zone.local(2023, 3, 1)
125+
)
126+
127+
# テスト用のイベント履歴を作成(実際のデータパターンに近づける)
128+
# 成長曲線を再現:初期は少なく、徐々に増加、COVID後に減少、その後回復
129+
test_data = {
130+
2012 => { events: 2, participants_per_event: 4 }, # 創成期
131+
2013 => { events: 3, participants_per_event: 4 }, # 徐々に増加
132+
2014 => { events: 5, participants_per_event: 5 },
133+
2015 => { events: 6, participants_per_event: 6 },
134+
2016 => { events: 8, participants_per_event: 6 },
135+
2017 => { events: 12, participants_per_event: 7 }, # 成長期
136+
2018 => { events: 15, participants_per_event: 7 },
137+
2019 => { events: 20, participants_per_event: 8 }, # ピーク
138+
2020 => { events: 16, participants_per_event: 5 }, # COVID影響
139+
2021 => { events: 14, participants_per_event: 4 }, # 低迷継続
140+
2022 => { events: 16, participants_per_event: 5 }, # 回復開始
141+
2023 => { events: 18, participants_per_event: 6 }, # 回復継続
142+
2024 => { events: 18, participants_per_event: 6 } # 安定
143+
}
144+
145+
test_data.each do |year, data|
146+
data[:events].times do |i|
147+
# dojo1のイベント(継続的に活動)
148+
EventHistory.create!(
149+
dojo_id: dojo1.id,
150+
dojo_name: dojo1.name,
151+
service_name: 'connpass',
152+
event_id: "test_#{year}_#{i}",
153+
event_url: "https://test.connpass.com/event/#{year}_#{i}/",
154+
evented_at: Date.new(year, 3, 1) + (i * 2).weeks,
155+
participants: data[:participants_per_event]
156+
)
157+
end
158+
end
159+
end
160+
161+
it '開催回数のグラフに負の値が含まれないこと' do
162+
# テストデータから集計
163+
event_data = stat.annual_count_of_event_histories
164+
165+
# 各年の開催回数が0以上であることを確認
166+
event_data.each do |year, count|
167+
expect(count).to be >= 0, "#{year}年の開催回数が負の値です: #{count}"
168+
end
169+
170+
# 期待される値を確認(明示的なテストデータに基づく)
171+
# annual_count_of_event_historiesは文字列キーを返す
172+
expect(event_data['2012']).to eq(2)
173+
expect(event_data['2013']).to eq(3)
174+
expect(event_data['2014']).to eq(5)
175+
expect(event_data['2015']).to eq(6)
176+
expect(event_data['2016']).to eq(8)
177+
expect(event_data['2017']).to eq(12)
178+
expect(event_data['2018']).to eq(15)
179+
expect(event_data['2019']).to eq(20)
180+
expect(event_data['2020']).to eq(16)
181+
expect(event_data['2021']).to eq(14)
182+
expect(event_data['2022']).to eq(16)
183+
expect(event_data['2023']).to eq(18)
184+
expect(event_data['2024']).to eq(18)
185+
186+
# グラフデータを生成
187+
chart = HighChartsBuilder.build_annual_event_histories(event_data)
188+
series_data = chart.series_data
189+
190+
if series_data
191+
# 年間の開催回数(棒グラフ)が負でないことを確認
192+
annual_counts = series_data.find { |s| s[:type] == 'column' }
193+
if annual_counts && annual_counts[:data]
194+
annual_counts[:data].each_with_index do |count, i|
195+
year = 2012 + i # テストデータの開始年は2012
196+
expect(count).to be >= 0, "#{year}年の開催回数が負の値としてグラフに表示されます: #{count}"
197+
end
198+
end
199+
end
200+
end
201+
202+
it '参加者数のグラフに負の値が含まれないこと' do
203+
# テストデータから集計
204+
participant_data = stat.annual_sum_of_participants
205+
206+
# 各年の参加者数が0以上であることを確認
207+
participant_data.each do |year, count|
208+
expect(count).to be >= 0, "#{year}年の参加者数が負の値です: #{count}"
209+
end
210+
211+
# 期待される値を確認(明示的なテストデータに基づく)
212+
# annual_sum_of_participantsも文字列キーを返す
213+
expect(participant_data['2012']).to eq(8) # 2イベント × 4人
214+
expect(participant_data['2013']).to eq(12) # 3イベント × 4人
215+
expect(participant_data['2014']).to eq(25) # 5イベント × 5人
216+
expect(participant_data['2015']).to eq(36) # 6イベント × 6人
217+
expect(participant_data['2016']).to eq(48) # 8イベント × 6人
218+
expect(participant_data['2017']).to eq(84) # 12イベント × 7人
219+
expect(participant_data['2018']).to eq(105) # 15イベント × 7人
220+
expect(participant_data['2019']).to eq(160) # 20イベント × 8人
221+
expect(participant_data['2020']).to eq(80) # 16イベント × 5人
222+
expect(participant_data['2021']).to eq(56) # 14イベント × 4人
223+
expect(participant_data['2022']).to eq(80) # 16イベント × 5人
224+
expect(participant_data['2023']).to eq(108) # 18イベント × 6人
225+
expect(participant_data['2024']).to eq(108) # 18イベント × 6人
226+
227+
# グラフデータを生成
228+
chart = HighChartsBuilder.build_annual_participants(participant_data)
229+
series_data = chart.series_data
230+
231+
if series_data
232+
# 年間の参加者数(棒グラフ)が負でないことを確認
233+
annual_counts = series_data.find { |s| s[:type] == 'column' }
234+
if annual_counts && annual_counts[:data]
235+
annual_counts[:data].each_with_index do |count, i|
236+
year = 2012 + i # テストデータの開始年は2012
237+
expect(count).to be >= 0, "#{year}年の参加者数が負の値としてグラフに表示されます: #{count}"
238+
end
239+
end
240+
end
241+
end
242+
243+
it '道場数の「開設数」は負の値にならない(新規開設数のため)' do
244+
# 道場数のデータを取得(新しい形式)
245+
dojo_data = {
246+
active_dojos: stat.annual_dojos_with_historical_data,
247+
new_dojos: stat.annual_new_dojos_count
248+
}
249+
250+
# グラフデータを生成
251+
chart = HighChartsBuilder.build_annual_dojos(dojo_data)
252+
series_data = chart.series_data
253+
254+
if series_data
255+
# 開設数(棒グラフ)- 新規開設された道場の数
256+
change_data = series_data.find { |s| s[:type] == 'column' }
257+
if change_data && change_data[:data]
258+
change_data[:data].each_with_index do |value, i|
259+
year = 2012 + i
260+
# 「開設数」は新規開設を意味するため、負の値は論理的に不適切
261+
expect(value).to be >= 0, "#{year}年の「開設数」が負の値です: #{value}。開設数は新規開設された道場数を表すため、0以上である必要があります"
262+
end
263+
end
264+
265+
# 累積数(線グラフ)は常に0以上
266+
total_data = series_data.find { |s| s[:type] == 'line' }
267+
if total_data && total_data[:data]
268+
total_data[:data].each_with_index do |count, i|
269+
year = 2012 + i
270+
expect(count).to be >= 0, "#{year}年の累積道場数が負の値です: #{count}"
271+
end
272+
end
273+
end
274+
end
275+
end
276+
end

0 commit comments

Comments
 (0)