-
Notifications
You must be signed in to change notification settings - Fork 54
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add function to calculate seasonal uncertainty
- Loading branch information
Showing
2 changed files
with
134 additions
and
0 deletions.
There are no files selected for viewing
58 changes: 58 additions & 0 deletions
58
priv/repo/migrations/20241209014803_season_uncertainty_function.exs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
defmodule Teiserver.Repo.Migrations.SeasonUncertaintyFunction do | ||
use Ecto.Migration | ||
|
||
def up do | ||
query = """ | ||
create or replace function calculate_season_uncertainty(current_uncertainty float, last_updated timestamp, min_uncertainty float default 5) | ||
returns float | ||
language plpgsql | ||
as | ||
$$ | ||
declare | ||
-- variable declaration | ||
default_uncertainty float; | ||
days_not_played float; | ||
one_month float; | ||
one_year float; | ||
interpolated_uncertainty float; | ||
min_days float; | ||
max_days float; | ||
max_uncertainty float; | ||
begin | ||
-- Your new uncertainty will be: greatest(target_uncertainty, current_uncertainty) | ||
-- Where target_uncertainty will be default if you have not played for over a year | ||
-- 5 (min_uncertainty) if you have played within one month | ||
-- And use linear interpolation for values in between | ||
one_year = 365.0; | ||
default_uncertainty = 25.0/3; | ||
one_month = one_year / 12; | ||
days_not_played = abs(DATE_PART('day', (now()- last_updated ))); | ||
min_days = one_month; | ||
max_days = one_year; | ||
max_uncertainty = default_uncertainty; | ||
if(days_not_played >= max_days) then | ||
return default_uncertainty; | ||
elsif days_not_played <= min_days then | ||
return GREATEST(current_uncertainty, min_uncertainty); | ||
else | ||
-- Use linear interpolation | ||
interpolated_uncertainty = min_uncertainty +(days_not_played - min_days) * (max_uncertainty - min_uncertainty) /(max_days - min_days); | ||
return GREATEST(current_uncertainty, interpolated_uncertainty); | ||
end if; | ||
end; | ||
$$; | ||
""" | ||
|
||
execute(query) | ||
end | ||
|
||
def down do | ||
query = "drop function calculate_season_uncertainty" | ||
execute(query) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
defmodule Teiserver.Sql.SeasonUncertaintyResetTest do | ||
@moduledoc false | ||
use Teiserver.DataCase | ||
|
||
test "it can calculate seasonal uncertainty reset target" do | ||
# Start by removing all anon properties | ||
{:ok, now} = DateTime.now("Etc/UTC") | ||
|
||
result = calculate_seasonal_uncertainty(now) | ||
assert result == 5 | ||
|
||
# Now 30 days ago | ||
month = 365 / 12 | ||
last_updated = DateTime.add(now, -month |> trunc(), :day) | ||
result = calculate_seasonal_uncertainty(last_updated) | ||
|
||
# assert_in_delta checks that the result is close to expected. Helps deal with rounding issues | ||
assert_in_delta(result, 5, 0.1) | ||
|
||
# Now 2 months ago | ||
last_updated = DateTime.add(now, (-2 * month) |> trunc(), :day) | ||
result = calculate_seasonal_uncertainty(last_updated) | ||
assert_in_delta(result, 5.294728102947281, 0.1) | ||
|
||
# Now 3 months ago | ||
last_updated = DateTime.add(now, (-3 * month) |> trunc(), :day) | ||
result = calculate_seasonal_uncertainty(last_updated) | ||
assert_in_delta(result, 5.6035699460357, 0.1) | ||
|
||
# Now 6 months ago | ||
last_updated = DateTime.add(now, (-6 * month) |> trunc(), :day) | ||
result = calculate_seasonal_uncertainty(last_updated) | ||
assert_in_delta(result, 6.510170195101702, 0.1) | ||
|
||
# Now 9 months ago | ||
last_updated = DateTime.add(now, (-9 * month) |> trunc(), :day) | ||
result = calculate_seasonal_uncertainty(last_updated) | ||
assert_in_delta(result, 7.416770444167705, 0.1) | ||
|
||
# Now 11 months ago | ||
last_updated = DateTime.add(now, (-11 * month) |> trunc(), :day) | ||
result = calculate_seasonal_uncertainty(last_updated) | ||
assert_in_delta(result, 8.024491490244916, 0.1) | ||
|
||
# Now 12 months ago | ||
last_updated = DateTime.add(now, (-12 * month) |> trunc(), :day) | ||
result = calculate_seasonal_uncertainty(last_updated) | ||
assert_in_delta(result, 8.333333333333334, 0.1) | ||
|
||
# Now 13 months ago | ||
last_updated = DateTime.add(now, (-13 * month) |> trunc(), :day) | ||
|
||
result = calculate_seasonal_uncertainty(last_updated) | ||
assert_in_delta(result, 8.333333333333334, 0.1) | ||
end | ||
|
||
# This will calculate the new uncertainty during a season reset | ||
# The longer your last_updated is from today, the more your uncertainty will be reset | ||
# The number should range from min_uncertainty and default_uncertainty (8.333) | ||
# Your new_uncertainty can grow from current_uncertainty but never reduce | ||
# Full details in comments of the sql function calculate_season_uncertainty | ||
defp calculate_seasonal_uncertainty(last_updated) do | ||
current_uncertainty = 1 | ||
min_uncertainty = 5 | ||
query = "SELECT calculate_season_uncertainty($1, $2, $3);" | ||
|
||
results = | ||
Ecto.Adapters.SQL.query!(Repo, query, [current_uncertainty, last_updated, min_uncertainty]) | ||
|
||
[new_uncertainty] = | ||
results.rows | ||
|> Enum.at(0) | ||
|
||
new_uncertainty | ||
end | ||
end |