updates on the api endpoints on the customer page and more

This commit is contained in:
2026-09-02 16:32:53 +05:30
parent 8e2c484bcb
commit d12629a1e4
31 changed files with 3541 additions and 121 deletions

71
internal/legs/legs.go Normal file
View File

@@ -0,0 +1,71 @@
// Package legs answers one question, in one place: does a parcel go straight to
// its receiver, or does it go via a base first?
//
// It exists because two packages need that answer and neither can import the
// other. controllers decides it at pickup-complete (which state the consignment
// lands in, and which base the rider is sent to); internal/routing needs it when
// it sequences a rider's stops, because a hub-routed parcel's next stop is the
// BASE, not the address on the booking. controllers already imports
// internal/routing, so routing cannot import back — and a second copy of the
// rule in routing is exactly the kind of duplication this codebase has been
// bitten by before (the rival status table in dispatchShared, the batch windows
// that drifted between two pages).
//
// So the rule lives here and both call it. Neither owns it.
package legs
import "math"
// HaversineKM is the straight-line distance between two points, in kilometres.
// One definition for the whole codebase — controllers.haversineKM delegates to
// it rather than keeping a second copy.
func HaversineKM(lat1, lon1, lat2, lon2 float64) float64 {
const earthRadiusKM = 6371.0
toRad := func(deg float64) float64 { return deg * math.Pi / 180 }
dLat := toRad(lat2 - lat1)
dLon := toRad(lon2 - lon1)
a := math.Sin(dLat/2)*math.Sin(dLat/2) +
math.Cos(toRad(lat1))*math.Cos(toRad(lat2))*math.Sin(dLon/2)*math.Sin(dLon/2)
return earthRadiusKM * 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a))
}
// MaxHyperlocalKM bounds the straight-line pickup→delivery distance under which
// a booking with a missing or unusable pincode is still treated as hyperlocal.
// It is ONLY consulted when the pincode rule cannot decide: console-created
// kitchen→customer bookings frequently carry accurate coordinates and no
// delivery pincode, and must not be routed through a base because of it.
const MaxHyperlocalKM = 30.0
// SamePostalArea reports whether two pincodes fall in the same 3-digit postal
// area. Pincodes shorter than 3 characters are treated as unknown rather than
// matching, so bad data falls back to the safe hub route instead of quietly
// claiming two parcels belong together.
func SamePostalArea(pickupPincode, deliveryPincode string) bool {
if len(pickupPincode) < 3 || len(deliveryPincode) < 3 {
return false
}
return pickupPincode[:3] == deliveryPincode[:3]
}
// IsHyperlocal decides whether a parcel can skip the base and be carried
// straight to the receiver by the collecting rider.
//
// The pincode-prefix rule decides outright when both pincodes are present — a
// matching prefix is hyperlocal, a differing one is genuinely inter-area, and
// distance does not get a vote. Pollachi is 40km from Coimbatore, closer than
// plenty of runs treated as local, and it is still a different postal area.
//
// Only when a pincode is missing or too short does the straight-line distance
// decide instead. With neither pincodes nor coordinates the answer is false,
// which routes via a base — the safe direction to be wrong in, because a parcel
// that reaches a base can still be forwarded, while one handed to a rider who
// cannot reach the receiver is stuck.
func IsHyperlocal(pickupPincode, deliveryPincode string, pLat, pLng, dLat, dLng float64) bool {
if len(pickupPincode) >= 3 && len(deliveryPincode) >= 3 {
return SamePostalArea(pickupPincode, deliveryPincode)
}
if pLat != 0 && pLng != 0 && dLat != 0 && dLng != 0 {
return HaversineKM(pLat, pLng, dLat, dLng) <= MaxHyperlocalKM
}
return false
}

117
internal/legs/legs_test.go Normal file
View File

