clean up warnings

This commit is contained in:
Graham McIntire 2025-07-16 08:55:46 -05:00
parent 46340e008a
commit 566198afa7
No known key found for this signature in database
41 changed files with 138 additions and 580 deletions

View file

@ -2,5 +2,6 @@
import_deps: [:ecto, :ecto_sql, :phoenix, :stream_data],
subdirectories: ["priv/*/migrations"],
plugins: [Phoenix.LiveView.HTMLFormatter, Styler],
inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}", "priv/*/seeds.exs"]
inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}", "priv/*/seeds.exs"],
line_length: 120
]

View file

View file

@ -1,221 +0,0 @@
# INSERT Performance Optimizations for Slow Queries
## Problem Analysis
The server was experiencing slow INSERT queries with durations of 6+ seconds:
```
LOG: duration: 6069.632 ms execute ecto_insert_all_packets: INSERT INTO "packets"
```
## Root Causes Identified
1. **Heavy Index Maintenance**: Multiple indexes were being updated on each INSERT
2. **Inefficient Batch Processing**: Excessive overhead in packet preparation
3. **Suboptimal Batch Sizes**: Fixed batch sizes not adapting to system load
4. **Redundant Field Processing**: Expensive operations during INSERT preparation
## Optimizations Implemented
### 1. Fast Packet Preparation ✅
**File**: `/Users/graham/dev/aprs.me/lib/aprsme/packet_consumer.ex`
**Created optimized packet preparation pipeline**:
```elixir
# Old approach - expensive processing
defp prepare_packet_for_insert(packet_data) do
# ... extensive processing including data extraction,
# normalization, sanitization, etc.
end
# New approach - minimal essential processing
defp prepare_packet_for_insert_fast(packet_data, current_time) do
attrs = if is_struct(packet_data), do: Map.from_struct(packet_data), else: packet_data
attrs
|> Map.put(:received_at, current_time)
|> Map.put(:inserted_at, current_time)
|> Map.put(:updated_at, current_time)
|> extract_essential_fields()
|> create_location_geometry_fast()
|> validate_essential_fields()
end
```
**Benefits**:
- ~70% reduction in packet preparation time
- Single timestamp calculation per batch
- Essential fields only extraction
- Fast validation with pattern matching
### 2. INSERT Performance Optimizer ✅
**File**: `/Users/graham/dev/aprs.me/lib/aprsme/performance/insert_optimizer.ex` (NEW)
**Adaptive batch sizing based on INSERT performance**:
```elixir
# Dynamic batch size optimization
defp calculate_optimal_batch_size(avg_throughput, avg_duration, current_batch_size) do
cond do
# If throughput is low and duration is high, reduce batch size
avg_throughput < 50 and avg_duration > 5000 ->
max(@min_batch_size, round(current_batch_size * 0.8))
# If throughput is good and duration is acceptable, increase batch size
avg_throughput > 200 and avg_duration < 2000 ->
min(@max_batch_size, round(current_batch_size * 1.2))
true -> current_batch_size
end
end
```
**Features**:
- Monitors INSERT throughput (packets/second)
- Adjusts batch size based on performance
- Optimizes INSERT options (returning: false, on_conflict: :nothing)
- Telemetry integration for monitoring
### 3. Optimized INSERT Options ✅
**Before**:
```elixir
Repo.insert_all(Aprsme.Packet, valid_packets, returning: [:id])
```
**After**:
```elixir
insert_options = Aprsme.Performance.InsertOptimizer.get_insert_options()
Repo.insert_all(Aprsme.Packet, valid_packets, insert_options)
# Options include:
%{
returning: false, # Don't return IDs unless needed
on_conflict: :nothing, # Skip conflicts instead of raising
timeout: 15_000 # Appropriate timeout
}
```
**Benefits**:
- Eliminates unnecessary ID return processing
- Handles conflicts gracefully
- Prevents timeout issues
### 4. Index Optimization for INSERT Performance ✅
**File**: `/Users/graham/dev/aprs.me/priv/repo/migrations/20250714210000_optimize_insert_performance.exs`
**Removed heavy indexes that slow INSERTs**:
```sql
-- Dropped expensive covering index
DROP INDEX packets_sender_received_covering_idx;
-- Replaced with lighter, more selective indexes
CREATE INDEX packets_weather_selective_idx ON packets(received_at DESC)
WHERE data_type IN ('weather', 'Weather', 'WX', 'wx');
CREATE INDEX packets_device_recent_idx ON packets(device_identifier, received_at DESC)
WHERE device_identifier IS NOT NULL;
CREATE INDEX packets_location_selective_idx ON packets USING GIST (location)
WHERE has_position = true;
```
**Benefits**:
- Reduced index maintenance overhead during INSERTs
- Maintained query performance for common patterns
- Selective indexes only on relevant data
### 5. Batch Processing Improvements ✅
**Enhanced batch processing**:
```elixir
# Old - fixed batch size
|> Enum.chunk_every(50)
# New - adaptive batch sizing
batch_size = Aprsme.Performance.InsertOptimizer.get_optimal_batch_size()
|> Enum.chunk_every(batch_size)
```
**Optimized reduce operation**:
```elixir
# Single-pass validation and preparation
defp prepare_packets_batch(packets, current_time) do
packets
|> Enum.reduce({[], 0}, fn packet_data, {valid_acc, invalid_count} ->
case prepare_packet_for_insert_fast(packet_data, current_time) do
nil -> {valid_acc, invalid_count + 1}
attrs -> {[attrs | valid_acc], invalid_count}
end
end)
end
```
### 6. Performance Monitoring & Telemetry ✅
**Added comprehensive INSERT performance metrics**:
```elixir
# Telemetry events
:telemetry.execute([:aprsme, :insert_optimizer, :batch_size], %{value: new_batch_size})
:telemetry.execute([:aprsme, :insert_optimizer, :throughput], %{value: avg_throughput})
:telemetry.execute([:aprsme, :insert_optimizer, :duration], %{value: avg_duration})
```
**LiveDashboard integration**:
- INSERT throughput monitoring (packets/second)
- Batch size optimization tracking
- Duration trend analysis
- Performance optimization events
## Performance Impact
### Before Optimization:
- INSERT duration: 6+ seconds
- Fixed batch size: 50 packets
- Heavy index maintenance
- Extensive packet processing overhead
### After Optimization:
- **Expected INSERT duration**: 1-2 seconds (70% improvement)
- **Adaptive batch size**: 100-500 packets based on load
- **Reduced index overhead**: Selective indexes only
- **Minimal processing**: Essential fields only during INSERT
## Implementation Status
**Fast packet preparation** - Implemented with pattern matching optimization
**INSERT performance optimizer** - Adaptive batch sizing with telemetry
**Optimized INSERT options** - Reduced returning overhead
**Index optimization** - Selective indexes for better INSERT performance
**Batch processing improvements** - Single-pass validation and preparation
**Performance monitoring** - Comprehensive telemetry integration
**All tests passing** - 351 tests verified
## Monitoring
Use the following telemetry metrics to monitor INSERT performance:
1. **`aprsme.insert_optimizer.throughput`** - Packets per second throughput
2. **`aprsme.insert_optimizer.duration`** - INSERT duration per batch
3. **`aprsme.insert_optimizer.batch_size`** - Current optimized batch size
4. **`aprsme.insert_optimizer.optimizations`** - Number of adjustments made
## Next Steps
1. **Monitor production performance** - Watch INSERT durations and throughput
2. **Tune thresholds** - Adjust optimization thresholds based on real data
3. **Consider connection pooling** - If needed, optimize database connections
4. **Index maintenance** - Monitor if additional index optimizations are needed
## Files Modified
1. **`lib/aprsme/packet_consumer.ex`** - Fast packet preparation and batch processing
2. **`lib/aprsme/performance/insert_optimizer.ex`** - NEW: Adaptive INSERT optimization
3. **`priv/repo/migrations/20250714210000_optimize_insert_performance.exs`** - Index optimization
4. **`lib/aprsme/application.ex`** - Added InsertOptimizer to supervision tree
5. **`lib/aprsme_web/telemetry.ex`** - INSERT performance telemetry
## Expected Results
These optimizations should reduce INSERT query times from 6+ seconds to 1-2 seconds, representing a **70% performance improvement** in packet insertion workloads.

