Minecart · Concept Note

Visit Schedule for Accounts

A per-account visit cadence concept for sales managers running seasonal, contractual, and national-account workflows. Lets agents apply templates, lets reps focus on what matters today, and flags accounts that drift outside their expected visit gap.

Drafted 2026-05-18 · Status: design, no code yet

Why this exists

Sales managers we spoke with — including a prospective client running a marine-supply business and discussions referencing landscapers and golf courses — want to move off rigid weekly schedules toward something that respects seasonality, contractual obligation, and national-account commitments.

The marina example concretized it: visit monthly Jan–Mar, ramp in April, weekly through summer, taper in September, off the rest of the year. Today there is no way to encode that in Minecart. There is also no way to detect that an account is silently drifting out of its expected visit gap, and no way to feed a rep a prioritized daily list based on what's overdue.

The shape of the feature: templates a manager defines, applied manually or in bulk to accounts, that produce expected-visit gaps. The system tracks each account's last qualifying visit, flags breaches when the gap is exceeded, and feeds a scored pool of accounts that reps and agents draw from when building each day's work.

Core concepts

Qualifying visit

A visit "qualifies" when Activity.status = completed and Activity.outcome = substantive. The rep actually spoke with someone at the account. Phone calls and Zoom count — the qualifying definition is medium-agnostic.

A visit that was attempted but unsuccessful ("drove there, no one was around") is a separate outcome. The rep gets credit toward their personal "visits done" KPI but the account does not get its cadence satisfied.

Two-axis outcome model

"Did anything happen?" and "who did you talk to?" are different questions. We model them on two axes so one client's nice-to-have (decision-maker tracking, requested by 1-800-Radiator) doesn't muddy the universal breach math.

Activity.outcome

  • substantive
  • attempted_no_contact
  • cancelled_by_customer
  • cancelled_by_rep

Activity.engagement_level (optional)

  • decision_maker
  • staff
  • none

Cadence as a sequence of annual segments

A template is a list of annual-recurring segments. Each segment covers a month-day range (e.g. 05-01 → 08-31) and specifies a max_gap_days and a preferred_gap_days. Months not covered by any segment have no expected cadence — intentional, for seasonal businesses that should not be flagged in the off-season.

Contract-window cases ("we must visit weekly for the next six months") are handled by end-dating the account's schedule assignment, not by making the template's segments absolute.

Example: Marina template

Jan
Feb
Mar
Apr
May
Jun
Jul
Aug
Sep
Oct
Nov
Dec
cadence
quarterly (gap ≤ 90d)
monthly
weekly (gap ≤ 10d)
monthly
no cadence

Q1: light touch. April: warming up. May–Aug: peak season, weekly. September: winding down. Oct–Dec: nothing expected, no breaches will fire.

Display calendar-shaped, evaluate gap-based

Calendar-month boundaries are how humans understand cadence ("monthly", "quarterly"), so that's what we show. But pure calendar math has nasty edges: two satisfied "calendar quarters" can be 91 days apart, two satisfied "calendar weeks" can be 13 days apart.

Internally we compute breach by gap: days_since_last_qualifying_visit > max_gap_days. Externally we render "Monthly cadence, last visit May 3, due by early June." Best of both: legible UI, defensible math, no quarterly cadence that silently allows 91-day gaps.

Data model

All new tables follow the existing UUID-PK / team-scoped / sync-metadata pattern. Two new fields on Activity, two denormalized convenience columns on Account.

ModelKey fieldsNotes
VisitCadenceTemplate team, name, is_preset, segments (JSON) Team-scoped. Per-team copies of preset templates (Weekly / Bi-Weekly / Monthly / Quarterly).
segment (JSON shape) start_mmdd, end_mmdd, max_gap_days, preferred_gap_days Annual-recurring. Non-overlapping. Gaps between segments mean "no cadence."
AccountVisitSchedule account, template (nullable), segments_override (JSON), effective_from, effective_to Per-account, time-bounded. One active row per account. History preserved by closing old rows.
Activity (additions) outcome, engagement_level, outcome_backfilled All existing completed visits backfilled as substantive with the flag set.
Account (additions) last_qualifying_visit_at, last_qualifying_visit_id, current_schedule_id Denormalized for fast list rendering. Maintained by post-save signal + nightly recompute.
RepUnavailability user, start_date, end_date, kind, source, external_ref Populated manually or by an agent that reads the rep's Google Calendar.
TeamHoliday team, date, name Team-level calendar. Each team owns its own list.

Override semantics: snapshot-and-detach

When a manager hits "customize" on a template-linked account, we copy the template's segments into segments_override and set template=NULL. The account is now detached — future edits to the template do not flow through. The UI shows "12 accounts customized, won't be affected" when a manager edits a template.

