Skip to content

Commit f99e5d1

Browse files
committed
Perf: refactor DistributionsByCountry#report using raw SQL query
1 parent 9f0f81b commit f99e5d1

File tree

6 files changed

+155
-101
lines changed

6 files changed

+155
-101
lines changed

app/controllers/distributions_by_county_controller.rb

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,12 @@ class DistributionsByCountyController < ApplicationController
44

55
def report
66
setup_date_range_picker
7-
distributions = current_organization.distributions.includes(:partner).during(helpers.selected_range)
8-
@breakdown = DistributionByCountyReportService.new.get_breakdown(distributions)
7+
start_date = helpers.selected_range.first.iso8601
8+
end_date = helpers.selected_range.last.iso8601
9+
@breakdown = DistributionSummaryByCountyQuery.new(
10+
organization_id: current_organization.id,
11+
start_date: start_date,
12+
end_date: end_date
13+
).call
914
end
1015
end
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
class DistributionSummaryByCountyQuery
2+
DISTRIBUTION_BY_COUNTY_SQL = <<~SQL.squish.freeze
3+
/* Calculate total item quantity and value per distribution */
4+
WITH distribution_totals AS
5+
(
6+
SELECT DISTINCT d.id,
7+
d.partner_id,
8+
COALESCE(SUM(li.quantity) OVER (PARTITION BY d.id), 0) AS quantity,
9+
COALESCE(SUM(COALESCE(i.value_in_cents, 0) * li.quantity) OVER (PARTITION BY d.id), 0) AS amount
10+
FROM distributions d
11+
JOIN line_items li ON li.itemizable_id = d.id AND li.itemizable_type = 'Distribution'
12+
JOIN items i ON i.id = li.item_id
13+
WHERE d.issued_at BETWEEN :start_date AND :end_date
14+
AND d.organization_id = :organization_id
15+
GROUP BY d.id, li.id, i.id
16+
),
17+
/* Match distribution totals with client share and counties.
18+
If distribution has no associated county, set county name to "Unspecified"
19+
and set region to ZZZ so it will be last when sorted */
20+
totals_by_county AS
21+
(
22+
SELECT dt.id,
23+
dt.quantity,
24+
dt.amount,
25+
COALESCE(psa.client_share::float / 100, 1) AS percentage,
26+
COALESCE(c.name, 'Unspecified') county_name,
27+
COALESCE(c.region, 'ZZZ') county_region
28+
FROM distribution_totals dt
29+
JOIN partner_profiles pp ON pp.id = dt.partner_id
30+
LEFT JOIN partner_served_areas psa ON psa.partner_profile_id = pp.partner_id
31+
LEFT JOIN counties c ON c.id = psa.county_id
32+
UNION
33+
/* Previous behavior was to add a row for unspecified counties
34+
even if all distributions have an associated county */
35+
SELECT 0 AS id,
36+
0 AS quantity,
37+
0 AS amount,
38+
1 AS percentage,
39+
'Unspecified' AS county_name,
40+
'ZZZ' AS county_region
41+
)
42+
/* Distribution value and quantities per county share may not be whole numbers,
43+
so we cast to an integer for rounding purposes */
44+
SELECT tbc.county_name AS name,
45+
SUM((tbc.quantity * percentage)::int) AS quantity,
46+
SUM((tbc.amount * percentage)::int) AS amount
47+
FROM totals_by_county tbc
48+
GROUP BY county_name, county_region
49+
ORDER BY county_region ASC;
50+
SQL
51+
52+
def initialize(organization_id:, start_date: nil, end_date: nil)
53+
@organization_id = organization_id
54+
@start_date = start_date || "1000-01-01"
55+
@end_date = end_date || "3000-01-01"
56+
end
57+
58+
def call
59+
execute(to_sql(DISTRIBUTION_BY_COUNTY_SQL)).to_a
60+
end
61+
62+
private
63+
64+
def execute(sql)
65+
ActiveRecord::Base.connection.execute(sql)
66+
end
67+
68+
def to_sql(query)
69+
ActiveRecord::Base.sanitize_sql_array(
70+
[
71+
query,
72+
organization_id: @organization_id,
73+
start_date: @start_date,
74+
end_date: @end_date
75+
]
76+
)
77+
end
78+
end

