Skip to content

Commit 8c33151

Browse files
committed
implement Algora.Analytics.get_company_analytics/0-2
1 parent d66d521 commit 8c33151

File tree

3 files changed

+222
-36
lines changed

3 files changed

+222
-36
lines changed

lib/algora/analytics/analytics.ex

Lines changed: 123 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,126 @@
11
defmodule Algora.Analytics do
22
@moduledoc false
3-
def get_company_analytics(period \\ "30d") do
3+
import Ecto.Query
4+
5+
alias Algora.Accounts.User
6+
alias Algora.Contracts.Contract
7+
alias Algora.Repo
8+
9+
require Algora.SQL
10+
11+
def get_company_analytics(period \\ "30d", from \\ DateTime.utc_now()) do
412
days = period |> String.replace("d", "") |> String.to_integer()
5-
_since = DateTime.add(DateTime.utc_now(), -days * 24 * 3600)
13+
period_start = DateTime.add(from, -days * 24 * 3600)
14+
previous_period_start = DateTime.add(period_start, -days * 24 * 3600)
615

7-
# Mock data for demonstration
8-
%{
9-
total_companies: 150,
10-
companies_change: 12,
11-
companies_trend: :up,
12-
active_companies: 85,
13-
active_change: 5,
14-
active_trend: :up,
15-
avg_time_to_fill: 4.2,
16-
time_to_fill_change: -0.8,
17-
time_to_fill_trend: :down,
18-
contract_success_rate: 92,
19-
success_rate_change: 2,
20-
success_rate_trend: :up,
21-
companies: mock_companies()
22-
}
16+
orgs_query =
17+
from u in User,
18+
where: u.type == :organization,
19+
select: %{
20+
count_all: count(u.id),
21+
count_current: u.id |> count() |> filter(u.inserted_at <= ^from and u.inserted_at >= ^period_start),
22+
count_previous:
23+
u.id |> count() |> filter(u.inserted_at <= ^period_start and u.inserted_at >= ^previous_period_start),
24+
active_all: u.id |> count() |> filter(u.seeded and u.activated),
25+
active_current:
26+
u.id
27+
|> count()
28+
|> filter(u.seeded and u.activated and u.inserted_at <= ^from and u.inserted_at >= ^period_start),
29+
active_previous:
30+
u.id
31+
|> count()
32+
|> filter(
33+
u.seeded and u.activated and u.inserted_at <= ^period_start and u.inserted_at >= ^previous_period_start
34+
)
35+
}
36+
37+
contracts_query =
38+
from u in Contract,
39+
where: u.inserted_at >= ^previous_period_start,
40+
select: %{
41+
count_current: u.id |> count() |> filter(u.inserted_at < ^from and u.inserted_at >= ^period_start),
42+
count_previous:
43+
u.id |> count() |> filter(u.inserted_at < ^period_start and u.inserted_at >= ^previous_period_start),
44+
success_current:
45+
u.id
46+
|> count()
47+
|> filter(
48+
u.inserted_at < ^from and u.inserted_at >= ^period_start and (u.status == :active or u.status == :paid)
49+
),
50+
success_previous:
51+
u.id
52+
|> count()
53+
|> filter(
54+
u.inserted_at < ^period_start and u.inserted_at >= ^previous_period_start and
55+
(u.status == :active or u.status == :paid)
56+
)
57+
}
58+
59+
companies_query =
60+
from u in User,
61+
where: u.inserted_at >= ^period_start and u.type == :organization,
62+
right_join: c in Contract,
63+
on: c.client_id == u.id,
64+
group_by: u.id,
65+
select: %{
66+
id: u.id,
67+
name: u.name,
68+
handle: u.handle,
69+
joined_at: u.inserted_at,
70+
total_contracts: c.id |> count() |> filter(c.inserted_at >= ^period_start),
71+
successful_contracts:
72+
c.id |> count() |> filter(c.status == :active or (c.status == :paid and c.inserted_at >= ^period_start)),
73+
last_active_at: u.updated_at,
74+
avatar_url: u.avatar_url
75+
}
76+
77+
Ecto.Multi.new()
78+
|> Ecto.Multi.one(:orgs, orgs_query)
79+
|> Ecto.Multi.one(:contracts, contracts_query)
80+
|> Ecto.Multi.all(:companies, companies_query)
81+
|> Repo.transaction()
82+
|> case do
83+
{:ok, resp} ->
84+
%{
85+
orgs: orgs,
86+
contracts: contracts,
87+
companies: companies
88+
} = resp
89+
90+
current_success_rate = calculate_success_rate(contracts.success_current, contracts.count_current)
91+
previous_success_rate = calculate_success_rate(contracts.success_previous, contracts.count_previous)
92+
93+
{:ok,
94+
%{
95+
total_companies: orgs.count_all,
96+
companies_change: orgs.count_current,
97+
companies_trend: calculate_trend(orgs.count_current, orgs.count_previous),
98+
active_companies: orgs.active_all,
99+
active_change: orgs.active_current,
100+
active_trend: calculate_trend(orgs.active_current, orgs.active_previous),
101+
# TODO track time when contract is filled
102+
avg_time_to_fill: 4.2,
103+
time_to_fill_change: -0.8,
104+
time_to_fill_trend: :down,
105+
contract_success_rate: current_success_rate,
106+
previous_contract_success_rate: previous_success_rate,
107+
success_rate_change: current_success_rate - previous_success_rate,
108+
success_rate_trend: calculate_trend(current_success_rate, previous_success_rate),
109+
companies:
110+
Enum.map(companies, fn company ->
111+
Map.merge(company, %{
112+
success_rate: calculate_success_rate(company.successful_contracts, company.total_contracts),
113+
status: if(company.successful_contracts > 0, do: :active, else: :inactive)
114+
})
115+
end)
116+
}}
117+
118+
{:error, reason} ->
119+
{:error, reason}
120+
end
23121
end
24122

