Three of every ten lawn quotes are unprofitable before you even drive out
Driving across town for a $40 quote that won’t close is the most expensive marketing spend in a lawn business. Groundcut rejects the unprofitable quotes before they hit your schedule — using zone rules, per-zone job-value minimums, and a target-hourly-rate flag that runs on every quote.
Below: six protections, what they do, and the database column or API route that backs each one.
Six ways Groundcut rejects unprofitable quotes
Each one is a shipped feature with a column, route, or pricing-rule field you can audit.
- 01
Four service-area zones, each with its own rule
The default zone setup uses four bands by distance from your home base: 0–10mi accept, 10–20mi accept with a $10 distance fee, 20–30mi accept only if the quoted price clears an $80 minimum, 30+ mi reject. Each row in the `zones` table has a `rule` (`accept` / `accept_with_fee` / `high_value_only` / `reject`), a `distance_fee`, and a `min_job_value`. You edit the bands in `/admin/zones` to fit your market — the defaults are a starting point, not a constraint.
zones.rule, zones.distance_fee, zones.min_job_value (default zones seeded in supabase/seed.sql)
- 02
Per-zone minimum job value blocks low-dollar bookings far out
When a customer lands in a `high_value_only` zone, the booking page runs the quote first, then checks the quoted price against the zone’s `min_job_value`. If the quote falls below the floor, the zone-check API returns `accepted: false` with a `reject_reason` ("Job value $52 is below the $80 minimum for this zone") and the booking page never offers a slot. The customer sees a clean "outside our service area for that size of job" message — not a confused checkout.
POST /api/zone-check; zones.min_job_value compared against the polygon-driven quoted_price
- 03
Target hourly rate flag on every quote
You set `target_hourly_rate` in the service’s pricing rules (in `/admin/services`). The pricing engine divides `net_after_fees` (what you actually keep after Stripe’s 2.9% + 30¢) by `estimated_time_mins / 60` and returns `below_target_rate: true` on quotes that don’t clear the threshold. The flag rides along with the quote response so the booking page, the admin job detail, and the operator queue can all surface it. A $48 mow at 65 minutes on a $40/hr target is flagged the moment the customer finishes their drawing — not three months later in QuickBooks.
services.pricing_rules.target_hourly_rate → below_target_rate on /api/quote response
- 04
Hard-reject beyond your maximum distance
Tenant settings carry a `max_distance_miles` value (default 25). Any property whose great-circle distance from your home base exceeds it gets `rule: reject` from `/api/zone-check` — no zone evaluated, no quote attempted. The customer sees a reject message that includes the actual distance, and the booking flow ends before they even start drawing.
tenants.settings.max_distance_miles enforced in /api/zone-check (haversine distance from home_lat / home_lng)
- 05
Distance fees are baked into the quote, not bolted on after
When a property falls in an `accept_with_fee` zone, the `distance_fee` on that zone row is added to the quote as a line item ("Distance fee, $10") and rolls into `quoted_price` before the customer ever sees the total. There’s no "I forgot to charge for the drive" conversation later. The fee is visible on the booking page, on the invoice, and on the operator job card.
zones.distance_fee → input.zone_fee in calculateQuote (packages/shared/src/pricing.ts), line item "Distance fee"
- 06
Capacity awareness so the wrong booking can’t even squeeze in
The booking page calls `/api/capacity` before showing slot pickers. The check sums `estimated_time_mins` across already-scheduled jobs for the requested date and compares against your workday budget. If the day is full, the slot doesn’t appear. A 90-minute job that pushes you over the daily limit never enters the queue in the first place.
POST /api/capacity; sums jobs.estimated_time_mins per date against the workday budget
Why this matters more than another sales channel
Most lawn-care software is built around getting more quotes. Groundcut is built around quoting fewer of the wrong ones. The difference shows up in the first season.
A flagged job is information, not a punishment. The target-hourly-rate flag rides on the quote, the job detail, and the operator card. It doesn’t block the booking by default — you decide. Some operators run a quarter with all zones on `accept` and just watch the flag, then tighten the bands once they see where the unprofitable jobs concentrate.
Zones aren’t arbitrary. The default four-band setup is a sane starting point for a residential operator working out of a single shop. The bands are editable in `/admin/zones` — distance, fee, minimum job value, and rule — so you can shape them to your truck count, your fuel cost, and your local competition.
The economic case is the customers you don’t drive to. A $40 mow 22 miles out costs more in fuel + labor + Stripe’s cut than it returns. Rejecting it isn’t a lost sale — it’s an avoided cost.
Where you configure each piece
/admin/zones— edit the four-zone default. Add a band, change a `rule`, raise a `min_job_value`, drop the `distance_fee` for a band you decide is too aggressive./admin/services— set `target_hourly_rate` on each service. The flag is per-service, so basic mowing and full landscape installs can have different targets./admin/settings— your home-base address (becomes `home_lat` / `home_lng` after geocoding) and `max_distance_miles`. The home base is the anchor for every zone check./admin/jobs— where flagged jobs land for triage. You can accept, cancel, or rebook from here.
Stop-unprofitable-jobs FAQs
I already turn down low-value jobs by ignoring the phone. Why do I need this?+
What if a great recurring customer moves to a new house that’s now 22 miles out?+
How does the target-hourly-rate calculation actually work?+
Doesn’t rejecting bookings lose me revenue I would have grabbed otherwise?+
Is the distance check accurate or is it as-the-crow-flies?+
What if I want to accept everything and just see the flag, not block the booking?+
Where does the rejected-booking customer go?+
Quote fewer of the wrong jobs
14-day free trial. Set your zones, your target hourly rate, and your distance cap; run a few real bookings; see the unprofitable ones get filtered out before they hit your schedule.
14-day free trial · No card required · Cancel any time