View file

@ -221,7 +221,11 @@ defmodule Aprsme.CircuitBreaker do
}
end
defp calculate_current_state(%{state: :open, last_failure_time: last_failure_time, recovery_timeout: recovery_timeout}) do
defp calculate_current_state(%{
state: :open,
last_failure_time: last_failure_time,
recovery_timeout: recovery_timeout
}) do
if last_failure_time && DateTime.diff(DateTime.utc_now(), last_failure_time, :millisecond) >= recovery_timeout do
:half_open
else

View file

@ -56,7 +56,7 @@ defmodule Aprsme.EncodingUtils do
[codepoint] ->
# Allow printable characters and common whitespace
# Remove C0 controls (0x00-0x1F except tab, newline, carriage return)
# Remove C1 controls (0x80-0x9F)
# Remove C1 controls (0x80-0x9F)
# Remove DEL (0x7F)
cond do
# Tab
@ -138,8 +138,9 @@ defmodule Aprsme.EncodingUtils do
# Helper to check if a float is finite (not infinity or NaN)
defp finite_float?(float) when is_float(float) do
# NaN != NaN
float != :infinity and float != :neg_infinity and float == float
# In Elixir, we can't have infinity or NaN in regular floats
# This function is kept for defensive programming
true
end
defp finite_float?(_), do: false

View file

@ -330,10 +330,10 @@ defmodule Aprsme.ErrorHandler do
defp categorize_and_handle_error(error, context) do
cond do
is_database_error?(error) ->
database_error?(error) ->
handle_database_error(error, context)
is_network_error?(error) ->
network_error?(error) ->
handle_network_error(error, :unknown_service, context)
true ->
@ -349,19 +349,19 @@ defmodule Aprsme.ErrorHandler do
end
end
defp is_database_error?(%CastError{}), do: true
defp is_database_error?(%Ecto.ConstraintError{}), do: true
defp is_database_error?(%Postgrex.Error{}), do: true
defp is_database_error?(%DBConnection.ConnectionError{}), do: true
defp is_database_error?(%Ecto.QueryError{}), do: true
defp is_database_error?(_), do: false
defp database_error?(%CastError{}), do: true
defp database_error?(%Ecto.ConstraintError{}), do: true
defp database_error?(%Postgrex.Error{}), do: true
defp database_error?(%DBConnection.ConnectionError{}), do: true
defp database_error?(%Ecto.QueryError{}), do: true
defp database_error?(_), do: false
defp is_network_error?(%Req.TransportError{}), do: true
defp is_network_error?(%{reason: :circuit_open}), do: true
defp is_network_error?(%{reason: :timeout}), do: true
defp is_network_error?(%RuntimeError{message: "circuit_open"}), do: true
defp is_network_error?(%RuntimeError{message: "timeout"}), do: true
defp is_network_error?(_), do: false
defp network_error?(%Req.TransportError{}), do: true
defp network_error?(%{reason: :circuit_open}), do: true
defp network_error?(%{reason: :timeout}), do: true
defp network_error?(%RuntimeError{message: "circuit_open"}), do: true
defp network_error?(%RuntimeError{message: "timeout"}), do: true
defp network_error?(_), do: false
defp store_bad_packet(packet_data, reason) do
# This would store the bad packet for later analysis

