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 } }