This is a real-time odor scoring heuristic that runs entirely in a web browser. It ingests publicly available weather observations and forecasts, combines them with emissions data from LDEQ air permits, and produces a 0–100 odor intensity score for each point on a map, updated every 30 minutes across a 48-hour forecast horizon.
This is not a regulatory dispersion model. It does not replace AERMOD, CALPUFF, or any EPA-approved model. Key differences:
The model borrows specific physics formulations from AERMOD (EPA’s regulatory Gaussian plume model, version 24142) where possible and documents every deviation where simplification was necessary.
The core prediction is a product of seven dimensionless factors:
$$\text{Score} = E \times T \times I \times D \times H \times G \times M \times 100$$| Symbol | Factor | Range | What it captures |
|---|---|---|---|
| $E$ | Emission | 0–1 | Source strength: temperature-driven volatilization, source category weighting |
| $T$ | Transport | 0–1 | Wind direction, distance decay, meander |
| $I$ | Inversion | 1.0–1.8 | Multi-hour accumulation under persistent trapping |
| $D$ | Diurnal | 0.5–1.0 | Time-of-day atmospheric mixing regime |
| $H$ | Humidity | 0.6–1.3 | Moisture effects on odor perception and droplet absorption |
| $G$ | Terrain | 0.5–1.6 | Elevation-based drainage, valley channeling |
| $M$ | Mixing height | 0.6–1.0 | Boundary layer depth from AERMOD-inspired vertical physics |
| Source | Parameters | Update frequency | Role |
|---|---|---|---|
| KAEX ASOS | Temperature, dewpoint, humidity, wind speed/direction, pressure, visibility, ceiling, cloud cover | ~30 min (METAR) | Ground truth for current hour; blended into forecast |
| NWS hourly forecast (LCH gridpoint 88,143) | Temperature, dewpoint, humidity, wind speed/direction, cloud cover, short forecast text | ~1 hr | Primary forecast source (0–48h) |
| Open-Meteo (ECMWF) | Temperature, dewpoint, humidity, wind speed/direction, cloud cover, surface pressure | ~6 hr (model runs) | Secondary forecast; sole source of forecast pressure |
| NWS Area Forecast Discussion (LCH) | Free-text meteorological analysis | ~2×/day | Scanned for inversion/stagnation keywords |
NWS and Open-Meteo forecasts are blended with time-dependent weights. Near-term forecasts favor NWS (local forecaster skill); extended forecasts favor Open-Meteo (ECMWF global model). Wind direction is blended using vector averaging to handle the 360°/0° wraparound.
$$w_{\text{NWS}} = \begin{cases} 0.70 & \text{lead} \leq 6\text{h} \\ 0.70 - \frac{0.40}{42}(\text{lead} - 6) & 6\text{h} < \text{lead} < 48\text{h} \\ 0.30 & \text{lead} \geq 48\text{h} \end{cases}$$The following standard AERMOD/AERMET inputs are unavailable to this model:
Atmospheric stability is represented as a single value from 0 (strongly unstable/convective) to 1 (strongly stable/inversion). This proxy is derived from a weighted combination of nine surface observation signals processed by the inversion detection module. The inversion score (0–1) is then mapped to stability:
$$\text{stability} = 0.5 + \text{inversionScore} \times 0.45$$With overrides:
The inversion score is a weighted sum of nine proxy signals. Weights depend on whether KAEX observation data is blended into the current period:
| Signal | What it measures | Obs weight | Fcst weight |
|---|---|---|---|
| Dewpoint spread | $|T - T_d|$; tight spread suggests fog/saturation | 0.10 | 0.10 |
| Temperature trend | Overnight temp rising/flat = inversion; cooling = normal | 0.10 | 0.10 |
| Wind speed | Calm wind allows stratification to persist | 0.15 | 0.20 |
| Time-of-day prior | Bayesian prior for radiation inversions (peak 3–8 AM) | 0.05 | 0.05 |
| AFD text keywords | NWS Area Forecast Discussion scanned for “inversion,” “fog,” “stagnation,” “high pressure,” “ridge” | 0.10 | 0.15 |
| Visibility | KAEX ASOS visibility; low = trapping confirmed | 0.15 | — |
| Pressure trend | Steady/rising = stagnation; falling = frontal mixing | 0.10 | — |
| Sky condition | Clear night sky + Brutsaert radiative cooling estimate | 0.10 | 0.20 |
| Persistence | Exponential carryover from overnight formation (half-life ~2h) | 0.15 | 0.20 |
Wind veto: When wind speed $\geq 8$ mph, the inversion score is floored to $\min(0.2,\; \text{AFD} \times 0.2)$ regardless of other signals.
When surface pressure is $\geq 1025$ mb, wind $\leq 8$ mph, and it is evening/nighttime (18:00–09:00), a floor is applied to the inversion score:
| Pressure (mb) | Inversion score floor |
|---|---|
| $\geq 1030$ | 0.65 |
| $\geq 1025$ | 0.50 |
| $\geq 1020$ | 0.35 |
AERMOD classifies stability solely from the Monin-Obukhov length $L$:
$L$ is computed from friction velocity $u_*$, sensible heat flux $H_0$, and surface temperature via the surface energy balance in AERMET. This requires net radiation measurements or cloud cover with a radiation model, plus surface roughness and Bowen ratio — none of which we have.
The boundary layer depth $z_i$ (meters) is estimated from three mechanisms, taking the minimum of applicable constraints:
where $u_*$ is estimated from neutral log-law:
$$u_* = \frac{\kappa \cdot U_{10}}{\ln(z_{\text{ref}} / z_0)}$$with $\kappa = 0.4$, $z_{\text{ref}} = 10$ m, $z_0 = 0.5$ m (suburban/light industrial).
where $I_{\text{solar}}$ is the normalized solar intensity (0 at horizon, 1 at solar noon) from Spencer (1971) solar position equations, and $f_T$ is a temperature boost (1.0 below 70°F, 1.1 at 70–85°F, 1.3 above 85°F).
An earlier version of this model included a pressure-based subsidence cap: $z_{i,\text{sub}} = \max(80,\; 800 - 20 \cdot (P - 1013))$. This was removed after analysis of KSHV (Shreveport) radiosonde soundings from March 21–29, 2026, which showed no reliable relationship between surface pressure and inversion height:
| Date/Time (CDT) | Sfc P (mb) | Inversion base (m AGL) | Strength |
|---|---|---|---|
| Mar 21, 1 PM | 1005 | ~830 | Strong (+3.6°C) |
| Mar 24, 7 PM | 1007 | ~1700 | Weak |
| Mar 25, 7 PM | 1007 | ~1900 | Strong (+2.4°C) |
| Mar 26, 7 PM | 1005 | ~1500 | Very strong (+4.2°C) |
| Mar 27, 7 PM | 1011 | None | Unstable (pre-frontal) |
| Mar 28, 7 PM | 1017 (SHV) / 1026 (KAEX) | ~830 | Moderate (+1.8°C) |
The coefficient 20 m/mb had no literature basis, and the data show that inversions at ~830 m can occur at both 1005 mb and 1017 mb, while no inversion exists at 1011 mb. The synoptic pattern (ridge position, frontal timing, warm advection) determines inversion structure, not surface pressure alone. Mixing height estimation without upper-air soundings remains the model’s weakest link.
When inversion score $> 0.5$:
$$z_i \leq 300 \times (1.5 - \text{inversionScore})$$At score 1.0, this caps $z_i$ at 150 m. At score 0.85 (strong fog inversion), $z_i \leq 195$ m.
The transport factor uses an empirical angular-sigma approach rather than Gaussian lateral dispersion ($\sigma_y$). For a receptor at distance $d$ (km) and bearing offset $\theta$ from the downwind direction:
| Wind speed | $\sigma_{\theta}$ (degrees) | Base max distance (km) |
|---|---|---|
| $\leq 8$ mph | 50° | 5 |
| 8–15 mph | 30° | 7 |
| $> 15$ mph | 20° | 10 |
where the decay exponent $\alpha$ and max distance $d_{\max}$ are stability-dependent:
$$\alpha = 2.2 - 1.2 \times \text{stability}$$ $$d_{\max} = d_{\text{base}} \times (0.85 + 0.75 \times \text{stability})$$
Under strong stability ($s = 0.9$): $\alpha = 1.12$ (slow decay), $d_{\max} = 5 \times 1.525 = 7.6$ km.
Under instability ($s = 0.2$): $\alpha = 1.96$ (fast decay), $d_{\max} = 5 \times 1.0 = 5.0$ km.
Under calm winds, the plume meanders in all directions rather than maintaining a coherent fan.
The meander fraction $m$ determines the blend between circular (pooling) and directional (fan) modes.
The FRAN equation follows AERMOD’s MEANDR subroutine in calc2.f:
where $\overline{U}^2_{\text{mean}} = \max(0.01,\; U^2 - 2\sigma_v^2)$, $T = 86400$ s (24-hour meander timescale), and $t_{\text{trav}} = d/U$ is the travel time.
Why we kept these values: In AERMOD, $\sigma_v$ drives both the meander fraction AND the lateral dispersion parameter $\sigma_y$. We use $\sigma_v$ only for meander — our lateral spread uses a separate angular-sigma model (Section 5.1). The “too high” $\sigma_v$ may compensate for the angular model’s different treatment of crosswind spread. Field validation of plume boundaries during morning inversion events showed good agreement with observed odor extent using the current parameterization. Correcting $\sigma_v$ to the AERMOD formula (using our estimated $u_*$, which is itself approximate) would lose this empirical validation without gaining a more physically grounded input.
where $F_{\text{dilution}}$ accounts for wind dilution at wind speeds above 5 mph.
where $d_{\text{calm}} = 4.0 \times (0.85 + 0.75 \times s)$ km and $\alpha_c = \max(0.8, \alpha - 0.4)$.
sigmas.f), producing a Gaussian crosswind profile $F_y = \exp(-y^2 / 2\sigma_y^2) / (\sqrt{2\pi}\,\sigma_y)$.
We use an angular sigma ($\sigma_{\theta} = 20$–$50$°) instead.
The 50° angular sigma at low wind speeds is intentionally wider than the Gaussian $\sigma_y$ would predict —
this accounts for sub-hourly wind direction variability that an hourly-averaged observation cannot resolve.
Field validation confirmed that the angular model produces plume boundaries matching observed odor extent
during morning inversion events. The distance decay exponent $\alpha$ is empirically tuned, not derived
from $\sigma_y \times \sigma_z$ physics.
Estimated from friction velocity with stability reduction (AERMOD siggrid.f form):
The factor 1.3 is the AERMOD mechanical $\sigma_w$ coefficient near the surface. The $(1 - 0.6s)$ term reduces vertical turbulence under stable conditions.
This is the AERMOD formula from sigmas.f for stable conditions:
where $t = x / U$ is travel time and $N$ is the Brunt-Väisälä frequency:
$$N = 0.005 + 0.035 \times s$$Asymptotic behavior: As $x \to \infty$, $\sigma_z$ saturates at $\sqrt{0.54 \cdot \sigma_w / N}$. For strong stability ($s = 0.9$, $N = 0.037$ s$^{-1}$), this cap is approximately 5–8 m. This is the key mechanism that extends plume range under stable conditions: vertical spread stops growing, so concentration decays only as $1/\sigma_y$ (one dimension) instead of $1/(\sigma_y \cdot \sigma_z)$ (two dimensions).
$\sigma_z$ is additionally capped at $0.8 \times z_i$ (cannot exceed mixing layer depth).
Ground-level concentration from an elevated source requires accounting for reflections off the
ground ($z = 0$) and the mixing height lid ($z = z_i$). Following AERMOD calc2.f,
the vertical term uses an image-source sum:
where $H_e$ is the effective release height. For the transport scoring, $H_e = 2$ m (ground-level sources dominate community odor). The sum uses 5 image pairs ($n = -2$ to $2$), which is sufficient when $\sigma_z < z_i$.
Well-mixed limit: When $\sigma_z \to z_i$, the image sum converges to $1/z_i$. This is the classic box-model result: concentration is inversely proportional to mixing height. Under subsidence (low $z_i$), $F_z$ is large, naturally producing higher concentrations without a separate multiplier.
The mixing height factor compares the vertical concentration under current conditions to a neutral reference at 2 km downwind:
$$M = \frac{F_z(z_i, \sigma_z, U_{\text{eff}})}{F_z(z_{i,\text{ref}}, \sigma_{z,\text{ref}}, U_{\text{ref}})}$$Reference conditions: $z_{i,\text{ref}} = 500$ m, stability $= 0.4$, $U_{\text{ref}} = 3.0$ m/s. The ratio is clamped to [0.6, 1.0] — the factor can reduce scores during strong daytime mixing but does not amplify above 1.0 because the morning inversion model was already calibrated without it.
Elevated stack emissions (Pineville kilns, 25–32 ft / ~9 m stacks) undergo buoyancy-driven
plume rise. The formulas follow Briggs (1975, 1984) as implemented in AERMOD prise.f:
where $x_f = 49 \cdot F_b^{5/8}$ when $F_b < 55$ m$^4$/s$^3$.
Note: Plume rise is used in the chemical overlay visualization and the
groundLevelFraction calculation, but the transport scoring uses $H_e = 2$ m for
all plants because ground-level sources (cylinder operations, storage yards, fugitives) dominate
the odor signal at community distances.
KAEX ASOS measures wind at 10 m. Wind at other heights uses neutral log-law (AERMOD windgrid.f):
with $z_0 = 0.5$ m and $z_{\text{ref}} = 10$ m. The ratio is capped at 2.0 to prevent extreme extrapolation.
VOC emission rates from open-air storage yards follow the Clausius-Clapeyron relation for vapor pressure. For naphthalene (the dominant odor compound at the Alexandria facility):
$$\text{VP ratio} = 2^{(T_{\text{eff}} - 77) / 15}$$where 77°F is the reference temperature and the vapor pressure doubles every 15°F. For cresol (dominant at Pineville): the doubling interval is 18°F. The effective temperature $T_{\text{eff}}$ includes a solar heating boost for dark creosote surfaces during daytime, scaled by astronomical solar elevation angle (Spencer 1971).
Each facility’s emissions are split into source categories with time-of-day-dependent weights. Source characterization is derived from LDEQ air permits and production data.
| Category | Release height | Day weight | Night weight | Driver |
|---|---|---|---|---|
| Alexandria: tie yard (area) | Ground | 0.65 | 0.40 | Solar-heated surfaces |
| Alexandria: cylinders (point) | Ground | 0.35 | 0.60 | Process at 140°F, 24/7 |
| Pineville: pole yard (area) | Ground | 0.30 | 0.10 | Solar-heated surfaces |
| Pineville: ground process | Ground | 0.25 | 0.35 | Cylinders, tanks, fugitives |
| Pineville: kiln stacks | 9 m | 0.45 | 0.55 | Batch process, 65% duty cycle |
Terrain elevation is interpolated from 60+ USGS 1/3 arc-second DEM control points using inverse-distance-weighted (IDW) interpolation with $w = 1/d^2$.
During calm/inversion conditions, terrain modifies the score:
The sky condition signal in the inversion detector uses the Brutsaert (1975) clear-sky atmospheric emissivity to estimate net radiative cooling rate:
$$\varepsilon_{\text{clear}} = 1.24 \cdot \left(\frac{e_a}{T_K}\right)^{1/7}$$with the Li et al. (2017) nighttime correction ($+0.035$) and Crawford & Duchon (1999) cloud modification ($\varepsilon_{\text{all}} = \varepsilon_{\text{clear}} \times (1 + 0.22 \cdot CF^{2.75})$). Net cooling: $R = \sigma T^4 (1 - \varepsilon)$, normalized to a 0–1 signal over the range 20–70 W/m².
These formulas are applied as published with no modification.
| Component | AERMOD approach | Our approach | Rationale |
|---|---|---|---|
| Stability | Monin-Obukhov length $L$ from energy balance | 0–1 proxy from 9 surface signals | No flux measurements available |
| Mixing height | AERMET with upper-air soundings | Estimated from $u_*$, solar heating, inversion score (pressure proxy tested and removed) | No radiosonde data; pressure-to-zi relationship not supported by KSHV sounding analysis |
| Lateral dispersion | $\sigma_y$ from M-O similarity | Angular sigma (20–50°) with empirical decay | Sub-hourly wind variability; validated against field obs |
| Vertical dispersion | $\sigma_z$ from M-O with measured $N$ | $\sigma_z$ from AERMOD formula with estimated $N$ | Formula is standard; $N$ input is heuristic |
| BVF ($N$) | $\sqrt{g \cdot d\theta/dz \;/\; T}$ from soundings | $0.005 + 0.035 \times s$ (linear mapping) | No temperature profile data |
| Wind profile | M-O similarity with $\psi_M$ correction | Neutral log-law only | $L$ not available for stability correction |
| Concentration output | $\mu$g/m³ | Dimensionless 0–100 score | No $Q$ in g/s; calibrated to odor perception |
| Emission rates | Direct input ($Q$ in g/s) | Relative intensity from permits + temperature | Real-time emission data not available |
| Terrain | AERMAP with dividing streamline | Elevation-difference heuristic | Simplified for browser computation |
| Subsidence detection | Not needed (receives $z_i$ from AERMET) | Inversion score floor when P $\geq$ 1025 mb (stability proxy, not $z_i$ estimate) | Surface pressure indicates stable conditions but does not reliably predict $z_i$ |
This section lists every place where the model bridges a gap between published physics and
available data with an invented coefficient. These are known unknowns —
we know exactly what we don’t know, we know what measurement would answer it, and we
are using a placeholder until that data is available. Each constant is extracted by name at
the top of prediction.js so it can be found, questioned, and replaced.
Constant: ZI_MECH_COEFF = 2300
| What we know | The Zilitinkevich (1972) formula relates stable mixing height to $u_*$, Monin-Obukhov length $L$, and the Coriolis parameter: $z_i = 0.4\sqrt{u_* L / f}$. This is published, peer-reviewed physics. |
| What we don’t know | The Monin-Obukhov length $L$. Computing $L$ requires sensible heat flux, which requires net radiation measurements we do not have. We substituted our 0–1 stability proxy for $L$ and absorbed the dimensional mismatch into the coefficient. |
| What the coefficient is | The number 2300 has no literature basis. It was chosen to produce $z_i \approx 150$ m at $u_* = 0.3$ m/s, stability $= 0.7$ (a typical stable night). It has not been validated against sounding data. |
| What would replace it | Regression of KSHV radiosonde mixing heights against concurrent KAEX surface observations. The nearest sounding station (Shreveport, 190 km NW) launches at 00Z and 12Z daily. A dataset of $z_i$ vs. ($u_*$, stability, season) would yield a real coefficient — or reveal that the functional form $u_*^{3/2}/\sqrt{s}$ is itself wrong. |
Constants: ZI_CONV_BASE = 400, ZI_CONV_SOLAR = 1200
| What we know | Daytime mixing height is driven by surface sensible heat flux, which is driven by solar radiation. $z_i$ grows through the morning and peaks in the early afternoon. Typical values for central Louisiana: 500–2000 m depending on season and cloud cover. |
| What we don’t know | The actual surface heat flux. Computing it requires net radiation (measured or parameterized from cloud cover + albedo + soil moisture), none of which we have. We substituted solar elevation angle as a proxy for heat flux. |
| What the coefficients are | 400 and 1200 are invented. They were chosen to produce $z_i \approx 500$ m at sunrise and $\sim$1600 m at solar noon, which seemed reasonable. There is no published formula of this form. |
| What would replace them | KSHV 12Z (7 AM) and 00Z (7 PM) soundings paired with KAEX surface conditions. Published alternatives exist: Betts (1992) and Stull (1988) provide convective scaling diagnostics, but they still require heat flux estimates. A simpler approach: build a $z_i$ climatology for KSHV binned by month, hour, and cloud cover. |
Constants: BVF_BASE = 0.005, BVF_SLOPE = 0.035
| What we know | The Brunt-Väisälä frequency is $N = \sqrt{g \cdot (\partial\theta/\partial z) / T}$. This is fundamental atmospheric physics. Typical values: 0.005–0.01 s$^{-1}$ (neutral/weakly stable), 0.02–0.04 s$^{-1}$ (strongly stable nocturnal inversion). The BVF controls the asymptotic cap on $\sigma_z$ in the AERMOD formula. |
| What we don’t know | The potential temperature gradient $\partial\theta/\partial z$. This requires a vertical temperature profile from a radiosonde. We have no vertical measurements of any kind. |
| What the coefficients are | 0.005 and 0.035 define a linear mapping from our 0–1 stability proxy to the BVF range. We assumed the relationship is linear. It may not be — inversions can form abruptly (a step change in $N$) rather than scaling smoothly with surface signals. |
| What would replace them | Compute $N$ from KSHV soundings at multiple levels (surface to 1000 m AGL), then plot observed $N$ against our stability proxy for the same hour. This would reveal the actual functional form — linear, exponential, step function, or something else entirely. |
Constants: SW_COEFF = 1.3 (AERMOD, published), SW_STAB_DAMP = 0.6 (invented)
| What we know | AERMOD computes mechanical $\sigma_w = 1.3\,u_* \cdot \sqrt{1 - z/z_i}$ near the surface (from siggrid.f). The 1.3 coefficient is published and standard. Under stable conditions, vertical turbulence is suppressed by stratification. |
| What we don’t know | How much stability suppresses $\sigma_w$ in our model. AERMOD uses the full Monin-Obukhov similarity profile with height dependence. We evaluate only at the surface and need a single reduction factor. |
| What the coefficient is | The 0.6 stability damping factor is invented. It reduces $\sigma_w$ to 40% of neutral under strong stability ($s = 1.0$). We do not know if this is correct. It could be 0.4 or 0.8 — we have no direct measurement to constrain it. |
| What would replace it | Indirectly, through the odor observation dataset. If the model systematically under-predicts at long range under stable conditions, the damping may be too strong ($\sigma_w$ too small, $\sigma_z$ capped too low, concentration too near-source). If it over-predicts at long range, the damping is too weak. The regression residuals from geo-tagged odor reports, binned by stability class and distance, would constrain this. |
Constants: INV_CAP_BASE = 300, INV_CAP_OFFSET = 1.5, INV_CAP_THRESHOLD = 0.5
| What we know | Strong surface inversions compress the mixing height. A fog event with strong radiation cooling can produce $z_i$ as low as 50–200 m. Our inversion detection score (Section 3) correlates with these conditions. |
| What we don’t know | The actual mapping between our inversion score and $z_i$. Is a score of 0.85 really associated with $z_i = 195$ m? Or is it 100 m? Or 400 m? |
| What the coefficients are | 300, 1.5, and 0.5 are invented. They were chosen because the resulting scores (60–65 near Pineville under fog) matched one validated morning event (March 25, 2026). One data point is not a calibration. |
| What would replace them | Pair our inversion scores with the concurrent KSHV 12Z sounding $z_i$. If the sounding shows $z_i = 200$ m when our score is 0.85, the formula is close. If it shows 400 m, we are over-compressing. A scatter plot of (inversion score, observed $z_i$) from 30+ sounding pairs would determine these constants — or show that a linear formula is the wrong shape. |
The $\sigma_v$ input to the FRAN equation (Section 5.1) and the meander timescale $T$ are the highest-priority known unknowns. See Section 5.1 for the full deviation analysis. These will be the first targets for the odor observation regression described in Section 13.
| What we know | The FRAN equation structure is from AERMOD. Hanna (1990) showed $\sigma_v$ maintains ~0.5 m/s in calm conditions. EPA’s 2021 low-wind white paper recommended a 12-hour meander timescale. |
| What we don’t know | The correct $\sigma_v$ for our angular-sigma lateral model (which differs from AERMOD’s $\sigma_y$-based model). The current value may be compensating for other simplifications. |
| What the values are | $\sigma_v = 0.5 \times \min(U, 2.0)$ and $T = 86400$ s (24 hours). Neither is derived from local data. |
| What would replace them | Regression of model residuals (predicted − observed intensity) against wind speed, distance, and angle from downwind. Systematic over-prediction at off-axis points means too much meander. Systematic under-prediction means too little. |