61 lines
1.7 KiB
Go
61 lines
1.7 KiB
Go
package growth
|
|
|
|
import "slices"
|
|
|
|
type PeerState string
|
|
|
|
const (
|
|
PeerReady PeerState = "READY"
|
|
PeerSuppressed PeerState = "SUPPRESSED"
|
|
)
|
|
|
|
type PeerMerchantValue struct {
|
|
PaidSessions int64 `json:"paidSessions"`
|
|
SessionCount int64 `json:"sessionCount"`
|
|
}
|
|
|
|
type PeerInput struct {
|
|
TerminalScoped bool `json:"terminalScoped"`
|
|
MerchantPaidSessions int64 `json:"merchantPaidSessions"`
|
|
Peers []PeerMerchantValue `json:"peers"`
|
|
}
|
|
|
|
type PeerResult struct {
|
|
State PeerState `json:"state"`
|
|
Reason SuppressionReason `json:"reason,omitempty"`
|
|
EligiblePeerCount int `json:"eligiblePeerCount"`
|
|
CombinedPeerSessions int64 `json:"combinedPeerSessions"`
|
|
MedianPaidSessions int64 `json:"medianPaidSessions"`
|
|
PercentileBPS int64 `json:"percentileBps"`
|
|
}
|
|
|
|
func EvaluatePeer(input PeerInput) PeerResult {
|
|
result := PeerResult{State: PeerSuppressed, EligiblePeerCount: len(input.Peers)}
|
|
if input.TerminalScoped {
|
|
result.Reason = SuppressionTerminalScope
|
|
return result
|
|
}
|
|
values := make([]int64, 0, len(input.Peers))
|
|
for _, peer := range input.Peers {
|
|
result.CombinedPeerSessions += peer.SessionCount
|
|
values = append(values, peer.PaidSessions)
|
|
}
|
|
if len(values) < 10 || result.CombinedPeerSessions < 1_000 {
|
|
result.Reason = SuppressionInsufficientCohort
|
|
return result
|
|
}
|
|
slices.Sort(values)
|
|
result.State = PeerReady
|
|
result.MedianPaidSessions = median(values)
|
|
var below, equal int64
|
|
for _, value := range values {
|
|
if input.MerchantPaidSessions > value {
|
|
below++
|
|
} else if input.MerchantPaidSessions == value {
|
|
equal++
|
|
}
|
|
}
|
|
result.PercentileBPS = (2*below + equal) * 5_000 / int64(len(values))
|
|
return result
|
|
}
|