View file

@ -402,7 +402,12 @@ defmodule Aprsme.Is do
case connect_to_aprs_is(state.server, state.port) do
{:ok, socket} ->
case send_login_string(socket, state.login_params.user_id, state.login_params.passcode, state.login_params.filter) do
case send_login_string(
socket,
state.login_params.user_id,
state.login_params.passcode,
state.login_params.filter
) do
:ok ->
Logger.info("Successfully reconnected to APRS-IS")
timer = create_timer(@aprs_timeout)

View file

@ -192,7 +192,9 @@ defmodule Aprsme.PacketReplay do
]
# Log the start of replay with map bounds
Logger.info("Starting packet replay for user #{state.user_id} in map area #{inspect(state.bounds)} for the last hour")
Logger.info(
"Starting packet replay for user #{state.user_id} in map area #{inspect(state.bounds)} for the last hour"
)
# Send notification to client that replay is starting
Endpoint.broadcast(state.replay_topic, "replay_started", %{

View file

@ -30,7 +30,9 @@ defmodule Aprsme.Packets do
packet_attrs =
sanitized_packet_data
|> Packet.extract_additional_data(sanitized_packet_data[:raw_packet] || sanitized_packet_data["raw_packet"] || "")
|> Packet.extract_additional_data(
sanitized_packet_data[:raw_packet] || sanitized_packet_data["raw_packet"] || ""
)
|> normalize_packet_attrs()
|> set_received_at()
|> patch_lat_lon_from_data_extended()

View file

@ -195,17 +195,8 @@ defmodule AprsmeWeb.CoreComponents do
{@title}
</p>
<p class="mt-2 text-[0.8125rem] leading-5">{msg}</p>
<button
:if={@close}
type="button"
class="group absolute top-2 right-1 p-2"
aria-label={gettext("close")}
>
<.icon
name="x-mark"
outline={false}
class="h-5 w-5 stroke-current opacity-40 group-hover:opacity-70"
/>
<button :if={@close} type="button" class="group absolute top-2 right-1 p-2" aria-label={gettext("close")}>
<.icon name="x-mark" outline={false} class="h-5 w-5 stroke-current opacity-40 group-hover:opacity-70" />
</button>
</div>
"""
@ -463,18 +454,10 @@ defmodule AprsmeWeb.CoreComponents do
<div class="dropdown dropdown-end">
<div tabindex="0" role="button" class="btn btn-ghost lg:hidden">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M4 6h16M4 12h16M4 18h16"
/>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />
</svg>
</div>
<ul
tabindex="0"
class="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-100 rounded-box w-52"
>
<ul tabindex="0" class="menu menu-sm dropdown-content mt-3 z-[1] p-2 shadow bg-base-100 rounded-box w-52">
<.navigation variant={:vertical} current_user={@current_user} />
</ul>
</div>
@ -576,11 +559,7 @@ defmodule AprsmeWeb.CoreComponents do
</tr>
</thead>
<tbody class="relative divide-y divide-zinc-100 border-t border-zinc-200 text-sm leading-6 text-zinc-700">
<tr
:for={row <- @rows}
id={"#{@id}-#{safe_row_id(row)}"}
class="relative group hover:bg-zinc-50"
>
<tr :for={row <- @rows} id={"#{@id}-#{safe_row_id(row)}"} class="relative group hover:bg-zinc-50">
<td
:for={{col, i} <- Enum.with_index(@col)}
phx-click={@row_click && @row_click.(row)}
@ -653,10 +632,7 @@ defmodule AprsmeWeb.CoreComponents do
def back(assigns) do
~H"""
<div class="mt-16">
<.link
navigate={@navigate}
class="text-sm font-semibold leading-6 text-zinc-900 hover:text-zinc-700"
>
<.link navigate={@navigate} class="text-sm font-semibold leading-6 text-zinc-900 hover:text-zinc-700">
<.icon name="arrow-left" outline={false} class="w-3 h-3 stroke-current inline" />
{render_slot(@inner_block)}
</.link>

View file

@ -219,8 +219,7 @@
phx-disconnected={show("#disconnected")}
phx-connected={hide("#disconnected")}
>
Attempting to reconnect
<.icon name="arrow-path" outline={true} class="ml-1 w-3 h-3 inline animate-spin" />
Attempting to reconnect <.icon name="arrow-path" outline={true} class="ml-1 w-3 h-3 inline animate-spin" />
</.flash>
{@inner_content}
</div>

View file

@ -206,8 +206,7 @@
phx-disconnected={show("#disconnected")}
phx-connected={hide("#disconnected")}
>
Attempting to reconnect
<.icon name="arrow-path" outline={true} class="ml-1 w-3 h-3 inline animate-spin" />
Attempting to reconnect <.icon name="arrow-path" outline={true} class="ml-1 w-3 h-3 inline animate-spin" />
</.flash>
{@inner_content}
</div>

View file

@ -248,12 +248,7 @@ defmodule AprsmeWeb.ApiDocsLive do
</div>
<div class="alert alert-warning mt-6">
<svg
xmlns="http://www.w3.org/2000/svg"
class="stroke-current shrink-0 h-6 w-6"
fill="none"
viewBox="0 0 24 24"
>
<svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
@ -578,11 +573,7 @@ defmodule AprsmeWeb.ApiDocsLive do
class="input input-bordered flex-1"
disabled={@loading}
/>
<button
type="submit"
disabled={@loading or String.trim(@test_callsign) == ""}
class="btn btn-primary"
>
<button type="submit" disabled={@loading or String.trim(@test_callsign) == ""} class="btn btn-primary">
<%= if @loading do %>
<span class="loading loading-spinner loading-sm"></span> Testing...
<% else %>

View file

@ -68,12 +68,7 @@
<div class="card-body text-center">
<div class="flex justify-center mb-4">
<div class="w-16 h-16 bg-success/20 rounded-full flex items-center justify-center">
<svg
class="w-8 h-8 text-success"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<svg class="w-8 h-8 text-success" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"

View file

@ -51,18 +51,8 @@
<div class="card-body">
<div class="flex items-center mb-3">
<div class="flex-shrink-0">
<svg
class="h-4 w-4 opacity-70"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M15 10.5a3 3 0 11-6 0 3 3 0 016 0z"
/>
<svg class="h-4 w-4 opacity-70" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 10.5a3 3 0 11-6 0 3 3 0 016 0z" />
<path
stroke-linecap="round"
stroke-linejoin="round"
@ -187,13 +177,7 @@
<div class="card-body">
<div class="flex items-center mb-3">
<div class="flex-shrink-0">
<svg
class="h-4 w-4 opacity-70"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
>
<svg class="h-4 w-4 opacity-70" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path
stroke-linecap="round"
stroke-linejoin="round"
@ -275,13 +259,7 @@
<div class="card-body">
<div class="flex items-center mb-3">
<div class="flex-shrink-0">
<svg
class="h-4 w-4 opacity-70"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
>
<svg class="h-4 w-4 opacity-70" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path
stroke-linecap="round"
stroke-linejoin="round"
@ -313,10 +291,7 @@
<tr>
<td class="font-medium">
<div class="flex items-center space-x-2">
<.link
navigate={~p"/info/#{ssid_info.callsign}"}
class="link link-primary"
>
<.link navigate={~p"/info/#{ssid_info.callsign}"} class="link link-primary">
{ssid_info.callsign}
</.link>
<%= if ssid_info.packet do %>
@ -385,13 +360,7 @@
<div class="card-body">
<div class="flex items-center mb-3">
<div class="flex-shrink-0">
<svg
class="h-4 w-4 opacity-70"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
>
<svg class="h-4 w-4 opacity-70" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path
stroke-linecap="round"
stroke-linejoin="round"
@ -473,13 +442,7 @@
<div class="card-body">
<div class="flex items-center mb-3">
<div class="flex-shrink-0">
<svg
class="h-4 w-4 opacity-70"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
>
<svg class="h-4 w-4 opacity-70" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path
stroke-linecap="round"
stroke-linejoin="round"
@ -520,10 +483,7 @@
<%= for station <- @heard_by_stations do %>
<tr>
<td class="font-medium">
<.link
navigate={~p"/info/#{station.digipeater}"}
class="link link-primary"
>
<.link navigate={~p"/info/#{station.digipeater}"} class="link link-primary">
{station.digipeater}
</.link>
</td>
@ -578,13 +538,7 @@
<div class="card-body">
<div class="flex items-center mb-3">
<div class="flex-shrink-0">
<svg
class="h-4 w-4 opacity-70"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
>
<svg class="h-4 w-4 opacity-70" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path
stroke-linecap="round"
stroke-linejoin="round"

View file

@ -20,7 +20,7 @@ defmodule AprsmeWeb.LocaleHook do
end
# Set map_page assign based on the view module
map_page = is_map_page?(socket, params)
map_page = map_page?(socket, params)
{:cont, assign(socket, locale: locale, map_page: map_page)}
end
@ -35,7 +35,7 @@ defmodule AprsmeWeb.LocaleHook do
end
end
defp is_map_page?(socket, _params) do
defp map_page?(socket, _params) do
# Only check the view module from socket private data
case socket.private[:phoenix_live_view] do
%{view: AprsmeWeb.MapLive.Index} -> true

View file

@ -4,6 +4,7 @@ defmodule AprsmeWeb.MapLive.BoundsManager do
"""
alias AprsmeWeb.Live.Shared.BoundsUtils
alias AprsmeWeb.Live.Shared.PacketUtils
# Delegate to shared utilities
defdelegate calculate_bounds_from_center_and_zoom(center, zoom), to: BoundsUtils
@ -19,6 +20,6 @@ defmodule AprsmeWeb.MapLive.BoundsManager do
@spec filter_packets_by_time_and_bounds(map(), map(), DateTime.t()) :: map()
def filter_packets_by_time_and_bounds(packets, bounds, time_threshold) do
# Use shared packet utils for this functionality
AprsmeWeb.Live.Shared.PacketUtils.filter_packets_by_time_and_bounds(packets, bounds, time_threshold)
PacketUtils.filter_packets_by_time_and_bounds(packets, bounds, time_threshold)
end
end

View file

@ -157,7 +157,14 @@ defmodule AprsmeWeb.MapLive.DataBuilder do
{lat, lon, _} = get_coordinates(packet)
if lat && lon do
distance_meters = CoordinateUtils.calculate_distance_meters(most_recent_lat, most_recent_lon, lat, lon)
distance_meters =
CoordinateUtils.calculate_distance_meters(
most_recent_lat,
most_recent_lon,
lat,
lon
)
# Only show if 10+ meters away
distance_meters >= 10.0
else
@ -330,7 +337,7 @@ defmodule AprsmeWeb.MapLive.DataBuilder do
weather_link: weather_link
}
# Optimize string conversion - avoid intermediate iodata step
# Optimize string conversion - avoid intermediate iodata step
popup_data
|> PopupComponent.popup()
|> Safe.to_iodata()
@ -475,7 +482,7 @@ defmodule AprsmeWeb.MapLive.DataBuilder do
end
defp convert_tuples_to_strings(list) when is_list(list) do
# Use Stream for memory efficiency on large lists
# Use Stream for memory efficiency on large lists
list
|> Stream.map(&convert_tuples_to_strings/1)
|> Enum.to_list()

View file

@ -585,7 +585,8 @@ defmodule AprsmeWeb.MapLive.Index do
defp parse_historical_hours(hours), do: SharedPacketUtils.parse_historical_hours(hours)
@impl true
def handle_info({:process_bounds_update, map_bounds}, socket), do: handle_info_process_bounds_update(map_bounds, socket)
def handle_info({:process_bounds_update, map_bounds}, socket),
do: handle_info_process_bounds_update(map_bounds, socket)
def handle_info(:initialize_replay, socket), do: handle_info_initialize_replay(socket)
@ -710,14 +711,8 @@ defmodule AprsmeWeb.MapLive.Index do
integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
crossorigin=""
/>
<link
rel="stylesheet"
href="https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.css"
/>
<link
rel="stylesheet"
href="https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.Default.css"
/>
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.css" />
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.5.3/dist/MarkerCluster.Default.css" />
<script
src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
@ -726,9 +721,7 @@ defmodule AprsmeWeb.MapLive.Index do
</script>
<script src="https://unpkg.com/leaflet.markercluster@1.5.3/dist/leaflet.markercluster.js">
</script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/OverlappingMarkerSpiderfier-Leaflet/0.2.6/oms.min.js"
>
<script src="https://cdnjs.cloudflare.com/ajax/libs/OverlappingMarkerSpiderfier-Leaflet/0.2.6/oms.min.js">
</script>
<style>
@ -858,19 +851,8 @@ defmodule AprsmeWeb.MapLive.Index do
</div>
</.error_boundary>
<button
class="locate-button"
phx-click="locate_me"
title={Gettext.gettext(AprsmeWeb.Gettext, "Find my location")}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 24 24"
fill="#374151"
stroke="none"
>
<button class="locate-button" phx-click="locate_me" title={Gettext.gettext(AprsmeWeb.Gettext, "Find my location")}>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="#374151" stroke="none">
<path d="M12 2L4.5 20.29l.71.71L12 18l6.79 3 .71-.71z" />
</svg>
</button>
@ -932,10 +914,7 @@ defmodule AprsmeWeb.MapLive.Index do
<!-- Mobile Backdrop -->
<%= if @slideover_open do %>
<div
class="fixed inset-0 bg-black bg-opacity-50 z-[999] lg:hidden backdrop-blur-sm"
phx-click="toggle_slideover"
>
<div class="fixed inset-0 bg-black bg-opacity-50 z-[999] lg:hidden backdrop-blur-sm" phx-click="toggle_slideover">
</div>
<% end %>
@ -951,13 +930,7 @@ defmodule AprsmeWeb.MapLive.Index do
<!-- Header -->
<div class="flex items-center justify-between p-6 border-b border-slate-200 bg-gradient-to-r from-indigo-600 to-purple-600 text-white">
<div class="flex items-center space-x-2">
<svg
class="w-6 h-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path
stroke-linecap="round"
stroke-linejoin="round"
@ -1043,12 +1016,7 @@ defmodule AprsmeWeb.MapLive.Index do
<!-- Trail Duration -->
<div class="space-y-4">
<label class="block text-sm font-semibold text-slate-700 flex items-center space-x-2">
<svg
class="w-4 h-4 text-emerald-500"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<svg class="w-4 h-4 text-emerald-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path
stroke-linecap="round"
stroke-linejoin="round"
@ -1083,18 +1051,8 @@ defmodule AprsmeWeb.MapLive.Index do
</option>
</select>
<div class="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
<svg
class="w-4 h-4 text-slate-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 9l-7 7-7-7"
/>
<svg class="w-4 h-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</div>
</form>
@ -1135,18 +1093,8 @@ defmodule AprsmeWeb.MapLive.Index do
</option>
</select>
<div class="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none">
<svg
class="w-4 h-4 text-slate-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M19 9l-7 7-7-7"
/>
<svg class="w-4 h-4 text-slate-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</div>
</form>

View file

@ -111,7 +111,7 @@ defmodule AprsmeWeb.MapLive.PacketManager do
visible_packets = get_visible_packets(packet_state)
{keep_visible, remove_visible} = Enum.split_with(visible_packets, predicate_fn)
# Get historical packets and apply predicate
# Get historical packets and apply predicate
historical_packets = get_historical_packets(packet_state)
{keep_historical, remove_historical} = Enum.split_with(historical_packets, predicate_fn)

View file

@ -6,6 +6,7 @@ defmodule AprsmeWeb.MapLive.PacketProcessor do
import Phoenix.Component, only: [assign: 3]
alias Aprsme.GeoUtils
alias AprsmeWeb.Live.Shared.BoundsUtils
alias AprsmeWeb.Live.Shared.CoordinateUtils
alias AprsmeWeb.Live.Shared.PacketUtils, as: SharedPacketUtils
alias AprsmeWeb.MapLive.PacketUtils
@ -147,7 +148,7 @@ defmodule AprsmeWeb.MapLive.PacketProcessor do
# Use shared bounds utility
defp within_bounds?(coords, bounds) do
AprsmeWeb.Live.Shared.BoundsUtils.within_bounds?(coords, bounds)
BoundsUtils.within_bounds?(coords, bounds)
end
# Helper to get locale from socket

View file

@ -21,9 +21,7 @@
<tr>
<td>
<.link
navigate={
~p"/packets/#{Map.get(packet, :base_callsign, Map.get(packet, "base_callsign", ""))}"
}
navigate={~p"/packets/#{Map.get(packet, :base_callsign, Map.get(packet, "base_callsign", ""))}"}
class="link link-primary font-medium"
>
{Map.get(packet, :sender, Map.get(packet, "sender", ""))}
@ -36,9 +34,7 @@
</td>
<td>
<.link
navigate={
~p"/packets/#{Map.get(packet, :base_callsign, Map.get(packet, "base_callsign", ""))}"
}
navigate={~p"/packets/#{Map.get(packet, :base_callsign, Map.get(packet, "base_callsign", ""))}"}
class="link link-primary font-medium"
>
{Map.get(packet, :base_callsign, Map.get(packet, "base_callsign", ""))}

View file

@ -11,8 +11,8 @@ defmodule AprsmeWeb.Live.Shared.BoundsUtils do
"""
@spec valid_bounds?(map()) :: boolean()
def valid_bounds?(%{north: north, south: south, east: east, west: west})
when is_number(north) and is_number(south) and is_number(east) and is_number(west) and north <= 90 and south >= -90 and
north > south and east >= -180 and west <= 180, do: true
when is_number(north) and is_number(south) and is_number(east) and is_number(west) and north <= 90 and
south >= -90 and north > south and east >= -180 and west <= 180, do: true
def valid_bounds?(_invalid_bounds), do: false

View file

@ -85,7 +85,7 @@ defmodule AprsmeWeb.Live.SharedPacketHandler do
device_identifiers =
packets
|> Enum.map(&get_device_identifier/1)
|> Enum.reject(&is_nil_or_empty/1)
|> Enum.reject(&nil_or_empty?/1)
|> Enum.uniq()
# Batch lookup devices (DeviceCache is already optimized with caching)
@ -117,9 +117,9 @@ defmodule AprsmeWeb.Live.SharedPacketHandler do
end
end
defp is_nil_or_empty(nil), do: true
defp is_nil_or_empty(""), do: true
defp is_nil_or_empty(_), do: false
defp nil_or_empty?(nil), do: true
defp nil_or_empty?(""), do: true
defp nil_or_empty?(_), do: false
@doc """
Creates a filter function that checks both callsign and weather data.

View file

@ -4,6 +4,8 @@ defmodule AprsmeWeb.Live.Shared.PacketUtils do
Used across multiple LiveView modules for consistent packet handling.
"""
alias AprsmeWeb.Live.Shared.BoundsUtils
@doc """
Get unique callsign key from packet.
"""
@ -81,7 +83,7 @@ defmodule AprsmeWeb.Live.Shared.PacketUtils do
def filter_packets_by_time_and_bounds(packets, bounds, time_threshold) do
packets
|> Enum.filter(fn {_callsign, packet} ->
AprsmeWeb.Live.Shared.BoundsUtils.within_bounds?(packet, bounds) &&
BoundsUtils.within_bounds?(packet, bounds) &&
packet_within_time_threshold?(packet, time_threshold)
end)
|> Map.new()

View file

@ -80,8 +80,9 @@ defmodule AprsmeWeb.Live.Shared.ParamUtils do
"""
@spec finite?(float() | any()) :: boolean()
def finite?(float) when is_float(float) do
# NaN != NaN
float != :infinity and float != :neg_infinity and float == float
# In Elixir, we can't have infinity or NaN in regular floats
# This function is kept for API compatibility
true
end
def finite?(_), do: false

View file

@ -97,9 +97,7 @@ defmodule AprsmeWeb.StatusLive.ConnectionCardComponent do
<span class="text-sm font-medium opacity-70 mr-2">Last Packet:</span>
<span class="text-sm">
<%= if @aprs_status.packet_stats.last_packet_at do %>
{AprsmeWeb.StatusLive.Index.format_time_ago(
@aprs_status.packet_stats.last_packet_at
)}
{AprsmeWeb.StatusLive.Index.format_time_ago(@aprs_status.packet_stats.last_packet_at)}
<% else %>
<span class="opacity-50">None</span>
<% end %>

View file

@ -64,7 +64,11 @@ defmodule AprsmeWeb.StatusLive.Index do
def handle_info({:aprs_status_update, status}, socket) do
# Handle real-time status updates via PubSub
socket =
assign(socket, aprs_status: status, current_time: DateTime.utc_now(), health_score: calculate_health_score(status))
assign(socket,
aprs_status: status,
current_time: DateTime.utc_now(),
health_score: calculate_health_score(status)
)
{:noreply, socket}
end

View file

@ -18,13 +18,7 @@ defmodule AprsmeWeb.UserConfirmationInstructionsLive do
Enter your email address to receive a new confirmation link
</p>
<.simple_form
:let={_f}
for={%{}}
as={:user}
id="resend_confirmation_form"
phx-submit="send_instructions"
>
<.simple_form :let={_f} for={%{}} as={:user} id="resend_confirmation_form" phx-submit="send_instructions">
<div class="form-control w-full">
<label class="label">
<span class="label-text">Email</span>

View file

@ -14,21 +14,11 @@ defmodule AprsmeWeb.UserConfirmationLive do
<h1 class="card-title text-3xl mb-4 justify-center">Confirm your account</h1>
<p class="text-base-content/70 mb-6">Click the button below to confirm your account</p>
<.simple_form
:let={_f}
for={%{}}
as={:user}
id="confirmation_form"
phx-submit="confirm_account"
>
<.simple_form :let={_f} for={%{}} as={:user} id="confirmation_form" phx-submit="confirm_account">
<input type="hidden" name="user[token]" value={@token} />
<div class="form-control mt-6">
<button
type="submit"
class="btn btn-primary w-full"
phx-disable-with="Confirming..."
>
<button type="submit" class="btn btn-primary w-full" phx-disable-with="Confirming...">
Confirm my account
</button>
</div>

View file

@ -14,13 +14,7 @@ defmodule AprsmeWeb.UserForgotPasswordLive do
<h1 class="card-title text-3xl mb-4 justify-center">Forgot your password?</h1>
<p class="text-base-content/70 mb-6">We'll send a password reset link to your inbox</p>
<.simple_form
:let={_f}
id="reset_password_form"
for={%{}}
as={:user}
phx-submit="send_email"
>
<.simple_form :let={_f} id="reset_password_form" for={%{}} as={:user} phx-submit="send_email">
<div class="form-control w-full">
<label class="label">
<span class="label-text">Email</span>

View file

@ -76,11 +76,7 @@ defmodule AprsmeWeb.UserLoginLive do
</div>
<div class="form-control mt-6">
<button
type="submit"
class="btn btn-primary w-full"
phx-disable-with="Signing in..."
>
<button type="submit" class="btn btn-primary w-full" phx-disable-with="Signing in...">
Sign in
</button>
</div>

View file

@ -105,11 +105,7 @@ defmodule AprsmeWeb.UserRegistrationLive do
</div>
<div class="form-control mt-6">
<button
type="submit"
class="btn btn-primary w-full"
phx-disable-with="Creating account..."
>
<button type="submit" class="btn btn-primary w-full" phx-disable-with="Creating account...">
Create an account
</button>
</div>

View file

@ -70,10 +70,7 @@ defmodule AprsmeWeb.UserSettingsLive do
placeholder="Enter current password"
required
/>
<label
:if={Keyword.has_key?(@email_changeset.errors, :current_password)}
class="label"
>
<label :if={Keyword.has_key?(@email_changeset.errors, :current_password)} class="label">
<span class="label-text-alt text-error">
{translate_error(Keyword.get(@email_changeset.errors, :current_password))}
</span>
@ -143,10 +140,7 @@ defmodule AprsmeWeb.UserSettingsLive do
placeholder="Enter current password"
required
/>
<label
:if={Keyword.has_key?(@callsign_changeset.errors, :current_password)}
class="label"
>
<label :if={Keyword.has_key?(@callsign_changeset.errors, :current_password)} class="label">
<span class="label-text-alt text-error">
{translate_error(Keyword.get(@callsign_changeset.errors, :current_password))}
</span>
@ -181,11 +175,7 @@ defmodule AprsmeWeb.UserSettingsLive do
<span>Oops, something went wrong! Please check the errors below.</span>
</div>
<input
type="hidden"
name={Phoenix.HTML.Form.input_name(f, :email)}
value={@current_email}
/>
<input type="hidden" name={Phoenix.HTML.Form.input_name(f, :email)} value={@current_email} />
<div class="form-control w-full">
<label class="label">
@ -225,14 +215,9 @@ defmodule AprsmeWeb.UserSettingsLive do
placeholder="Confirm new password"
required
/>
<label
:if={Keyword.has_key?(@password_changeset.errors, :password_confirmation)}
class="label"
>
<label :if={Keyword.has_key?(@password_changeset.errors, :password_confirmation)} class="label">
<span class="label-text-alt text-error">
{translate_error(
Keyword.get(@password_changeset.errors, :password_confirmation)
)}
{translate_error(Keyword.get(@password_changeset.errors, :password_confirmation))}
</span>
</label>
</div>
@ -253,10 +238,7 @@ defmodule AprsmeWeb.UserSettingsLive do
placeholder="Enter current password"
required
/>
<label
:if={Keyword.has_key?(@password_changeset.errors, :current_password)}
class="label"
>
<label :if={Keyword.has_key?(@password_changeset.errors, :current_password)} class="label">
<span class="label-text-alt text-error">
{translate_error(Keyword.get(@password_changeset.errors, :current_password))}
</span>

View file

@ -62,12 +62,7 @@
) %>
<div class="flex items-center mb-3">
<div class="flex-shrink-0">
<svg
class="h-4 w-4 text-primary"
viewBox="0 0 20 20"
fill="currentColor"
aria-hidden="true"
>
<svg class="h-4 w-4 text-primary" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
<path
fill-rule="evenodd"
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a.75.75 0 000 1.5h.253a.75.75 0 00.736-.686L11.477 4.5a.75.75 0 00-1.491-.154L9.477 9.5H9z"
@ -84,9 +79,7 @@
<dt class="text-xs font-medium opacity-70">{gettext("Last WX report")}</dt>
<dd class="mt-1 text-sm font-semibold">
<%= if dt do %>
{Calendar.strftime(dt, "%Y-%m-%d %H:%M:%S UTC")} ({AprsmeWeb.TimeHelpers.time_ago_in_words(
dt
)})<br />
{Calendar.strftime(dt, "%Y-%m-%d %H:%M:%S UTC")} ({AprsmeWeb.TimeHelpers.time_ago_in_words(dt)})<br />
<%!-- <span class="text-xs text-gray-500">{Calendar.strftime(dt, "%Y-%m-%d %H:%M:%S %Z")} local time at {Map.get(@weather_packet, :region) || Map.get(@weather_packet, "region") || "Unknown location"} [?]</span> --%>
<% else %>
{gettext("Unknown")}

View file

@ -88,7 +88,10 @@ defmodule AprsmeWeb.Telemetry do
description: "Number of successful inserts per batch"
),
summary("aprsme.packet_pipeline.batch.error", unit: :event, description: "Number of errors per batch"),
summary("aprsme.packet_pipeline.batch.duration_ms", unit: :millisecond, description: "Batch insert duration (ms)"),
summary("aprsme.packet_pipeline.batch.duration_ms",
unit: :millisecond,
description: "Batch insert duration (ms)"
),
# Note: SystemMonitor and InsertOptimizer metrics removed after reverting performance optimizations
@ -100,7 +103,9 @@ defmodule AprsmeWeb.Telemetry do
counter("aprsme.spatial_pubsub.broadcasts.filtered", description: "Broadcasts filtered by viewport"),
counter("aprsme.spatial_pubsub.broadcasts.packets", description: "Total packets processed"),
last_value("aprsme.spatial_pubsub.efficiency.ratio", unit: :percent, description: "Broadcast efficiency ratio"),
counter("aprsme.spatial_pubsub.efficiency.saved_broadcasts", description: "Number of broadcasts saved by filtering")
counter("aprsme.spatial_pubsub.efficiency.saved_broadcasts",
description: "Number of broadcasts saved by filtering"
)
]
end

View file

@ -31,7 +31,8 @@ defmodule AprsmeWeb.TimeHelpers do
defp format_time_diff(seconds) when seconds < 2_592_000, do: gettext("%{count} days", count: div(seconds, 86_400))
defp format_time_diff(seconds) when seconds < 5_184_000, do: gettext("1 month")
defp format_time_diff(seconds) when seconds < 31_536_000, do: gettext("%{count} months", count: div(seconds, 2_592_000))
defp format_time_diff(seconds) when seconds < 31_536_000,
do: gettext("%{count} months", count: div(seconds, 2_592_000))
defp format_time_diff(seconds) when seconds < 63_072_000, do: gettext("1 year")
defp format_time_diff(seconds), do: gettext("%{count} years", count: div(seconds, 31_536_000))

View file

@ -64,7 +64,7 @@ defmodule Aprsme.GeoUtilsTest do
# With 3 meter threshold, should be significant
assert GeoUtils.significant_movement?(lat1, lon1, lat2, lon2, 3)
# With 10 meter threshold (default), should not be significant
# With 10 meter threshold (default), should not be significant
refute GeoUtils.significant_movement?(lat1, lon1, lat2, lon2, 10)
# With 20 meter threshold, definitely not significant

View file

@ -4,11 +4,13 @@ defmodule Aprsme.PacketPipelineIntegrationTest do
import ExUnit.CaptureLog
alias Aprsme.PacketProducer
alias Aprsme.Performance.InsertOptimizer
alias Aprsme.SystemMonitor
describe "packet pipeline under load" do
test "system adjusts batch sizes based on load" do
# Test the actual running system without starting new processes
initial_batch_size = Aprsme.SystemMonitor.get_recommended_batch_size()
initial_batch_size = SystemMonitor.get_recommended_batch_size()
assert initial_batch_size >= 100
assert initial_batch_size <= 800
@ -38,13 +40,13 @@ defmodule Aprsme.PacketPipelineIntegrationTest do
end
test "insert optimizer provides reasonable batch sizes" do
batch_size = Aprsme.Performance.InsertOptimizer.get_optimal_batch_size()
batch_size = InsertOptimizer.get_optimal_batch_size()
assert batch_size >= 100
assert batch_size <= 800
end
test "system monitor provides metrics" do
metrics = Aprsme.SystemMonitor.get_metrics()
metrics = SystemMonitor.get_metrics()
assert is_map(metrics)
assert Map.has_key?(metrics, :memory)

View file

@ -96,7 +96,7 @@ defmodule Aprsme.Packets.ClusteringTest do
{:heat_map, zoom3_clusters} = Clustering.cluster_packets(packets, 3, %{})
assert length(zoom3_clusters) == 1
# At zoom 8, these should be two clusters (radius 0.039 degrees)
# At zoom 8, these should be two clusters (radius 0.039 degrees)
# but our points are only 0.014 degrees apart, so they'll still cluster
# Let's use points that are further apart
far_packets = [

View file

@ -1,61 +0,0 @@
# Warning Fixes Summary
## Issue
Running `mix compile --warnings-as-errors` was failing due to 23 unused function warnings in the packet consumer module.
## Root Cause
During the INSERT performance optimization, we implemented a new fast packet processing pipeline but left the old implementation functions in place, causing unused function warnings.
## Functions Removed
### Old Packet Processing Functions (Unused after optimization):
- `prepare_packet_for_insert/1` - Old slow packet preparation
- `normalize_packet_attrs/1` - Complex normalization logic
- `set_received_at/1` - Timestamp setting
- `patch_lat_lon_from_data_extended/1` - Position extraction from extended data
- `extract_position/1` - Position extraction logic
- `extract_position_from_data_extended/1` - Extended data position parsing
- `extract_position_from_data_extended_case/1` - Case-based position extraction
- `has_standard_position?/1` - Position validation
- `extract_standard_position/1` - Standard position extraction
- `extract_lat_from_ext_map/1` - Latitude extraction
- `extract_lon_from_ext_map/1` - Longitude extraction
- `set_lat_lon/3` - Coordinate setting and rounding
- `normalize_ssid/1` - SSID normalization
- `create_location_geometry/1` - Old geometry creation
- `valid_coordinates?/2` - Coordinate validation
- `normalize_coordinate/1` - Coordinate normalization
- `create_point/2` - Point geometry creation
- `valid_packet?/1` - Packet validation
- `sanitize_packet_strings/1` - String sanitization
- `to_float/1` - Float conversion
- `normalize_data_type/1` - Data type normalization
- `struct_to_map/1` - Struct conversion
- `truncate_datetimes_to_second/1` - DateTime truncation
- `normalize_numeric_types/1` - Numeric type conversion
### Functions Kept:
- `validate_essential_fields/2` - Used by the new fast pipeline
- `prepare_packet_for_insert_fast/2` - New optimized packet preparation
- `extract_essential_fields/1` - Essential field extraction
- `create_location_geometry_fast/1` - Fast geometry creation
- All new fast helper functions
## Result
- ✅ All 23 unused function warnings eliminated
- ✅ `mix compile --warnings-as-errors` now passes
- ✅ All 351 tests still passing
- ✅ Code properly formatted
- ✅ No functional impact - the new fast pipeline remains intact
## Performance Impact
The cleanup removed ~250 lines of unused legacy code, which:
- Reduces module size and compilation time
- Eliminates confusion about which functions are actually used
- Makes the codebase cleaner and more maintainable
- Ensures only the optimized fast packet processing pipeline is used
## File Modified
- `/Users/graham/dev/aprs.me/lib/aprsme/packet_consumer.ex` - Removed 23 unused functions
The module now only contains the optimized INSERT performance code, making it much cleaner and easier to maintain.