When a wireless AP has a strong same-channel neighbor (RSSI >= -75 dBm) and a measurably cleaner alternative channel exists in the same band, the system now emits an ap_frequency_change insight with a specific recommended channel and the top interfering BSSIDs as evidence. - New schema wireless_neighbor_scans (append-only) storing observed neighboring APs per device: bssid, ssid, channel, frequency_mhz, channel_width_mhz, rssi_dbm, is_own_fleet, seen_at - New columns on snmp_devices: current_channel, current_frequency_mhz, current_channel_width_mhz, last_radio_seen_at — populated by vendor SNMP profiles for APs that expose wireless OIDs - Towerops.Recommendations.Rules.FrequencyChange — pure rule that scores each candidate channel using sum-of-linear-power over the last 24h of neighbor scans, recommends the cleanest if it beats the current by at least 6 dB. Critical urgency when current score >= 100. - Towerops.Workers.RecommendationsRunWorker — hourly Oban cron, iterates organizations and runs all rules, uses insert_insight_if_new/1 for idempotent upsert - LiveView /insights renders a purple frequency-change card with current vs recommended channel, dB-cleaner badge, and a top-offender list (BSSID, SSID, RSSI, own-fleet flag) - LLM enrichment automatically applies to the new insight type Also fixes two pre-existing flaky tests that surfaced in CI: - test/towerops_web/live/agent_live_test.exs:567 — now sets an explicit Req.Test transport_error stub for ReleaseChecker and uses Req.Test.allow so the LiveView process inherits it. Previously relied on absence of a stub, which leaked across tests and produced different behavior under full-suite parallel runs. - test/towerops_web/telemetry_test.exs:140 — relaxes the "no log" check to refute the specific messages the function under test would emit, rather than asserting an empty capture_log/1 buffer (which catches stray logs from concurrent tests, e.g. Postgrex sandbox disconnects).
31 lines
1.1 KiB
Elixir
31 lines
1.1 KiB
Elixir
defmodule Towerops.Repo.Migrations.CreateWirelessNeighborScans do
|
|
use Ecto.Migration
|
|
|
|
def change do
|
|
create table(:wireless_neighbor_scans, primary_key: false) do
|
|
add :id, :binary_id, primary_key: true
|
|
|
|
add :device_id, references(:devices, type: :binary_id, on_delete: :delete_all), null: false
|
|
|
|
add :organization_id,
|
|
references(:organizations, type: :binary_id, on_delete: :delete_all),
|
|
null: false
|
|
|
|
add :bssid, :string, null: false
|
|
add :ssid, :string
|
|
add :channel, :integer
|
|
add :frequency_mhz, :integer
|
|
add :channel_width_mhz, :integer
|
|
add :rssi_dbm, :integer
|
|
add :is_own_fleet, :boolean, default: false, null: false
|
|
add :seen_at, :utc_datetime, null: false
|
|
|
|
timestamps(type: :utc_datetime, updated_at: false)
|
|
end
|
|
|
|
create index(:wireless_neighbor_scans, [:device_id, :seen_at])
|
|
create index(:wireless_neighbor_scans, [:device_id, :channel])
|
|
create index(:wireless_neighbor_scans, [:bssid])
|
|
create index(:wireless_neighbor_scans, [:organization_id, :seen_at])
|
|
end
|
|
end
|