app/services/distribution_by_county_report_service.rb

Lines changed: 0 additions & 33 deletions
This file was deleted.

app/views/distributions_by_county/report.html.erb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,9 @@
5050
<tbody>
5151
<% @breakdown.each do |bd| %>
5252
<tr>
53-
<td><%= bd.name %></td>
54-
<td class="numeric"><%= number_with_delimiter(bd.num_items) %></td>
55-
<td class="numeric"><%= dollar_presentation(bd.amount) %></td>
53+
<td><%= bd["name"] %></td>
54+
<td class="numeric"><%= number_with_delimiter(bd["quantity"]) %></td>
55+
<td class="numeric"><%= dollar_presentation(bd["amount"]) %></td>
5656
</tr>
5757
<% end %>
5858

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
RSpec.describe DistributionSummaryByCountyQuery do
2+
let(:year) { Time.current.year }
3+
let(:issued_at_last_year) { Time.current.change(year: year - 1).to_datetime }
4+
let(:distributions) { [] }
5+
let(:organization_id) { organization.id }
6+
let(:start_date) { nil }
7+
let(:end_date) { nil }
8+
let(:params) { {organization_id:, start_date:, end_date:} }
9+
10+
include_examples "distribution_by_county"
11+
12+
before do
13+
create(:storage_location, organization: organization)
14+
end
15+
16+
describe "get_breakdown" do
17+
it "will have 100% unspecified shows if no served_areas" do
18+
create(:distribution, :with_items, item: item_1, organization: user.organization)
19+
breakdown = DistributionSummaryByCountyQuery.new(**params).call
20+
expect(breakdown.size).to eq(1)
21+
expect(breakdown[0]["quantity"]).to eq(100)
22+
expect(breakdown[0]["amount"]).to be_within(0.01).of(105000.0)
23+
end
24+
25+
it "divides the item numbers and values according to the partner profile" do
26+
create(:distribution, :with_items, item: item_1, organization: user.organization, partner: partner_1)
27+
breakdown = DistributionSummaryByCountyQuery.new(**params).call
28+
expect(breakdown.size).to eq(5)
29+
expect(breakdown[4]["quantity"]).to eq(0)
30+
expect(breakdown[4]["amount"]).to be_within(0.01).of(0)
31+
3.times do |i|
32+
expect(breakdown[i]["quantity"]).to eq(25)
33+
expect(breakdown[i]["amount"]).to be_within(0.01).of(26250.0)
34+
end
35+
end
36+
37+
it "handles multiple partners with overlapping service areas properly" do
38+
create(:distribution, :with_items, item: item_1, organization: user.organization, partner: partner_1, issued_at: issued_at_present)
39+
create(:distribution, :with_items, item: item_1, organization: user.organization, partner: partner_2, issued_at: issued_at_present)
40+
breakdown = DistributionSummaryByCountyQuery.new(**params).call
41+
num_with_45 = 0
42+
num_with_20 = 0
43+
num_with_0 = 0
44+
# The result will have at least 1 45 and at least 1 20, and 1 0. Anything else will be either 45 or 25 or 20
45+
breakdown.each do |sa|
46+
if sa["quantity"] == 45
47+
expect(sa["amount"]).to be_within(0.01).of(47250.0)
48+
num_with_45 += 1
49+
end
50+
51+
if sa["quantity"] == 25
52+
expect(sa["amount"]).to be_within(0.01).of(26250.0)
53+
end
54+
if sa["quantity"] == 20
55+
expect(sa["amount"]).to be_within(0.01).of(21000.0)
56+
num_with_20 += 1
57+
end
58+
if sa["quantity"] == 0
59+
expect(sa["amount"]).to be_within(0.01).of(0)
60+
end
61+
end
62+
expect(num_with_45).to be > 0
63+
expect(num_with_20).to be > 0
64+
expect(num_with_0).to eq 0
65+
end
66+
end
67+
end

spec/services/distributions_by_county_report_service_spec.rb

Lines changed: 0 additions & 63 deletions
This file was deleted.

0 commit comments

Comments
 (0)