defmodule Microwaveprop.Propagation.BandConfig do
  @moduledoc """
  Data-driven band configuration for the propagation scoring algorithm.

  This is the single source of truth for all scoring parameters: weights,
  thresholds, seasonal tables, and band-specific coefficients. When the
  algorithm is tuned or new bands are added, only this module changes.
  """

  # Default weight vector. Originally fit by gradient descent on 5,000 QSOs
  # 2026-04-11 (loss 0.42 → 0.12) and retained as the 10 GHz prior. Bands
  # with ≥200 HRRR-matched contacts in the 2026-04-18 full-corpus
  # correlation analysis carry per-band `:weights` overrides in
  # `@band_configs` — see `algo.md` Part 2d. Bands without enough data
  # (50/144 MHz with 0 contacts, 47+ GHz with <200 matches) inherit this
  # vector.
  @weights %{
    humidity: 0.1243,
    time_of_day: 0.0496,
    td_depression: 0.0978,
    refractivity: 0.1049,
    sky: 0.08,
    season: 0.1112,
    wind: 0.08,
    rain: 0.1362,
    pressure: 0.1032,
    pwat: 0.1128
  }

  @sunrise_table [7.4, 7.3, 7.0, 6.7, 6.35, 6.25, 6.35, 6.65, 6.9, 7.1, 7.35, 7.45]

  @tiers [
    %{min_score: 80, label: "EXCELLENT", color: "#059669"},
    %{min_score: 65, label: "GOOD", color: "#0d9488"},
    %{min_score: 50, label: "MARGINAL", color: "#ca8a04"},
    %{min_score: 33, label: "POOR", color: "#ea580c"},
    %{min_score: 0, label: "NEGLIGIBLE", color: "#dc2626"}
  ]

  @humidity_beneficial_thresholds [{4, 55}, {7, 70}, {10, 82}, {14, 90}, {18, 95}, {22, 88}]
  @humidity_beneficial_default 75

  # Thresholds calibrated for HRRR-derived gradients (coarser than sounding data).
  # HRRR percentiles: p1=-230, p5=-162, p10=-130, p25=-94, p50=-70, p75=-53, p95=-40
  @refractivity_thresholds [
    {-200, 98, 85},
    {-150, 92, 80},
    {-100, 82, 72},
    {-75, 68, 62},
    {-55, 55, 55},
    {-40, 48, 48}
  ]
  @refractivity_default {42, 42}

  @band_configs %{
    # VHF / UHF bands share the tropo-ducting physics of low microwave
    # (humidity and refractivity gradient build ducts the same way),
    # so rain attenuation and gas absorption are effectively zero and
    # humidity is :beneficial. The difference from 902+ is mostly in
    # the achievable range — a 2m duct can reach 2500+ km under
    # exceptional conditions (e.g. documented Hawaii ↔ California
    # openings). These do NOT capture sporadic-E, F2, or meteor scatter,
    # which need ionospheric data we don't yet ingest. 6m is especially
    # affected — most 6m DX is via Es / F2, not tropo.
    50 => %{
      freq_mhz: 50,
      label: "50 MHz",
      o2_db_km: 0.0,
      h2o_coeff: 0.0,
      humidity_effect: :beneficial,
      humidity_penalty: 0.0,
      rain_k: 0.0,
      rain_alpha: 1.0,
      seasonal_base: %{
        1 => 30,
        2 => 32,
        3 => 25,
        4 => 50,
        5 => 70,
        6 => 95,
        7 => 95,
        8 => 70,
        9 => 65,
        10 => 70,
        11 => 60,
        12 => 25
      },
      seasonal_adj: %{},
      typical_range_km: 400,
      extended_range_km: 1500,
      exceptional_range_km: 4000
    },
    144 => %{
      freq_mhz: 144,
      label: "144 MHz",
      o2_db_km: 0.0,
      h2o_coeff: 0.0,
      humidity_effect: :beneficial,
      humidity_penalty: 0.0,
      rain_k: 0.0,
      rain_alpha: 1.0,
      seasonal_base: %{
        1 => 38,
        2 => 40,
        3 => 22,
        4 => 55,
        5 => 68,
        6 => 90,
        7 => 95,
        8 => 75,
        9 => 78,
        10 => 88,
        11 => 78,
        12 => 25
      },
      seasonal_adj: %{},
      typical_range_km: 350,
      extended_range_km: 900,
      exceptional_range_km: 2500
    },
    222 => %{
      freq_mhz: 222,
      label: "222 MHz",
      o2_db_km: 0.0,
      h2o_coeff: 0.0,
      humidity_effect: :beneficial,
      humidity_penalty: 0.0,
      rain_k: 0.0,
      rain_alpha: 1.0,
      # Per-band weights from 2026-04-18 full-corpus correlation (n=5,392
      # matched). Moisture (pwat, humidity) dominate; rain and time-of-day
      # suppressed vs the 10 GHz baseline.
      weights: %{
        humidity: 0.1593,
        time_of_day: 0.0350,
        td_depression: 0.1250,
        refractivity: 0.1401,
        sky: 0.0706,
        season: 0.1276,
        wind: 0.0706,
        rain: 0.0120,
        pwat: 0.1916,
        pressure: 0.0681
      },
      seasonal_base: %{
        1 => 38,
        2 => 40,
        3 => 22,
        4 => 55,
        5 => 68,
        6 => 90,
        7 => 95,
        8 => 75,
        9 => 78,
        10 => 88,
        11 => 78,
        12 => 25
      },
      seasonal_adj: %{},
      typical_range_km: 320,
      extended_range_km: 800,
      exceptional_range_km: 2000
    },
    432 => %{
      freq_mhz: 432,
      label: "432 MHz",
      o2_db_km: 0.0,
      h2o_coeff: 0.0,
      humidity_effect: :beneficial,
      humidity_penalty: 0.0,
      rain_k: 0.0,
      rain_alpha: 1.0,
      # Per-band weights from 2026-04-18 full-corpus correlation (n=6,583).
      weights: %{
        humidity: 0.2061,
        time_of_day: 0.0329,
        td_depression: 0.1200,
        refractivity: 0.1186,
        sky: 0.0663,
        season: 0.1106,
        wind: 0.0663,
        rain: 0.0113,
        pwat: 0.1870,
        pressure: 0.0809
      },
      seasonal_base: %{
        1 => 38,
        2 => 40,
        3 => 22,
        4 => 55,
        5 => 68,
        6 => 90,
        7 => 95,
        8 => 75,
        9 => 78,
        10 => 88,
        11 => 78,
        12 => 25
      },
      seasonal_adj: %{},
      typical_range_km: 300,
      extended_range_km: 700,
      exceptional_range_km: 1800
    },
    902 => %{
      freq_mhz: 902,
      label: "902 MHz",
      o2_db_km: 0.006,
      h2o_coeff: 0.0,
      humidity_effect: :beneficial,
      humidity_penalty: 0.0,
      rain_k: 0.000,
      rain_alpha: 1.0,
      # Per-band weights from 2026-04-18 full-corpus correlation (n=1,317).
      weights: %{
        humidity: 0.2201,
        time_of_day: 0.0437,
        td_depression: 0.1102,
        refractivity: 0.0888,
        sky: 0.0783,
        season: 0.1197,
        wind: 0.0783,
        rain: 0.0133,
        pwat: 0.1635,
        pressure: 0.0839
      },
      seasonal_base: %{
        1 => 38,
        2 => 40,
        3 => 22,
        4 => 55,
        5 => 68,
        6 => 90,
        7 => 95,
        8 => 75,
        9 => 78,
        10 => 88,
        11 => 78,
        12 => 25
      },
      seasonal_adj: %{},
      typical_range_km: 400,
      extended_range_km: 800,
      exceptional_range_km: 1500
    },
    1_296 => %{
      freq_mhz: 1_296,
      label: "1296 MHz",
      o2_db_km: 0.006,
      h2o_coeff: 0.0,
      humidity_effect: :beneficial,
      humidity_penalty: 0.0,
      rain_k: 0.000,
      rain_alpha: 1.0,
      # Per-band weights from 2026-04-18 full-corpus correlation (n=2,146).
      weights: %{
        humidity: 0.2131,
        time_of_day: 0.0494,
        td_depression: 0.0898,
        refractivity: 0.1392,
        sky: 0.0797,
        season: 0.1108,
        wind: 0.0797,
        rain: 0.0136,
        pwat: 0.1678,
        pressure: 0.0568
      },
      seasonal_base: %{
        1 => 38,
        2 => 40,
        3 => 22,
        4 => 55,
        5 => 68,
        6 => 90,
        7 => 95,
        8 => 75,
        9 => 78,
        10 => 88,
        11 => 78,
        12 => 25
      },
      seasonal_adj: %{},
      typical_range_km: 350,
      extended_range_km: 700,
      exceptional_range_km: 1200
    },
    2_304 => %{
      freq_mhz: 2_304,
      label: "2304 MHz",
      o2_db_km: 0.006,
      h2o_coeff: 0.0,
      humidity_effect: :beneficial,
      humidity_penalty: 0.0,
      rain_k: 0.001,
      rain_alpha: 1.15,
      # Per-band weights from 2026-04-18 full-corpus correlation (n=564).
      weights: %{
        humidity: 0.1941,
        time_of_day: 0.0387,
        td_depression: 0.1385,
        refractivity: 0.1638,
        sky: 0.0625,
        season: 0.0868,
        wind: 0.0625,
        rain: 0.0336,
        pwat: 0.1762,
        pressure: 0.0432
      },
      seasonal_base: %{
        1 => 38,
        2 => 40,
        3 => 22,
        4 => 55,
        5 => 68,
        6 => 90,
        7 => 95,
        8 => 75,
        9 => 78,
        10 => 88,
        11 => 78,
        12 => 25
      },
      seasonal_adj: %{},
      typical_range_km: 300,
      extended_range_km: 600,
      exceptional_range_km: 1000
    },
    3_400 => %{
      freq_mhz: 3_400,
      label: "3400 MHz",
      o2_db_km: 0.006,
      h2o_coeff: 0.0,
      humidity_effect: :beneficial,
      humidity_penalty: 0.0,
      rain_k: 0.002,
      rain_alpha: 1.20,
      # Per-band weights from 2026-04-18 full-corpus correlation (n=280).
      weights: %{
        humidity: 0.1996,
        time_of_day: 0.0398,
        td_depression: 0.1251,
        refractivity: 0.1310,
        sky: 0.0642,
        season: 0.0893,
        wind: 0.0642,
        rain: 0.0489,
        pwat: 0.1811,
        pressure: 0.0568
      },
      seasonal_base: %{
        1 => 38,
        2 => 40,
        3 => 22,
        4 => 55,
        5 => 68,
        6 => 90,
        7 => 95,
        8 => 75,
        9 => 78,
        10 => 88,
        11 => 78,
        12 => 25
      },
      seasonal_adj: %{},
      typical_range_km: 250,
      extended_range_km: 550,
      exceptional_range_km: 900
    },
    5_760 => %{
      freq_mhz: 5_760,
      label: "5760 MHz",
      o2_db_km: 0.007,
      h2o_coeff: 0.0,
      humidity_effect: :beneficial,
      humidity_penalty: 0.0,
      rain_k: 0.005,
      rain_alpha: 1.25,
      # Per-band weights from 2026-04-18 full-corpus correlation (n=246).
      weights: %{
        humidity: 0.1829,
        time_of_day: 0.0423,
        td_depression: 0.1205,
        refractivity: 0.0881,
        sky: 0.0683,
        season: 0.0949,
        wind: 0.0683,
        rain: 0.0822,
        pwat: 0.1925,
        pressure: 0.0602
      },
      seasonal_base: %{
        1 => 38,
        2 => 40,
        3 => 22,
        4 => 55,
        5 => 68,
        6 => 90,
        7 => 95,
        8 => 75,
        9 => 78,
        10 => 88,
        11 => 78,
        12 => 25
      },
      seasonal_adj: %{},
      typical_range_km: 220,
      extended_range_km: 500,
      exceptional_range_km: 1000
    },
    10_000 => %{
      freq_mhz: 10_000,
      label: "10 GHz",
      o2_db_km: 0.007,
      h2o_coeff: 0.0,
      humidity_effect: :beneficial,
      humidity_penalty: 0.0,
      rain_k: 0.010,
      rain_alpha: 1.28,
      seasonal_base: %{
        1 => 38,
        2 => 40,
        3 => 22,
        4 => 55,
        5 => 68,
        6 => 90,
        7 => 95,
        8 => 75,
        9 => 78,
        10 => 88,
        11 => 78,
        12 => 25
      },
      seasonal_adj: %{},
      typical_range_km: 200,
      extended_range_km: 500,
      exceptional_range_km: 1000
    },
    24_000 => %{
      freq_mhz: 24_000,
      label: "24 GHz",
      o2_db_km: 0.02,
      h2o_coeff: 0.002,
      humidity_effect: :harmful,
      humidity_penalty: 1.6,
      rain_k: 0.070,
      rain_alpha: 1.07,
      # Per-band weights from 2026-04-18 full-corpus correlation (n=613).
      # Rain climbs sharply (sqrt-dampened rain_k ratio = √7 ≈ 2.6× vs 10 G),
      # refractivity and pwat hold up, td_depression falls out because the
      # temperature signal alone is near-zero — moisture already carries it
      # via :harmful humidity.
      weights: %{
        humidity: 0.1481,
        time_of_day: 0.0532,
        td_depression: 0.0360,
        refractivity: 0.1250,
        sky: 0.0477,
        season: 0.0729,
        wind: 0.0477,
        rain: 0.2147,
        pwat: 0.1344,
        pressure: 0.1203
      },
      seasonal_base: %{
        1 => 88,
        2 => 84,
        3 => 68,
        4 => 62,
        5 => 51,
        6 => 34,
        7 => 18,
        8 => 18,
        9 => 48,
        10 => 68,
        11 => 96,
        12 => 88
      },
      seasonal_adj: %{5 => -4, 6 => -8, 7 => -10, 8 => -10, 9 => -4},
      typical_range_km: 100,
      extended_range_km: 250,
      exceptional_range_km: 500
    },
    47_000 => %{
      freq_mhz: 47_000,
      label: "47 GHz",
      o2_db_km: 0.04,
      h2o_coeff: 0.003,
      humidity_effect: :harmful,
      humidity_penalty: 1.0,
      rain_k: 0.187,
      rain_alpha: 0.93,
      seasonal_base: %{
        1 => 90,
        2 => 88,
        3 => 78,
        4 => 68,
        5 => 55,
        6 => 38,
        7 => 22,
        8 => 22,
        9 => 48,
        10 => 74,
        11 => 96,
        12 => 90
      },
      seasonal_adj: %{},
      typical_range_km: 70,
      extended_range_km: 150,
      exceptional_range_km: 300
    },
    68_000 => %{
      freq_mhz: 68_000,
      label: "68 GHz",
      o2_db_km: 0.90,
      h2o_coeff: 0.007,
      humidity_effect: :harmful,
      humidity_penalty: 1.4,
      rain_k: 0.310,
      rain_alpha: 0.86,
      seasonal_base: %{
        1 => 90,
        2 => 88,
        3 => 78,
        4 => 65,
        5 => 50,
        6 => 32,
        7 => 18,
        8 => 18,
        9 => 44,
        10 => 70,
        11 => 92,
        12 => 90
      },
      seasonal_adj: %{},
      typical_range_km: 40,
      extended_range_km: 80,
      exceptional_range_km: 150
    },
    75_000 => %{
      freq_mhz: 75_000,
      label: "75 GHz",
      o2_db_km: 0.012,
      h2o_coeff: 0.006,
      humidity_effect: :harmful,
      humidity_penalty: 1.2,
      rain_k: 0.345,
      rain_alpha: 0.84,
      seasonal_base: %{
        1 => 90,
        2 => 90,
        3 => 80,
        4 => 68,
        5 => 55,
        6 => 38,
        7 => 22,
        8 => 22,
        9 => 48,
        10 => 74,
        11 => 96,
        12 => 90
      },
      seasonal_adj: %{},
      typical_range_km: 50,
      extended_range_km: 120,
      exceptional_range_km: 250
    },
    122_000 => %{
      freq_mhz: 122_000,
      label: "122 GHz",
      o2_db_km: 0.80,
      h2o_coeff: 0.010,
      humidity_effect: :harmful,
      humidity_penalty: 1.0,
      rain_k: 0.498,
      rain_alpha: 0.77,
      seasonal_base: %{
        1 => 92,
        2 => 90,
        3 => 78,
        4 => 62,
        5 => 45,
        6 => 28,
        7 => 15,
        8 => 15,
        9 => 38,
        10 => 68,
        11 => 92,
        12 => 92
      },
      seasonal_adj: %{},
      typical_range_km: 30,
      extended_range_km: 80,
      exceptional_range_km: 140
    },
    134_000 => %{
      freq_mhz: 134_000,
      label: "134 GHz",
      o2_db_km: 0.08,
      h2o_coeff: 0.015,
      humidity_effect: :harmful,
      humidity_penalty: 1.3,
      rain_k: 0.520,
      rain_alpha: 0.75,
      seasonal_base: %{
        1 => 92,
        2 => 90,
        3 => 78,
        4 => 65,
        5 => 48,
        6 => 30,
        7 => 18,
        8 => 18,
        9 => 42,
        10 => 70,
        11 => 92,
        12 => 92
      },
      seasonal_adj: %{},
      typical_range_km: 40,
      extended_range_km: 100,
      exceptional_range_km: 160
    },
    # 142–145 GHz — between the 118 GHz O2 line and the 183 GHz H2O line.
    # Coefficients linearly interpolate between 134 GHz and 241 GHz; rain
    # tracks ITU-R P.838 in the K-band-extrapolated regime. Ranges scaled
    # from the handful of contacts in the corpus (n=4 at 142 GHz, n=2 at
    # 145 GHz) — these are scaffolding values and will need calibration as
    # the band sees more activity.
    142_000 => %{
      freq_mhz: 142_000,
      label: "142 GHz",
      o2_db_km: 0.05,
      h2o_coeff: 0.025,
      humidity_effect: :harmful,
      humidity_penalty: 1.4,
      rain_k: 0.530,
      rain_alpha: 0.74,
      seasonal_base: %{
        1 => 92,
        2 => 90,
        3 => 78,
        4 => 65,
        5 => 47,
        6 => 28,
        7 => 16,
        8 => 16,
        9 => 40,
        10 => 68,
        11 => 92,
        12 => 92
      },
      seasonal_adj: %{},
      typical_range_km: 35,
      extended_range_km: 80,
      exceptional_range_km: 160
    },
    145_000 => %{
      freq_mhz: 145_000,
      label: "145 GHz",
      o2_db_km: 0.06,
      h2o_coeff: 0.040,
      humidity_effect: :harmful,
      humidity_penalty: 1.5,
      rain_k: 0.535,
      rain_alpha: 0.74,
      seasonal_base: %{
        1 => 92,
        2 => 90,
        3 => 78,
        4 => 64,
        5 => 46,
        6 => 27,
        7 => 15,
        8 => 15,
        9 => 38,
        10 => 66,
        11 => 92,
        12 => 92
      },
      seasonal_adj: %{},
      typical_range_km: 35,
      extended_range_km: 80,
      exceptional_range_km: 150
    },
    241_000 => %{
      freq_mhz: 241_000,
      label: "241 GHz",
      o2_db_km: 0.08,
      h2o_coeff: 0.30,
      humidity_effect: :harmful,
      humidity_penalty: 3.0,
      rain_k: 0.550,
      rain_alpha: 0.70,
      seasonal_base: %{
        1 => 95,
        2 => 92,
        3 => 75,
        4 => 55,
        5 => 35,
        6 => 15,
        7 => 8,
        8 => 8,
        9 => 30,
        10 => 65,
        11 => 95,
        12 => 95
      },
      seasonal_adj: %{},
      typical_range_km: 10,
      extended_range_km: 50,
      exceptional_range_km: 115
    },
    # Sub-mm bands (288–411 GHz) — extrapolated scaffolding so the scoring
    # pipeline doesn't silently drop these contacts. The prod corpus has 1
    # contact per band, all under 1.5 km (effectively LOS), so the absorption
    # values dominate any duct-related score. Coefficients track ITU-R P.676/
    # 838 trends past the 325 GHz H2O line; ranges are nominal.
    288_000 => %{
      freq_mhz: 288_000,
      label: "288 GHz",
      o2_db_km: 0.10,
      h2o_coeff: 0.45,
      humidity_effect: :harmful,
      humidity_penalty: 3.5,
      rain_k: 0.560,
      rain_alpha: 0.68,
      seasonal_base: %{
        1 => 95,
        2 => 92,
        3 => 75,
        4 => 55,
        5 => 35,
        6 => 14,
        7 => 7,
        8 => 7,
        9 => 28,
        10 => 64,
        11 => 95,
        12 => 95
      },
      seasonal_adj: %{},
      typical_range_km: 5,
      extended_range_km: 15,
      exceptional_range_km: 50
    },
    322_000 => %{
      freq_mhz: 322_000,
      label: "322 GHz",
      o2_db_km: 0.12,
      h2o_coeff: 0.55,
      humidity_effect: :harmful,
      humidity_penalty: 4.0,
      rain_k: 0.570,
      rain_alpha: 0.66,
      seasonal_base: %{
        1 => 96,
        2 => 92,
        3 => 74,
        4 => 52,
        5 => 32,
        6 => 12,
        7 => 6,
        8 => 6,
        9 => 26,
        10 => 62,
        11 => 96,
        12 => 96
      },
      seasonal_adj: %{},
      typical_range_km: 5,
      extended_range_km: 15,
      exceptional_range_km: 40
    },
    403_000 => %{
      freq_mhz: 403_000,
      label: "403 GHz",
      o2_db_km: 0.15,
      h2o_coeff: 0.40,
      humidity_effect: :harmful,
      humidity_penalty: 3.0,
      rain_k: 0.580,
      rain_alpha: 0.64,
      seasonal_base: %{
        1 => 96,
        2 => 92,
        3 => 74,
        4 => 52,
        5 => 32,
        6 => 12,
        7 => 6,
        8 => 6,
        9 => 26,
        10 => 62,
        11 => 96,
        12 => 96
      },
      seasonal_adj: %{},
      typical_range_km: 3,
      extended_range_km: 10,
      exceptional_range_km: 30
    },
    411_000 => %{
      freq_mhz: 411_000,
      label: "411 GHz",
      o2_db_km: 0.15,
      h2o_coeff: 0.42,
      humidity_effect: :harmful,
      humidity_penalty: 3.2,
      rain_k: 0.580,
      rain_alpha: 0.64,
      seasonal_base: %{
        1 => 96,
        2 => 92,
        3 => 74,
        4 => 52,
        5 => 32,
        6 => 12,
        7 => 6,
        8 => 6,
        9 => 26,
        10 => 62,
        11 => 96,
        12 => 96
      },
      seasonal_adj: %{},
      typical_range_km: 3,
      extended_range_km: 10,
      exceptional_range_km: 30
    }
  }

  @doc "Returns the band config for the given frequency in MHz, or nil if not found."
  @spec get(integer()) :: map() | nil
  def get(freq_mhz), do: Map.get(@band_configs, freq_mhz)

  @doc "Returns all band configs sorted by frequency (ascending)."
  @spec all_bands() :: [map()]
  def all_bands do
    @band_configs
    |> Enum.sort_by(fn {freq, _config} -> freq end)
    |> Enum.map(fn {_freq, config} -> config end)
  end

  @doc "Returns all supported frequencies sorted ascending."
  @spec all_freqs() :: [integer()]
  def all_freqs do
    @band_configs
    |> Map.keys()
    |> Enum.sort()
  end

  @doc "Returns band options as {label, value_string} tuples for form dropdowns."
  @spec band_options() :: [{String.t(), String.t()}]
  def band_options do
    Enum.map(all_bands(), fn band -> {band.label, to_string(band.freq_mhz)} end)
  end

  @doc "Returns the default scoring weights map. All 10 values sum to 1.0."
  @spec weights() :: map()
  def weights, do: @weights

  @doc """
  Returns the scoring weights for a specific band configuration.

  Bands with enough contacts to support a stable gradient-descent fit
  carry a `:weights` override map. Bands without it fall back to the
  global defaults. `nil` is accepted so callers that don't know the band
  can ask for defaults without an extra guard.
  """
  @spec weights(map() | nil) :: map()
  def weights(nil), do: @weights
  def weights(%{weights: override}) when is_map(override), do: override
  def weights(%{}), do: @weights

  @doc "Returns the 12-element sunrise hour table (Jan-Dec, local time)."
  @spec sunrise_table() :: [float()]
  def sunrise_table, do: @sunrise_table

  @doc "Returns score tier definitions ordered by threshold descending."
  @spec tiers() :: [map()]
  def tiers, do: @tiers

  @doc "Returns humidity beneficial thresholds as {hour, score} tuples."
  @spec humidity_beneficial_thresholds() :: [{integer(), integer()}]
  def humidity_beneficial_thresholds, do: @humidity_beneficial_thresholds

  @doc "Returns the default humidity beneficial score."
  @spec humidity_beneficial_default() :: integer()
  def humidity_beneficial_default, do: @humidity_beneficial_default

  @doc "Returns refractivity thresholds as {gradient, score_beneficial, score_harmful} tuples."
  @spec refractivity_thresholds() :: [{number(), number(), number()}]
  def refractivity_thresholds, do: @refractivity_thresholds

  @doc "Returns the default refractivity scores as {beneficial, harmful}."
  @spec refractivity_default() :: {number(), number()}
  def refractivity_default, do: @refractivity_default
end
