126 lines
5.1 KiB
Go
126 lines
5.1 KiB
Go
package domain
|
|
|
|
import (
|
|
"errors"
|
|
"strings"
|
|
|
|
"git.cin.su/m/Assistant/contracts/capability"
|
|
)
|
|
|
|
var ErrInvalidSkillProfile = errors.New("invalid skill profile")
|
|
var ErrInvalidFastPathInput = errors.New("invalid fast path input")
|
|
|
|
func ValidateSkillProfile(value capability.SkillProfile) error {
|
|
if value.ID == "" || value.Revision < 1 || strings.TrimSpace(value.Name) == "" ||
|
|
strings.TrimSpace(value.Description) == "" || value.Status == "" ||
|
|
value.ConfidenceMillis < 0 || value.ConfidenceMillis > 1000 || value.RecordedAtUnixMillis < 1 {
|
|
return ErrInvalidSkillProfile
|
|
}
|
|
switch value.Status {
|
|
case capability.SkillCandidate, capability.SkillEvaluating, capability.SkillValidated, capability.SkillDegraded, capability.SkillSuspended, capability.SkillRetired:
|
|
default:
|
|
return ErrInvalidSkillProfile
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func ObserveSkillOutcome(profile capability.SkillProfile, observation capability.SkillOutcomeObservation) (capability.SkillProfile, error) {
|
|
if err := ValidateSkillProfile(profile); err != nil {
|
|
return capability.SkillProfile{}, err
|
|
}
|
|
if observation.ExperienceRef == "" || observation.ObservedAtUnixMillis < 1 {
|
|
return capability.SkillProfile{}, ErrInvalidSkillProfile
|
|
}
|
|
next := profile
|
|
next.Revision++
|
|
next.RecordedAtUnixMillis = observation.ObservedAtUnixMillis
|
|
switch observation.Outcome {
|
|
case capability.UsageSucceeded:
|
|
next.SupportingExperienceRefs = appendEvidenceRef(next.SupportingExperienceRefs, observation.ExperienceRef)
|
|
if next.Status == capability.SkillCandidate || next.Status == capability.SkillEvaluating {
|
|
next.Status = capability.SkillValidated
|
|
}
|
|
next.LastValidatedUnixMillis = observation.ObservedAtUnixMillis
|
|
next.ConfidenceMillis = clampConfidence(next.ConfidenceMillis + 25)
|
|
case capability.UsageFailed:
|
|
next.KnownFailureExperienceRefs = appendEvidenceRef(next.KnownFailureExperienceRefs, observation.ExperienceRef)
|
|
next.ObservedFailureCount++
|
|
next.ConfidenceMillis = clampConfidence(next.ConfidenceMillis - 150)
|
|
case capability.UsageUnknown, capability.UsageSkipped:
|
|
// UNKNOWN and SKIPPED are evidence of incomplete observation or a safe
|
|
// non-application, not evidence that the skill itself failed.
|
|
default:
|
|
return capability.SkillProfile{}, ErrInvalidSkillProfile
|
|
}
|
|
if observation.UserCorrected {
|
|
next.ObservedCorrectionCount++
|
|
next.KnownFailureExperienceRefs = appendEvidenceRef(next.KnownFailureExperienceRefs, observation.ExperienceRef)
|
|
next.ConfidenceMillis = clampConfidence(next.ConfidenceMillis - 200)
|
|
}
|
|
if next.Status == capability.SkillValidated && (next.ObservedFailureCount >= 3 || next.ObservedCorrectionCount >= 2 || next.ConfidenceMillis < 500) {
|
|
next.Status = capability.SkillDegraded
|
|
}
|
|
if next.Status == capability.SkillDegraded && next.ConfidenceMillis < 250 {
|
|
next.Status = capability.SkillSuspended
|
|
}
|
|
return next, nil
|
|
}
|
|
|
|
func DecideFastPath(profile capability.SkillProfile, supported bool, confidenceMillis int64, environmentVersionChanged bool, conflict bool, observedAtUnixMillis int64) (capability.FastPathDecision, error) {
|
|
if err := ValidateSkillProfile(profile); err != nil {
|
|
return capability.FastPathDecision{}, err
|
|
}
|
|
if confidenceMillis < 0 || confidenceMillis > 1000 || observedAtUnixMillis < 1 {
|
|
return capability.FastPathDecision{}, ErrInvalidFastPathInput
|
|
}
|
|
decision := capability.FastPathDecision{
|
|
SkillProfileID: profile.ID,
|
|
Decision: capability.FastPathApplied,
|
|
Explanation: "validated fast path applied",
|
|
ObservedAtUnixMillis: observedAtUnixMillis,
|
|
}
|
|
switch {
|
|
case profile.Status == capability.SkillSuspended || profile.Status == capability.SkillRetired:
|
|
return decline(decision, capability.FastPathStale, "skill profile is not eligible for fast-path use"), nil
|
|
case environmentVersionChanged:
|
|
return decline(decision, capability.FastPathStale, "environment or implementation version changed since validation"), nil
|
|
case conflict:
|
|
return decline(decision, capability.FastPathConflict, "input conflicts with known fast-path assumptions"), nil
|
|
case !supported:
|
|
return decline(decision, capability.FastPathOutOfDistribution, "input is outside supported semantics"), nil
|
|
case confidenceMillis < 700 || profile.ConfidenceMillis < 700:
|
|
return decline(decision, capability.FastPathLowConfidence, "confidence is too low for optimized path"), nil
|
|
case profile.Status != capability.SkillValidated:
|
|
return decline(decision, capability.FastPathNotApplicable, "skill is not validated"), nil
|
|
default:
|
|
return decision, nil
|
|
}
|
|
}
|
|
|
|
func decline(decision capability.FastPathDecision, reason capability.FastPathDeclineReason, explanation string) capability.FastPathDecision {
|
|
decision.Decision = capability.FastPathEscalate
|
|
decision.DeclineReason = reason
|
|
decision.RequiresCognition = true
|
|
decision.Explanation = explanation
|
|
return decision
|
|
}
|
|
|
|
func appendEvidenceRef(values []capability.SkillEvidenceRef, ref capability.SkillEvidenceRef) []capability.SkillEvidenceRef {
|
|
for _, value := range values {
|
|
if value == ref {
|
|
return values
|
|
}
|
|
}
|
|
return append(values, ref)
|
|
}
|
|
|
|
func clampConfidence(value int64) int64 {
|
|
if value < 0 {
|
|
return 0
|
|
}
|
|
if value > 1000 {
|
|
return 1000
|
|
}
|
|
return value
|
|
}
|