[{"data":1,"prerenderedAt":2375},["ShallowReactive",2],{"doc:\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-sales-commission-report":3,"surround:\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-sales-commission-report":2340,"ancestors:\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-sales-commission-report":2349,"children:\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-sales-commission-report":2354,"related:\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-sales-commission-report":2355},{"id":4,"title":5,"body":6,"dateModified":2304,"datePublished":2304,"description":2305,"extension":2306,"faq":2307,"keyword":2316,"kind":2317,"meta":2318,"navigation":265,"path":2328,"seo":2329,"slug":2332,"stem":2333,"topics":2334,"type":2338,"__hash__":2339},"docs\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-sales-commission-report\u002Findex.md","Build a Sales Commission Report with Python and Excel",{"type":7,"value":8,"toc":2291},"minimark",[9,19,181,186,220,230,234,237,434,437,441,448,691,697,778,782,794,910,916,1048,1052,1055,1295,1298,1302,1305,2150,2158,2162,2206,2210,2213,2217,2226,2230,2236,2242,2248,2254,2258,2287],[10,11,12,13,18],"p",{},"Commission is the report where every rep checks the arithmetic. It has to apply the plan exactly as written — which tier rates apply, whether they are marginal or apply to the whole amount, what happens to cancelled deals — and it has to show the working, because a statement that just says \"commission: 9,700\" generates questions. The inputs are simple: a month of deals from the CRM and a plan table. This page, part of ",[14,15,17],"a",{"href":16},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002F","Building Finance and Operations Reports",", calculates commissions for both common tier styles, applies a clawback, flags reps close to the next tier and writes a payroll summary plus one statement per rep.",[20,21,29,30,29,34,29,38,29,46,29,56,29,67,29,74,29,79,29,85,29,90,29,93,29,99,29,103,29,106,29,110,29,113,29,116,29,122,29,126,29,129,29,133,29,136,29,139,29,145,29,149,29,152,29,156,29,159,29,162,29,168,29,173,29,177],"svg",{"viewBox":22,"role":23,"ariaLabelledBy":24,"xmlns":27,"style":28},"4 0 752 142","img",[25,26],"com-flow-t","com-flow-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[31,32,33],"title",{"id":25},"From closed deals to commission statements",[35,36,37],"desc",{"id":26},"Five steps. Filter the deals to those closed in the month. Total the sales per rep. Apply the tier table. Subtract clawbacks for cancelled deals. Write a payroll summary and one statement per rep.",[39,40],"rect",{"x":41,"y":42,"width":43,"height":44,"fill":45},"4","0","752","142","#ffffff",[39,47],{"x":48,"y":49,"width":50,"height":51,"rx":52,"fill":53,"stroke":54,"style":55},"20.0","16.0","115.2","110.5","12","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:2px",[57,58,60],"g",{"transform":59},"translate(65.6,37.8) scale(1.000)",[61,62],"path",{"d":63,"fill":64,"stroke":65,"style":66},"M3 5h18l-7 8v6l-4 2v-8z","none","var(--brand-strong,#4338ca)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[68,69,73],"text",{"x":70,"y":71,"style":72},"77.6","84.2","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Filter deals",[68,75,78],{"x":70,"y":76,"style":77},"103.8","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","closed in month",[80,81],"line",{"x1":82,"y1":83,"x2":84,"y2":83,"stroke":54,"style":55},"140.2","71.2","162.2",[86,87],"polygon",{"points":88,"fill":89},"166.2,71.2 157.2,76.2 157.2,66.2","#5b5cf0",[39,91],{"x":92,"y":49,"width":50,"height":51,"rx":52,"fill":53,"stroke":54,"style":55},"171.2",[57,94,96],{"transform":95},"translate(216.8,37.8) scale(1.000)",[61,97],{"d":98,"fill":64,"stroke":65,"style":66},"M9 11a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7z M2.5 20c0-3.6 2.9-6 6.5-6s6.5 2.4 6.5 6 M16 4.5a3.3 3.3 0 0 1 0 6.3 M18 14.3c2.2.7 3.5 2.8 3.5 5.7",[68,100,102],{"x":101,"y":71,"style":72},"228.8","Total per rep",[68,104,105],{"x":101,"y":76,"style":77},"groupby",[80,107],{"x1":108,"y1":83,"x2":109,"y2":83,"stroke":54,"style":55},"291.4","313.4",[86,111],{"points":112,"fill":89},"317.4,71.2 308.4,76.2 308.4,66.2",[39,114],{"x":115,"y":49,"width":50,"height":51,"rx":52,"fill":53,"stroke":54,"style":55},"322.4",[57,117,119],{"transform":118},"translate(368.0,37.8) scale(1.000)",[61,120],{"d":121,"fill":64,"stroke":65,"style":66},"M12 9a3 3 0 1 0 .01 0z M12 2.5v3 M12 18.5v3 M2.5 12h3 M18.5 12h3 M5.3 5.3l2.1 2.1 M16.6 16.6l2.1 2.1 M5.3 18.7l2.1-2.1 M16.6 7.4l2.1-2.1",[68,123,125],{"x":124,"y":71,"style":72},"380.0","Apply tiers",[68,127,128],{"x":124,"y":76,"style":77},"plan table",[80,130],{"x1":131,"y1":83,"x2":132,"y2":83,"stroke":54,"style":55},"442.6","464.6",[86,134],{"points":135,"fill":89},"468.6,71.2 459.6,76.2 459.6,66.2",[39,137],{"x":138,"y":49,"width":50,"height":51,"rx":52,"fill":53,"stroke":54,"style":55},"473.6",[57,140,142],{"transform":141},"translate(519.2,37.8) scale(1.000)",[61,143],{"d":144,"fill":64,"stroke":65,"style":66},"M20 11a8 8 0 1 0-2.3 5.7 M20 4v7h-7",[68,146,148],{"x":147,"y":71,"style":72},"531.2","Clawbacks",[68,150,151],{"x":147,"y":76,"style":77},"cancelled deals",[80,153],{"x1":154,"y1":83,"x2":155,"y2":83,"stroke":54,"style":55},"593.8","615.8",[86,157],{"points":158,"fill":89},"619.8,71.2 610.8,76.2 610.8,66.2",[39,160],{"x":161,"y":49,"width":50,"height":51,"rx":52,"fill":53,"stroke":54,"style":55},"624.8",[57,163,165],{"transform":164},"translate(670.4,29.0) scale(1.000)",[61,166],{"d":167,"fill":64,"stroke":65,"style":66},"M6 3h9l4 4v14H6z M15 3v4h4 M9.5 11.5l4.5 6 M14 11.5l-4.5 6",[68,169,172],{"x":170,"y":171,"style":72},"682.4","75.5","Statements",[68,174,176],{"x":170,"y":175,"style":77},"95.0","one sheet per",[68,178,180],{"x":170,"y":179,"style":77},"112.5","rep",[182,183,185],"h2",{"id":184},"prerequisites","Prerequisites",[187,188,193],"pre",{"className":189,"code":190,"language":191,"meta":192,"style":192},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas numpy openpyxl xlsxwriter\n","bash","",[194,195,196],"code",{"__ignoreMap":192},[197,198,200,204,208,211,214,217],"span",{"class":80,"line":199},1,[197,201,203],{"class":202},"sMTad","pip",[197,205,207],{"class":206},"srMev"," install",[197,209,210],{"class":206}," pandas",[197,212,213],{"class":206}," numpy",[197,215,216],{"class":206}," openpyxl",[197,218,219],{"class":206}," xlsxwriter\n",[10,221,222,225,226,229],{},[194,223,224],{},"deals.xlsx"," has the September deals for three reps, one of them still pending, plus an August deal that has since been cancelled. ",[194,227,228],{},"commission_plan.xlsx"," holds the tier table — 5% up to 50,000, 8% from 50,000 to 100,000, 10% above — and a record of the commission already paid on the cancelled deal.",[182,231,233],{"id":232},"step-1-total-the-commissionable-sales","Step 1 — Total the commissionable sales",[10,235,236],{},"Only deals closed in the month count. Pending deals wait for next month; cancelled ones are handled as clawbacks, not as negative sales:",[187,238,242],{"className":239,"code":240,"language":241,"meta":192,"style":192},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\nmonth_start, month_end = pd.Timestamp(\"2026-09-01\"), pd.Timestamp(\"2026-09-30\")\ndeals = pd.read_excel(\"deals.xlsx\", parse_dates=[\"Close Date\"])\nin_month = deals[deals[\"Close Date\"].between(month_start, month_end)]\nclosed = in_month[in_month[\"Status\"] == \"closed\"]\nsales = closed.groupby(\"Rep\")[\"Amount\"].sum()\nprint(sales.to_dict())\nprint(\"not paid this month:\", in_month.loc[in_month[\"Status\"] != \"closed\", [\"Deal\", \"Status\"]].values.tolist())\n","python",[194,243,244,260,267,291,323,339,365,388,398],{"__ignoreMap":192},[197,245,246,250,254,257],{"class":80,"line":199},[197,247,249],{"class":248},"s-kum","import",[197,251,253],{"class":252},"skGVy"," pandas ",[197,255,256],{"class":248},"as",[197,258,259],{"class":252}," pd\n",[197,261,263],{"class":80,"line":262},2,[197,264,266],{"emptyLinePlaceholder":265},true,"\n",[197,268,270,273,276,279,282,285,288],{"class":80,"line":269},3,[197,271,272],{"class":252},"month_start, month_end ",[197,274,275],{"class":248},"=",[197,277,278],{"class":252}," pd.Timestamp(",[197,280,281],{"class":206},"\"2026-09-01\"",[197,283,284],{"class":252},"), pd.Timestamp(",[197,286,287],{"class":206},"\"2026-09-30\"",[197,289,290],{"class":252},")\n",[197,292,294,297,299,302,305,308,312,314,317,320],{"class":80,"line":293},4,[197,295,296],{"class":252},"deals ",[197,298,275],{"class":248},[197,300,301],{"class":252}," pd.read_excel(",[197,303,304],{"class":206},"\"deals.xlsx\"",[197,306,307],{"class":252},", ",[197,309,311],{"class":310},"sa561","parse_dates",[197,313,275],{"class":248},[197,315,316],{"class":252},"[",[197,318,319],{"class":206},"\"Close Date\"",[197,321,322],{"class":252},"])\n",[197,324,326,329,331,334,336],{"class":80,"line":325},5,[197,327,328],{"class":252},"in_month ",[197,330,275],{"class":248},[197,332,333],{"class":252}," deals[deals[",[197,335,319],{"class":206},[197,337,338],{"class":252},"].between(month_start, month_end)]\n",[197,340,342,345,347,350,353,356,359,362],{"class":80,"line":341},6,[197,343,344],{"class":252},"closed ",[197,346,275],{"class":248},[197,348,349],{"class":252}," in_month[in_month[",[197,351,352],{"class":206},"\"Status\"",[197,354,355],{"class":252},"] ",[197,357,358],{"class":248},"==",[197,360,361],{"class":206}," \"closed\"",[197,363,364],{"class":252},"]\n",[197,366,368,371,373,376,379,382,385],{"class":80,"line":367},7,[197,369,370],{"class":252},"sales ",[197,372,275],{"class":248},[197,374,375],{"class":252}," closed.groupby(",[197,377,378],{"class":206},"\"Rep\"",[197,380,381],{"class":252},")[",[197,383,384],{"class":206},"\"Amount\"",[197,386,387],{"class":252},"].sum()\n",[197,389,391,395],{"class":80,"line":390},8,[197,392,394],{"class":393},"sP0c6","print",[197,396,397],{"class":252},"(sales.to_dict())\n",[197,399,401,403,406,409,412,414,416,419,421,424,427,429,431],{"class":80,"line":400},9,[197,402,394],{"class":393},[197,404,405],{"class":252},"(",[197,407,408],{"class":206},"\"not paid this month:\"",[197,410,411],{"class":252},", in_month.loc[in_month[",[197,413,352],{"class":206},[197,415,355],{"class":252},[197,417,418],{"class":248},"!=",[197,420,361],{"class":206},[197,422,423],{"class":252},", [",[197,425,426],{"class":206},"\"Deal\"",[197,428,307],{"class":252},[197,430,352],{"class":206},[197,432,433],{"class":252},"]].values.tolist())\n",[10,435,436],{},"Ana has sold 132,000, Ben 48,500 and Cai 96,000; Cai's pending deal D-508 is listed so the rep can see why it is missing. Status values are a frequent source of silent errors — \"Closed Won\" versus \"closed\" — so check the distinct values of the column each month, or map them through a table.",[182,438,440],{"id":439},"step-2-apply-marginal-tiers","Step 2 — Apply marginal tiers",[10,442,443,444,447],{},"With marginal tiers, each rate applies only to the sales inside its band, like income tax bands. For each band, the amount inside it is the rep's total clipped to the band's limits, minus the band's lower limit. numpy's ",[194,445,446],{},"clip"," computes that for every rep and band at once:",[187,449,451],{"className":239,"code":450,"language":241,"meta":192,"style":192},"import numpy as np\n\ntiers = pd.read_excel(\"commission_plan.xlsx\", sheet_name=\"Tiers\")\nlower = tiers[\"From\"].to_numpy()\nupper = tiers[\"To\"].fillna(np.inf).to_numpy()\nrate = tiers[\"Rate\"].to_numpy()\n\nin_band = np.clip(sales.to_numpy()[:, None], lower, upper) - lower\nband_commission = in_band * rate\nmarginal = pd.Series(band_commission.sum(axis=1), index=sales.index)\nworking = pd.DataFrame(band_commission, index=sales.index, columns=[f\"{r:.0%} band\" for r in rate])\nprint(working.assign(Total=marginal).round(2))\n",[194,452,453,465,469,493,509,524,538,542,564,580,610,669],{"__ignoreMap":192},[197,454,455,457,460,462],{"class":80,"line":199},[197,456,249],{"class":248},[197,458,459],{"class":252}," numpy ",[197,461,256],{"class":248},[197,463,464],{"class":252}," np\n",[197,466,467],{"class":80,"line":262},[197,468,266],{"emptyLinePlaceholder":265},[197,470,471,474,476,478,481,483,486,488,491],{"class":80,"line":269},[197,472,473],{"class":252},"tiers ",[197,475,275],{"class":248},[197,477,301],{"class":252},[197,479,480],{"class":206},"\"commission_plan.xlsx\"",[197,482,307],{"class":252},[197,484,485],{"class":310},"sheet_name",[197,487,275],{"class":248},[197,489,490],{"class":206},"\"Tiers\"",[197,492,290],{"class":252},[197,494,495,498,500,503,506],{"class":80,"line":293},[197,496,497],{"class":252},"lower ",[197,499,275],{"class":248},[197,501,502],{"class":252}," tiers[",[197,504,505],{"class":206},"\"From\"",[197,507,508],{"class":252},"].to_numpy()\n",[197,510,511,514,516,518,521],{"class":80,"line":325},[197,512,513],{"class":252},"upper ",[197,515,275],{"class":248},[197,517,502],{"class":252},[197,519,520],{"class":206},"\"To\"",[197,522,523],{"class":252},"].fillna(np.inf).to_numpy()\n",[197,525,526,529,531,533,536],{"class":80,"line":341},[197,527,528],{"class":252},"rate ",[197,530,275],{"class":248},[197,532,502],{"class":252},[197,534,535],{"class":206},"\"Rate\"",[197,537,508],{"class":252},[197,539,540],{"class":80,"line":367},[197,541,266],{"emptyLinePlaceholder":265},[197,543,544,547,549,552,555,558,561],{"class":80,"line":390},[197,545,546],{"class":252},"in_band ",[197,548,275],{"class":248},[197,550,551],{"class":252}," np.clip(sales.to_numpy()[:, ",[197,553,554],{"class":393},"None",[197,556,557],{"class":252},"], lower, upper) ",[197,559,560],{"class":248},"-",[197,562,563],{"class":252}," lower\n",[197,565,566,569,571,574,577],{"class":80,"line":400},[197,567,568],{"class":252},"band_commission ",[197,570,275],{"class":248},[197,572,573],{"class":252}," in_band ",[197,575,576],{"class":248},"*",[197,578,579],{"class":252}," rate\n",[197,581,583,586,588,591,594,596,599,602,605,607],{"class":80,"line":582},10,[197,584,585],{"class":252},"marginal ",[197,587,275],{"class":248},[197,589,590],{"class":252}," pd.Series(band_commission.sum(",[197,592,593],{"class":310},"axis",[197,595,275],{"class":248},[197,597,598],{"class":393},"1",[197,600,601],{"class":252},"), ",[197,603,604],{"class":310},"index",[197,606,275],{"class":248},[197,608,609],{"class":252},"sales.index)\n",[197,611,613,616,618,621,623,625,628,631,633,635,638,641,645,648,651,654,657,660,663,666],{"class":80,"line":612},11,[197,614,615],{"class":252},"working ",[197,617,275],{"class":248},[197,619,620],{"class":252}," pd.DataFrame(band_commission, ",[197,622,604],{"class":310},[197,624,275],{"class":248},[197,626,627],{"class":252},"sales.index, ",[197,629,630],{"class":310},"columns",[197,632,275],{"class":248},[197,634,316],{"class":252},[197,636,637],{"class":248},"f",[197,639,640],{"class":206},"\"",[197,642,644],{"class":643},"sSjpA","{",[197,646,647],{"class":252},"r",[197,649,650],{"class":248},":.0%",[197,652,653],{"class":643},"}",[197,655,656],{"class":206}," band\"",[197,658,659],{"class":248}," for",[197,661,662],{"class":252}," r ",[197,664,665],{"class":248},"in",[197,667,668],{"class":252}," rate])\n",[197,670,672,674,677,680,682,685,688],{"class":80,"line":671},12,[197,673,394],{"class":393},[197,675,676],{"class":252},"(working.assign(",[197,678,679],{"class":310},"Total",[197,681,275],{"class":248},[197,683,684],{"class":252},"marginal).round(",[197,686,687],{"class":393},"2",[197,689,690],{"class":252},"))\n",[10,692,693,696],{},[194,694,695],{},"sales.to_numpy()[:, None]"," makes a column of totals, which broadcasts against the row of band limits to give one row per rep and one column per band. Ana's 132,000 is 50,000 at 5%, 50,000 at 8% and 32,000 at 10%: 2,500 + 4,000 + 3,200 = 9,700. The per-band columns are exactly what the statement needs to show the working.",[20,698,29,704,29,707,29,710,29,714,29,719,29,728,29,734,29,739,29,744,29,747,29,751,29,756,29,761,29,764,29,768,29,773],{"viewBox":699,"role":23,"ariaLabelledBy":700,"xmlns":27,"style":703},"4 0 744 172",[701,702],"com-bands-t","com-bands-d","width:100%;max-width:744px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[31,705,706],{"id":701},"How Ana's 132,000 is split across the bands",[35,708,709],{"id":702},"Bar chart of Ana's commission by band under marginal tiers: 2,500 from the first 50,000 at 5 percent, 4,000 from the next 50,000 at 8 percent and 3,200 from the remaining 32,000 at 10 percent, totalling 9,700.",[39,711],{"x":41,"y":42,"width":712,"height":713,"fill":45},"744","172",[68,715,718],{"x":124,"y":716,"style":717},"31.0","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Marginal tiers — total 9,700",[39,720],{"x":48,"y":721,"width":722,"height":723,"rx":724,"fill":725,"stroke":726,"style":727},"48.0","126.0","28.0","7","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[68,729,733],{"x":730,"y":731,"style":732},"83.0","66.5","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","5% on 50,000",[39,735],{"x":736,"y":721,"width":737,"height":723,"rx":724,"fill":53,"stroke":54,"style":738},"156.0","333.8","stroke-width:1.5px",[68,740,743],{"x":741,"y":731,"style":742},"497.7","font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:start","2500",[39,745],{"x":48,"y":746,"width":722,"height":723,"rx":724,"fill":725,"stroke":726,"style":727},"88.0",[68,748,750],{"x":730,"y":749,"style":732},"106.5","8% on 50,000",[39,752],{"x":736,"y":746,"width":753,"height":723,"rx":724,"fill":754,"stroke":755,"style":738},"534.1","#d9f4f1","var(--teal,#0f9488)",[68,757,760],{"x":758,"y":749,"style":759},"698.0","font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:start","4000",[39,762],{"x":48,"y":763,"width":722,"height":723,"rx":724,"fill":725,"stroke":726,"style":727},"128.0",[68,765,767],{"x":730,"y":766,"style":732},"146.5","10% on 32,000",[39,769],{"x":736,"y":763,"width":770,"height":723,"rx":724,"fill":771,"stroke":772,"style":738},"427.2","#fdefd8","var(--gold,#b4740a)",[68,774,777],{"x":775,"y":766,"style":776},"591.2","font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:start","3200",[182,779,781],{"id":780},"step-3-or-apply-whole-amount-tiers","Step 3 — Or apply whole-amount tiers",[10,783,784,785,789,790,793],{},"Some plans pay the highest tier reached on ",[786,787,788],"em",{},"all"," sales: sell over 100,000 and everything earns 10%. That is a lookup rather than a sum — find the band the total falls in and apply its rate to the whole amount. ",[194,791,792],{},"pd.cut"," does the lookup:",[187,795,797],{"className":239,"code":796,"language":241,"meta":192,"style":192},"edges = list(lower) + [np.inf]\nwhole_rate = pd.cut(sales, bins=edges, right=False, labels=rate).astype(float)\nwhole = sales * whole_rate\nprint(pd.DataFrame({\"sales\": sales, \"rate\": whole_rate, \"whole-amount\": whole, \"marginal\": marginal.round(2)}))\n",[194,798,799,818,859,874],{"__ignoreMap":192},[197,800,801,804,806,809,812,815],{"class":80,"line":199},[197,802,803],{"class":252},"edges ",[197,805,275],{"class":248},[197,807,808],{"class":393}," list",[197,810,811],{"class":252},"(lower) ",[197,813,814],{"class":248},"+",[197,816,817],{"class":252}," [np.inf]\n",[197,819,820,823,825,828,831,833,836,839,841,844,846,849,851,854,857],{"class":80,"line":262},[197,821,822],{"class":252},"whole_rate ",[197,824,275],{"class":248},[197,826,827],{"class":252}," pd.cut(sales, ",[197,829,830],{"class":310},"bins",[197,832,275],{"class":248},[197,834,835],{"class":252},"edges, ",[197,837,838],{"class":310},"right",[197,840,275],{"class":248},[197,842,843],{"class":393},"False",[197,845,307],{"class":252},[197,847,848],{"class":310},"labels",[197,850,275],{"class":248},[197,852,853],{"class":252},"rate).astype(",[197,855,856],{"class":393},"float",[197,858,290],{"class":252},[197,860,861,864,866,869,871],{"class":80,"line":269},[197,862,863],{"class":252},"whole ",[197,865,275],{"class":248},[197,867,868],{"class":252}," sales ",[197,870,576],{"class":248},[197,872,873],{"class":252}," whole_rate\n",[197,875,876,878,881,884,887,890,893,896,899,902,905,907],{"class":80,"line":293},[197,877,394],{"class":393},[197,879,880],{"class":252},"(pd.DataFrame({",[197,882,883],{"class":206},"\"sales\"",[197,885,886],{"class":252},": sales, ",[197,888,889],{"class":206},"\"rate\"",[197,891,892],{"class":252},": whole_rate, ",[197,894,895],{"class":206},"\"whole-amount\"",[197,897,898],{"class":252},": whole, ",[197,900,901],{"class":206},"\"marginal\"",[197,903,904],{"class":252},": marginal.round(",[197,906,687],{"class":393},[197,908,909],{"class":252},")}))\n",[10,911,912,915],{},[194,913,914],{},"right=False"," makes each band include its lower limit, so exactly 50,000 earns 8%, matching \"from 50,000\". Ana earns 13,200 under this plan against 9,700 marginally. Whole-amount tiers create a cliff: Ben, at 48,500, earns 2,425; one more 1,500 sale would take Ben to 50,000 and 4,000 — 1,575 more commission for 1,500 of sales. That incentive is sometimes intended and sometimes a surprise, which is why the report should use the plan document's exact wording, and why the next step flags reps near a threshold.",[20,917,29,922,29,925,29,928,29,931,29,936,29,944,29,950,29,955,29,961,29,968,29,972,29,977,29,981,29,986,29,990,29,995,29,998,29,1005,29,1010,29,1014,29,1018,29,1023,29,1025,29,1030,29,1033,29,1038,29,1041],{"viewBox":918,"role":23,"ariaLabelledBy":919,"xmlns":27,"style":28},"4 0 752 233",[920,921],"com-styles-t","com-styles-d",[31,923,924],{"id":920},"Marginal and whole-amount tiers on the same sales",[35,926,927],{"id":921},"Two panels. Marginal tiers pay each rate only on the sales inside its band: Ana 9,700, Ben 2,425, Cai 6,180, and no jump at a threshold. Whole-amount tiers pay the rate reached on all sales: Ana 13,200, Ben 2,425, Cai 7,680, and crossing 50,000 or 100,000 raises commission on everything sold.",[39,929],{"x":41,"y":42,"width":43,"height":930,"fill":45},"233",[39,932],{"x":48,"y":49,"width":933,"height":934,"rx":935,"fill":754,"stroke":755,"style":55},"351.0","201.0","14",[57,937,939],{"transform":938},"translate(182.5,29.0) scale(1.083)",[61,940],{"d":941,"fill":64,"stroke":942,"style":943},"M12 3l9 5-9 5-9-5z M3 13l9 5 9-5 M3 17l9 5 9-5","var(--teal-ink,#0b6157)","stroke-width:1.66px;stroke-linecap:round;stroke-linejoin:round",[68,945,949],{"x":946,"y":947,"style":948},"195.5","78.0","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Marginal tiers",[80,951],{"x1":952,"y1":953,"x2":954,"y2":953,"stroke":755,"style":727},"36.0","91.0","355.0",[68,956,960],{"x":957,"y":958,"style":959},"60.0","111.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:start","Ana 9,700",[57,962,964],{"transform":963},"translate(38.5,99.5) scale(0.625)",[61,965],{"d":966,"fill":64,"stroke":755,"style":967},"M5 12.5l4.5 4.5L19 7","stroke-width:2.88px;stroke-linecap:round;stroke-linejoin:round",[68,969,971],{"x":957,"y":970,"style":959},"137.0","Ben 2,425",[57,973,975],{"transform":974},"translate(38.5,125.5) scale(0.625)",[61,976],{"d":966,"fill":64,"stroke":755,"style":967},[68,978,980],{"x":957,"y":979,"style":959},"163.0","Cai 6,180",[57,982,984],{"transform":983},"translate(38.5,151.5) scale(0.625)",[61,985],{"d":966,"fill":64,"stroke":755,"style":967},[68,987,989],{"x":957,"y":988,"style":959},"189.0","smooth — no cliffs",[57,991,993],{"transform":992},"translate(38.5,177.5) scale(0.625)",[61,994],{"d":966,"fill":64,"stroke":755,"style":967},[39,996],{"x":997,"y":49,"width":933,"height":934,"rx":935,"fill":771,"stroke":772,"style":55},"389.0",[57,999,1001],{"transform":1000},"translate(551.5,29.0) scale(1.083)",[61,1002],{"d":1003,"fill":64,"stroke":1004,"style":943},"M12 3.5l9.5 17h-19z M12 10v5 M12 17.8v.4","var(--gold-ink,#7a4e06)",[68,1006,1009],{"x":1007,"y":947,"style":1008},"564.5","font-size:14px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Whole-amount tiers",[80,1011],{"x1":1012,"y1":953,"x2":1013,"y2":953,"stroke":772,"style":727},"405.0","724.0",[68,1015,1017],{"x":1016,"y":958,"style":959},"429.0","Ana 13,200",[57,1019,1021],{"transform":1020},"translate(407.5,99.5) scale(0.625)",[61,1022],{"d":1003,"fill":64,"stroke":772,"style":967},[68,1024,971],{"x":1016,"y":970,"style":959},[57,1026,1028],{"transform":1027},"translate(407.5,125.5) scale(0.625)",[61,1029],{"d":1003,"fill":64,"stroke":772,"style":967},[68,1031,1032],{"x":1016,"y":979,"style":959},"Cai 7,680",[57,1034,1036],{"transform":1035},"translate(407.5,151.5) scale(0.625)",[61,1037],{"d":1003,"fill":64,"stroke":772,"style":967},[68,1039,1040],{"x":1016,"y":988,"style":959},"jumps at each threshold",[57,1042,1044],{"transform":1043},"translate(407.5,177.5) scale(0.625)",[61,1045],{"d":1046,"fill":64,"stroke":1047,"style":967},"M6 6l12 12 M18 6L6 18","var(--accent,#f43f8f)",[182,1049,1051],{"id":1050},"step-4-apply-clawbacks-and-flag-reps-near-a-tier","Step 4 — Apply clawbacks and flag reps near a tier",[10,1053,1054],{},"Deal D-471 closed in August, earned commission, and has now been cancelled. The plan claws back what was paid on it. Take the amount from the record of the August payment, not by recalculating: the rep's August tier determined what they were paid, and recalculating with September's totals would give a different figure:",[187,1056,1058],{"className":239,"code":1057,"language":241,"meta":192,"style":192},"basis = pd.read_excel(\"commission_plan.xlsx\", sheet_name=\"Clawback basis\").set_index(\"Rep\")[\"Paid on D-471\"]\nclawback = -basis.reindex(sales.index, fill_value=0)\n\nsummary = pd.DataFrame({\"Sales\": sales, \"Commission\": marginal.round(2), \"Clawback\": clawback})\nsummary[\"Payable\"] = summary[\"Commission\"] + summary[\"Clawback\"]\n\nnext_limit = pd.Series([lower[lower > s].min() if (lower > s).any() else np.nan for s in sales], index=sales.index)\nsummary[\"To next tier\"] = next_limit - sales\nsummary[\"Near tier\"] = summary[\"To next tier\"] \u003C= 5000\nprint(summary)\nprint(\"payroll total:\", summary[\"Payable\"].sum())\n",[194,1059,1060,1092,1114,1118,1148,1175,1179,1229,1248,1271,1278],{"__ignoreMap":192},[197,1061,1062,1065,1067,1069,1071,1073,1075,1077,1080,1083,1085,1087,1090],{"class":80,"line":199},[197,1063,1064],{"class":252},"basis ",[197,1066,275],{"class":248},[197,1068,301],{"class":252},[197,1070,480],{"class":206},[197,1072,307],{"class":252},[197,1074,485],{"class":310},[197,1076,275],{"class":248},[197,1078,1079],{"class":206},"\"Clawback basis\"",[197,1081,1082],{"class":252},").set_index(",[197,1084,378],{"class":206},[197,1086,381],{"class":252},[197,1088,1089],{"class":206},"\"Paid on D-471\"",[197,1091,364],{"class":252},[197,1093,1094,1097,1099,1102,1105,1108,1110,1112],{"class":80,"line":262},[197,1095,1096],{"class":252},"clawback ",[197,1098,275],{"class":248},[197,1100,1101],{"class":248}," -",[197,1103,1104],{"class":252},"basis.reindex(sales.index, ",[197,1106,1107],{"class":310},"fill_value",[197,1109,275],{"class":248},[197,1111,42],{"class":393},[197,1113,290],{"class":252},[197,1115,1116],{"class":80,"line":269},[197,1117,266],{"emptyLinePlaceholder":265},[197,1119,1120,1123,1125,1128,1131,1133,1136,1138,1140,1142,1145],{"class":80,"line":293},[197,1121,1122],{"class":252},"summary ",[197,1124,275],{"class":248},[197,1126,1127],{"class":252}," pd.DataFrame({",[197,1129,1130],{"class":206},"\"Sales\"",[197,1132,886],{"class":252},[197,1134,1135],{"class":206},"\"Commission\"",[197,1137,904],{"class":252},[197,1139,687],{"class":393},[197,1141,601],{"class":252},[197,1143,1144],{"class":206},"\"Clawback\"",[197,1146,1147],{"class":252},": clawback})\n",[197,1149,1150,1153,1156,1158,1160,1163,1165,1167,1169,1171,1173],{"class":80,"line":325},[197,1151,1152],{"class":252},"summary[",[197,1154,1155],{"class":206},"\"Payable\"",[197,1157,355],{"class":252},[197,1159,275],{"class":248},[197,1161,1162],{"class":252}," summary[",[197,1164,1135],{"class":206},[197,1166,355],{"class":252},[197,1168,814],{"class":248},[197,1170,1162],{"class":252},[197,1172,1144],{"class":206},[197,1174,364],{"class":252},[197,1176,1177],{"class":80,"line":341},[197,1178,266],{"emptyLinePlaceholder":265},[197,1180,1181,1184,1186,1189,1192,1195,1198,1201,1203,1206,1209,1212,1215,1218,1220,1223,1225,1227],{"class":80,"line":367},[197,1182,1183],{"class":252},"next_limit ",[197,1185,275],{"class":248},[197,1187,1188],{"class":252}," pd.Series([lower[lower ",[197,1190,1191],{"class":248},">",[197,1193,1194],{"class":252}," s].min() ",[197,1196,1197],{"class":248},"if",[197,1199,1200],{"class":252}," (lower ",[197,1202,1191],{"class":248},[197,1204,1205],{"class":252}," s).any() ",[197,1207,1208],{"class":248},"else",[197,1210,1211],{"class":252}," np.nan ",[197,1213,1214],{"class":248},"for",[197,1216,1217],{"class":252}," s ",[197,1219,665],{"class":248},[197,1221,1222],{"class":252}," sales], ",[197,1224,604],{"class":310},[197,1226,275],{"class":248},[197,1228,609],{"class":252},[197,1230,1231,1233,1236,1238,1240,1243,1245],{"class":80,"line":390},[197,1232,1152],{"class":252},[197,1234,1235],{"class":206},"\"To next tier\"",[197,1237,355],{"class":252},[197,1239,275],{"class":248},[197,1241,1242],{"class":252}," next_limit ",[197,1244,560],{"class":248},[197,1246,1247],{"class":252}," sales\n",[197,1249,1250,1252,1255,1257,1259,1261,1263,1265,1268],{"class":80,"line":400},[197,1251,1152],{"class":252},[197,1253,1254],{"class":206},"\"Near tier\"",[197,1256,355],{"class":252},[197,1258,275],{"class":248},[197,1260,1162],{"class":252},[197,1262,1235],{"class":206},[197,1264,355],{"class":252},[197,1266,1267],{"class":248},"\u003C=",[197,1269,1270],{"class":393}," 5000\n",[197,1272,1273,1275],{"class":80,"line":582},[197,1274,394],{"class":393},[197,1276,1277],{"class":252},"(summary)\n",[197,1279,1280,1282,1284,1287,1290,1292],{"class":80,"line":612},[197,1281,394],{"class":393},[197,1283,405],{"class":252},[197,1285,1286],{"class":206},"\"payroll total:\"",[197,1288,1289],{"class":252},", summary[",[197,1291,1155],{"class":206},[197,1293,1294],{"class":252},"].sum())\n",[10,1296,1297],{},"Ben's September commission of 2,425 less the 1,600 clawback leaves 825 payable. Ben is 1,500 below the 50,000 threshold, and Cai 4,000 below 100,000, so both are flagged — useful for the sales manager, and a prompt to double-check any deal that just missed the month end. Ana is past the top threshold, so there is no next tier to reach.",[182,1299,1301],{"id":1300},"step-5-write-the-payroll-summary-and-statements","Step 5 — Write the payroll summary and statements",[10,1303,1304],{},"Payroll needs one number per rep. Each rep needs their own sheet showing their deals, the band working and the adjustments — without seeing anyone else's figures:",[187,1306,1308],{"className":239,"code":1307,"language":241,"meta":192,"style":192},"out = \"commission_2026-09.xlsx\"\nwith pd.ExcelWriter(out, engine=\"xlsxwriter\") as writer:\n    wb = writer.book\n    money = wb.add_format({\"num_format\": \"#,##0.00;(#,##0.00);-\"})\n    bold = wb.add_format({\"bold\": True})\n    summary.reset_index().to_excel(writer, sheet_name=\"Payroll\", index=False)\n    writer.sheets[\"Payroll\"].set_column(\"A:A\", 10)\n    writer.sheets[\"Payroll\"].set_column(\"B:F\", 13, money)\n\n    for rep in sales.index:\n        ws = wb.add_worksheet(rep)\n        ws.write(\"A1\", f\"Commission statement — {rep} — September 2026\", wb.add_format({\"bold\": True, \"font_size\": 13}))\n        rep_deals = closed[closed[\"Rep\"] == rep][[\"Deal\", \"Close Date\", \"Amount\"]]\n        ws.write_row(2, 0, [\"Deal\", \"Close date\", \"Amount\"], bold)\n        for i, (deal, closed_on, amount) in enumerate(rep_deals.itertuples(index=False, name=None), start=3):\n            ws.write(i, 0, deal)\n            ws.write_datetime(i, 1, closed_on.to_pydatetime(), wb.add_format({\"num_format\": \"yyyy-mm-dd\"}))\n            ws.write_number(i, 2, amount, money)\n        r = 3 + len(rep_deals)\n        ws.write(r, 0, \"Total sales\", bold)\n        ws.write_number(r, 2, sales[rep], money)\n        r += 2\n        ws.write_row(r, 0, [\"Band\", \"Sales in band\", \"Rate\", \"Commission\"], bold)\n        for j, (lo, hi, rt) in enumerate(zip(lower, upper, rate), start=1):\n            label = f\"{lo:,.0f} – {hi:,.0f}\" if np.isfinite(hi) else f\"over {lo:,.0f}\"\n            ws.write_row(r + j, 0, [label])\n            ws.write_number(r + j, 1, in_band[sales.index.get_loc(rep), j - 1], money)\n            ws.write_number(r + j, 2, rt, wb.add_format({\"num_format\": \"0%\"}))\n            ws.write_number(r + j, 3, band_commission[sales.index.get_loc(rep), j - 1], money)\n        r += len(rate) + 1\n        for label, value in [(\"Commission\", summary.at[rep, \"Commission\"]), (\"Clawback (D-471)\", summary.at[rep, \"Clawback\"]),\n                             (\"Payable\", summary.at[rep, \"Payable\"])]:\n            ws.write(r, 0, label, bold if label == \"Payable\" else None)\n            ws.write_number(r, 3, value, money)\n            r += 1\n        ws.set_column(\"A:A\", 20)\n        ws.set_column(\"B:D\", 14)\n\nprint(pd.ExcelFile(out).sheet_names)\nprint(pd.read_excel(out, sheet_name=\"Ben\", header=None).dropna(how=\"all\").fillna(\"\").to_string(index=False, header=False))\n",[194,1309,1310,1320,1344,1354,1376,1395,1417,1437,1456,1460,1473,1483,1528,1561,1589,1634,1645,1665,1676,1696,1712,1723,1734,1762,1790,1851,1867,1890,1913,1933,1950,1983,1998,2027,2038,2048,2063,2077,2082,2090],{"__ignoreMap":192},[197,1311,1312,1315,1317],{"class":80,"line":199},[197,1313,1314],{"class":252},"out ",[197,1316,275],{"class":248},[197,1318,1319],{"class":206}," \"commission_2026-09.xlsx\"\n",[197,1321,1322,1325,1328,1331,1333,1336,1339,1341],{"class":80,"line":262},[197,1323,1324],{"class":248},"with",[197,1326,1327],{"class":252}," pd.ExcelWriter(out, ",[197,1329,1330],{"class":310},"engine",[197,1332,275],{"class":248},[197,1334,1335],{"class":206},"\"xlsxwriter\"",[197,1337,1338],{"class":252},") ",[197,1340,256],{"class":248},[197,1342,1343],{"class":252}," writer:\n",[197,1345,1346,1349,1351],{"class":80,"line":269},[197,1347,1348],{"class":252},"    wb ",[197,1350,275],{"class":248},[197,1352,1353],{"class":252}," writer.book\n",[197,1355,1356,1359,1361,1364,1367,1370,1373],{"class":80,"line":293},[197,1357,1358],{"class":252},"    money ",[197,1360,275],{"class":248},[197,1362,1363],{"class":252}," wb.add_format({",[197,1365,1366],{"class":206},"\"num_format\"",[197,1368,1369],{"class":252},": ",[197,1371,1372],{"class":206},"\"#,##0.00;(#,##0.00);-\"",[197,1374,1375],{"class":252},"})\n",[197,1377,1378,1381,1383,1385,1388,1390,1393],{"class":80,"line":325},[197,1379,1380],{"class":252},"    bold ",[197,1382,275],{"class":248},[197,1384,1363],{"class":252},[197,1386,1387],{"class":206},"\"bold\"",[197,1389,1369],{"class":252},[197,1391,1392],{"class":393},"True",[197,1394,1375],{"class":252},[197,1396,1397,1400,1402,1404,1407,1409,1411,1413,1415],{"class":80,"line":341},[197,1398,1399],{"class":252},"    summary.reset_index().to_excel(writer, ",[197,1401,485],{"class":310},[197,1403,275],{"class":248},[197,1405,1406],{"class":206},"\"Payroll\"",[197,1408,307],{"class":252},[197,1410,604],{"class":310},[197,1412,275],{"class":248},[197,1414,843],{"class":393},[197,1416,290],{"class":252},[197,1418,1419,1422,1424,1427,1430,1432,1435],{"class":80,"line":367},[197,1420,1421],{"class":252},"    writer.sheets[",[197,1423,1406],{"class":206},[197,1425,1426],{"class":252},"].set_column(",[197,1428,1429],{"class":206},"\"A:A\"",[197,1431,307],{"class":252},[197,1433,1434],{"class":393},"10",[197,1436,290],{"class":252},[197,1438,1439,1441,1443,1445,1448,1450,1453],{"class":80,"line":390},[197,1440,1421],{"class":252},[197,1442,1406],{"class":206},[197,1444,1426],{"class":252},[197,1446,1447],{"class":206},"\"B:F\"",[197,1449,307],{"class":252},[197,1451,1452],{"class":393},"13",[197,1454,1455],{"class":252},", money)\n",[197,1457,1458],{"class":80,"line":400},[197,1459,266],{"emptyLinePlaceholder":265},[197,1461,1462,1465,1468,1470],{"class":80,"line":582},[197,1463,1464],{"class":248},"    for",[197,1466,1467],{"class":252}," rep ",[197,1469,665],{"class":248},[197,1471,1472],{"class":252}," sales.index:\n",[197,1474,1475,1478,1480],{"class":80,"line":612},[197,1476,1477],{"class":252},"        ws ",[197,1479,275],{"class":248},[197,1481,1482],{"class":252}," wb.add_worksheet(rep)\n",[197,1484,1485,1488,1491,1493,1495,1498,1500,1502,1504,1507,1510,1512,1514,1516,1518,1521,1523,1525],{"class":80,"line":671},[197,1486,1487],{"class":252},"        ws.write(",[197,1489,1490],{"class":206},"\"A1\"",[197,1492,307],{"class":252},[197,1494,637],{"class":248},[197,1496,1497],{"class":206},"\"Commission statement — ",[197,1499,644],{"class":643},[197,1501,180],{"class":252},[197,1503,653],{"class":643},[197,1505,1506],{"class":206}," — September 2026\"",[197,1508,1509],{"class":252},", wb.add_format({",[197,1511,1387],{"class":206},[197,1513,1369],{"class":252},[197,1515,1392],{"class":393},[197,1517,307],{"class":252},[197,1519,1520],{"class":206},"\"font_size\"",[197,1522,1369],{"class":252},[197,1524,1452],{"class":393},[197,1526,1527],{"class":252},"}))\n",[197,1529,1531,1534,1536,1539,1541,1543,1545,1548,1550,1552,1554,1556,1558],{"class":80,"line":1530},13,[197,1532,1533],{"class":252},"        rep_deals ",[197,1535,275],{"class":248},[197,1537,1538],{"class":252}," closed[closed[",[197,1540,378],{"class":206},[197,1542,355],{"class":252},[197,1544,358],{"class":248},[197,1546,1547],{"class":252}," rep][[",[197,1549,426],{"class":206},[197,1551,307],{"class":252},[197,1553,319],{"class":206},[197,1555,307],{"class":252},[197,1557,384],{"class":206},[197,1559,1560],{"class":252},"]]\n",[197,1562,1564,1567,1569,1571,1573,1575,1577,1579,1582,1584,1586],{"class":80,"line":1563},14,[197,1565,1566],{"class":252},"        ws.write_row(",[197,1568,687],{"class":393},[197,1570,307],{"class":252},[197,1572,42],{"class":393},[197,1574,423],{"class":252},[197,1576,426],{"class":206},[197,1578,307],{"class":252},[197,1580,1581],{"class":206},"\"Close date\"",[197,1583,307],{"class":252},[197,1585,384],{"class":206},[197,1587,1588],{"class":252},"], bold)\n",[197,1590,1592,1595,1598,1600,1603,1606,1608,1610,1612,1614,1617,1619,1621,1623,1626,1628,1631],{"class":80,"line":1591},15,[197,1593,1594],{"class":248},"        for",[197,1596,1597],{"class":252}," i, (deal, closed_on, amount) ",[197,1599,665],{"class":248},[197,1601,1602],{"class":393}," enumerate",[197,1604,1605],{"class":252},"(rep_deals.itertuples(",[197,1607,604],{"class":310},[197,1609,275],{"class":248},[197,1611,843],{"class":393},[197,1613,307],{"class":252},[197,1615,1616],{"class":310},"name",[197,1618,275],{"class":248},[197,1620,554],{"class":393},[197,1622,601],{"class":252},[197,1624,1625],{"class":310},"start",[197,1627,275],{"class":248},[197,1629,1630],{"class":393},"3",[197,1632,1633],{"class":252},"):\n",[197,1635,1637,1640,1642],{"class":80,"line":1636},16,[197,1638,1639],{"class":252},"            ws.write(i, ",[197,1641,42],{"class":393},[197,1643,1644],{"class":252},", deal)\n",[197,1646,1648,1651,1653,1656,1658,1660,1663],{"class":80,"line":1647},17,[197,1649,1650],{"class":252},"            ws.write_datetime(i, ",[197,1652,598],{"class":393},[197,1654,1655],{"class":252},", closed_on.to_pydatetime(), wb.add_format({",[197,1657,1366],{"class":206},[197,1659,1369],{"class":252},[197,1661,1662],{"class":206},"\"yyyy-mm-dd\"",[197,1664,1527],{"class":252},[197,1666,1668,1671,1673],{"class":80,"line":1667},18,[197,1669,1670],{"class":252},"            ws.write_number(i, ",[197,1672,687],{"class":393},[197,1674,1675],{"class":252},", amount, money)\n",[197,1677,1679,1682,1684,1687,1690,1693],{"class":80,"line":1678},19,[197,1680,1681],{"class":252},"        r ",[197,1683,275],{"class":248},[197,1685,1686],{"class":393}," 3",[197,1688,1689],{"class":248}," +",[197,1691,1692],{"class":393}," len",[197,1694,1695],{"class":252},"(rep_deals)\n",[197,1697,1699,1702,1704,1706,1709],{"class":80,"line":1698},20,[197,1700,1701],{"class":252},"        ws.write(r, ",[197,1703,42],{"class":393},[197,1705,307],{"class":252},[197,1707,1708],{"class":206},"\"Total sales\"",[197,1710,1711],{"class":252},", bold)\n",[197,1713,1715,1718,1720],{"class":80,"line":1714},21,[197,1716,1717],{"class":252},"        ws.write_number(r, ",[197,1719,687],{"class":393},[197,1721,1722],{"class":252},", sales[rep], money)\n",[197,1724,1726,1728,1731],{"class":80,"line":1725},22,[197,1727,1681],{"class":252},[197,1729,1730],{"class":248},"+=",[197,1732,1733],{"class":393}," 2\n",[197,1735,1737,1740,1742,1744,1747,1749,1752,1754,1756,1758,1760],{"class":80,"line":1736},23,[197,1738,1739],{"class":252},"        ws.write_row(r, ",[197,1741,42],{"class":393},[197,1743,423],{"class":252},[197,1745,1746],{"class":206},"\"Band\"",[197,1748,307],{"class":252},[197,1750,1751],{"class":206},"\"Sales in band\"",[197,1753,307],{"class":252},[197,1755,535],{"class":206},[197,1757,307],{"class":252},[197,1759,1135],{"class":206},[197,1761,1588],{"class":252},[197,1763,1765,1767,1770,1772,1774,1776,1779,1782,1784,1786,1788],{"class":80,"line":1764},24,[197,1766,1594],{"class":248},[197,1768,1769],{"class":252}," j, (lo, hi, rt) ",[197,1771,665],{"class":248},[197,1773,1602],{"class":393},[197,1775,405],{"class":252},[197,1777,1778],{"class":393},"zip",[197,1780,1781],{"class":252},"(lower, upper, rate), ",[197,1783,1625],{"class":310},[197,1785,275],{"class":248},[197,1787,598],{"class":393},[197,1789,1633],{"class":252},[197,1791,1793,1796,1798,1801,1803,1805,1808,1811,1813,1816,1818,1821,1823,1825,1827,1830,1833,1835,1837,1840,1842,1844,1846,1848],{"class":80,"line":1792},25,[197,1794,1795],{"class":252},"            label ",[197,1797,275],{"class":248},[197,1799,1800],{"class":248}," f",[197,1802,640],{"class":206},[197,1804,644],{"class":643},[197,1806,1807],{"class":252},"lo",[197,1809,1810],{"class":248},":,.0f",[197,1812,653],{"class":643},[197,1814,1815],{"class":206}," – ",[197,1817,644],{"class":643},[197,1819,1820],{"class":252},"hi",[197,1822,1810],{"class":248},[197,1824,653],{"class":643},[197,1826,640],{"class":206},[197,1828,1829],{"class":248}," if",[197,1831,1832],{"class":252}," np.isfinite(hi) ",[197,1834,1208],{"class":248},[197,1836,1800],{"class":248},[197,1838,1839],{"class":206},"\"over ",[197,1841,644],{"class":643},[197,1843,1807],{"class":252},[197,1845,1810],{"class":248},[197,1847,653],{"class":643},[197,1849,1850],{"class":206},"\"\n",[197,1852,1854,1857,1859,1862,1864],{"class":80,"line":1853},26,[197,1855,1856],{"class":252},"            ws.write_row(r ",[197,1858,814],{"class":248},[197,1860,1861],{"class":252}," j, ",[197,1863,42],{"class":393},[197,1865,1866],{"class":252},", [label])\n",[197,1868,1870,1873,1875,1877,1879,1882,1884,1887],{"class":80,"line":1869},27,[197,1871,1872],{"class":252},"            ws.write_number(r ",[197,1874,814],{"class":248},[197,1876,1861],{"class":252},[197,1878,598],{"class":393},[197,1880,1881],{"class":252},", in_band[sales.index.get_loc(rep), j ",[197,1883,560],{"class":248},[197,1885,1886],{"class":393}," 1",[197,1888,1889],{"class":252},"], money)\n",[197,1891,1893,1895,1897,1899,1901,1904,1906,1908,1911],{"class":80,"line":1892},28,[197,1894,1872],{"class":252},[197,1896,814],{"class":248},[197,1898,1861],{"class":252},[197,1900,687],{"class":393},[197,1902,1903],{"class":252},", rt, wb.add_format({",[197,1905,1366],{"class":206},[197,1907,1369],{"class":252},[197,1909,1910],{"class":206},"\"0%\"",[197,1912,1527],{"class":252},[197,1914,1916,1918,1920,1922,1924,1927,1929,1931],{"class":80,"line":1915},29,[197,1917,1872],{"class":252},[197,1919,814],{"class":248},[197,1921,1861],{"class":252},[197,1923,1630],{"class":393},[197,1925,1926],{"class":252},", band_commission[sales.index.get_loc(rep), j ",[197,1928,560],{"class":248},[197,1930,1886],{"class":393},[197,1932,1889],{"class":252},[197,1934,1936,1938,1940,1942,1945,1947],{"class":80,"line":1935},30,[197,1937,1681],{"class":252},[197,1939,1730],{"class":248},[197,1941,1692],{"class":393},[197,1943,1944],{"class":252},"(rate) ",[197,1946,814],{"class":248},[197,1948,1949],{"class":393}," 1\n",[197,1951,1953,1955,1958,1960,1963,1965,1968,1970,1973,1976,1978,1980],{"class":80,"line":1952},31,[197,1954,1594],{"class":248},[197,1956,1957],{"class":252}," label, value ",[197,1959,665],{"class":248},[197,1961,1962],{"class":252}," [(",[197,1964,1135],{"class":206},[197,1966,1967],{"class":252},", summary.at[rep, ",[197,1969,1135],{"class":206},[197,1971,1972],{"class":252},"]), (",[197,1974,1975],{"class":206},"\"Clawback (D-471)\"",[197,1977,1967],{"class":252},[197,1979,1144],{"class":206},[197,1981,1982],{"class":252},"]),\n",[197,1984,1986,1989,1991,1993,1995],{"class":80,"line":1985},32,[197,1987,1988],{"class":252},"                             (",[197,1990,1155],{"class":206},[197,1992,1967],{"class":252},[197,1994,1155],{"class":206},[197,1996,1997],{"class":252},"])]:\n",[197,1999,2001,2004,2006,2009,2011,2014,2016,2019,2022,2025],{"class":80,"line":2000},33,[197,2002,2003],{"class":252},"            ws.write(r, ",[197,2005,42],{"class":393},[197,2007,2008],{"class":252},", label, bold ",[197,2010,1197],{"class":248},[197,2012,2013],{"class":252}," label ",[197,2015,358],{"class":248},[197,2017,2018],{"class":206}," \"Payable\"",[197,2020,2021],{"class":248}," else",[197,2023,2024],{"class":393}," None",[197,2026,290],{"class":252},[197,2028,2030,2033,2035],{"class":80,"line":2029},34,[197,2031,2032],{"class":252},"            ws.write_number(r, ",[197,2034,1630],{"class":393},[197,2036,2037],{"class":252},", value, money)\n",[197,2039,2041,2044,2046],{"class":80,"line":2040},35,[197,2042,2043],{"class":252},"            r ",[197,2045,1730],{"class":248},[197,2047,1949],{"class":393},[197,2049,2051,2054,2056,2058,2061],{"class":80,"line":2050},36,[197,2052,2053],{"class":252},"        ws.set_column(",[197,2055,1429],{"class":206},[197,2057,307],{"class":252},[197,2059,2060],{"class":393},"20",[197,2062,290],{"class":252},[197,2064,2066,2068,2071,2073,2075],{"class":80,"line":2065},37,[197,2067,2053],{"class":252},[197,2069,2070],{"class":206},"\"B:D\"",[197,2072,307],{"class":252},[197,2074,935],{"class":393},[197,2076,290],{"class":252},[197,2078,2080],{"class":80,"line":2079},38,[197,2081,266],{"emptyLinePlaceholder":265},[197,2083,2085,2087],{"class":80,"line":2084},39,[197,2086,394],{"class":393},[197,2088,2089],{"class":252},"(pd.ExcelFile(out).sheet_names)\n",[197,2091,2093,2095,2098,2100,2102,2105,2107,2110,2112,2114,2117,2120,2122,2125,2128,2131,2134,2136,2138,2140,2142,2144,2146,2148],{"class":80,"line":2092},40,[197,2094,394],{"class":393},[197,2096,2097],{"class":252},"(pd.read_excel(out, ",[197,2099,485],{"class":310},[197,2101,275],{"class":248},[197,2103,2104],{"class":206},"\"Ben\"",[197,2106,307],{"class":252},[197,2108,2109],{"class":310},"header",[197,2111,275],{"class":248},[197,2113,554],{"class":393},[197,2115,2116],{"class":252},").dropna(",[197,2118,2119],{"class":310},"how",[197,2121,275],{"class":248},[197,2123,2124],{"class":206},"\"all\"",[197,2126,2127],{"class":252},").fillna(",[197,2129,2130],{"class":206},"\"\"",[197,2132,2133],{"class":252},").to_string(",[197,2135,604],{"class":310},[197,2137,275],{"class":248},[197,2139,843],{"class":393},[197,2141,307],{"class":252},[197,2143,2109],{"class":310},[197,2145,275],{"class":248},[197,2147,843],{"class":393},[197,2149,690],{"class":252},[10,2151,2152,2153,2157],{},"The statement lists Ben's two deals, the band working — all of Ben's sales fall in the first band — and the clawback line, ending at 825 payable. When statements are emailed, write one file per rep instead of one sheet each; ",[14,2154,2156],{"href":2155},"\u002Fautomating-reporting-workflows\u002Femailing-excel-reports-with-smtplib\u002Fsend-each-recipient-their-own-excel-report\u002F","Send Each Recipient Their Own Excel Report"," shows the distribution step. Before sending, protect the sheets or convert them to PDF so the figures are not edited in transit.",[182,2159,2161],{"id":2160},"common-pitfalls","Common pitfalls",[2163,2164,2165,2173,2182,2188,2194,2200],"ul",{},[2166,2167,2168,2172],"li",{},[2169,2170,2171],"strong",{},"Misreading the plan."," Marginal and whole-amount tiers give very different pay; implement the plan's wording.",[2166,2174,2175,2178,2179,2181],{},[2169,2176,2177],{},"Boundary rules."," Decide whether exactly 50,000 is in the lower or upper band and set ",[194,2180,838],{}," to match.",[2166,2183,2184,2187],{},[2169,2185,2186],{},"Recalculated clawbacks."," Claw back what was actually paid, from the payment record.",[2166,2189,2190,2193],{},[2169,2191,2192],{},"Status values."," \"Closed Won\" and \"closed\" differ; check distinct values each month.",[2166,2195,2196,2199],{},[2169,2197,2198],{},"Floating-point totals."," Round commission to cents once, before summing payroll.",[2166,2201,2202,2205],{},[2169,2203,2204],{},"One file for everyone."," Reps should not see each other's statements.",[182,2207,2209],{"id":2208},"performance-notes","Performance notes",[10,2211,2212],{},"The tier calculation is vectorised, so thousands of reps cost nothing. Writing one sheet per rep is fine for dozens; for hundreds, write one file per rep, or a single statement sheet filtered by rep, to keep the workbook manageable.",[182,2214,2216],{"id":2215},"conclusion","Conclusion",[10,2218,2219,2220,2222,2223,2225],{},"Filter deals to those closed in the month and total them per rep. Apply marginal tiers with numpy's ",[194,2221,446],{}," against the band limits, or whole-amount tiers with a ",[194,2224,792],{}," lookup, matching the plan's wording and boundary rules. Claw back the commission actually paid on cancelled deals, flag reps within reach of the next tier, and write a payroll summary with one statement per rep that shows every step of the working.",[182,2227,2229],{"id":2228},"frequently-asked-questions","Frequently asked questions",[10,2231,2232,2235],{},[2169,2233,2234],{},"How do I calculate tiered commission in pandas?","\nFor marginal tiers, work out how much of each rep's sales falls inside each band — the total clipped to the band's lower and upper limits, minus the lower limit — multiply by the band's rate and add the bands up. numpy's clip does this for all reps at once.",[10,2237,2238,2241],{},[2169,2239,2240],{},"What is the difference between marginal and whole-amount tiers?","\nMarginal tiers pay each rate only on the sales inside its band, like income tax. Whole-amount (retroactive) tiers pay the highest rate reached on all sales. The second creates a cliff: one more sale that crosses a threshold can increase commission by far more than the sale itself.",[10,2243,2244,2247],{},[2169,2245,2246],{},"How do I handle a cancelled deal that was already paid commission?","\nClaw back the commission that was actually paid on it, taken from the record of the earlier payment rather than recalculated, and show it as a separate negative line on the rep's statement so the adjustment is visible.",[10,2249,2250,2253],{},[2169,2251,2252],{},"Should each rep get their own commission statement?","\nYes. A statement listing their deals, the tier working and any adjustments lets a rep check their pay without seeing colleagues' figures. Write one sheet per rep, or one file per rep when statements are emailed individually.",[182,2255,2257],{"id":2256},"related","Related",[2163,2259,2260,2266,2273,2280],{},[2166,2261,2262,2263,2265],{},"Up one level: ",[14,2264,17],{"href":16}," — the pipeline behind these reports.",[2166,2267,2268,2272],{},[14,2269,2271],{"href":2270},"\u002Fadvanced-data-transformation-and-cleaning\u002Fexcel-formula-equivalents-in-pandas\u002Fapproximate-match-vlookup-in-pandas\u002F","Approximate-Match VLOOKUP in pandas"," — rate tables looked up by value.",[2166,2274,2275,2279],{},[14,2276,2278],{"href":2277},"\u002Fadvanced-data-transformation-and-cleaning\u002Fcreating-pivot-tables-from-excel-data\u002Fbin-values-into-ranges-with-pandas-cut\u002F","Bin Values into Ranges with pandas cut"," — band boundaries in depth.",[2166,2281,2282,2286],{},[14,2283,2285],{"href":2284},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-budget-vs-actual-variance-report\u002F","Build a Budget vs Actual Variance Report"," — the month-end companion.",[2288,2289,2290],"style",{},"html pre.shiki code .sMTad, html code.shiki .sMTad{--shiki-default:#6F42C1;--shiki-dark:#FFB757}html pre.shiki code .srMev, html code.shiki .srMev{--shiki-default:#032F62;--shiki-dark:#ADDCFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .s-kum, html code.shiki .s-kum{--shiki-default:#D73A49;--shiki-dark:#FF9492}html pre.shiki code .skGVy, html code.shiki .skGVy{--shiki-default:#24292E;--shiki-dark:#F0F3F6}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}html pre.shiki code .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":192,"searchDepth":262,"depth":262,"links":2292},[2293,2294,2295,2296,2297,2298,2299,2300,2301,2302,2303],{"id":184,"depth":262,"text":185},{"id":232,"depth":262,"text":233},{"id":439,"depth":262,"text":440},{"id":780,"depth":262,"text":781},{"id":1050,"depth":262,"text":1051},{"id":1300,"depth":262,"text":1301},{"id":2160,"depth":262,"text":2161},{"id":2208,"depth":262,"text":2209},{"id":2215,"depth":262,"text":2216},{"id":2228,"depth":262,"text":2229},{"id":2256,"depth":262,"text":2257},"2026-09-25","Calculate commissions from a month of closed deals: tiered rates applied the way the plan defines them — marginal like tax bands or on the whole amount once a tier is reached — clawbacks for cancelled deals, reps close to the next tier flagged, a summary for payroll and one statement sheet per rep showing the working.","md",[2308,2310,2312,2314],{"q":2234,"a":2309},"For marginal tiers, work out how much of each rep's sales falls inside each band — the total clipped to the band's lower and upper limits, minus the lower limit — multiply by the band's rate and add the bands up. numpy's clip does this for all reps at once.",{"q":2240,"a":2311},"Marginal tiers pay each rate only on the sales inside its band, like income tax. Whole-amount (retroactive) tiers pay the highest rate reached on all sales. The second creates a cliff: one more sale that crosses a threshold can increase commission by far more than the sale itself.",{"q":2246,"a":2313},"Claw back the commission that was actually paid on it, taken from the record of the earlier payment rather than recalculated, and show it as a separate negative line on the rep's statement so the adjustment is visible.",{"q":2252,"a":2315},"Yes. A statement listing their deals, the tier working and any adjustments lets a rep check their pay without seeing colleagues' figures. Write one sheet per rep, or one file per rep when statements are emailed individually.","build a sales commission report",null,{"breadcrumb":2319},[2320,2323,2326],{"name":2321,"item":2322},"Home","\u002F",{"name":2324,"item":2325},"Automating Reporting Workflows with Python and Excel","\u002Fautomating-reporting-workflows\u002F",{"name":2327,"item":16},"Building Finance and Operations Reports in Excel with Python","\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-sales-commission-report",{"title":2330,"description":2331},"Sales Commission Report in Excel with Python (Tiered Rates)","Build a sales commission report with pandas: tiered commission tables, marginal versus whole-amount tiers, clawbacks for cancelled deals, per-rep plans, distance to the next tier, a payroll summary and a statement sheet per rep with xlsxwriter.","build-a-sales-commission-report","automating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-sales-commission-report\u002Findex",[2335,2336,2337],"report-layouts","pivots-and-summaries","pandas","how-to","9zWjswpzBVZwe3R2SCoDNWcF9odpu-shxsVoWM8UYLA",[2341,2345],{"title":2342,"path":2343,"stem":2344,"children":-1},"Build a Returns Analysis Report","\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-returns-analysis-report","automating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-returns-analysis-report\u002Findex",{"title":2346,"path":2347,"stem":2348,"children":-1},"Build a Sales Forecast with Exponential Smoothing","\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-sales-forecast-with-exponential-smoothing","automating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-sales-forecast-with-exponential-smoothing\u002Findex",[2350,2352],{"path":2351,"title":2327},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports",{"path":2353,"title":2324},"\u002Fautomating-reporting-workflows",[],[2356,2360,2363,2366,2369,2372],{"path":2357,"title":2358,"kind":2359},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards","Building Multi-Sheet Excel Dashboards","guide",{"path":2361,"title":2362,"kind":2338},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fadd-a-data-dictionary-sheet-to-an-excel-report","Add a Data Dictionary Sheet to an Excel Report",{"path":2364,"title":2365,"kind":2338},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fadd-a-kpi-summary-block-to-an-excel-dashboard","Add a KPI Summary Block to an Excel Dashboard",{"path":2367,"title":2368,"kind":2338},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fadd-a-table-of-contents-sheet-with-hyperlinks-in-excel","Add a Table of Contents Sheet with Hyperlinks in Excel",{"path":2370,"title":2371,"kind":2338},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fadd-slicer-style-filters-to-an-excel-dashboard","Add a Dropdown Filter That Drives an Excel Dashboard",{"path":2373,"title":2374,"kind":2338},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fadd-summary-sheet-to-excel-report-python","Add a Summary Sheet to an Excel Report with Python",1790972127210]