Last year I shipped a side project: a long-distance moving cost calculator plus a directory of city-to-city moving routes. The calculator UI took a weekend. The hard part — the part this post is about — was producing believable prices for 27,854 city pairs without hand-entering a single one.
Here's how the pricing model works, where the data comes from, and the engineering tradeoffs that keep 27,854 pages alive.
The demand signal: Census migration flows
The US Census Bureau publishes county-to-county migration flow tables (from the American Community Survey). They tell you, roughly, how many people moved from county X to county Y in a given year. What they don't tell you is price — but price pages for routes nobody travels are dead weight.
So the migration data became the route selector. I aggregated county-level flows up to city level, kept pairs with meaningful annual migration volume, and that became the route list: 27,854 city pairs, each carrying an estimated mover count.
Two practical gotchas. First, the data lags reality by about two years — the most recent release describes the world as it was, not as it is. That's fine for route selection; pricing inputs, which move faster, are handled separately. Second, small flows are suppressed for privacy. I treat suppressed pairs as "unknown demand" rather than zero, so thin but real routes don't get dropped silently.
The cost model: four levers
Every route price comes from one estimator with four weighted inputs:
- Distance. Base cost is roughly linear in miles. I compute great-circle distance between city centroids, then apply a circuity factor (~1.2×) because roads aren't straight lines. That factor is calibrated against published lane rates from FMCSA carrier data.
- Home size. A studio ships around 1,800 lbs; a four-bedroom house ships 10,000+ lbs. Weight determines truck space, and truck space is what you're really buying. The calculator lets users pick their home size; the route pages show ranges across typical sizes.
- Season. Summer moves run 20–30% above winter moves in this industry. A seasonal multiplier handles peak (May–September) vs. off-peak, derived from historical rate seasonality rather than vibes.
- Move type. Full-service movers, moving containers, and DIY truck rentals have completely different cost structures. The estimator outputs a range per move type instead of pretending one number fits all.
Roughly: base_rate(miles, weight) × seasonal_factor × move_type_factor, then rounded into a range. Deliberately a range — false precision ("$4,213.77") is worse than honesty ("$3,800–$4,600").
Where the model admits it's guessing
A model is not a quote. The carrier, fuel surcharges, stairs, long carries, exact dates — all move the real number. The site says this plainly and I'll say it here: these are planning figures. The value is comparative. Knowing that a 2-bedroom move from Austin to Denver costs roughly half of a 4-bedroom move from New York to Los Angeles is genuinely useful even when neither endpoint is exact.
The engineering: static generation at 27,854 pages
The route pages are fully static. The pipeline:
- A Python/pandas data job ingests the Census migration tables, aggregates to city pairs, and joins the pricing model output — one JSON record per route.
- A static site generator builds one page per city pair at build time. 27,854 pages sounds heavy, but each is a small HTML file; the full build takes minutes and the output sits on a CDN with trivial cache behavior.
- Sitemaps are sharded (the 50k-URL-per-sitemap limit means one sitemap index covers everything) and submitted to Search Console.
- Internal linking is structural, not an afterthought: every city page links to its top routes, every route page links back to both cities. That's 27,854 pages with a real link graph and zero orphans.
Why static instead of one dynamic template with query params? Crawlability — search engines index real URLs far more reliably — and speed: no database on the hot path, just a file.
Keeping the data fresh
Inputs drift on different schedules, so the pipeline splits them:
- Stable: city coordinates, the route demand list (rebuilt annually when new Census data lands).
- Volatile: fuel prices (weekly), seasonal multipliers, FMCSA carrier snapshots.
A weekly job recomputes prices and triggers incremental rebuilds of the affected pages. The annual Census refresh rebuilds the route list itself. Separating the two keeps a fuel-price spike from requiring a full 27,854-page rebuild.
The biggest lesson from the whole build: the model's credibility comes from named, real inputs. "We weight distance, home size, season, and move type — here's the data behind each" is checkable. "Our proprietary algorithm computes your perfect price" is not. For a calculator site, trust is the product.
You can poke at the live calculator at myfastmovers.com — one link, because that's the only place it belongs.
Top comments (0)