1- package api
1+ package aimlanom
22
33// Phase-50 / 0062 — ML1 Learned per-vehicle anomaly baselines.
44//
@@ -55,6 +55,7 @@ import (
5555 "github.com/ev-dev-labs/teslasync/internal/ai/strategy"
5656 "github.com/ev-dev-labs/teslasync/internal/ai/stream"
5757 "github.com/ev-dev-labs/teslasync/internal/ai/tools"
58+ "github.com/ev-dev-labs/teslasync/internal/api/httpx"
5859 tsauth "github.com/ev-dev-labs/teslasync/internal/auth"
5960 "github.com/ev-dev-labs/teslasync/internal/database"
6061 "github.com/ev-dev-labs/teslasync/internal/ml/anomaly"
@@ -80,21 +81,21 @@ const aiLearnedAnomalyDefaultDays = 7
8081// the LLM.
8182const aiLearnedAnomalyMaxDays = 30
8283
83- // AILearnedAnomalyBaselineHandler is the HTTP handler for
84+ // Handler is the HTTP handler for
8485// POST /api/v1/ai/ml/anomaly-baselines/train.
8586//
8687// Stateless beyond its constructor inputs; safe for concurrent use
8788// across requests. Construction is in router.go so the dispatcher's
8889// tool registry + provider registry are wired once at boot.
89- type AILearnedAnomalyBaselineHandler struct {
90+ type Handler struct {
9091 registry * provider.Registry
9192 tools * tools.Registry
9293 strategy strategy.Strategy
9394 headerName string
9495 maxIters int
9596}
9697
97- // NewAILearnedAnomalyBaselineHandler constructs the handler. All
98+ // NewHandler constructs the handler. All
9899// non-pointer arguments are required; the constructor panics on a
99100// nil so the wiring bug surfaces at boot, not at first request.
100101//
@@ -110,21 +111,21 @@ type AILearnedAnomalyBaselineHandler struct {
110111// (one per process).
111112//
112113// headerName: forward-auth header name; used to extract subject for audit.
113- func NewAILearnedAnomalyBaselineHandler (
114+ func NewHandler (
114115 registry * provider.Registry ,
115116 toolReg * tools.Registry ,
116117 strat strategy.Strategy ,
117118 headerName string ,
118- ) * AILearnedAnomalyBaselineHandler {
119+ ) * Handler {
119120 switch {
120121 case registry == nil :
121- panic ("api: NewAILearnedAnomalyBaselineHandler : nil provider.Registry" )
122+ panic ("aimlanom: NewHandler : nil provider.Registry" )
122123 case toolReg == nil :
123- panic ("api: NewAILearnedAnomalyBaselineHandler : nil tools.Registry" )
124+ panic ("aimlanom: NewHandler : nil tools.Registry" )
124125 case strat == nil :
125- panic ("api: NewAILearnedAnomalyBaselineHandler : nil strategy.Strategy" )
126+ panic ("aimlanom: NewHandler : nil strategy.Strategy" )
126127 }
127- return & AILearnedAnomalyBaselineHandler {
128+ return & Handler {
128129 registry : registry ,
129130 tools : toolReg ,
130131 strategy : strat ,
@@ -150,7 +151,7 @@ type aiLearnedAnomalyRequest struct {
150151// dispatcher's deferred WriteDone. Every error path either writes a
151152// structured frame onto the SSE stream (when the writer has been
152153// opened) or a plain JSON 4xx/5xx (before it has).
153- func (h * AILearnedAnomalyBaselineHandler ) ServeHTTP (w http.ResponseWriter , r * http.Request ) {
154+ func (h * Handler ) ServeHTTP (w http.ResponseWriter , r * http.Request ) {
154155 // 1) Decode + validate request body.
155156 var body aiLearnedAnomalyRequest
156157 if err := json .NewDecoder (r .Body ).Decode (& body ); err != nil {
@@ -245,27 +246,27 @@ func (h *AILearnedAnomalyBaselineHandler) ServeHTTP(w http.ResponseWriter, r *ht
245246 }
246247}
247248
248- // Compile-time assertion: AILearnedAnomalyBaselineHandler satisfies http.Handler.
249- var _ http.Handler = (* AILearnedAnomalyBaselineHandler )(nil )
249+ // Compile-time assertion: Handler satisfies http.Handler.
250+ var _ http.Handler = (* Handler )(nil )
250251
251- // AISignalSampleSource is the production *database.DB-backed adapter
252+ // SignalSampleSource is the production *database.DB-backed adapter
252253// that satisfies anomaly.SignalSampleSource. It runs ONE pgx query
253254// scoped to the requested vehicle, lookback days, and signal
254255// allowlist; rows are bucketed in-memory into the per-signal
255256// observation slices the trainer expects. No new SQL semantics —
256257// the same signal_log columns the deterministic detector at
257258// internal/api/anomaly_handler.go already reads.
258- type AISignalSampleSource struct {
259+ type SignalSampleSource struct {
259260 db * database.DB
260261}
261262
262- // NewAISignalSampleSource constructs the adapter. Panics on a nil
263+ // NewSignalSampleSource constructs the adapter. Panics on a nil
263264// DB so the wiring bug surfaces at boot, not at first request.
264- func NewAISignalSampleSource (db * database.DB ) * AISignalSampleSource {
265+ func NewSignalSampleSource (db * database.DB ) * SignalSampleSource {
265266 if db == nil {
266- panic ("api: NewAISignalSampleSource : nil *database.DB" )
267+ panic ("aimlanom: NewSignalSampleSource : nil *database.DB" )
267268 }
268- return & AISignalSampleSource {db : db }
269+ return & SignalSampleSource {db : db }
269270}
270271
271272// SamplesForVehicle implements anomaly.SignalSampleSource. Returns
@@ -277,7 +278,7 @@ func NewAISignalSampleSource(db *database.DB) *AISignalSampleSource {
277278// signal_log hypertable's primary index is (vehicle_id, ts), so
278279// the selectivity comes from the time predicate; the field
279280// allowlist is a final-stage filter.
280- func (s * AISignalSampleSource ) SamplesForVehicle (ctx context.Context , vehicleID int64 , days int , signals []string ) (map [string ][]float64 , error ) {
281+ func (s * SignalSampleSource ) SamplesForVehicle (ctx context.Context , vehicleID int64 , days int , signals []string ) (map [string ][]float64 , error ) {
281282 out := make (map [string ][]float64 , len (signals ))
282283 for _ , sig := range signals {
283284 out [sig ] = nil
@@ -295,22 +296,30 @@ func (s *AISignalSampleSource) SamplesForVehicle(ctx context.Context, vehicleID
295296 AND (float_value IS NOT NULL OR int_value IS NOT NULL)` ,
296297 vehicleID , since , signals )
297298 if err != nil {
298- return nil , fmt .Errorf ("AISignalSampleSource : vehicle %d days %d: %w" , vehicleID , days , err )
299+ return nil , fmt .Errorf ("SignalSampleSource : vehicle %d days %d: %w" , vehicleID , days , err )
299300 }
300301 defer rows .Close ()
301302 for rows .Next () {
302303 var field string
303304 var v float64
304305 if err := rows .Scan (& field , & v ); err != nil {
305- return nil , fmt .Errorf ("AISignalSampleSource : scan: %w" , err )
306+ return nil , fmt .Errorf ("SignalSampleSource : scan: %w" , err )
306307 }
307308 out [field ] = append (out [field ], v )
308309 }
309310 if err := rows .Err (); err != nil {
310- return nil , fmt .Errorf ("AISignalSampleSource : rows.Err: %w" , err )
311+ return nil , fmt .Errorf ("SignalSampleSource : rows.Err: %w" , err )
311312 }
312313 return out , nil
313314}
314315
315- // Compile-time assertion: AISignalSampleSource satisfies anomaly.SignalSampleSource.
316- var _ anomaly.SignalSampleSource = (* AISignalSampleSource )(nil )
316+ // Compile-time assertion: SignalSampleSource satisfies anomaly.SignalSampleSource.
317+ var _ anomaly.SignalSampleSource = (* SignalSampleSource )(nil )
318+
319+ func writeError (w http.ResponseWriter , status int , msg string ) {
320+ httpx .WriteError (w , status , msg )
321+ }
322+
323+ func denyAllConfirm (_ context.Context , _ dispatch.ConfirmRequest ) (dispatch.ConfirmDecision , error ) {
324+ return dispatch .ConfirmDenied , nil
325+ }
0 commit comments