25-
def get_funnel_data(_period \\ "30d") do
123+
def get_funnel_data(_period \\ "30d", _from \\ DateTime.utc_now()) do
26124
# Mock funnel data
27125
%{
28126
registered: 100,
@@ -34,19 +132,10 @@ defmodule Algora.Analytics do
34132
}
35133
end
36134

37-
defp mock_companies do
38-
[
39-
%{
40-
name: "TechCorp",
41-
handle: "techcorp",
42-
avatar_url: "https://example.com/avatar1.jpg",
43-
joined_at: ~U[2024-01-15 00:00:00Z],
44-
status: :active,
45-
total_contracts: 12,
46-
success_rate: 95,
47-
last_active_at: ~U[2024-03-18 14:30:00Z]
48-
}
49-
# Add more mock companies...
50-
]
51-
end
135+
defp calculate_success_rate(successful, total) when successful == 0 or total == 0, do: 0.0
136+
defp calculate_success_rate(successful, total), do: Float.ceil(successful / total * 100, 0)
137+
138+
defp calculate_trend(a, b) when a > b, do: :up
139+
defp calculate_trend(a, b) when a < b, do: :down
140+
defp calculate_trend(a, b) when a == b, do: :same
52141
end

lib/algora_web/live/admin/company_analytics_live.ex

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ defmodule AlgoraWeb.Admin.CompanyAnalyticsLive do
55
alias Algora.Analytics
66

77
def mount(_params, _session, socket) do
8-
analytics = Analytics.get_company_analytics()
8+
{:ok, analytics} = Analytics.get_company_analytics()
99
funnel_data = Analytics.get_funnel_data()
1010

