The baseline engine must be deterministic, transparent, and auditable. It should optimize for realizable value under perishability risk rather than nominal price.
PriorityScore(L) is computed first and then used in eligibility filtering, candidate ranking, scarce-capacity allocation, and reassignment sensitivity.
Baseline normalized formulation:
PriorityScore(L) =
0.24 * decay_rate_index
+ 0.20 * value_loss_per_hour_index
+ 0.20 * freshness_window_risk_index
+ 0.10 * storage_absence_index
+ 0.11 * weather_exposure_index
+ 0.15 * dispatch_delay_risk_index
Additional derived outputs:
latest_viable_dispatch_timelatest_viable_arrival_timeby destinationmax_detour_tolerance_km
The baseline implementation also uses PriorityScore(L) as a multiplier inside delay and spoilage loss estimation. That is what makes buyer assignment change when perishability priority changes, even when the visible bid pair is unchanged.
For a lot L and bid or destination candidate D:
RealizableValueScore(L, D) =
BidValue
- TransportCost
- ExpectedDelayLoss
- ExpectedSpoilageLoss
- CongestionPenalty
- RejectionRiskPenalty
- ExpectedHaircutLoss
- PaymentRiskPenalty
+ PriorityPreservationBenefit
Where:
BidValueis computed from bid price and feasible delivered quantity.TransportCostuses distance, fuel estimate, driver time cost, and toll.ExpectedDelayLossscales hourly value decay by weather delay, queue wait, road-risk exposure, and the lot priority multiplier.ExpectedSpoilageLossscales decay sensitivity by adjusted travel plus waiting exposure and the lot priority multiplier.CongestionPenaltyuses projected unloading queue and queue-derived wait.RejectionRiskPenaltyconverts quality mismatch or rule strictness into expected economic loss.ExpectedHaircutLosscaptures expected post-arrival price haircuts under buyer stress.PaymentRiskPenaltypenalizes lanes with weaker payment reliability.PriorityPreservationBenefitrewards routes that preserve safety margin for high-priority lots, with extra weight when capacity is scarce.
For a split-eligible lot, the allocator compares:
- the best feasible whole-lot candidate,
- the best feasible split plan.
The split-plan objective is:
TotalLotRealizableValue =
sum(slice_realizable_value_scores)
- split_penalty_total
- extra_handling_delay_loss
Where:
- each slice is scored with the same realizable-value model used for whole lots,
split_penalty_totalis applied for additional slices beyond the first,extra_handling_delay_lossis introduced through later dispatch times for later slices.
The baseline engine chooses the split plan only when its total realized value beats both:
- the best whole-lot score,
- a zero-realization floor.
This keeps splitting economically grounded instead of turning it into a default dispatch behavior.
The destination-behavior layer is deterministic and interpretable. It computes:
effective_acceptance_reliability =
baseline_acceptance_reliability
* queue_pressure_adjustment
* capacity_pressure_adjustment
* late_arrival_adjustment
* quality_mismatch_adjustment
rejection_probability =
base_rejection_risk
+ quality_rule_risk
+ quality_mismatch_penalty
+ low_acceptance_penalty
+ queue_pressure_penalty
+ capacity_pressure_penalty
effective_haircut_pct =
baseline_haircut_pct
+ late_arrival_haircut
+ capacity_pressure_haircut
+ queue_pressure_haircut
+ quality_mismatch_haircut
These terms matter because a higher nominal bid can now lose without becoming hard-infeasible. The engine can conclude that the buyer is still reachable but too risky on expected realization.
For each destination snapshot:
queue(t+1) = max(0, queue(t) + arrivals - unload_capacity)
expected_wait_time = queue_length / unload_rate
The engine projects queue state over the route travel horizon. That projected wait becomes:
- a direct part of adjusted travel time,
- a direct part of delay loss,
- the basis of the congestion penalty,
- part of the explanation payload stored for each candidate.
The projected wait no longer uses one flat unload rate for every lot. The engine computes:
effective_unload_rate =
base_unload_rate
* commodity_factor
* packaging_factor
* lot_size_factor
* queue_sensitivity_effect
Where:
commodity_factorcomes from the destination behavior profile,packaging_factorcomes from the destination handling profile,lot_size_factorpenalizes slower handling of larger lots,queue_sensitivity_effectreduces unload speed as queue pressure rises.
This is how loose tomatoes, mesh-bag onions, and crate-based processor intake can produce materially different queue times at the same destination family.
The deterministic baseline uses:
adjusted_travel_time =
base_travel_time
+ weather_delay_hours
+ queue_wait_time
And:
transport_cost =
max(fuel_cost_estimate, distance_km * fuel_rate_inr_per_km)
+ adjusted_travel_time * driver_time_cost_inr_per_hour
+ toll_cost_inr
Road risk is modeled as delay exposure rather than direct travel time inflation:
road_delay_exposure =
eta_uncertainty_hours * (1 + road_risk_factor)
That term increases expected delay loss without collapsing the model into a shortest-path optimizer.
Actual dispatch time also respects vehicle block timing. A later-available truck is still considered if the lot remains viable after the wait.
Each slice is scored against its own lane conditions:
- slice-specific requested quantity,
- slice-specific vehicle availability,
- destination queue state after earlier slices are applied,
- destination-specific rejection and haircut exposure,
- marginal split penalty for later slices,
- extra dispatch delay from split handling.
That means one slice of a tomato lot can go to a nearby processor while another slice goes to a farther mandi, with different queue wait, haircut loss, and transport cost on each lane.
The allocator distinguishes:
- bid slot capacity,
- destination market capacity.
Effective destination capacity is the minimum viable value across:
market_demand_kg * buyer_acceptance_ratedaily_capacity_kgremaining_capacity_kg
Winning allocations decrement both:
- remaining bid capacity,
- remaining destination capacity.
This is what lets the engine avoid oversaturating a mandi or processor even when a nominally attractive bid is still active.
Capacity pressure also degrades acceptance reliability and increases rejection pressure before hard infeasibility.
A candidate is infeasible when any of the following hold:
- arrival is later than the lot hard freshness deadline,
- arrival is later than the bid latest acceptable arrival,
- the buyer bid is not active at the selected dispatch time,
- required cold chain is unavailable,
- remaining bid slot capacity is exhausted,
- remaining vehicle capacity is insufficient and the lot is unsplittable,
- destination restrictions or state constraints are violated.
The deterministic baseline uses a greedy priority-aware pass:
- compute
PriorityScorefor all lots, - sort lots by descending priority and ascending freshness slack,
- evaluate all feasible candidates for each lot,
- when a lot is split-eligible, compare the best whole-lot outcome against the best split plan,
- assign the winner while decrementing destination and vehicle capacity,
- persist losing candidates and split alternatives as counterfactuals.
This is intentionally simple but preserves the invention core. OR-Tools can replace the allocator later without changing the domain contract.
Winning candidates are rejected immediately if any invariant would be violated:
- arrival slack is negative,
- destination slot capacity would go negative,
- destination market remaining capacity would go negative,
- vehicle capacity would go negative.
These are enforced in code so the allocator cannot silently drift into a generic best-effort scheduler.
The same fail-fast rules apply to slices. A split plan cannot win if any chosen slice would violate freshness, destination capacity, or vehicle capacity.
Human-readable explanations now include:
- lot identity and quantity,
- candidate destination list,
- bid value per kg,
- transport time,
- queue wait time,
- effective unload rate,
- acceptance reliability,
- payment reliability,
- rejection probability,
- expected haircut loss,
- queue sensitivity effect,
- transport cost,
- spoilage loss per kg,
- final score,
- best unsplit option when a split plan wins,
- chosen slices and quantities when a split plan wins,
- split penalties and unassigned remainder,
- chosen destination and concise reason.
That format is used both for operator trust and for preserving the distinct technical story of the engine.