2026-03-12 23:49:16 +00:00
|
|
|
package platform
|
2025-11-21 05:46:31 +00:00
|
|
|
|
|
|
|
|
import (
|
2025-11-21 17:26:49 +00:00
|
|
|
"sort"
|
2025-11-21 05:46:31 +00:00
|
|
|
"time"
|
2025-11-21 17:26:49 +00:00
|
|
|
//"github.com/rs/zerolog/log"
|
2025-11-21 05:46:31 +00:00
|
|
|
)
|
|
|
|
|
|
2025-11-21 17:26:49 +00:00
|
|
|
// TreatmentModel represents the calculated model for a year's treatments
|
|
|
|
|
type TreatmentModel struct {
|
|
|
|
|
Year int
|
|
|
|
|
SeasonStart time.Time
|
|
|
|
|
SeasonEnd time.Time
|
|
|
|
|
Interval time.Duration
|
|
|
|
|
ActualDates []time.Time
|
|
|
|
|
PredictedDates []time.Time
|
|
|
|
|
Errors []time.Duration
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-12 23:49:16 +00:00
|
|
|
func ModelTreatment(treatments []Treatment) []TreatmentModel {
|
2025-11-21 17:26:49 +00:00
|
|
|
treatment_times := make([]time.Time, 0)
|
|
|
|
|
for _, treatment := range treatments {
|
2025-12-24 17:49:39 -07:00
|
|
|
if treatment.Date != nil {
|
|
|
|
|
treatment_times = append(treatment_times, *treatment.Date)
|
|
|
|
|
}
|
2025-11-21 17:26:49 +00:00
|
|
|
}
|
|
|
|
|
models := calculateTreatmentModels(treatment_times)
|
|
|
|
|
/*models_by_year := make(map[int]TreatmentModel)
|
|
|
|
|
for _, m := range models {
|
|
|
|
|
models_by_year[m.Year] = m
|
|
|
|
|
}*/
|
|
|
|
|
offset := 0
|
|
|
|
|
for _, model := range models {
|
|
|
|
|
for _, e := range model.Errors {
|
|
|
|
|
treatments[offset].CadenceDelta = e
|
|
|
|
|
offset = offset + 1
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
/*
|
|
|
|
|
for i, treatment := range treatments {
|
|
|
|
|
model
|
|
|
|
|
treatment.CadenceDelta = deltas[i]
|
|
|
|
|
treatments[i] = treatment
|
|
|
|
|
}
|
|
|
|
|
*/
|
|
|
|
|
/*cadence, deltas := calculateCadenceVariance(treatment_times)
|
|
|
|
|
for i, treatment := range treatments {
|
|
|
|
|
if i >= len(deltas) {
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
treatment.CadenceDelta = deltas[i]
|
|
|
|
|
treatments[i] = treatment
|
|
|
|
|
}*/
|
|
|
|
|
return models
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// calculateTreatmentModels segments treatments by year and calculates a model for each year
|
|
|
|
|
func calculateTreatmentModels(treatments []time.Time) []TreatmentModel {
|
|
|
|
|
// Group treatments by year
|
|
|
|
|
yearMap := make(map[int][]time.Time)
|
|
|
|
|
for _, t := range treatments {
|
|
|
|
|
year := t.Year()
|
|
|
|
|
yearMap[year] = append(yearMap[year], t)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Calculate a model for each year
|
|
|
|
|
var models []TreatmentModel
|
|
|
|
|
for year, dates := range yearMap {
|
|
|
|
|
// Sort dates within the year
|
|
|
|
|
sort.Slice(dates, func(i, j int) bool {
|
|
|
|
|
return dates[i].Before(dates[j])
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
model := calculateYearModel(year, dates)
|
|
|
|
|
models = append(models, model)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Sort models by year
|
|
|
|
|
sort.Slice(models, func(i, j int) bool {
|
|
|
|
|
return models[i].Year < models[j].Year
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
return models
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// calculateYearModel creates a model for a specific year using linear regression
|
|
|
|
|
func calculateYearModel(year int, dates []time.Time) TreatmentModel {
|
|
|
|
|
n := len(dates)
|
|
|
|
|
if n < 2 {
|
|
|
|
|
// Not enough data for a model
|
|
|
|
|
return TreatmentModel{
|
|
|
|
|
Year: year,
|
|
|
|
|
ActualDates: dates,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Convert dates to numeric values (seconds since epoch)
|
|
|
|
|
var x []float64
|
|
|
|
|
var y []float64
|
|
|
|
|
for i, date := range dates {
|
|
|
|
|
x = append(x, float64(i))
|
|
|
|
|
y = append(y, float64(date.Unix()))
|
2025-11-21 05:46:31 +00:00
|
|
|
}
|
|
|
|
|
|
2025-11-21 17:26:49 +00:00
|
|
|
// Calculate linear regression
|
|
|
|
|
slope, intercept := linearRegression(x, y)
|
|
|
|
|
|
|
|
|
|
// Convert back to time.Time and time.Duration
|
|
|
|
|
startTime := time.Unix(int64(intercept), 0)
|
|
|
|
|
intervalSeconds := int64(slope)
|
|
|
|
|
interval := time.Duration(intervalSeconds) * time.Second
|
|
|
|
|
|
|
|
|
|
// Calculate end of season
|
|
|
|
|
endTime := time.Unix(int64(intercept+slope*float64(n-1)), 0)
|
|
|
|
|
|
|
|
|
|
// Generate predicted dates and calculate errors
|
|
|
|
|
var predictedDates []time.Time
|
|
|
|
|
var errors []time.Duration
|
2025-11-21 05:46:31 +00:00
|
|
|
|
2025-11-21 17:26:49 +00:00
|
|
|
for i := 0; i < n; i++ {
|
|
|
|
|
predicted := time.Unix(int64(intercept+slope*float64(i)), 0)
|
|
|
|
|
predictedDates = append(predictedDates, predicted)
|
|
|
|
|
|
|
|
|
|
// Calculate error
|
|
|
|
|
actualTime := dates[i]
|
|
|
|
|
error := actualTime.Sub(predicted)
|
|
|
|
|
errors = append(errors, error)
|
2025-11-21 05:46:31 +00:00
|
|
|
}
|
|
|
|
|
|
2025-11-21 17:26:49 +00:00
|
|
|
return TreatmentModel{
|
|
|
|
|
Year: year,
|
|
|
|
|
SeasonStart: startTime,
|
|
|
|
|
SeasonEnd: endTime,
|
|
|
|
|
Interval: interval,
|
|
|
|
|
ActualDates: dates,
|
|
|
|
|
PredictedDates: predictedDates,
|
|
|
|
|
Errors: errors,
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-11-21 05:46:31 +00:00
|
|
|
|
2025-11-21 17:26:49 +00:00
|
|
|
// linearRegression calculates the slope and intercept of the best-fit line
|
|
|
|
|
func linearRegression(x, y []float64) (float64, float64) {
|
|
|
|
|
n := float64(len(x))
|
|
|
|
|
if n < 2 {
|
|
|
|
|
return 0, 0
|
|
|
|
|
}
|
2025-11-21 05:46:31 +00:00
|
|
|
|
2025-11-21 17:26:49 +00:00
|
|
|
var sumX, sumY, sumXY, sumX2 float64
|
|
|
|
|
for i := 0; i < len(x); i++ {
|
|
|
|
|
sumX += x[i]
|
|
|
|
|
sumY += y[i]
|
|
|
|
|
sumXY += x[i] * y[i]
|
|
|
|
|
sumX2 += x[i] * x[i]
|
2025-11-21 05:46:31 +00:00
|
|
|
}
|
|
|
|
|
|
2025-11-21 17:26:49 +00:00
|
|
|
// Calculate slope
|
|
|
|
|
slope := (n*sumXY - sumX*sumY) / (n*sumX2 - sumX*sumX)
|
|
|
|
|
|
|
|
|
|
// Calculate intercept
|
|
|
|
|
intercept := (sumY - slope*sumX) / n
|
|
|
|
|
|
|
|
|
|
return slope, intercept
|
2025-11-21 05:46:31 +00:00
|
|
|
}
|