FMCG beat productivity: bills cut, lines per call and secondary sales
In this article (7 sections)
Related programme: the Data Analytics with Generative AI course (3–4 months; ₹90,000 (inclusive of GST) · EMI from ₹4,000/month).
Beat productivity is productive calls (outlets that placed an order) divided by outlets scheduled on the beat, and lines per call is the number of distinct SKUs billed per productive call. Together with average value per call, they explain where a salesman's secondary sales come from: reaching more outlets, selling more of the range into each one, or selling bigger quantities of a few packs. Build them from the beat plan and the distributor's invoice lines, and count an outlet billed twice on one day as one call.
Two salesmen, one number that hides the difference
An area sales manager (ASM) looking only at weekly secondary sales sees Ravi at ₹9,250 and Imran at ₹4,600 and concludes that Ravi is the better salesman. The scorecard below shows a different picture: Imran converts more of his beat, while Ravi sells a wider range into fewer outlets. They need different coaching, and the value figure alone cannot tell you which.
Where the data comes from
Beat plan (from the sales force app or a planning sheet): salesman, beat_id, visit_date, outlets_scheduled, and ideally one row per scheduled outlet.
Distributor invoice lines (from the distributor management system, DMS): bill_no, bill_date, salesman, outlet_id, sku, qty, gross_value, scheme_discount, net_value.
This is secondary sales — distributor to retailer. Primary sales (company to distributor) are a different table and should never be mixed into a beat scorecard; a distributor stocking up before a price increase says nothing about outlet demand.
Metric definitions
| Metric | Formula |
|---|---|
| Productive calls | Distinct (outlet, date) pairs with at least one line of positive net value |
| Beat productivity % | Productive calls ÷ outlets scheduled × 100 |
| Lines per call | Σ distinct SKUs per productive call ÷ productive calls |
| Average value per call | Net secondary value ÷ productive calls |
| Secondary value | Σ net_value after scheme discounts, before returns adjustment |
Scorecard code (synthetic week, scaled down)
The beats below are shortened to two or three outlets a day so the example stays readable.
import pandas as pd
# Synthetic: one week of beat plans and distributor (DMS) invoice lines
plan = pd.DataFrame({
"salesman": ["Ravi"]*3 + ["Imran"]*3,
"visit_date": ["2026-09-21","2026-09-22","2026-09-23"]*2,
"outlets_scheduled": [3, 2, 3, 3, 3, 3],
})
lines = pd.DataFrame({
"salesman": ["Ravi"]*9 + ["Imran"]*9,
"visit_date": ["2026-09-21"]*4 + ["2026-09-22"]*3 + ["2026-09-23"]*2
+ ["2026-09-21"]*3 + ["2026-09-22"]*3 + ["2026-09-23"]*3,
"outlet_id": ["O1","O1","O2","O2","O3","O3","O4","O5","O5",
"K1","K2","K3","K4","K4","K5","K6","K7","K7"],
"bill_no": ["B1","B1","B2","B3","B4","B4","B5","B6","B6",
"C1","C2","C3","C4","C4b","C5","C6","C7","C8"],
"sku": ["Soap75","Paste100","Soap75","Shampoo","Soap75","Paste100","Soap75","Paste100","Shampoo",
"Soap75","Soap75","Soap75","Soap75","Paste100","Soap75","Soap75","Soap75","FreeSachet"],
"net_value": [1200, 800, 900, 1500, 700, 650, 1000, 1400, 1100,
600, 550, 500, 650, 400, 700, 580, 620, 0],
})
lines = lines[lines["net_value"] > 0] # scheme freebies are not selling lines
# A productive call = an outlet billed on a day, however many invoices it produced
calls = (lines.groupby(["salesman","visit_date","outlet_id"])
.agg(sku_lines=("sku","nunique"), value=("net_value","sum")).reset_index())
scorecard = (calls.groupby("salesman")
.agg(productive_calls=("outlet_id","size"),
lines=("sku_lines","sum"), value=("value","sum"))
.join(plan.groupby("salesman")["outlets_scheduled"].sum()))
scorecard["productivity_pct"] = (100 * scorecard["productive_calls"] / scorecard["outlets_scheduled"]).round(1)
scorecard["lines_per_call"] = (scorecard["lines"] / scorecard["productive_calls"]).round(2)
scorecard["avg_value_per_call"] = (scorecard["value"] / scorecard["productive_calls"]).round(0)
print(scorecard[["outlets_scheduled","productive_calls","productivity_pct","lines_per_call","avg_value_per_call","value"]].to_string())
naive = lines.groupby("salesman")["bill_no"].nunique()
print("\nInvoices counted as calls (the wrong way):", naive.to_dict()) outlets_scheduled productive_calls productivity_pct lines_per_call avg_value_per_call value
salesman
Imran 9 7 77.8 1.14 657.0 4600
Ravi 8 5 62.5 1.80 1850.0 9250
Invoices counted as calls (the wrong way): {'Imran': 8, 'Ravi': 6}Imran billed 78% of his scheduled outlets but almost always a single SKU — 1.14 lines per call, and Soap75 in every one of his calls. Ravi billed only 62.5% of his outlets but sold close to two SKUs per call at nearly three times the value.
What each pattern tells the ASM to do
| Pattern | Likely reading | Coaching action |
|---|---|---|
| High productivity, low lines per call | Reaches outlets, sells only the "must-stock" pack | Range-selling targets; a focus-SKU list per beat |
| Low productivity, high lines per call | Strong with a core set of outlets, skipping or failing at the rest | Check visit compliance (GPS/app check-ins) before judging selling skill |
| Both low | Beat may be badly designed, or the salesman is struggling | Ride along for a day before acting |
| Both high, low value per call | Selling small packs into small outlets | May be correct for that beat's outlet mix; compare with outlet class |
Mistakes that distort the scorecard
Counting invoices as calls. Imran's outlet K4 received two bills on 22 September. Counting invoices gives Imran 8 calls and Ravi 6, and overstates productivity. Use distinct outlet-date pairs.
Free-goods lines. The zero-value FreeSachet line is a scheme freebie. Left in, it would raise lines per call for a salesman who sold nothing extra. The code drops lines with non-positive net value; adjust if your DMS records schemes differently.
Returns and credit notes. Expiry returns taken back on the next visit arrive as negative lines or separate credit notes. Net them in the value metric for the month, but do not let a return day make a productive call look unproductive.
Unplanned outlets. A salesman who bills an outlet not on the day's beat plan raises productive calls without raising the denominator. Decide whether off-beat calls count and apply it to everyone.
SKU codes that are the same product. A soap sold in a four-pack and singly may carry two SKU codes. Lines per call then rewards pack variety, not range. Map to a product family if range selling is the intent.
Putting it in front of the ASM
ASMs read scorecards on phones between market visits. One row per salesman, the four metrics, and one arrow per metric against the previous four weeks is enough. Put secondary value last, not first, so the conversation starts with how the value was earned. Pair each salesman with the matching row of the coaching table rather than a ranking; a league table of value per salesman rewards whoever has the richest beat.
Scorecards like this are standard exercises in the Data Analyst programme. For people already in sales roles, sales executive to sales analyst describes how to turn beat data you already handle into a portfolio project.
Frequently asked questions
What is beat productivity in FMCG sales?
It is the share of outlets scheduled on a salesman's beat that actually placed an order that day — productive calls divided by scheduled calls. Some companies divide by outlets visited instead, so always state the denominator.
What does lines per call mean?
Lines per call is the number of distinct SKUs billed per productive call. It shows range selling — whether the salesman sells only the fast-moving pack or gets the outlet to stock more of the portfolio.
Pankit Kumar has 10 years in Data Science & AI, building and shipping production systems in regulated pharma and clinical environments. He is a freelance trainer at Boston Institute of Analytics, AnalytixLabs and Scaler, and has taught this material to thousands of working professionals.
This article is part of our Data Analytics with Generative AI programme — 3–4 months. The full analyst stack — Excel, SQL, Power BI and Python pipelines — then a generative-AI layer you can prove is right.
Explore Data Analytics with Generative AI