Skip to content

Commit

Permalink
initial commit - really rough calculations based on temp & relative h…
Browse files Browse the repository at this point in the history
…umidity
  • Loading branch information
babattles committed Feb 21, 2023
0 parents commit 8baef24
Show file tree
Hide file tree
Showing 9 changed files with 593 additions and 0 deletions.
53 changes: 53 additions & 0 deletions crust/crust.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package crust

import (
"github.com/babattles/snoqualmie-crust-calculator/inversion"
"github.com/babattles/snoqualmie-crust-calculator/models"
"github.com/babattles/snoqualmie-crust-calculator/sun"
)

type CrustConfidence string

const (
CrustYes CrustConfidence = "yes"
CrustNo CrustConfidence = "no"
CrustMaybe CrustConfidence = "maybe"
)

// returns an array of bools where the index is true when there likely
// exists a sun crust based on temperature inversions & sun effect
func FindSunCrust(data []models.WeatherStationData) []CrustConfidence {
res := make([]CrustConfidence, len(data))
inversionsBelow := inversion.FindInversionsBelow(data)
sunExposures := sun.FindSunEffect(data)
for i, layer := range(data) {
gotSun := sunExposures[i]
inversionBelow := inversionsBelow[i]

// if there was a temperature inversion detected below
// AND this layer might have recieved sun exposure
// AND the temperature is above freezing
// there is very likely a sun crust
// NOTE: this assumption is likely to upset many people
if inversionBelow &&
gotSun &&
!layer.BelowFreezing() {
res[i] = CrustYes
continue
}

// because our sun exposure estimate isn't perfect, we provide a maybe if we can't
// guess more precisely because we detected an inversion
// so if the layer might have received sun exposure
// AND is above freezing, it might have a crust
// NOTE: this check assumes mid-winter conditions that will return this layer to freezing at some point
// YMMV
if gotSun && !layer.BelowFreezing() {
res[i] = CrustMaybe
continue
}

res[i] = CrustNo
}
return res
}
155 changes: 155 additions & 0 deletions crust/crust_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
package crust_test

import (
"testing"

"github.com/babattles/snoqualmie-crust-calculator/crust"
"github.com/babattles/snoqualmie-crust-calculator/models"
"github.com/stretchr/testify/assert"
)

func TestFindSunCrust(t *testing.T) {
t.Parallel()

tests := []struct {
name string
data []models.WeatherStationData
expected []crust.CrustConfidence
}{
{
name: "no inversions - no clouds - all above freezing - all maybes",
data: []models.WeatherStationData{
{
ElevationFt: 0,
TemperatureF: 36,
RelativeHumidityPercent: 0,
},
{
ElevationFt: 500,
TemperatureF: 34,
RelativeHumidityPercent: 0,
},
{
ElevationFt: 1000,
TemperatureF: 32,
RelativeHumidityPercent: 0,
},
},
expected: []crust.CrustConfidence{crust.CrustMaybe, crust.CrustMaybe, crust.CrustMaybe},
},
{
name: "no inversions - all clouds - all above freezing - all nos",
data: []models.WeatherStationData{
{
ElevationFt: 0,
TemperatureF: 36,
RelativeHumidityPercent: 100,
},
{
ElevationFt: 500,
TemperatureF: 34,
RelativeHumidityPercent: 100,
},
{
ElevationFt: 1000,
TemperatureF: 32,
RelativeHumidityPercent: 100,
},
},
expected: []crust.CrustConfidence{crust.CrustNo, crust.CrustNo, crust.CrustNo},
},
{
name: "no inversions - no clouds - all below freezing - all nos",
data: []models.WeatherStationData{
{
ElevationFt: 0,
TemperatureF: 28,
RelativeHumidityPercent: 0,
},
{
ElevationFt: 500,
TemperatureF: 26,
RelativeHumidityPercent: 0,
},
{
ElevationFt: 1000,
TemperatureF: 24,
RelativeHumidityPercent: 0,
},
},
expected: []crust.CrustConfidence{crust.CrustNo, crust.CrustNo, crust.CrustNo},
},
{
name: "all inversions & all clouds - no crusts",
data: []models.WeatherStationData{
{
ElevationFt: 0,
TemperatureF: 30,
RelativeHumidityPercent: 100,
},
{
ElevationFt: 500,
TemperatureF: 32,
RelativeHumidityPercent: 100,
},
{
ElevationFt: 1000,
TemperatureF: 34,
RelativeHumidityPercent: 100,
},
},
expected: []crust.CrustConfidence{crust.CrustNo, crust.CrustNo, crust.CrustNo},
},
{
name: "all inversions - no clouds - all below freezing - no crusts",
data: []models.WeatherStationData{
{
ElevationFt: 0,
TemperatureF: 12,
RelativeHumidityPercent: 0,
},
{
ElevationFt: 500,
TemperatureF: 20,
RelativeHumidityPercent: 0,
},
{
ElevationFt: 1000,
TemperatureF: 30,
RelativeHumidityPercent: 0,
},
},
expected: []crust.CrustConfidence{crust.CrustNo, crust.CrustNo, crust.CrustNo},
},
{
name: "all inversions - no clouds - one below freezing - two crusts",
data: []models.WeatherStationData{
{
ElevationFt: 0,
TemperatureF: 30,
RelativeHumidityPercent: 0,
},
{
ElevationFt: 500,
TemperatureF: 32,
RelativeHumidityPercent: 0,
},
{
ElevationFt: 1000,
TemperatureF: 34,
RelativeHumidityPercent: 0,
},
},
expected: []crust.CrustConfidence{crust.CrustNo, crust.CrustYes, crust.CrustYes},
},
}

for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
tc := tc
res := crust.FindSunCrust(tc.data)
assert.Equal(t, tc.expected, res)
})
}
}
10 changes: 10 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module github.com/babattles/snoqualmie-crust-calculator

go 1.20

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/stretchr/testify v1.8.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
16 changes: 16 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
64 changes: 64 additions & 0 deletions inversion/inversion.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package inversion

import (
"errors"

"github.com/babattles/snoqualmie-crust-calculator/models"
)

var (
ErrImproperElevationOrdering = errors.New("improper elevation ordering")
)

type InversionData struct {
LowerElevationFt int
HigherElevationFt int
InversionPresent bool
}

// for each elevation band, return if there was an inversion between it and the above elevation band
// (the uppermost elevation band will always be false)
func FindInversionsAbove(data []models.WeatherStationData) []bool {
// sort first for peace of mind
models.SortByElevation(data)

res := make([]bool, len(data))
for i, layer := range(data) {
// uppermost layer
if i == len(data) - 1 {
res[i] = false
return res
}

res[i] = temperatureInversionExists(layer, data[i+1])
}

return res
}

// for each elevation band, return if there was an inversion between it and the elevation band below
// (the lowest elevation band will always be false)
func FindInversionsBelow(data []models.WeatherStationData) []bool {
// sort first for peace of mind
models.SortByElevation(data)

res := make([]bool, len(data))
for i := len(data)-1; i >= 0; i-- {
// lowest layer
if i == 0 {
res[i] = false
return res
}

res[i] = temperatureInversionExists(data[i-1], data[i])
}

return res
}

// calculates if there was a temperature inversion between two elevation bands
func temperatureInversionExists(
lowerBand models.WeatherStationData, higherBand models.WeatherStationData,
) bool {
return higherBand.TemperatureF > lowerBand.TemperatureF
}
Loading

0 comments on commit 8baef24

Please sign in to comment.