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) // - Regional — different city, same state // - National — 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 "Regional" } return "National" } // pincodeToLatLon returns approximate coordinates for a pincode using its // 3-digit postal division. Returns ok=false for unrecognised pincodes. func pincodeToLatLon(pincode string) (lat, lon float64, ok bool) { if len(pincode) < 3 { return } p, err := strconv.Atoi(pincode[:3]) if err != nil { return } switch { case p >= 500 && p <= 535: return 17.385044, 78.486671, true // Hyderabad / Telangana case p >= 560 && p <= 591: return 12.971599, 77.594566, true // Bengaluru / Karnataka case p >= 600 && p <= 643: return 13.082680, 80.270718, true // Chennai / Tamil Nadu case p >= 670 && p <= 695: return 10.850516, 76.271080, true // Kerala case p >= 380 && p <= 396: return 23.022505, 72.571365, true // Ahmedabad / Gujarat case p >= 400 && p <= 444: return 19.075984, 72.877656, true // Mumbai / Maharashtra } return } // 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 (midpoint price, pricingID, true) on match, or (0, nil, false) when no rule matches. func lookupDoormilePrice(zone, pricingServiceType string, weight float64, category string) (float64, *int, 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, nil, false } } matched := applyFilters(allRules, weight, category) if len(matched) == 0 && category != "General" { matched = applyFilters(allRules, weight, "General") } if len(matched) == 0 { return 0, nil, false } rule := matched[0] id := rule.Doormile_pricing_id return (rule.Minprice + rule.Maxprice) / 2, &id, true }