How FLITO Decides Which Truck Fits a Load
FLITO filters out every truck that can't do a job, scores the rest out of 100 on fit, proximity, reputation, price and readiness, and reserves a truck's days with one conditional write so two bookings can never land on the same truck. Here's how the matching works and why.
A freight marketplace lives or dies on one screen: a shipper posts a load and asks which trucks can take it. FLITO, the freight-matching platform I'm building for Nepal, exists to put trucks that would otherwise drive home empty back to work, so that answer has to be correct - never offer a truck that can't actually do the job - and useful, with the best option first and a reason a shipper can read. Here's how the matching service does both, and how it stops two bookings from ever landing on the same truck.
Filter first: a truck either can do the job or it can't
Before anything gets a score, every truck has to pass a set of hard rules. It must be active, its owner must be active and verified, it must carry at least its share of the load (the whole load, or an even part of it when the load needs several trucks), both the pickup and the drop-off must sit inside its service area (all of Nepal, one province or one district), and it can't already be booked on any day the trip needs. The MongoDB query handles the cheap parts - status, capacity, reserved days - and the rest runs in code over what comes back.
The function behind those rules doesn't return true or false. It returns a reason: 'Carries up to 5,000 kg, and this load is 7,000 kg', or 'Already booked on some of the days this trip needs'. And the same function answers two questions - which trucks a shipper sees for a load, and which trucks an owner may offer on it. One rule set with two callers means the shipper's view and the owner's view can never disagree about what's possible.
Availability in trip days, not timestamps
Availability is the rule that's easiest to get wrong. Kathmandu to Nepalgunj is about 530 km, and a loaded truck on mixed highway and hill road averages nowhere near what a car does. FLITO plans with 28 km/h on average, two hours of loading and unloading, and ten driving hours a day. That makes Kathmandu to Pokhara a one-day trip and Kathmandu to Nepalgunj a three-day one, capped at fourteen days for anything longer.
A booking blocks whole Nepal calendar days, from the pickup day until the trip should be over - but not the drive back, so the owner can still pick up a return load on the way home, which is the whole point of the platform. The days are stored on the truck as plain day keys like 2026-09-16, which turns 'is this truck free?' into a set-membership check instead of overlapping-interval arithmetic on timestamps.
Scoring what is left, out of 100
Every truck that passes the filter is scored on five signals, each between 0 and 1, then weighted into a score out of 100:
- Fit (30 points): how much of the truck its share of the load fills. Filling 60% counts as a full fit. A much bigger truck than the load needs still works, but it costs more to run, so it scores lower, down to a floor of 15%.
- Proximity (25 points): the road distance from where the truck is based to the pickup, falling to zero at 250 km.
- Reputation (20 points): the owner's rating, smoothed as if every owner started with three 4-star reviews so a single 5-star review can't outrank a long good record, plus completed trips counting up to twenty. Rating is three quarters of this signal and experience one quarter.
- Price (15 points): the owner's asking price - their rate per km times the trip distance, rounded to the nearest 100 and never below their minimum charge - compared with the cheapest asking price in the list.
- Readiness (10 points): a verified driver already assigned to the truck, current insurance, and a truck an admin has verified.
Unknowns score neutral, and ties go to the cheaper truck
Real data is patchy. A load might not have a weight, a truck might not have a base location, an owner might not have set a rate. Scoring any of those as zero would quietly punish a truck for missing information rather than for being a worse match, so an unknown signal scores a neutral 0.5 instead. Equal scores are broken by the lower asking price, and the shipper sees the top thirty.
Reasons, not just a number
A score of 78 means nothing to a shipper choosing between trucks. So the same function that scores a truck also produces up to three reasons, strongest first, and only when a signal is genuinely strong: 'Fills 85% of the truck', 'Based near the pickup', 'Lowest asking price', 'Rated 4.6 by 12 shippers', 'Driver assigned'. 'Lowest asking price' only appears when more than one truck has a price to compare. Because the reasons come from the same numbers as the ranking, the explanation can't drift away from the order.
What a shipper sees about the truck itself is limited on purpose too: its type, capacity, body, features and where it's based - never its registration, chassis, engine or insurance numbers before there's a booking.
Real road distances, but never a hard dependency
Proximity and price both depend on distance, and in Nepal a straight line is misleading: Kathmandu and Pokhara are about 145 km apart as the crow flies and about 200 km by road. FLITO asks an OSRM routing server, built on OpenStreetMap roads, for real road distances. By default that's the free public demo server, which asks for at most about one request a second, so requests are queued a second apart, up to 80 truck bases are sent in one request, and every answer is cached for a week.
Routing is best-effort. If the server is slow (there is a six-second timeout), down, or switched off in configuration, the distance falls back to the straight line times 1.4. Matching then gets slightly less precise instead of failing, and a shipper never sees an error because a third-party server had a bad minute.
Two bookings, one truck: a single conditional write
Matching shows a truck as free, but between that screen and an accepted offer another booking can land. So accepting an offer doesn't check availability and then write. It reserves the truck in one conditional update: find this truck where it's active and none of the trip's days are already in its reserved days, and add all of them in the same operation. MongoDB applies an update to a single document atomically, so when two acceptances race for overlapping days exactly one of them matches. The other gets a clear 409: that truck is no longer available for all the days this trip needs.
A load can need up to ten trucks, and each truck is a slot, so a slot on the load is claimed the same way: its count of booked trucks goes up by one only if it's still taking offers and has a slot free, which means that when acceptances race for the last truck exactly one gets it. Then the offer itself is accepted, only if it hasn't changed since it was read. If a later step loses its race, the earlier steps are undone: the truck's days are released and the load gets its slot back. There's no multi-document transaction involved; each step is a compare-and-set on one document, with a clean-up path when it fails.
The matching service is about 250 lines, and none of it is machine learning. For a marketplace that's just starting, hand-set weights you can read in one object are the right place to begin: every ranking can be explained to a shipper, every weight can be argued about, and once there's enough booking history to learn from, there's a clear baseline to beat. The parts I'd keep no matter how the scoring evolves: filter on hard rules before scoring anything, give every rejection a reason, and let the database - not a check-then-write in application code - decide who gets the truck.
