99 lines
2.7 KiB
Go
99 lines
2.7 KiB
Go
package probe
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"supply-intelligence/internal/domain"
|
|
"supply-intelligence/internal/metrics"
|
|
)
|
|
|
|
type RoutingStateRepository interface {
|
|
GetRoutingStateContext(ctx context.Context, accountID int64) (domain.AccountRoutingState, bool)
|
|
UpsertRoutingStateContext(ctx context.Context, state domain.AccountRoutingState) domain.AccountRoutingState
|
|
}
|
|
|
|
type Service struct {
|
|
repo RoutingStateRepository
|
|
now func() time.Time
|
|
}
|
|
|
|
type EvaluateInput struct {
|
|
AccountID int64
|
|
Platform string
|
|
CurrentStatus domain.AccountStatus
|
|
StatusCode int
|
|
TransportError error
|
|
ConsecutiveExplicitFailures int
|
|
}
|
|
|
|
type EvaluateOutput struct {
|
|
Classification domain.ProbeClassification `json:"classification"`
|
|
ReasonCode string `json:"reason_code"`
|
|
RoutingState domain.AccountRoutingState `json:"routing_state"`
|
|
}
|
|
|
|
func NewService(repo RoutingStateRepository) *Service {
|
|
return &Service{
|
|
repo: repo,
|
|
now: func() time.Time {
|
|
return time.Now().UTC()
|
|
},
|
|
}
|
|
}
|
|
|
|
func (s *Service) EvaluateHTTPResult(ctx context.Context, input EvaluateInput) (EvaluateOutput, error) {
|
|
classification, reasonCode, err := ClassifyHTTPResult(input.StatusCode, input.TransportError)
|
|
metrics.ProbeEvaluationsTotal.WithLabelValues(input.Platform, string(classification)).Inc()
|
|
if err != nil {
|
|
return EvaluateOutput{}, err
|
|
}
|
|
|
|
observedAt := s.now()
|
|
nextStatus := NextAccountStatus(input.CurrentStatus, classification, input.ConsecutiveExplicitFailures)
|
|
state := domain.AccountRoutingState{
|
|
AccountID: input.AccountID,
|
|
Platform: input.Platform,
|
|
AccountStatus: nextStatus,
|
|
RoutingEnabled: nextStatus == domain.AccountStatusActive,
|
|
RiskScore: riskScoreFor(nextStatus, classification),
|
|
ReasonCode: reasonCode,
|
|
LastProbeAt: observedAt,
|
|
Version: 1,
|
|
}
|
|
|
|
if previous, ok := s.repo.GetRoutingStateContext(ctx, input.AccountID); ok {
|
|
state.Version = previous.Version + 1
|
|
if state.Platform == "" {
|
|
state.Platform = previous.Platform
|
|
}
|
|
}
|
|
|
|
persisted := s.repo.UpsertRoutingStateContext(ctx, state)
|
|
return EvaluateOutput{
|
|
Classification: classification,
|
|
ReasonCode: reasonCode,
|
|
RoutingState: persisted,
|
|
}, nil
|
|
}
|
|
|
|
func riskScoreFor(status domain.AccountStatus, classification domain.ProbeClassification) int {
|
|
switch classification {
|
|
case domain.ProbeClassificationSuccess:
|
|
return 20
|
|
case domain.ProbeClassificationExplicitFailure:
|
|
switch status {
|
|
case domain.AccountStatusDisabled:
|
|
return 100
|
|
case domain.AccountStatusSuspended:
|
|
return 90
|
|
default:
|
|
return 80
|
|
}
|
|
case domain.ProbeClassificationInconclusive:
|
|
return 60
|
|
default:
|
|
return 0
|
|
}
|
|
}
|