Skip to content

Latest commit

 

History

History
288 lines (209 loc) · 9.13 KB

File metadata and controls

288 lines (209 loc) · 9.13 KB

Scoring Model

Baseline Philosophy

The baseline engine must be deterministic, transparent, and auditable. It should optimize for realizable value under perishability risk rather than nominal price.

Priority Score

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_time
  • latest_viable_arrival_time by destination
  • max_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.

Candidate Destination Score

For a lot L and bid or destination candidate D:

RealizableValueScore(L, D) =
  BidValue
  - TransportCost
  - ExpectedDelayLoss
  - ExpectedSpoilageLoss
  - CongestionPenalty
  - RejectionRiskPenalty
  - ExpectedHaircutLoss
  - PaymentRiskPenalty
  + PriorityPreservationBenefit

Where:

  • BidValue is computed from bid price and feasible delivered quantity.
  • TransportCost uses distance, fuel estimate, driver time cost, and toll.
  • ExpectedDelayLoss scales hourly value decay by weather delay, queue wait, road-risk exposure, and the lot priority multiplier.
  • ExpectedSpoilageLoss scales decay sensitivity by adjusted travel plus waiting exposure and the lot priority multiplier.
  • CongestionPenalty uses projected unloading queue and queue-derived wait.
  • RejectionRiskPenalty converts quality mismatch or rule strictness into expected economic loss.
  • ExpectedHaircutLoss captures expected post-arrival price haircuts under buyer stress.
  • PaymentRiskPenalty penalizes lanes with weaker payment reliability.
  • PriorityPreservationBenefit rewards routes that preserve safety margin for high-priority lots, with extra weight when capacity is scarce.

Split-Lot Decision Score

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_total is applied for additional slices beyond the first,
  • extra_handling_delay_loss is 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.

Destination Behavior Terms

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.

Queue-Based Congestion Inputs

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.

Commodity-Specific Unloading Behavior

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_factor comes from the destination behavior profile,
  • packaging_factor comes from the destination handling profile,
  • lot_size_factor penalizes slower handling of larger lots,
  • queue_sensitivity_effect reduces 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.

Transport Formulation

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.

Slice-Level Scoring

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.

Dynamic Destination Capacity

The allocator distinguishes:

  • bid slot capacity,
  • destination market capacity.

Effective destination capacity is the minimum viable value across:

  • market_demand_kg * buyer_acceptance_rate
  • daily_capacity_kg
  • remaining_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.

Feasibility Filters

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.

Scarce Capacity Logic

The deterministic baseline uses a greedy priority-aware pass:

  1. compute PriorityScore for all lots,
  2. sort lots by descending priority and ascending freshness slack,
  3. evaluate all feasible candidates for each lot,
  4. when a lot is split-eligible, compare the best whole-lot outcome against the best split plan,
  5. assign the winner while decrementing destination and vehicle capacity,
  6. 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.

Fail-Fast Invariants

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.

Explanation Format

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.