-
Notifications
You must be signed in to change notification settings - Fork 1
/
shouldwater.go
137 lines (109 loc) · 3.86 KB
/
shouldwater.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package shouldwater
import (
"fmt"
"time"
)
type WeatherRecord struct {
Timestamp time.Time
Temperature float64
Humidity float64
WindSpeed float64
Precipitation float64
}
const HoursInWeek = 7 * 24
const HoursInFiveDays = 5 * 24
const HighTempHistoricalPrecipitationMax = 25.4 // 1 inch in mm
const HighTempForecastPrecipitationMax = 25.4 // 1 inch in mm
const HistoricalPrecipitationMax = 20.32 // .8 inches in mm
const ForecastPrecipitationMax = 12.7 // .5 inches in mm
const WateringMax = 75.71 // liters
// ShouldWater returns the amount of liters required for watering an unestablished street tree
// given a weeks worth of historical and five days worth of forecasted weather data, in hourly granularity
func ShouldWater(
historicalRecords []WeatherRecord,
forecastRecords []WeatherRecord,
) (float64, error) {
err := validateShouldWaterInputData(historicalRecords, forecastRecords)
if err != nil {
return 0.0, err
}
totalHistoricalPrecipitation := totalNonFastFallPrecipitation(historicalRecords)
totalForecastPrecipitation := totalNonFastFallPrecipitation(forecastRecords)
totalPrecipitation := totalHistoricalPrecipitation + totalForecastPrecipitation
averageHistoricalHighTemperature := averageDayHighTemperature(historicalRecords)
var totalPrecipitationMax float64
if averageHistoricalHighTemperature > 29.4 { // 85 F in C
totalPrecipitationMax = HighTempHistoricalPrecipitationMax + HighTempForecastPrecipitationMax
} else {
totalPrecipitationMax = HistoricalPrecipitationMax + ForecastPrecipitationMax
}
if totalPrecipitation < totalPrecipitationMax {
percentPrecipitation := totalPrecipitation / totalPrecipitationMax
return WateringMax * percentPrecipitation, nil
}
return 0.0, nil
}
func ShouldHaveWatered(weatherRecords []WeatherRecord) (float64, error) {
err := validateShouldHaveWateredInputData(weatherRecords)
if err != nil {
return 0.0, err
}
totalPrecipitation := totalNonFastFallPrecipitation(weatherRecords)
averageHistoricalHighTemperature := averageDayHighTemperature(weatherRecords)
var totalPrecipitationMax float64
if averageHistoricalHighTemperature > 29.4 { // 85 F in C
totalPrecipitationMax = HighTempHistoricalPrecipitationMax * 2
} else {
totalPrecipitationMax = HistoricalPrecipitationMax * 2
}
if totalPrecipitation < totalPrecipitationMax {
percentPrecipitation := totalPrecipitation / totalPrecipitationMax
return WateringMax * percentPrecipitation, nil
}
return 0.0, nil
}
func validateShouldWaterInputData(
historicalRecords []WeatherRecord,
forecastRecords []WeatherRecord,
) error {
if len(historicalRecords) != HoursInWeek {
return fmt.Errorf("need exactly a week's worth of historical data to run (%d records)", HoursInWeek)
}
if len(forecastRecords) != HoursInFiveDays {
return fmt.Errorf("need exactly five days worth of forecast data to run (%d records)", HoursInFiveDays)
}
return nil
}
func validateShouldHaveWateredInputData(weatherRecords []WeatherRecord) error {
if len(weatherRecords) != HoursInWeek * 2 {
return fmt.Errorf("need exactly two week's worth of hourly records (%d records)", HoursInWeek * 2)
}
return nil
}
func totalNonFastFallPrecipitation(records []WeatherRecord) float64 {
var total float64
for _, record := range records {
if record.Precipitation < 25.4 { // 1 inches in mm
total += record.Precipitation
}
}
return total
}
func averageDayHighTemperature(records []WeatherRecord) float64 {
dayHighs := make(map[string]float64)
for _, record := range records {
day := record.Timestamp.Format("YYYYMMDD")
if dayHigh, ok := dayHighs[day]; ok {
if dayHigh < record.Temperature {
dayHighs[day] = record.Temperature
}
} else {
dayHighs[day] = record.Temperature
}
}
var dayHighSum float64
for _, dayHigh := range dayHighs {
dayHighSum += dayHigh
}
return dayHighSum / float64(len(dayHighs))
}