64 lines
1.9 KiB
Go
64 lines
1.9 KiB
Go
|
|
package classify
|
||
|
|
|
||
|
|
import "sort"
|
||
|
|
|
||
|
|
const (
|
||
|
|
StatusAssigned = "assigned"
|
||
|
|
StatusNeedsReview = "needs_review"
|
||
|
|
|
||
|
|
// DefaultMinConfidence is the score below which even a single matching
|
||
|
|
// kind is sent to manual review rather than auto-assigned.
|
||
|
|
DefaultMinConfidence = 0.6
|
||
|
|
)
|
||
|
|
|
||
|
|
// RuleKind is one active workout kind's rule, as loaded from the store.
|
||
|
|
type RuleKind struct {
|
||
|
|
WorkoutKindID int64
|
||
|
|
Name string
|
||
|
|
Rule Node
|
||
|
|
}
|
||
|
|
|
||
|
|
// ScoredKind is one kind that matched an activity's metrics, with its
|
||
|
|
// confidence score.
|
||
|
|
type ScoredKind struct {
|
||
|
|
WorkoutKindID int64 `json:"workout_kind_id"`
|
||
|
|
Name string `json:"name"`
|
||
|
|
Score float64 `json:"score"`
|
||
|
|
}
|
||
|
|
|
||
|
|
// Result is the outcome of classifying one activity.
|
||
|
|
type Result struct {
|
||
|
|
Status string
|
||
|
|
WorkoutKindID *int64
|
||
|
|
Confidence *float64
|
||
|
|
Candidates []ScoredKind // every kind that matched, sorted by score descending
|
||
|
|
}
|
||
|
|
|
||
|
|
// Classify evaluates every active kind's rule against ctx and decides
|
||
|
|
// whether the activity is cleanly assignable, or needs manual review
|
||
|
|
// because zero kinds matched, multiple kinds matched, or the single match's
|
||
|
|
// confidence fell below minConfidence.
|
||
|
|
func Classify(ctx MetricContext, kinds []RuleKind, minConfidence float64) Result {
|
||
|
|
candidates := []ScoredKind{}
|
||
|
|
for _, k := range kinds {
|
||
|
|
matched, score := k.Rule.Evaluate(ctx)
|
||
|
|
if matched {
|
||
|
|
candidates = append(candidates, ScoredKind{WorkoutKindID: k.WorkoutKindID, Name: k.Name, Score: score})
|
||
|
|
}
|
||
|
|
}
|
||
|
|
sort.Slice(candidates, func(i, j int) bool { return candidates[i].Score > candidates[j].Score })
|
||
|
|
|
||
|
|
if len(candidates) != 1 {
|
||
|
|
return Result{Status: StatusNeedsReview, Candidates: candidates}
|
||
|
|
}
|
||
|
|
|
||
|
|
only := candidates[0]
|
||
|
|
if only.Score < minConfidence {
|
||
|
|
return Result{Status: StatusNeedsReview, Candidates: candidates}
|
||
|
|
}
|
||
|
|
|
||
|
|
id := only.WorkoutKindID
|
||
|
|
score := only.Score
|
||
|
|
return Result{Status: StatusAssigned, WorkoutKindID: &id, Confidence: &score, Candidates: candidates}
|
||
|
|
}
|