Files
2025-11-25 01:52:06 +00:00

219 lines
7.1 KiB
Elixir

defmodule WandererApp.Character.Activity do
@moduledoc """
Functions for processing and managing character activity data.
"""
require Logger
@doc """
Finds the character with the most activity from a map of activities grouped by character_id.
## Parameters
- `activities_by_character`: Map of activities grouped by character_id
## Returns
- Character ID of the character with the most activity, or nil if no activities
"""
def find_most_active_character(activities_by_character) do
if Enum.empty?(activities_by_character) do
nil
else
{char_id, _} =
activities_by_character
|> Enum.map(fn {char_id, activities} ->
total_activity =
activities
|> Enum.map(fn a ->
Map.get(a, :passages, 0) +
Map.get(a, :connections, 0) +
Map.get(a, :signatures, 0)
end)
|> Enum.sum()
{char_id, total_activity}
end)
|> Enum.max_by(fn {_, count} -> count end, fn -> {nil, 0} end)
char_id
end
end
@doc """
Processes character activity data for display.
## Parameters
- `map_id`: ID of the map
- `current_user`: Current user struct (used only to get user settings)
- `days`: Optional number of days to filter activity (nil for all time)
## Returns
- List of processed activity data
"""
def process_character_activity(map_id, current_user, days \\ nil) do
with {:ok, map_user_settings} <- get_map_user_settings(map_id, current_user.id),
{:ok, raw_activity} <- WandererApp.Map.get_character_activity(map_id, days),
{:ok, user_characters} <-
WandererApp.Api.Character.active_by_user(%{user_id: current_user.id}) do
process_activity_data(raw_activity, map_user_settings, user_characters)
else
_ ->
[]
end
end
def get_map_user_settings(map_id, user_id) do
case WandererApp.MapUserSettingsRepo.get(map_id, user_id) do
{:ok, settings} when not is_nil(settings) ->
{:ok, settings}
_ ->
{:ok, %{main_character_eve_id: nil}}
end
end
@doc """
Gets character settings for a map.
## Parameters
- `map_id`: ID of the map
## Returns
- `{:ok, settings}` with list of settings or empty list
"""
def get_map_character_settings(map_id) do
case WandererApp.MapCharacterSettingsRepo.get_all_by_map(map_id) do
{:ok, settings} -> {:ok, settings}
_ -> {:ok, []}
end
end
# Handle empty activity list
defp process_activity_data([], _map_user_settings, _all_characters), do: []
# Process activity data
defp process_activity_data(all_activity, map_user_settings, all_characters) do
# Group activities by user ID
activities_by_user = Enum.group_by(all_activity, &Map.get(&1, :user_id, "unknown"))
# Process each user's activities
activities_by_user
|> Enum.flat_map(fn {user_id, user_activities} ->
process_user_activity(user_id, user_activities, map_user_settings, all_characters)
end)
|> sort_by_timestamp()
end
defp process_user_activity(
user_id,
user_activities,
%{user_id: user_id, main_character_eve_id: main_id} = _map_user_settings,
all_characters
)
when not is_nil(main_id) do
# Group activities by character
activities_by_character = group_activities_by_character(user_activities)
main_id_str = to_string(main_id)
display_character =
case Enum.find(all_characters, &(to_string(&1.eve_id) == main_id_str)) do
# Fall back to most active
nil -> find_most_active_character_details(activities_by_character)
main_char -> main_char
end
build_activity_entry_if_valid(display_character, activities_by_character, user_id)
end
defp process_user_activity(user_id, user_activities, _map_user_settings, _all_characters) do
# Group activities by character
activities_by_character = group_activities_by_character(user_activities)
# Find the most active character
display_character = find_most_active_character_details(activities_by_character)
build_activity_entry_if_valid(display_character, activities_by_character, user_id)
end
# Helper function to build activity entry only if display character is valid
defp build_activity_entry_if_valid(nil, _activities_by_character, user_id) do
Logger.warning("No suitable character found for user #{user_id}")
[]
end
defp build_activity_entry_if_valid(display_character, activities_by_character, _user_id) do
build_activity_entry(display_character, activities_by_character)
end
# Group activities by character ID
defp group_activities_by_character(activities) do
Enum.group_by(activities, fn activity ->
cond do
character = Map.get(activity, :character) -> Map.get(character, :id)
id = Map.get(activity, :character_id) -> id
id = Map.get(activity, :character_eve_id) -> id
true -> "unknown_#{System.unique_integer([:positive])}"
end
end)
end
# Find the details of the most active character
defp find_most_active_character_details(activities_by_character) do
with most_active_id when not is_nil(most_active_id) <-
find_most_active_character(activities_by_character),
most_active_activities <- Map.get(activities_by_character, most_active_id, []),
[first_activity | _] <- most_active_activities,
character when not is_nil(character) <- Map.get(first_activity, :character) do
character
else
_ ->
Logger.warning("Could not find most active character")
nil
end
end
# Build activity entry with the provided character and sum all activities
defp build_activity_entry(character, activities_by_character) do
# Sum up all activities
all_passages = sum_all_activities(activities_by_character, :passages)
all_connections = sum_all_activities(activities_by_character, :connections)
all_signatures = sum_all_activities(activities_by_character, :signatures)
# Only create entry if there's at least some activity
if all_passages + all_connections + all_signatures > 0 do
[
%{
character: character,
passages: all_passages,
connections: all_connections,
signatures: all_signatures,
timestamp: get_latest_timestamp(activities_by_character)
}
]
else
Logger.warning("Character has no activity, not creating entry")
[]
end
end
# Sum up activities of a specific type across all characters
defp sum_all_activities(activities_by_character, key) do
activities_by_character
|> Enum.flat_map(fn {_, char_activities} -> char_activities end)
|> Enum.map(&Map.get(&1, key, 0))
|> Enum.sum()
end
# Get the most recent timestamp across all characters
defp get_latest_timestamp(activities_by_character) do
activities_by_character
|> Enum.flat_map(fn {_, char_activities} -> char_activities end)
|> Enum.map(&Map.get(&1, :timestamp, DateTime.utc_now()))
|> Enum.sort_by(& &1, {:desc, DateTime})
|> List.first() || DateTime.utc_now()
end
defp sort_by_timestamp(activities) do
Enum.sort_by(activities, & &1.timestamp, {:desc, DateTime})
end
end