updates on the ai agents and time series prediction and updates on the api to

This commit is contained in:
2026-10-09 13:50:30 +05:30
parent 29690c56f2
commit 153be40e5c
42 changed files with 4889 additions and 186 deletions

53
models/demand_forecast.go Normal file
View File

@@ -0,0 +1,53 @@
package models
import "time"
// DemandForecast is one zone-day the engine expects.
//
// ─── Why this is stored rather than computed on read ───────────────────────
//
// The forecast is produced in Python (AI_engine/prediction), because the model
// is: Prophet where it beats a seasonal baseline, the baseline otherwise. The
// console and any staffing decision need it in Go. So the engine writes here
// and the backend serves it — the same split the agent registry and the
// decision log already use, and the reason the /internal/* surface exists.
//
// It is also the honest shape: a forecast is a thing produced at a moment by a
// model, not a function of the current table. Recomputing it on every read
// would make yesterday's number unrecoverable, which is exactly what you want
// when asking "was the forecast any good".
type DemandForecast struct {
Forecastid int `json:"forecastid" gorm:"primaryKey;column:forecastid;autoIncrement"`
// Zone is the first three digits of a pickup pincode — the same grain the
// hub console scopes on (pickuppincode LIKE '641%') and the same grain
// internal/prediction calibrates ETA at. Keeping one definition of "zone"
// across both is deliberate.
Zone string `json:"zone" gorm:"column:zone;size:8;not null;uniqueIndex:uq_demandforecast_zone_day,priority:1"`
// Forday is the day being predicted, not the day it was predicted on.
Forday time.Time `json:"forday" gorm:"column:forday;not null;uniqueIndex:uq_demandforecast_zone_day,priority:2;index"`
Expectedbookings int `json:"expectedbookings" gorm:"column:expectedbookings;not null"`
// Model and Reason record WHICH model produced this and why it was chosen —
// "prophet beat the weekly baseline over 12 folds (18% lower MAE)", or
// "prophet is not installed in this image". Stored because the choice is
// made per zone from that zone's own history, so without it nobody can tell
// whether a bad forecast came from a bad model or from a thin series.
Model string `json:"model" gorm:"column:model;size:32;not null"`
Reason string `json:"reason" gorm:"column:reason"`
// Observations is how many days of history the forecast was fitted on, and
// Baselinemae/Modelmae are the backtest scores. A forecast with 31
// observations and a model barely beating the baseline deserves less trust
// than one with 400, and this is what lets a reader see that rather than
// taking the number at face value.
Observations int `json:"observations" gorm:"column:observations;default:0"`
Baselinemae *float64 `json:"baselinemae" gorm:"column:baselinemae"`
Modelmae *float64 `json:"modelmae" gorm:"column:modelmae"`
Generatedat time.Time `json:"generatedat" gorm:"column:generatedat;not null"`
}
func (DemandForecast) TableName() string { return "demandforecast" }