updates on the ai agents and time series prediction and updates on the api to
This commit is contained in:
92
models/ai_findings.go
Normal file
92
models/ai_findings.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package models
|
||||
|
||||
import "time"
|
||||
|
||||
// AISkillFinding is one thing a console ops skill noticed.
|
||||
//
|
||||
// ─── Why this table exists ─────────────────────────────────────────────────
|
||||
//
|
||||
// The eight rule skills (SlaGuardian, DoorstepStall, FleetBalancer,
|
||||
// HighValueCod, RiderBatterySafety, HubCongestion, LateDispatch, CashExposure)
|
||||
// run in the operator's browser, on a 60-second React Query interval, and their
|
||||
// findings were thrown away. Nothing persisted them, so three questions had no
|
||||
// answer at all:
|
||||
//
|
||||
// Has this booking been flagged before, and how many times?
|
||||
// Which skills fire most, and which are ignored every time they do?
|
||||
// Did the proposal an operator carried out actually clear the finding?
|
||||
//
|
||||
// The third is the one that matters. The console already re-runs its scan after
|
||||
// executing a proposal to see whether the finding disappears — that evidence
|
||||
// existed for one render and then vanished. Persisting it turns "the skills seem
|
||||
// useful" into something measurable, and it is the raw material for any later
|
||||
// per-rider or per-zone memory.
|
||||
//
|
||||
// ─── What this is NOT ──────────────────────────────────────────────────────
|
||||
//
|
||||
// Not an exception. `consignmentexceptions` records a real operational problem
|
||||
// with a parcel (Lost, Damaged, Misrouted) raised by a person. A finding is a
|
||||
// rule noticing a pattern, most of which resolve themselves without anyone
|
||||
// doing anything. Writing findings into that table would flood a queue people
|
||||
// are supposed to work.
|
||||
//
|
||||
// Not a decision either: `agent_decisions` is the engine's model-made choices,
|
||||
// with embeddings for retrieval. A finding is deterministic and carries no
|
||||
// vector.
|
||||
type AISkillFinding struct {
|
||||
Findingid int `json:"findingid" gorm:"primaryKey;column:findingid;autoIncrement"`
|
||||
|
||||
// Skillid matches aiskills.skillid, so a finding can be grouped by the rule
|
||||
// that raised it and read alongside the thresholds in force at the time.
|
||||
Skillid string `json:"skillid" gorm:"column:skillid;size:64;not null;index:idx_aiskillfindings_skill_time,priority:1"`
|
||||
|
||||
// Fingerprint is what makes a finding the SAME finding across polls. The
|
||||
// skills re-evaluate every 60 seconds and will re-raise an unchanged
|
||||
// problem every time; without this the table would grow by the number of
|
||||
// open findings per minute per operator with the page open.
|
||||
//
|
||||
// Computed by the console from the skill id and the scope's booking ids —
|
||||
// deliberately not including severity or counts, which drift while the
|
||||
// underlying problem stays the same.
|
||||
Fingerprint string `json:"fingerprint" gorm:"column:fingerprint;size:120;not null;uniqueIndex:uq_aiskillfindings_fingerprint"`
|
||||
|
||||
Severity string `json:"severity" gorm:"column:severity;size:20"` // critical, warning, info
|
||||
Title string `json:"title" gorm:"column:title"`
|
||||
|
||||
// Proposaltool is the verb the finding proposes, e.g. notifyRider,
|
||||
// assignMiler, enforce_cash_handoff. Matches aitools.toolname where one
|
||||
// exists; review-only verbs are recorded too, which is how "this skill
|
||||
// keeps proposing something nobody can act on" becomes visible.
|
||||
Proposaltool string `json:"proposaltool" gorm:"column:proposaltool;size:64;index"`
|
||||
|
||||
// Bookingcount and Scope: the count is for grouping, the scope is the
|
||||
// booking ids as a JSON array so a specific parcel's history can be found.
|
||||
// Not a join table — a finding's scope is read whole or not at all, and a
|
||||
// row per booking per finding would multiply this table by average scope
|
||||
// size for no query anyone needs.
|
||||
Bookingcount int `json:"bookingcount" gorm:"column:bookingcount;default:0"`
|
||||
Scope string `json:"scope" gorm:"column:scope;type:jsonb"`
|
||||
|
||||
Tenantid *int `json:"tenantid" gorm:"column:tenantid;index"`
|
||||
|
||||
// Firstseenat vs Lastseenat: an upsert on fingerprint bumps the last-seen
|
||||
// and the count, so how LONG a finding has been open is answerable. That is
|
||||
// the actual signal of an ignored finding — not that it exists, but that it
|
||||
// has existed for six hours.
|
||||
Firstseenat time.Time `json:"firstseenat" gorm:"column:firstseenat;not null;index:idx_aiskillfindings_skill_time,priority:2"`
|
||||
Lastseenat time.Time `json:"lastseenat" gorm:"column:lastseenat;not null"`
|
||||
Seencount int `json:"seencount" gorm:"column:seencount;default:1"`
|
||||
|
||||
// Actedat / Actedby / Actionresult record an operator carrying out the
|
||||
// proposal. Null means nobody did — which is data, not a gap.
|
||||
Actedat *time.Time `json:"actedat" gorm:"column:actedat"`
|
||||
Actedby *int `json:"actedby" gorm:"column:actedby"`
|
||||
Actionresult string `json:"actionresult" gorm:"column:actionresult;size:20"` // ok, partial, failed
|
||||
|
||||
// Clearedat is set when a later scan no longer raises this fingerprint.
|
||||
// Paired with Actedat it answers the question the whole table is for: did
|
||||
// acting clear it, or did it clear on its own?
|
||||
Clearedat *time.Time `json:"clearedat" gorm:"column:clearedat"`
|
||||
}
|
||||
|
||||
func (AISkillFinding) TableName() string { return "aiskillfindings" }
|
||||
Reference in New Issue
Block a user