feat(atis): inline METAR expansion, airport name lookup, acronym fix

ATIS text often arrives in raw METAR form (e.g.
"METAR EDDF 281050Z AUTO 02008KT 320V070 CAVOK 24/02 Q1025 NOSIG"),
which TTS reads as letter-by-letter spelling. The normalizer now expands
the full WMO Code Form FM 15-XV vocabulary inline: DDHHMMZ date stamps,
compressed wind (with gusts, VRB, calm), wind variability ranges, RVR
(R25L/1500N), wind shear, slash-form temp/dewpoint, Q/A pressure,
NSC/SKC/CLR/NCD/VV cloud codes, weather phenomena (with intensity and
descriptors), recent-weather RE prefix, BECMG/TEMPO/FM/TL/AT trend
codes, and strips RMK remarks. Plus ATIS/METAR/SPECI get lowercased
so TTS pronounces them as words (pilots SPELL ILS/QNH/VOR so those
stay uppercase).

Airport ICAO codes are substituted with their OpenAIP name when the
frequencies endpoint returns one. New `airportName` field added to
the FrequencyResponse for that. Adds 7 test cases covering the user-
reported EDDF sample plus calm/VRB/gust winds, RVR, weather codes,
cloud specials, trend codes, and RMK stripping.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
itsrubberduck
2026-05-28 13:45:15 +02:00
parent a9a391c301
commit f894eb4928
5 changed files with 385 additions and 15 deletions

View File

@@ -14,6 +14,7 @@ interface FrequencyEntry {
interface FrequencyResponse {
icao: string
airportName?: string
sources: {
vatsim: boolean
openaip: boolean
@@ -124,6 +125,7 @@ export default defineEventHandler(async (event): Promise<FrequencyResponse> => {
const icao = icaoParam.toUpperCase()
const frequencyMap = new Map<string, FrequencyEntry>()
let airportName: string | undefined
let vatsimSuccess = false
let openaipSuccess = false
@@ -205,6 +207,12 @@ export default defineEventHandler(async (event): Promise<FrequencyResponse> => {
for (const airport of items) {
// Real field is `icaoCode`, not `icao`
if ((airport?.icaoCode || '').toUpperCase() !== icao) continue
// Airport-level metadata: `name` (e.g. "Frankfurt am Main"), `municipality` (city only)
if (!airportName) {
const name = typeof airport?.name === 'string' ? airport.name.trim() : ''
const muni = typeof airport?.municipality === 'string' ? airport.municipality.trim() : ''
airportName = name || muni || undefined
}
// Frequencies live under `airport.frequencies[]`; each item has:
// value (MHz string, e.g. "122.035")
// type (numeric code, e.g. 5 for Delivery)
@@ -253,6 +261,7 @@ export default defineEventHandler(async (event): Promise<FrequencyResponse> => {
return {
icao,
airportName,
sources: {
vatsim: vatsimSuccess,
openaip: openaipSuccess