1111
{:ok,
@@ -134,7 +134,7 @@ defmodule AlgoraWeb.Admin.CompanyAnalyticsLive do
134134
def status_color(_), do: "secondary"
135135

136136
def handle_event("select_period", %{"period" => period}, socket) do
137-
analytics = Analytics.get_company_analytics(period)
137+
{:ok, analytics} = Analytics.get_company_analytics(period)
138138
funnel_data = Analytics.get_funnel_data(period)
139139

140140
{:noreply,

test/algora/analytics_test.exs

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
defmodule Algora.AnalyticsTest do
2+
use Algora.DataCase
3+
4+
import Algora.Factory
5+
6+
alias Algora.Analytics
7+
8+
setup do
9+
now = DateTime.utc_now()
10+
last_month = DateTime.add(now, -40 * 24 * 3600)
11+
12+
Enum.reduce(1..100, now, fn _n, date ->
13+
insert(:organization, %{inserted_at: date, seeded: false, activated: false})
14+
DateTime.add(date, -1 * 24 * 3600)
15+
end)
16+
17+
Enum.reduce(1..100, now, fn _n, date ->
18+
org = insert(:organization, %{inserted_at: date, seeded: true, activated: true})
19+
insert_list(2, :contract, %{client_id: org.id, status: :active})
20+
insert_list(1, :contract, %{client_id: org.id, status: :paid})
21+
insert_list(3, :contract, %{client_id: org.id, status: :cancelled})
22+
insert_list(1, :contract, %{inserted_at: last_month, client_id: org.id, status: :paid})
23+
insert_list(3, :contract, %{inserted_at: last_month, client_id: org.id, status: :cancelled})
24+
DateTime.add(date, -1 * 24 * 3600)
25+
end)
26+
27+
:ok
28+
end
29+
30+
describe "analytics" do
31+
test "get_company_analytics 30d" do
32+
{:ok, resp} = Analytics.get_company_analytics("30d")
33+
assert resp.total_companies == 200
34+
assert resp.active_companies == 100
35+
assert resp.companies_change == 60
36+
assert resp.active_change == 30
37+
assert resp.companies_trend == :same
38+
assert resp.active_trend == :same
39+
assert resp.contract_success_rate == 50.0
40+
assert resp.success_rate_change == 25.0
41+
assert resp.success_rate_trend == :up
42+
43+
assert length(resp.companies) > 0
44+
45+
assert %{total_contracts: 6, successful_contracts: 3, success_rate: 50.0, last_active_at: last_active_at} =
46+
List.first(resp.companies)
47+
48+
assert DateTime.before?(last_active_at, DateTime.utc_now())
49+
50+
now = DateTime.utc_now()
51+
last_month = DateTime.add(now, -40 * 24 * 3600)
52+
insert(:organization, %{inserted_at: last_month, seeded: true, activated: true})
53+
54+
{:ok, resp} = Analytics.get_company_analytics("30d")
55+
assert resp.total_companies == 201
56+
assert resp.active_companies == 101
57+
assert resp.companies_change == 60
58+
assert resp.active_change == 30
59+
assert resp.companies_trend == :down
60+
assert resp.active_trend == :down
61+
62+
insert(:organization, %{seeded: true, activated: true})
63+
insert(:organization, %{seeded: true, activated: true})
64+
insert(:organization, %{seeded: false, activated: false})
65+
66+
{:ok, resp} = Analytics.get_company_analytics("30d")
67+
68+
assert resp.total_companies == 204
69+
assert resp.active_companies == 103
70+
assert resp.companies_change == 63
71+
assert resp.active_change == 32
72+
assert resp.companies_trend == :up
73+
assert resp.active_trend == :up
74+
end
75+
76+
test "get_company_analytics 356d" do
77+
{:ok, resp} = Analytics.get_company_analytics("365d")
78+
assert resp.total_companies == 200
79+
assert resp.active_companies == 100
80+
assert resp.companies_change == 200
81+
assert resp.active_change == 100
82+
assert resp.companies_trend == :up
83+
assert resp.active_trend == :up
84+
end
85+
86+
test "get_company_analytics 7d" do
87+
insert(:organization, %{seeded: true, activated: true})
88+
{:ok, resp} = Analytics.get_company_analytics("7d")
89+
assert resp.total_companies == 201
90+
assert resp.active_companies == 101
91+
assert resp.companies_change == 15
92+
assert resp.active_change == 8
93+
assert resp.companies_trend == :up
94+
assert resp.active_trend == :up
95+
end
96+
end
97+
end

0 commit comments

Comments
 (0)