package platform import ( "sort" "time" //"github.com/rs/zerolog/log" ) // 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 } func ModelTreatment(treatments []Treatment) []TreatmentModel { treatment_times := make([]time.Time, 0) for _, treatment := range treatments { if treatment.Date != nil { treatment_times = append(treatment_times, *treatment.Date) } } 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())) } // 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 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) } return TreatmentModel{ Year: year, SeasonStart: startTime, SeasonEnd: endTime, Interval: interval, ActualDates: dates, PredictedDates: predictedDates, Errors: errors, } } // 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 } 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] } // Calculate slope slope := (n*sumXY - sumX*sumY) / (n*sumX2 - sumX*sumX) // Calculate intercept intercept := (sumY - slope*sumX) / n return slope, intercept }