We rejected partial-override-with-merge because it is a debugging trap. Either you follow a template fully, or you have your own segments. Nothing in between.

Mid-period template switches

Switching an account from Monthly to Weekly on May 15 stores the change as a new AccountVisitSchedule row with effective_from = 2026-05-15 and end-dates the prior row. Historical breach math reads the schedule that was active at the time. Three months from now we can still explain May's numbers.

Workflows

Sales manager

Sales rep

Agent

Breach detection

A breach fires when today - last_qualifying_visit_at > max_gap_days for the segment active today. PTO and holidays do not shorten the gap — they annotate the breach so managers can see "this slipped because Rep X was out May 5–10."

Single breaches are not punished. The weekly report surfaces patterns — two consecutive breach windows, or a trailing cumulative deficit — to avoid alert noise on the first miss. As the client put it: an account is not lost based on one missed visit, but on a pattern.

"Overdue at handover" badge. When an account is reassigned to a new rep, breaches that existed at handover time are tagged separately from breaches the new rep accumulated. New reps inheriting 80 accounts on day one shouldn't look like they're failing on day two.

Scored pool + EntityList

The scored pool is a query, not a stored object. It ranks each rep's assigned accounts by overdue severity weighted by account priority. The day's list — held in an EntityList, which Minecart already has — is built from that pool, by an agent / router / the rep, the night before.

A rep saying "Southside on Monday, Northside on Tuesday, doctor appointment Friday morning" maps cleanly onto five EntityLists with geographically clustered accounts. Once built, reps can reorder or add ad-hoc visits freely. Ad-hoc visits show up in the daily report whether or not they were on the planned list.

The cadence engine writes nothing to EntityList — it only supplies the score. EntityList remains a presentation concern.

KPI separation

Account cadence and rep performance are different metrics and should never be merged.

Per account

  • Cadence target (current segment)
  • Actual gap
  • Breach? (binary)
  • Pattern severity

Per rep

  • Visits done vs quota
  • Substantive ratio (quality signal)
  • Accounts overdue they're assigned to
  • "Overdue at handover" carve-out

Shared accounts: any rep's substantive visit satisfies the account's cadence. Each rep's individual visit count credits the rep who did the visit. We do not compute "rep cadence compliance" — that creates perverse incentives when accounts are shared.

Scope

v1 — in scope v1

Deferred v1.1+

Open threads design

Decisions we made and why

Two-axis outcome, not one rich enum.

"Did anything happen" and "who did you talk to" are different dimensions. Collapsing them couples one client's optional decision-maker tracking to the universal breach math. Keep them separate; let the second axis be opt-in per team.

GPS is a signal, not a requirement.

A visit can be qualifying without GPS proximity (Zoom call with the shop owner counts). GPS-near-account just auto-populates outcome=substantive when the rep saves the activity, which the rep can always override. Otherwise Zoom calls and parking-lot writes-ups don't count, and that's not the world we want.

Gap-based math, calendar-shaped display.

Calendar-quarter boundaries silently allow 91-day gaps between satisfied "quarterly" visits. Calendar weeks allow 13-day gaps for "weekly." Gap math fixes this without confusing the humans, who still read the surface as monthly / quarterly.

Annotate PTO, don't shorten the gap.

Hidden math no one can explain three months later is a trust-killer. Compute breach at face value, attach "Rep X PTO 5/5–5/10" annotations, let pattern detection decide what's noise vs signal.

Snapshot-and-detach, not partial override.

Trying to merge per-account tweaks with template changes is debugging hell. Either you follow a template, or you have your own segments. The customize button copies and unlinks; that's the deal.

No parent-account inheritance of cadence.

A national-account parent can be a convenient bulk-apply target, but the live cadence read always comes from the account itself. Avoids "I edited the parent and accidentally rescheduled 47 marinas."

Backfill historic visits as substantive, with a flag.

Asking reps to retroactively label 50,000 historical activities will never happen. The lie is small, the flag (outcome_backfilled=true) keeps it auditable, and the breach math works from day one.

No sales-tier → template auto-suggestion in v1.

Once you ship a rule engine, every stakeholder wants their condition added. Ship templates + manual application first. Add a ranker when there's real data on what tier-to-template mappings clients actually choose.

In one paragraph

Templates define seasonal visit gaps. Managers apply them to accounts (alone or in bulk) and customize as needed. Reps save activities with a clear outcome; substantive visits clear the cadence. The system tracks last qualifying visit and flags accounts whose gap exceeds the template's allowance, annotates breaches with PTO and holiday context, and feeds reps a prioritized scored pool of accounts to visit. Calendar display, gap-based math, snapshot-and-detach overrides, KPIs kept separate. v1 ships the model, the engine, the weekly report, and the carve-outs for handover and PTO.