@@ -0,0 +1,117 @@
package legs
import "testing"
// The one definition of "does this parcel go via a base?".
//
// It lives in its own package because two callers need it and neither can
// import the other: controllers decides it at pickup-complete, internal/routing
// needs it to know where a rider's leg ends. A second copy in routing was the
// alternative, and this codebase has been bitten by that shape before — a rival
// status table that drifted until the map and the list showed the same parcel
// two different colours.
//
// So the rule is tested here, once, and both callers inherit it.
func TestSamePostalArea(t *testing.T) {
cases := []struct {
name string
pickup string
delivery string
want bool
}{
{"same 3-digit area is one zone", "641012", "641004", true},
{"an identical pincode is trivially one zone", "641012", "641012", true},
{"Coimbatore to Chennai is not", "641012", "600001", false},
{"Coimbatore to Pollachi is not, though it is close", "641012", "642001", false},
{"a short pincode proves nothing", "64", "641004", false},
{"an empty pincode proves nothing", "", "641004", false},
{"two empty pincodes do not match each other", "", "", false},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
if got := SamePostalArea(tc.pickup, tc.delivery); got != tc.want {
t.Errorf("SamePostalArea(%q, %q) = %v, want %v", tc.pickup, tc.delivery, got, tc.want)
}
})
}
}
func TestIsHyperlocalPrefersThePincodeRule(t *testing.T) {
// Coimbatore pickup, Pollachi delivery: ~40 km apart, which is inside the
// distance fallback — but they are different postal areas, and when both
// pincodes are present the prefix rule decides outright. Distance does not
// get a vote, or a parcel would be routed one way on Tuesday and another on
// Wednesday because a geocoder moved a pin.
if IsHyperlocal("641025", "642001", 11.0168, 76.9558, 10.658, 77.008) {
t.Error("a different postal area must route via a base even when it is close")
}
// The mirror: same area, far apart. A 3-digit area can be large, and the
// prefix still decides.
if !IsHyperlocal("641025", "641999", 11.0168, 76.9558, 11.6, 77.5) {
t.Error("the same postal area must stay hyperlocal even when it is a long way across")
}
}
func TestIsHyperlocalFallsBackToDistance(t *testing.T) {
// Console-created bookings frequently carry accurate coordinates and no
// delivery pincode. Routing those through a base because of a missing field
// would send a kitchen-to-doorstep lunch order on a tour of the network.
if !IsHyperlocal("641025", "", 11.0168, 76.9558, 11.05, 77.01) {
t.Error("a short hop with no pincode should stay hyperlocal on the distance fallback")
}
if IsHyperlocal("641025", "", 11.0168, 76.9558, 13.0827, 80.2707) {
t.Error("a 430km haul with no pincode is not hyperlocal whatever the fallback")
}
}
func TestIsHyperlocalIsFalseWhenNothingCanBeProven(t *testing.T) {
// No usable pincodes and no usable coordinates. False routes via a base,
// which is the safe direction to be wrong in: a parcel that reaches a base
// can still be forwarded, while one handed to a rider who cannot reach the
// receiver is simply stuck.
if IsHyperlocal("", "", 0, 0, 0, 0) {
t.Error("with nothing to decide on, the answer must be the safe one")
}
if IsHyperlocal("64", "60", 0, 0, 0, 0) {
t.Error("unusable pincodes and no coordinates must not resolve to hyperlocal")
}
}
func TestIsHyperlocalTreatsAMissingCoordinateAsMissing(t *testing.T) {
// 0,0 is in the Gulf of Guinea. Treating it as a real position would make
// every un-geocoded booking look like a 7,000 km haul — or, worse, let two
// of them look like neighbours.
if IsHyperlocal("641025", "", 11.0168, 76.9558, 0, 0) {
t.Error("a 0,0 delivery point is an unset value, not a location near anything")
}
}
func TestHaversineKM(t *testing.T) {
// A known pair: Coimbatore to Chennai is roughly 430 km great-circle.
km := HaversineKM(11.0168, 76.9558, 13.0827, 80.2707)
if km < 400 || km > 460 {
t.Errorf("Coimbatore→Chennai = %.0f km, expected roughly 430", km)
}
if d := HaversineKM(11.0168, 76.9558, 11.0168, 76.9558); d != 0 {
t.Errorf("a point is %v km from itself, want 0", d)
}
// Symmetric, or distance-based decisions would depend on argument order.
a := HaversineKM(11.0168, 76.9558, 12.9716, 77.5946)
b := HaversineKM(12.9716, 77.5946, 11.0168, 76.9558)
if a != b {
t.Errorf("distance is not symmetric: %v vs %v", a, b)
}
}
func TestMaxHyperlocalKMBoundary(t *testing.T) {
// The fallback threshold is a real operational number, not a magic
// constant — a rider is expected to carry a parcel this far, and not
// further. Guarding it stops a silent widening.
if MaxHyperlocalKM != 30.0 {
t.Errorf("MaxHyperlocalKM = %v; changing it changes which parcels riders carry end to end", MaxHyperlocalKM)
}
}