package standards // This file provides small, shared helper functions for reasoning about WMO codes. // These are intentionally "coarse" categories that are useful for business logic, // dashboards, and alerting decisions. // // Example uses: // - jogging suitability: precipitation? thunderstorm? freezing precip? // - quick glance: "is it cloudy?" "is there any precip?" // - downstream normalizers / aggregators import ( "fmt" "gitea.maximumdirect.net/ejr/weatherfeeder/model" ) type WMODescription struct { Day string Night string } // WMODescriptions is the canonical internal mapping of WMO code -> day/night text. // These are used to populate model.WeatherObservation.ConditionText. var WMODescriptions = map[model.WMOCode]WMODescription{ 0: {Day: "Sunny", Night: "Clear"}, 1: {Day: "Mainly Sunny", Night: "Mainly Clear"}, 2: {Day: "Partly Cloudy", Night: "Partly Cloudy"}, 3: {Day: "Cloudy", Night: "Cloudy"}, 45: {Day: "Foggy", Night: "Foggy"}, 48: {Day: "Rime Fog", Night: "Rime Fog"}, 51: {Day: "Light Drizzle", Night: "Light Drizzle"}, 53: {Day: "Drizzle", Night: "Drizzle"}, 55: {Day: "Heavy Drizzle", Night: "Heavy Drizzle"}, 56: {Day: "Light Freezing Drizzle", Night: "Light Freezing Drizzle"}, 57: {Day: "Freezing Drizzle", Night: "Freezing Drizzle"}, 61: {Day: "Light Rain", Night: "Light Rain"}, 63: {Day: "Rain", Night: "Rain"}, 65: {Day: "Heavy Rain", Night: "Heavy Rain"}, 66: {Day: "Light Freezing Rain", Night: "Light Freezing Rain"}, 67: {Day: "Freezing Rain", Night: "Freezing Rain"}, 71: {Day: "Light Snow", Night: "Light Snow"}, 73: {Day: "Snow", Night: "Snow"}, 75: {Day: "Heavy Snow", Night: "Heavy Snow"}, 77: {Day: "Snow Grains", Night: "Snow Grains"}, 80: {Day: "Light Showers", Night: "Light Showers"}, 81: {Day: "Showers", Night: "Showers"}, 82: {Day: "Heavy Showers", Night: "Heavy Showers"}, 85: {Day: "Light Snow Showers", Night: "Light Snow Showers"}, 86: {Day: "Snow Showers", Night: "Snow Showers"}, 95: {Day: "Thunderstorm", Night: "Thunderstorm"}, 96: {Day: "Light Thunderstorms With Hail", Night: "Light Thunderstorms With Hail"}, 99: {Day: "Thunderstorm With Hail", Night: "Thunderstorm With Hail"}, } // WMOText returns the canonical text description for a WMO code. // If isDay is nil, it prefers the Day description (if present). // // This is intended to be used by drivers after they set ConditionCode. func WMOText(code model.WMOCode, isDay *bool) string { if code == model.WMOUnknown { return "Unknown" } desc, ok := WMODescriptions[code] if !ok { // Preserve the code in the message so it's diagnosable. return fmt.Sprintf("Unknown (WMO %d)", int(code)) } // If day/night is unknown, default to Day if it exists. if isDay == nil { if desc.Day != "" { return desc.Day } if desc.Night != "" { return desc.Night } return fmt.Sprintf("Unknown (WMO %d)", int(code)) } if *isDay { if desc.Day != "" { return desc.Day } // Fallback if desc.Night != "" { return desc.Night } return fmt.Sprintf("Unknown (WMO %d)", int(code)) } // Night if desc.Night != "" { return desc.Night } // Fallback if desc.Day != "" { return desc.Day } return fmt.Sprintf("Unknown (WMO %d)", int(code)) } // IsKnownWMO returns true if the code exists in our mapping table. func IsKnownWMO(code model.WMOCode) bool { if code == model.WMOUnknown { return false } _, ok := WMODescriptions[code] return ok } func IsThunderstorm(code model.WMOCode) bool { switch code { case 95, 96, 99: return true default: return false } } func IsHail(code model.WMOCode) bool { switch code { case 96, 99: return true default: return false } } func IsFog(code model.WMOCode) bool { switch code { case 45, 48: return true default: return false } } // IsPrecipitation returns true if the code represents any precipitation // (drizzle, rain, snow, showers, etc.). func IsPrecipitation(code model.WMOCode) bool { switch code { // Drizzle case 51, 53, 55, 56, 57: return true // Rain case 61, 63, 65, 66, 67: return true // Snow case 71, 73, 75, 77: return true // Showers case 80, 81, 82, 85, 86: return true // Thunderstorm (often includes rain/hail) case 95, 96, 99: return true default: return false } } func IsRainFamily(code model.WMOCode) bool { switch code { // Drizzle + freezing drizzle case 51, 53, 55, 56, 57: return true // Rain + freezing rain case 61, 63, 65, 66, 67: return true // Rain showers case 80, 81, 82: return true // Thunderstorm often implies rain case 95, 96, 99: return true default: return false } } func IsSnowFamily(code model.WMOCode) bool { switch code { // Snow and related case 71, 73, 75, 77: return true // Snow showers case 85, 86: return true default: return false } } // IsFreezingPrecip returns true if the code represents freezing drizzle/rain. func IsFreezingPrecip(code model.WMOCode) bool { switch code { case 56, 57, 66, 67: return true default: return false } } // IsSkyOnly returns true for codes that represent "sky condition only" // (clear/mostly/partly/cloudy) rather than fog/precip/etc. func IsSkyOnly(code model.WMOCode) bool { switch code { case 0, 1, 2, 3: return true default: return false } }