139 lines
4.1 KiB
Go
139 lines
4.1 KiB
Go
package probe
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"supply-intelligence/internal/domain"
|
|
"supply-intelligence/internal/integration"
|
|
)
|
|
|
|
// ProbeLogRepository defines where probe execution logs are persisted
|
|
type ProbeLogRepository interface {
|
|
AppendProbeLog(ctx context.Context, outcome ProbeOutcome) error
|
|
}
|
|
|
|
// Driver orchestrates a full probe run: load targets → execute → evaluate → persist state
|
|
type Driver struct {
|
|
executor *ProbeExecutor
|
|
evaluator *Service // reuse the existing probe.Service as evaluator
|
|
logRepo ProbeLogRepository
|
|
adapters map[string]integration.SupplierAdapter
|
|
now func() time.Time
|
|
}
|
|
|
|
// NewDriver creates a probe driver with all dependencies wired together
|
|
func NewDriver(
|
|
repo RoutingStateRepository,
|
|
logRepo ProbeLogRepository,
|
|
adapters map[string]integration.SupplierAdapter,
|
|
) *Driver {
|
|
return &Driver{
|
|
executor: NewProbeExecutor(integration.NewDefaultHTTPClient()),
|
|
evaluator: NewService(repo),
|
|
logRepo: logRepo,
|
|
adapters: adapters,
|
|
now: func() time.Time { return time.Now().UTC() },
|
|
}
|
|
}
|
|
|
|
// RunProbeForAccount probes a single account and persists the result through the full chain
|
|
func (d *Driver) RunProbeForAccount(ctx context.Context, account integration.SupplierAccount) error {
|
|
var outcome ProbeOutcome
|
|
|
|
if adapter, ok := d.adapters[account.Platform]; ok {
|
|
// Use platform-specific adapter
|
|
result := adapter.ProbeAccount(ctx, account)
|
|
outcome = ProbeOutcome{
|
|
AccountID: account.AccountID,
|
|
Platform: account.Platform,
|
|
StatusCode: result.StatusCode,
|
|
TransportError: result.TransportError,
|
|
ResponseBody: result.ResponseBody,
|
|
RequestID: "prb-" + uuid.New().String(),
|
|
ExecutedAt: d.now(),
|
|
}
|
|
} else {
|
|
// Fall back to generic HTTP probe
|
|
target := ProbeTarget{
|
|
AccountID: account.AccountID,
|
|
Platform: account.Platform,
|
|
Endpoint: account.Endpoint,
|
|
AuthHeader: "Bearer " + account.APIKey,
|
|
}
|
|
if target.Endpoint == "" {
|
|
target.Endpoint = account.BaseURL
|
|
}
|
|
|
|
var err error
|
|
outcome, err = d.executor.ExecuteProbe(ctx, target)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return d.persistOutcome(ctx, account.AccountID, account.Platform, outcome)
|
|
}
|
|
|
|
// persistOutcome drives the outcome through: load current state → evaluate → state machine → persist
|
|
func (d *Driver) persistOutcome(ctx context.Context, accountID int64, platform string, outcome ProbeOutcome) error {
|
|
// 1. Load current routing state
|
|
currentState, _ := d.evaluator.repo.GetRoutingStateContext(ctx, accountID)
|
|
|
|
// 2. Build evaluate input
|
|
var transportErr error
|
|
if outcome.TransportError != nil {
|
|
transportErr = outcome.TransportError
|
|
}
|
|
|
|
input := EvaluateInput{
|
|
AccountID: accountID,
|
|
Platform: platform,
|
|
CurrentStatus: currentState.AccountStatus,
|
|
StatusCode: outcome.StatusCode,
|
|
TransportError: transportErr,
|
|
}
|
|
|
|
// 3. Evaluate (uses the existing Service.EvaluateHTTPResult)
|
|
evalOutput, err := d.evaluator.EvaluateHTTPResult(ctx, input)
|
|
if err != nil {
|
|
log.Printf("[probe] failed to evaluate outcome for account %d: %v", accountID, err)
|
|
return err
|
|
}
|
|
|
|
// 4. Log the probe execution
|
|
if d.logRepo != nil {
|
|
logEntry := ProbeOutcome{
|
|
AccountID: accountID,
|
|
Platform: platform,
|
|
StatusCode: outcome.StatusCode,
|
|
TransportError: outcome.TransportError,
|
|
LatencyMs: outcome.LatencyMs,
|
|
RequestID: outcome.RequestID,
|
|
ExecutedAt: outcome.ExecutedAt,
|
|
}
|
|
_ = d.logRepo.AppendProbeLog(ctx, logEntry)
|
|
}
|
|
|
|
// 5. Log state transition
|
|
transition := describeTransition(currentState.AccountStatus, evalOutput.RoutingState.AccountStatus)
|
|
log.Printf("[probe] account=%d platform=%s %s->%s classification=%s risk=%d transition=%s",
|
|
accountID, platform,
|
|
currentState.AccountStatus, evalOutput.RoutingState.AccountStatus,
|
|
evalOutput.Classification, evalOutput.RoutingState.RiskScore,
|
|
transition)
|
|
|
|
return nil
|
|
}
|
|
|
|
// describeTransition returns a human-readable transition description
|
|
func describeTransition(from, to domain.AccountStatus) string {
|
|
if from == to {
|
|
return "no_change"
|
|
}
|
|
return string(from) + "_to_" + string(to)
|
|
}
|