168 lines
5.1 KiB
Go
168 lines
5.1 KiB
Go
package vipcardexperimentser
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"91porn-server/models/v/prdcthsomod"
|
|
"91porn-server/models/v/productmod"
|
|
"91porn-server/models/v/rchgordmod"
|
|
"91porn-server/models/v/vipcardexperimentmod"
|
|
|
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
)
|
|
|
|
type DisableRequest struct {
|
|
ExperimentID string `json:"experimentId" binding:"required"`
|
|
}
|
|
|
|
type StatisticsResponse struct {
|
|
Experiment *vipcardexperimentmod.Experiment `json:"experiment"`
|
|
Events []vipcardexperimentmod.EventStat `json:"events"`
|
|
ProductImpressions []vipcardexperimentmod.ProductImpressionStat `json:"productImpressions"`
|
|
Orders []rchgordmod.VIPExperimentOrderStat `json:"orders"`
|
|
}
|
|
|
|
func Current(now time.Time) (*vipcardexperimentmod.Experiment, error) {
|
|
return vipcardexperimentmod.Current(now)
|
|
}
|
|
|
|
func Publish(experiment *vipcardexperimentmod.Experiment, operator string) error {
|
|
if experiment == nil {
|
|
return fmt.Errorf("experiment is required")
|
|
}
|
|
experiment.Normalize()
|
|
if err := experiment.Validate(); err != nil {
|
|
return err
|
|
}
|
|
now := time.Now()
|
|
if err := validatePublicationWindow(experiment, now); err != nil {
|
|
return err
|
|
}
|
|
productIDs := uniqueProductIDs(experiment.VariantA.ProductIDs, experiment.VariantB.ProductIDs)
|
|
products, err := productmod.FindByProductIDs(productIDs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
enabled := make(map[primitive.ObjectID]struct{}, len(products))
|
|
for _, product := range products {
|
|
if product.Status {
|
|
enabled[product.ID] = struct{}{}
|
|
}
|
|
}
|
|
for _, productID := range productIDs {
|
|
if _, ok := enabled[productID]; !ok {
|
|
return fmt.Errorf("product is missing or disabled: %s", productID.Hex())
|
|
}
|
|
}
|
|
return vipcardexperimentmod.Publish(experiment, operator)
|
|
}
|
|
|
|
func validatePublicationWindow(experiment *vipcardexperimentmod.Experiment, now time.Time) error {
|
|
if experiment.StartAt != nil && experiment.StartAt.After(now.Add(time.Minute)) {
|
|
return fmt.Errorf("startAt cannot be in the future; publish starts the experiment immediately")
|
|
}
|
|
if experiment.EndAt != nil && !experiment.EndAt.After(now) {
|
|
return fmt.Errorf("endAt must be later than the current time")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func Disable(experimentID, operator string) error {
|
|
experimentID = strings.TrimSpace(experimentID)
|
|
if experimentID == "" {
|
|
return fmt.Errorf("experimentId is required")
|
|
}
|
|
updated, err := vipcardexperimentmod.Disable(experimentID, operator)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if !updated {
|
|
return fmt.Errorf("active experiment not found")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func Statistics(experimentID string) (StatisticsResponse, error) {
|
|
response := StatisticsResponse{
|
|
Events: make([]vipcardexperimentmod.EventStat, 0),
|
|
ProductImpressions: make([]vipcardexperimentmod.ProductImpressionStat, 0),
|
|
Orders: make([]rchgordmod.VIPExperimentOrderStat, 0),
|
|
}
|
|
experimentID = strings.TrimSpace(experimentID)
|
|
if experimentID == "" {
|
|
return response, fmt.Errorf("experimentId is required")
|
|
}
|
|
experiment, err := vipcardexperimentmod.FindByExperimentID(experimentID)
|
|
if err != nil {
|
|
return response, err
|
|
}
|
|
if experiment == nil {
|
|
return response, fmt.Errorf("experiment not found")
|
|
}
|
|
response.Experiment = experiment
|
|
response.Events, response.ProductImpressions, err = vipcardexperimentmod.EventStatistics(experimentID)
|
|
if err != nil {
|
|
return response, err
|
|
}
|
|
response.Orders, err = rchgordmod.VIPExperimentOrderStatistics(experimentID)
|
|
if err != nil {
|
|
return response, err
|
|
}
|
|
goldOrders, err := prdcthsomod.VIPExperimentGoldOrderStatistics(experimentID)
|
|
if err != nil {
|
|
return response, err
|
|
}
|
|
response.Orders = mergeOrderStatistics(response.Orders, goldOrders)
|
|
return response, nil
|
|
}
|
|
|
|
func mergeOrderStatistics(
|
|
cashOrders []rchgordmod.VIPExperimentOrderStat,
|
|
goldOrders []prdcthsomod.VIPExperimentGoldOrderStat,
|
|
) []rchgordmod.VIPExperimentOrderStat {
|
|
result := append(make([]rchgordmod.VIPExperimentOrderStat, 0, len(cashOrders)+len(goldOrders)), cashOrders...)
|
|
type orderKey struct {
|
|
variant string
|
|
productID primitive.ObjectID
|
|
}
|
|
positions := make(map[orderKey]int, len(result)+len(goldOrders))
|
|
for i := range result {
|
|
positions[orderKey{variant: result[i].Variant, productID: result[i].ProductID}] = i
|
|
}
|
|
for _, goldOrder := range goldOrders {
|
|
key := orderKey{variant: goldOrder.Variant, productID: goldOrder.ProductID}
|
|
position, exists := positions[key]
|
|
if !exists {
|
|
position = len(result)
|
|
positions[key] = position
|
|
result = append(result, rchgordmod.VIPExperimentOrderStat{
|
|
Variant: goldOrder.Variant,
|
|
ProductID: goldOrder.ProductID,
|
|
})
|
|
}
|
|
result[position].GoldPaidOrders += goldOrder.GoldPaidOrders
|
|
result[position].GoldPaidAmount += goldOrder.GoldPaidAmount
|
|
}
|
|
return result
|
|
}
|
|
|
|
func uniqueProductIDs(groups ...[]primitive.ObjectID) []primitive.ObjectID {
|
|
seen := make(map[primitive.ObjectID]struct{})
|
|
result := make([]primitive.ObjectID, 0)
|
|
for _, ids := range groups {
|
|
for _, id := range ids {
|
|
if id.IsZero() {
|
|
continue
|
|
}
|
|
if _, ok := seen[id]; ok {
|
|
continue
|
|
}
|
|
seen[id] = struct{}{}
|
|
result = append(result, id)
|
|
}
|
|
}
|
|
return result
|
|
}
|