feat: enhance tariff processing with error handling and category filtering

This commit is contained in:
2025-08-19 15:22:42 +03:00
parent e2b4b22095
commit b4e55197be
2 changed files with 77 additions and 20 deletions

View File

@@ -3,9 +3,13 @@ package products
import ( import (
"context" "context"
"fmt" "fmt"
"io"
"math" "math"
"net/http"
"sipro-mps/internal/redis" "sipro-mps/internal/redis"
"sipro-mps/pkg/utils"
"strconv" "strconv"
"strings"
"time" "time"
pb "sipro-mps/api/generated/v1/yandexmarket/products" pb "sipro-mps/api/generated/v1/yandexmarket/products"
@@ -15,6 +19,7 @@ import (
"git.denco.store/fakz9/yandex-go-client" "git.denco.store/fakz9/yandex-go-client"
"github.com/samber/lo" "github.com/samber/lo"
"github.com/tidwall/gjson"
) )
const ( const (
@@ -246,46 +251,90 @@ func (r *apiRepository) CalculateProductTariffs(ctx context.Context, marketplace
func (r *apiRepository) setTariffsRateLimit() { func (r *apiRepository) setTariffsRateLimit() {
ym.SetPathLimit("/tariffs/calculate", rateLimitWindow, tariffsRateLimit) ym.SetPathLimit("/tariffs/calculate", rateLimitWindow, tariffsRateLimit)
} }
func getCategoriesError(bodyData string) []int64 {
var result []int64
for _, v := range gjson.Get(bodyData, "errors.#.message").Array() {
errorMessage := v.String()
split := strings.Split(errorMessage, "categories: ")
if len(split) < 2 {
continue
}
categories := strings.Split(strings.TrimSpace(split[1]), ",")
for _, category := range categories {
category = strings.TrimSpace(category)
if category == "" {
continue
}
isDigit, categoryId := utils.IsDigit(category)
if !isDigit || categoryId == nil {
continue
}
result = append(result, *categoryId)
}
}
return result
}
// processTariffChunk processes a single chunk of offers for tariff calculation func filterOffersByCategories(offers []*pb.CalculateProductTariffsRequest_Offer, categories []int64) []*pb.CalculateProductTariffsRequest_Offer {
func (r *apiRepository) processTariffChunk(ctx context.Context, client *ymclient.APIClient, ymParameters *ymclient.CalculateTariffsParametersDTO, offerChunk []*pb.CalculateProductTariffsRequest_Offer, chunkIndex int) (*pb.CalculateProductTariffsResponse, error) { return lo.Filter(offers, func(offer *pb.CalculateProductTariffsRequest_Offer, _ int) bool {
return lo.Contains(categories, offer.CategoryId)
})
}
func (r *apiRepository) makeTariffRequest(context context.Context, client *ymclient.APIClient, ymParameters *ymclient.CalculateTariffsParametersDTO, offerChunk []*pb.CalculateProductTariffsRequest_Offer) (*ymclient.CalculateTariffsResponse, *http.Response, error) {
ymOffers := r.convertOffersToYM(offerChunk) ymOffers := r.convertOffersToYM(offerChunk)
if len(ymOffers) == 0 { if len(ymOffers) == 0 {
fmt.Printf("Skipping chunk %d: no valid offers\n", chunkIndex+1) return nil, nil, fmt.Errorf("no valid offers in chunk")
return nil, nil
} }
if ymParameters.CampaignId != nil && *ymParameters.CampaignId > 0 { if ymParameters.CampaignId != nil && *ymParameters.CampaignId > 0 {
ymParameters.SellingProgram = nil ymParameters.SellingProgram = nil
ymParameters.Frequency = nil ymParameters.Frequency = nil
ymParameters.Currency = nil ymParameters.Currency = nil
} else { } else {
ymParameters.CampaignId = nil ymParameters.CampaignId = nil
} }
ymRequest := ymclient.NewCalculateTariffsRequest(*ymParameters, ymOffers) ymRequest := ymclient.NewCalculateTariffsRequest(*ymParameters, ymOffers)
response, httpResp, err := client.TariffsAPI.CalculateTariffs(ctx). response, httpResp, err := client.TariffsAPI.CalculateTariffs(context).
CalculateTariffsRequest(*ymRequest). CalculateTariffsRequest(*ymRequest).
Execute() Execute()
return response, httpResp, err
}
if err != nil { // processTariffChunk processes a single chunk of offers for tariff calculation
if httpResp != nil && httpResp.Body != nil { func (r *apiRepository) processTariffChunk(ctx context.Context, client *ymclient.APIClient, ymParameters *ymclient.CalculateTariffsParametersDTO, offerChunk []*pb.CalculateProductTariffsRequest_Offer, chunkIndex int) (*pb.CalculateProductTariffsResponse, error) {
bodyData := make([]byte, 2048) globalError := fmt.Errorf("failed to process chunk %d", chunkIndex+1)
_, httpErr := httpResp.Body.Read(bodyData) for range 2 {
if httpErr == nil { response, httpResp, err := r.makeTariffRequest(ctx, client, ymParameters, offerChunk)
fmt.Printf("Error response for chunk %d: %s\n", chunkIndex+1, string(bodyData))
if err != nil {
globalError = err
if httpResp == nil || httpResp.Body == nil {
return nil, fmt.Errorf("failed to call Yandex Market API for chunk %d: %w", chunkIndex+1, err)
} }
bodyData, httpErr := io.ReadAll(httpResp.Body)
if httpErr != nil {
return nil, fmt.Errorf("failed to read response body for chunk %d: %w", chunkIndex+1, httpErr)
}
fmt.Printf("Error response for chunk %d: %s\n", chunkIndex+1, string(bodyData))
categoriesErrors := getCategoriesError(string(bodyData))
if len(categoriesErrors) == 0 {
return nil, fmt.Errorf("failed to call Yandex Market API for chunk %d: %w", chunkIndex+1, err)
}
offerChunk = filterOffersByCategories(offerChunk, categoriesErrors)
continue
} }
return nil, fmt.Errorf("failed to call Yandex Market API for chunk %d: %w", chunkIndex+1, err)
if response == nil || response.Result == nil {
return nil, fmt.Errorf("Warning: received empty response for chunk %d\n", chunkIndex+1)
}
return r.convertResponseToProto(response), nil
} }
return nil, globalError
if response == nil || response.Result == nil {
fmt.Printf("Warning: received empty response for chunk %d\n", chunkIndex+1)
return nil, nil
}
return r.convertResponseToProto(response), nil
} }
// convertOffersToYM converts protobuf offers to Yandex Market format // convertOffersToYM converts protobuf offers to Yandex Market format

View File

@@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"hash/fnv" "hash/fnv"
"math" "math"
"strconv"
"github.com/golang-jwt/jwt/v5" "github.com/golang-jwt/jwt/v5"
) )
@@ -123,3 +124,10 @@ func HashArray[T Hashable](arr []T) (string, error) {
return fmt.Sprintf("%x", h.Sum64()), nil return fmt.Sprintf("%x", h.Sum64()), nil
} }
func IsDigit(v string) (bool, *int64) {
if val, err := strconv.ParseInt(v, 10, 64); err == nil {
return true, &val
}
return false, nil
}