package controllers import ( "context" "encoding/json" "strconv" "time" "doormile/db" "doormile/models" "doormile/utils" ) // pincodeToState maps an Indian pincode to a state key using its first 3 digits. // Covers the states Doormile operates in; other pincodes fall back to their 2-digit prefix. func pincodeToState(pincode string) string { if len(pincode) < 3 { return pincode } p, err := strconv.Atoi(pincode[:3]) if err != nil { if len(pincode) >= 2 { return pincode[:2] } return pincode } switch { case p >= 500 && p <= 535: return "AP_TS" // Andhra Pradesh / Telangana case p >= 560 && p <= 591: return "KA" // Karnataka case p >= 600 && p <= 643: return "TN" // Tamil Nadu case p >= 670 && p <= 695: return "KL" // Kerala case p >= 380 && p <= 396: return "GJ" // Gujarat case p >= 400 && p <= 444: return "MH" // Maharashtra default: if len(pincode) >= 2 { return pincode[:2] } return pincode } } // resolveZone determines the DoormilePricing zone from pickup and delivery pincodes. // - Local — same 3-digit prefix (same city/sorting district) // - Interstate — different city, same state // - OtherState — different state func resolveZone(pickupPincode, deliveryPincode string) string { if len(pickupPincode) >= 3 && len(deliveryPincode) >= 3 && pickupPincode[:3] == deliveryPincode[:3] { return "Local" } if pincodeToState(pickupPincode) == pincodeToState(deliveryPincode) { return "Interstate" } return "OtherState" } // normalizePricingCategory returns a DoormilePricing-compatible category string. // Falls back to "General" for unknown or empty values. func normalizePricingCategory(category string) string { switch category { case "General", "Documents", "Electronics", "Clothing", "Fragile", "Medical", "Automotive", "Food": return category default: return "General" } } // mapServiceTypeToPricing maps the booking service type to a DoormilePricing service type. // DoormilePricing has only Normal and Express. func mapServiceTypeToPricing(serviceType string) string { if serviceType == "Fast" || serviceType == "Superfast" { return "Express" } return "Normal" } // lookupDoormilePrice fetches pricing for the given zone/serviceType/weight/category // from Redis first, falling back to Postgres on a cache miss. // Returns the midpoint of the matched price band and true, or 0 and false if no rule matches. func lookupDoormilePrice(zone, pricingServiceType string, weight float64, category string) (float64, bool) { var allRules []models.DoormilePricing if db.Rdb != nil { ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) cached, err := db.Rdb.Get(ctx, pricingCacheKey(zone, pricingServiceType)).Result() cancel() if err == nil { if jerr := json.Unmarshal([]byte(cached), &allRules); jerr != nil { utils.Warn("Corrupt pricing cache in booking, evicting", "zone", zone, "servicetype", pricingServiceType) invalidatePricingCache(zone, pricingServiceType) allRules = nil } } } if allRules == nil { var err error allRules, err = loadFromPostgres(zone, pricingServiceType) if err != nil || len(allRules) == 0 { return 0, false } } matched := applyFilters(allRules, weight, category) if len(matched) == 0 && category != "General" { matched = applyFilters(allRules, weight, "General") } if len(matched) == 0 { return 0, false } rule := matched[0] return (rule.Minprice + rule.Maxprice) / 2, true }