feat: Log unknown METAR parts with full string for debugging

Co-authored-by: aider (gemini/gemini-2.5-pro) <aider@aider.chat>
This commit is contained in:
2026-02-14 15:36:20 -07:00
parent cd87f98f18
commit 6f375aed92
+23 -13
View File
@@ -60,8 +60,9 @@
// --- Decoding Functions --- // --- Decoding Functions ---
function decodeAirport(code) { function decodeAirport(code, metarString) {
if (code === 'CYYC') return "CYYC: Calgary International Airport"; if (code === 'CYYC') return "CYYC: Calgary International Airport";
console.log(`Unknown Airport code: ${code} in METAR: ${metarString}`);
return `${code}: Unknown Airport`; return `${code}: Unknown Airport`;
} }
@@ -89,7 +90,7 @@
return `${code}: Visibility ${vis} statute miles`; return `${code}: Visibility ${vis} statute miles`;
} }
function decodeWeather(code) { function decodeWeather(code, metarString) {
const originalCode = code; const originalCode = code;
const intensityMap = { const intensityMap = {
'-': 'Light ', '-': 'Light ',
@@ -133,6 +134,7 @@
} else if (weatherMap[chunk]) { } else if (weatherMap[chunk]) {
decodedChunks.push(weatherMap[chunk]); decodedChunks.push(weatherMap[chunk]);
} else { } else {
console.log(`Unknown weather chunk: ${chunk} in code: ${code} in METAR: ${metarString}`);
decodedChunks.push(`unknown (${chunk})`); decodedChunks.push(`unknown (${chunk})`);
} }
if (precipitationTypes.includes(chunk)) { if (precipitationTypes.includes(chunk)) {
@@ -149,7 +151,7 @@
return `${originalCode}: ${decoding.trim()}${vicinity}`; return `${originalCode}: ${decoding.trim()}${vicinity}`;
} }
function decodeClouds(code) { function decodeClouds(code, metarString) {
const coverMap = { const coverMap = {
'SKC': 'Sky clear', 'SKC': 'Sky clear',
'CLR': 'Sky clear', 'CLR': 'Sky clear',
@@ -160,7 +162,10 @@
}; };
const coverType = code.substring(0, 3); const coverType = code.substring(0, 3);
const cover = coverMap[coverType]; const cover = coverMap[coverType];
if (!cover) return `${code}: Unknown cloud information`; if (!cover) {
console.log(`Unknown cloud information: ${code} in METAR: ${metarString}`);
return `${code}: Unknown cloud information`;
}
if (coverType === 'SKC' || coverType === 'CLR') { if (coverType === 'SKC' || coverType === 'CLR') {
return `${code}: ${cover}`; return `${code}: ${cover}`;
@@ -188,7 +193,7 @@
return `SLP${pressure}: Sea-level pressure ${hpa.toFixed(1)} hPa`; return `SLP${pressure}: Sea-level pressure ${hpa.toFixed(1)} hPa`;
} }
function decodeCloudTypesRemark(code) { function decodeCloudTypesRemark(code, metarString) {
const cloudTypes = { const cloudTypes = {
'CI': 'Cirrus', 'CI': 'Cirrus',
'CC': 'Cirrocumulus', 'CC': 'Cirrocumulus',
@@ -208,7 +213,11 @@
for (const match of matches) { for (const match of matches) {
const cloudCode = match[1]; const cloudCode = match[1];
const oktas = match[2]; const oktas = match[2];
const cloudName = cloudTypes[cloudCode] || `Unknown cloud type (${cloudCode})`; let cloudName = cloudTypes[cloudCode];
if (!cloudName) {
cloudName = `Unknown cloud type (${cloudCode})`;
console.log(`Unknown cloud type in remark: ${cloudCode} in METAR: ${metarString}`);
}
decodedParts.push(`${oktas}/8 ${cloudName}`); decodedParts.push(`${oktas}/8 ${cloudName}`);
} }
@@ -230,7 +239,7 @@
return `${code}: ${weatherName} obscuring ${oktas}/8 of the sky`; return `${code}: ${weatherName} obscuring ${oktas}/8 of the sky`;
} }
function decodeRemarks(parts) { function decodeRemarks(parts, metarString) {
let decoded = ["RMK: Remarks"]; let decoded = ["RMK: Remarks"];
const weatherRegex = /^([+-]|VC)?(MI|PR|BC|DR|BL|SH|TS|FZ|DZ|RA|SN|SG|IC|PL|GR|GS|UP|BR|FG|FU|VA|DU|SA|HZ|PY|PO|SQ|FC|SS|DS|TR)+$/; const weatherRegex = /^([+-]|VC)?(MI|PR|BC|DR|BL|SH|TS|FZ|DZ|RA|SN|SG|IC|PL|GR|GS|UP|BR|FG|FU|VA|DU|SA|HZ|PY|PO|SQ|FC|SS|DS|TR)+$/;
const obscurationRegex = /^(FG|BR|FU|HZ|DU|SA)\d$/; const obscurationRegex = /^(FG|BR|FU|HZ|DU|SA)\d$/;
@@ -263,7 +272,7 @@
} else if (obscurationRegex.test(part)) { } else if (obscurationRegex.test(part)) {
decoded.push(` - ${decodeObscurationRemark(part)}`); decoded.push(` - ${decodeObscurationRemark(part)}`);
} else if (part.match(/^([A-Z]{2}\d)+$/)) { } else if (part.match(/^([A-Z]{2}\d)+$/)) {
decoded.push(` - ${decodeCloudTypesRemark(part)}`); decoded.push(` - ${decodeCloudTypesRemark(part, metarString)}`);
} else if (part === 'VIRGA' && i + 1 < parts.length && directions[parts[i+1]]) { } else if (part === 'VIRGA' && i + 1 < parts.length && directions[parts[i+1]]) {
const direction = directions[parts[i+1]]; const direction = directions[parts[i+1]];
decoded.push(` - VIRGA ${parts[i+1]}: Virga to the ${direction.toLowerCase()}`); decoded.push(` - VIRGA ${parts[i+1]}: Virga to the ${direction.toLowerCase()}`);
@@ -279,10 +288,11 @@
decoded.push(` - ${part} ${parts[i+1]}: ${cloudName} clouds are translucent (thin)`); decoded.push(` - ${part} ${parts[i+1]}: ${cloudName} clouds are translucent (thin)`);
i++; // Consume 'TR' part i++; // Consume 'TR' part
} else if (weatherRegex.test(part)) { } else if (weatherRegex.test(part)) {
decoded.push(` - ${decodeWeather(part)}`); decoded.push(` - ${decodeWeather(part, metarString)}`);
} else if (part.match(/^(AC|CI|CC)/)) { } else if (part.match(/^(AC|CI|CC)/)) {
decoded.push(` - ${part}: Cloud types and coverage details`); decoded.push(` - ${part}: Cloud types and coverage details`);
} else { } else {
console.log(`Other remark: ${part} in METAR: ${metarString}`);
decoded.push(` - ${part}: Other remark`); decoded.push(` - ${part}: Other remark`);
} }
} }
@@ -300,7 +310,7 @@
if (parts[index] && parts[index].length === 4 && parts[index].match(/^[A-Z][A-Z0-9]{3}$/)) { if (parts[index] && parts[index].length === 4 && parts[index].match(/^[A-Z][A-Z0-9]{3}$/)) {
airportTimeAutoRaw.push(parts[index]); airportTimeAutoRaw.push(parts[index]);
airportTimeAutoDecoded.push(decodeAirport(parts[index])); airportTimeAutoDecoded.push(decodeAirport(parts[index], metarString));
index++; index++;
} }
if (parts[index] && parts[index].endsWith('Z')) { if (parts[index] && parts[index].endsWith('Z')) {
@@ -331,9 +341,9 @@
} else if (part.endsWith('SM') || part.match(/^\d+$/) || part.match(/^\d\/\dSM$/)) { } else if (part.endsWith('SM') || part.match(/^\d+$/) || part.match(/^\d\/\dSM$/)) {
sections.push({ raw: part, decoded: decodeVisibility(part) }); sections.push({ raw: part, decoded: decodeVisibility(part) });
} else if (part.match(/^([+-]|VC)?(MI|PR|BC|DR|BL|SH|TS|FZ|DZ|RA|SN|SG|IC|PL|GR|GS|UP|BR|FG|FU|VA|DU|SA|HZ|PY|PO|SQ|FC|SS|DS|TR)+$/)) { } else if (part.match(/^([+-]|VC)?(MI|PR|BC|DR|BL|SH|TS|FZ|DZ|RA|SN|SG|IC|PL|GR|GS|UP|BR|FG|FU|VA|DU|SA|HZ|PY|PO|SQ|FC|SS|DS|TR)+$/)) {
sections.push({ raw: part, decoded: decodeWeather(part) }); sections.push({ raw: part, decoded: decodeWeather(part, metarString) });
} else if (part.match(/^(SKC|CLR|FEW|SCT|BKN|OVC)/)) { } else if (part.match(/^(SKC|CLR|FEW|SCT|BKN|OVC)/)) {
sections.push({ raw: part, decoded: decodeClouds(part) }); sections.push({ raw: part, decoded: decodeClouds(part, metarString) });
} else if (part.match(/^(M?\d{2})\/(M?\d{2})$/)) { } else if (part.match(/^(M?\d{2})\/(M?\d{2})$/)) {
sections.push({ raw: part, decoded: decodeTempDew(part) }); sections.push({ raw: part, decoded: decodeTempDew(part) });
} else if (part.startsWith('A') && part.length === 5 && !isNaN(part.substring(1))) { } else if (part.startsWith('A') && part.length === 5 && !isNaN(part.substring(1))) {
@@ -346,7 +356,7 @@
const remarkParts = parts.slice(rmkIndex + 1); const remarkParts = parts.slice(rmkIndex + 1);
sections.push({ sections.push({
raw: `RMK ${remarkParts.join(' ')}`, raw: `RMK ${remarkParts.join(' ')}`,
decoded: decodeRemarks(remarkParts) decoded: decodeRemarks(remarkParts, metarString)
}); });
} }