[{"data":1,"prerenderedAt":1897},["ShallowReactive",2],{"doc:\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-discount-leakage-report":3,"surround:\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-discount-leakage-report":1862,"ancestors:\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-discount-leakage-report":1871,"children:\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-discount-leakage-report":1876,"related:\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-discount-leakage-report":1877},{"id":4,"title":5,"body":6,"dateModified":1828,"datePublished":1828,"description":1829,"extension":1830,"faq":1831,"keyword":1840,"kind":1841,"meta":1842,"navigation":93,"path":1851,"seo":1852,"slug":1855,"stem":1856,"topics":1857,"type":1860,"__hash__":1861},"docs\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-discount-leakage-report\u002Findex.md","Build a Discount Leakage Report",{"type":7,"value":8,"toc":1816},"minimark",[9,24,29,58,61,65,699,717,851,928,932,1136,1139,1143,1281,1284,1470,1474,1691,1694,1698,1701,1705,1740,1744,1747,1751,1757,1763,1773,1779,1783,1812],[10,11,12,13,17,18,23],"p",{},"Most companies have a discount policy: a table that says how much discount a salesperson may give, depending on the customer's tier and the product group, with anything larger needing a manager's approval. Most companies also suspect the policy is not followed — that discounts drift upwards deal by deal, and that some customers pay far less than others for the same thing. The money lost this way is called ",[14,15,16],"em",{},"discount leakage",", and it is measurable from invoice data. This page builds a leakage report with pandas: each invoice line's discount compared with the approved maximum for its tier and product group, the excess valued in currency, approved exceptions separated from the rest, the leakage broken down by sales rep and customer, and the spread of discounts for the same product and tier — the clearest sign of inconsistent pricing. It belongs to ",[19,20,22],"a",{"href":21},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002F","Building Finance and Operations Reports",".",[25,26,28],"h2",{"id":27},"prerequisites","Prerequisites",[30,31,36],"pre",{"className":32,"code":33,"language":34,"meta":35,"style":35},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[37,38,39],"code",{"__ignoreMap":35},[40,41,44,48,52,55],"span",{"class":42,"line":43},"line",1,[40,45,47],{"class":46},"sMTad","pip",[40,49,51],{"class":50},"srMev"," install",[40,53,54],{"class":50}," pandas",[40,56,57],{"class":50}," openpyxl\n",[10,59,60],{},"Four inputs: invoice lines for the quarter with the unit price actually charged and a flag for lines where a manager approved an exception; customers with their tier and sales rep; products with their product group and list price; and the discount policy, the maximum discount for each combination of tier and product group.",[25,62,64],{"id":63},"step-1-each-lines-discount-against-the-policy","Step 1 — Each line's discount against the policy",[30,66,70],{"className":67,"code":68,"language":69,"meta":35,"style":35},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\nlines = pd.read_excel(\"invoice_lines.xlsx\", keep_default_na=False)\ncustomers = pd.read_excel(\"customers.xlsx\")\nproducts = pd.read_excel(\"products.xlsx\")\npolicy = pd.read_excel(\"discount_policy.xlsx\")\n\ndf = (lines.merge(customers, on=\"Customer\", validate=\"many_to_one\")\n           .merge(products, on=\"Product\", validate=\"many_to_one\")\n           .merge(policy, on=[\"Tier\", \"Group\"], validate=\"many_to_one\"))\ndf[\"Discount\"] = 1 - df[\"Unit price\"] \u002F df[\"List price\"]\ndf[\"Excess\"] = (df[\"Discount\"] - df[\"Max discount\"]).clip(lower=0)\ndf[\"List value\"] = df[\"List price\"] * df[\"Qty\"]\ndf[\"Leakage\"] = (df[\"Excess\"] * df[\"List value\"]).round(2)\ndf[\"Category\"] = \"Within policy\"\ndf.loc[(df[\"Excess\"] > 0) & (df[\"Approved exception\"] == \"Y\"), \"Category\"] = \"Approved exception\"\ndf.loc[(df[\"Excess\"] > 0) & (df[\"Approved exception\"] != \"Y\"), \"Category\"] = \"Unapproved\"\n\nsummary = df.groupby(\"Category\").agg(lines=(\"Invoice\", \"size\"), revenue=(\"List value\", \"sum\"), leakage=(\"Leakage\", \"sum\")).round(0)\nprint(summary.to_string())\nprint(f\"unapproved leakage: {df.loc[df['Category'] == 'Unapproved', 'Leakage'].sum():,.0f} \"\n      f\"({df.loc[df['Category'] == 'Unapproved', 'Leakage'].sum() \u002F (df['List value'] * (1 - df['Discount'])).sum():.2%} of net revenue)\")\n","python",[37,71,72,88,95,126,141,156,171,176,207,230,263,302,342,370,402,417,464,503,508,578,587,634],{"__ignoreMap":35},[40,73,74,78,82,85],{"class":42,"line":43},[40,75,77],{"class":76},"s-kum","import",[40,79,81],{"class":80},"skGVy"," pandas ",[40,83,84],{"class":76},"as",[40,86,87],{"class":80}," pd\n",[40,89,91],{"class":42,"line":90},2,[40,92,94],{"emptyLinePlaceholder":93},true,"\n",[40,96,98,101,104,107,110,113,117,119,123],{"class":42,"line":97},3,[40,99,100],{"class":80},"lines ",[40,102,103],{"class":76},"=",[40,105,106],{"class":80}," pd.read_excel(",[40,108,109],{"class":50},"\"invoice_lines.xlsx\"",[40,111,112],{"class":80},", ",[40,114,116],{"class":115},"sa561","keep_default_na",[40,118,103],{"class":76},[40,120,122],{"class":121},"sP0c6","False",[40,124,125],{"class":80},")\n",[40,127,129,132,134,136,139],{"class":42,"line":128},4,[40,130,131],{"class":80},"customers ",[40,133,103],{"class":76},[40,135,106],{"class":80},[40,137,138],{"class":50},"\"customers.xlsx\"",[40,140,125],{"class":80},[40,142,144,147,149,151,154],{"class":42,"line":143},5,[40,145,146],{"class":80},"products ",[40,148,103],{"class":76},[40,150,106],{"class":80},[40,152,153],{"class":50},"\"products.xlsx\"",[40,155,125],{"class":80},[40,157,159,162,164,166,169],{"class":42,"line":158},6,[40,160,161],{"class":80},"policy ",[40,163,103],{"class":76},[40,165,106],{"class":80},[40,167,168],{"class":50},"\"discount_policy.xlsx\"",[40,170,125],{"class":80},[40,172,174],{"class":42,"line":173},7,[40,175,94],{"emptyLinePlaceholder":93},[40,177,179,182,184,187,190,192,195,197,200,202,205],{"class":42,"line":178},8,[40,180,181],{"class":80},"df ",[40,183,103],{"class":76},[40,185,186],{"class":80}," (lines.merge(customers, ",[40,188,189],{"class":115},"on",[40,191,103],{"class":76},[40,193,194],{"class":50},"\"Customer\"",[40,196,112],{"class":80},[40,198,199],{"class":115},"validate",[40,201,103],{"class":76},[40,203,204],{"class":50},"\"many_to_one\"",[40,206,125],{"class":80},[40,208,210,213,215,217,220,222,224,226,228],{"class":42,"line":209},9,[40,211,212],{"class":80},"           .merge(products, ",[40,214,189],{"class":115},[40,216,103],{"class":76},[40,218,219],{"class":50},"\"Product\"",[40,221,112],{"class":80},[40,223,199],{"class":115},[40,225,103],{"class":76},[40,227,204],{"class":50},[40,229,125],{"class":80},[40,231,233,236,238,240,243,246,248,251,254,256,258,260],{"class":42,"line":232},10,[40,234,235],{"class":80},"           .merge(policy, ",[40,237,189],{"class":115},[40,239,103],{"class":76},[40,241,242],{"class":80},"[",[40,244,245],{"class":50},"\"Tier\"",[40,247,112],{"class":80},[40,249,250],{"class":50},"\"Group\"",[40,252,253],{"class":80},"], ",[40,255,199],{"class":115},[40,257,103],{"class":76},[40,259,204],{"class":50},[40,261,262],{"class":80},"))\n",[40,264,266,269,272,275,277,280,283,286,289,291,294,296,299],{"class":42,"line":265},11,[40,267,268],{"class":80},"df[",[40,270,271],{"class":50},"\"Discount\"",[40,273,274],{"class":80},"] ",[40,276,103],{"class":76},[40,278,279],{"class":121}," 1",[40,281,282],{"class":76}," -",[40,284,285],{"class":80}," df[",[40,287,288],{"class":50},"\"Unit price\"",[40,290,274],{"class":80},[40,292,293],{"class":76},"\u002F",[40,295,285],{"class":80},[40,297,298],{"class":50},"\"List price\"",[40,300,301],{"class":80},"]\n",[40,303,305,307,310,312,314,317,319,321,324,326,329,332,335,337,340],{"class":42,"line":304},12,[40,306,268],{"class":80},[40,308,309],{"class":50},"\"Excess\"",[40,311,274],{"class":80},[40,313,103],{"class":76},[40,315,316],{"class":80}," (df[",[40,318,271],{"class":50},[40,320,274],{"class":80},[40,322,323],{"class":76},"-",[40,325,285],{"class":80},[40,327,328],{"class":50},"\"Max discount\"",[40,330,331],{"class":80},"]).clip(",[40,333,334],{"class":115},"lower",[40,336,103],{"class":76},[40,338,339],{"class":121},"0",[40,341,125],{"class":80},[40,343,345,347,350,352,354,356,358,360,363,365,368],{"class":42,"line":344},13,[40,346,268],{"class":80},[40,348,349],{"class":50},"\"List value\"",[40,351,274],{"class":80},[40,353,103],{"class":76},[40,355,285],{"class":80},[40,357,298],{"class":50},[40,359,274],{"class":80},[40,361,362],{"class":76},"*",[40,364,285],{"class":80},[40,366,367],{"class":50},"\"Qty\"",[40,369,301],{"class":80},[40,371,373,375,378,380,382,384,386,388,390,392,394,397,400],{"class":42,"line":372},14,[40,374,268],{"class":80},[40,376,377],{"class":50},"\"Leakage\"",[40,379,274],{"class":80},[40,381,103],{"class":76},[40,383,316],{"class":80},[40,385,309],{"class":50},[40,387,274],{"class":80},[40,389,362],{"class":76},[40,391,285],{"class":80},[40,393,349],{"class":50},[40,395,396],{"class":80},"]).round(",[40,398,399],{"class":121},"2",[40,401,125],{"class":80},[40,403,405,407,410,412,414],{"class":42,"line":404},15,[40,406,268],{"class":80},[40,408,409],{"class":50},"\"Category\"",[40,411,274],{"class":80},[40,413,103],{"class":76},[40,415,416],{"class":50}," \"Within policy\"\n",[40,418,420,423,425,427,430,433,436,439,441,444,446,449,452,455,457,459,461],{"class":42,"line":419},16,[40,421,422],{"class":80},"df.loc[(df[",[40,424,309],{"class":50},[40,426,274],{"class":80},[40,428,429],{"class":76},">",[40,431,432],{"class":121}," 0",[40,434,435],{"class":80},") ",[40,437,438],{"class":76},"&",[40,440,316],{"class":80},[40,442,443],{"class":50},"\"Approved exception\"",[40,445,274],{"class":80},[40,447,448],{"class":76},"==",[40,450,451],{"class":50}," \"Y\"",[40,453,454],{"class":80},"), ",[40,456,409],{"class":50},[40,458,274],{"class":80},[40,460,103],{"class":76},[40,462,463],{"class":50}," \"Approved exception\"\n",[40,465,467,469,471,473,475,477,479,481,483,485,487,490,492,494,496,498,500],{"class":42,"line":466},17,[40,468,422],{"class":80},[40,470,309],{"class":50},[40,472,274],{"class":80},[40,474,429],{"class":76},[40,476,432],{"class":121},[40,478,435],{"class":80},[40,480,438],{"class":76},[40,482,316],{"class":80},[40,484,443],{"class":50},[40,486,274],{"class":80},[40,488,489],{"class":76},"!=",[40,491,451],{"class":50},[40,493,454],{"class":80},[40,495,409],{"class":50},[40,497,274],{"class":80},[40,499,103],{"class":76},[40,501,502],{"class":50}," \"Unapproved\"\n",[40,504,506],{"class":42,"line":505},18,[40,507,94],{"emptyLinePlaceholder":93},[40,509,511,514,516,519,521,524,527,529,532,535,537,540,542,545,547,549,551,553,556,558,561,563,565,567,569,571,574,576],{"class":42,"line":510},19,[40,512,513],{"class":80},"summary ",[40,515,103],{"class":76},[40,517,518],{"class":80}," df.groupby(",[40,520,409],{"class":50},[40,522,523],{"class":80},").agg(",[40,525,526],{"class":115},"lines",[40,528,103],{"class":76},[40,530,531],{"class":80},"(",[40,533,534],{"class":50},"\"Invoice\"",[40,536,112],{"class":80},[40,538,539],{"class":50},"\"size\"",[40,541,454],{"class":80},[40,543,544],{"class":115},"revenue",[40,546,103],{"class":76},[40,548,531],{"class":80},[40,550,349],{"class":50},[40,552,112],{"class":80},[40,554,555],{"class":50},"\"sum\"",[40,557,454],{"class":80},[40,559,560],{"class":115},"leakage",[40,562,103],{"class":76},[40,564,531],{"class":80},[40,566,377],{"class":50},[40,568,112],{"class":80},[40,570,555],{"class":50},[40,572,573],{"class":80},")).round(",[40,575,339],{"class":121},[40,577,125],{"class":80},[40,579,581,584],{"class":42,"line":580},20,[40,582,583],{"class":121},"print",[40,585,586],{"class":80},"(summary.to_string())\n",[40,588,590,592,594,597,600,604,607,610,612,614,617,619,622,625,628,631],{"class":42,"line":589},21,[40,591,583],{"class":121},[40,593,531],{"class":80},[40,595,596],{"class":76},"f",[40,598,599],{"class":50},"\"unapproved leakage: ",[40,601,603],{"class":602},"sSjpA","{",[40,605,606],{"class":80},"df.loc[df[",[40,608,609],{"class":50},"'Category'",[40,611,274],{"class":80},[40,613,448],{"class":76},[40,615,616],{"class":50}," 'Unapproved'",[40,618,112],{"class":80},[40,620,621],{"class":50},"'Leakage'",[40,623,624],{"class":80},"].sum()",[40,626,627],{"class":76},":,.0f",[40,629,630],{"class":602},"}",[40,632,633],{"class":50}," \"\n",[40,635,637,640,643,645,647,649,651,653,655,657,659,662,664,666,669,671,673,676,679,681,683,686,689,692,694,697],{"class":42,"line":636},22,[40,638,639],{"class":76},"      f",[40,641,642],{"class":50},"\"(",[40,644,603],{"class":602},[40,646,606],{"class":80},[40,648,609],{"class":50},[40,650,274],{"class":80},[40,652,448],{"class":76},[40,654,616],{"class":50},[40,656,112],{"class":80},[40,658,621],{"class":50},[40,660,661],{"class":80},"].sum() ",[40,663,293],{"class":76},[40,665,316],{"class":80},[40,667,668],{"class":50},"'List value'",[40,670,274],{"class":80},[40,672,362],{"class":76},[40,674,675],{"class":80}," (",[40,677,678],{"class":121},"1",[40,680,282],{"class":76},[40,682,285],{"class":80},[40,684,685],{"class":50},"'Discount'",[40,687,688],{"class":80},"])).sum()",[40,690,691],{"class":76},":.2%",[40,693,630],{"class":602},[40,695,696],{"class":50}," of net revenue)\"",[40,698,125],{"class":80},[10,700,701,702,704,705,708,709,712,713,716],{},"Joining the invoice lines to customers, products and the policy gives every line its tier, product group, list price and allowed maximum, with ",[37,703,199],{}," making sure each join matches exactly one row — a product missing from the price list or a tier missing from the policy would otherwise duplicate or drop lines silently. The discount actually given is worked out from the price charged and the list price, rather than taken from a discount field, because invoice discount fields are often incomplete when salespeople simply type a lower price. The ",[14,706,707],{},"excess"," is how far the discount goes beyond the policy maximum, and multiplying it by the list value gives the leakage: the revenue that would have been collected had the discount stopped at the limit. Lines over the limit are split into ",[14,710,711],{},"approved exceptions"," — deals a manager signed off, which are a pricing decision — and ",[14,714,715],{},"unapproved"," ones, which are the real leakage. In this quarter 1,107 of the 1,200 lines are within policy, 30 are approved exceptions worth 20,376 in excess discount, and 63 are unapproved, worth 58,911 — 0.44% of net revenue. That sounds small until it is set against margin: every pound of leakage comes straight off profit, and at this rate it is roughly 236,000 a year.",[718,719,727,728,727,732,727,736,727,743,727,753,727,760,727,765,727,770,727,775,727,779,727,784,727,788,727,792,727,795,727,800,727,805,727,809,727,813,727,816,727,821,727,826,727,830,727,834,727,837,727,842,727,847],"svg",{"viewBox":720,"role":721,"ariaLabelledBy":722,"xmlns":725,"style":726},"84 0 592 471","img",[723,724],"disc-waterfall-t","disc-waterfall-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:592px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[729,730,731],"title",{"id":723},"From list price to leakage",[733,734,735],"desc",{"id":724},"List price less the discount allowed by policy gives the lowest approved price. Any further discount is excess. The excess on lines with an approved exception is a deliberate decision; the excess on unapproved lines is the leakage.",[737,738],"rect",{"x":739,"y":339,"width":740,"height":741,"fill":742},"84","592","471","#ffffff",[737,744],{"x":745,"y":746,"width":747,"height":748,"rx":749,"fill":750,"stroke":751,"style":752},"100.0","16.0","560.0","59.0","12","#e7ebef","var(--line,#cdd5e6)","stroke-width:2px",[754,755,759],"text",{"x":756,"y":757,"style":758},"380.0","41.5","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","List price",[754,761,764],{"x":756,"y":762,"style":763},"61.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","from the price list",[42,766],{"x1":756,"y1":767,"x2":756,"y2":768,"stroke":769,"style":752},"80.0","102.0","var(--brand,#5b5cf0)",[771,772],"polygon",{"points":773,"fill":774},"380.0,106.0 375.0,97.0 385.0,97.0","#5b5cf0",[737,776],{"x":745,"y":777,"width":747,"height":748,"rx":749,"fill":778,"stroke":769,"style":752},"111.0","#f0f4ff",[754,780,783],{"x":756,"y":781,"style":782},"136.5","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","− Allowed discount",[754,785,787],{"x":756,"y":786,"style":763},"156.0","tier × product group",[42,789],{"x1":756,"y1":790,"x2":756,"y2":791,"stroke":769,"style":752},"175.0","197.0",[771,793],{"points":794,"fill":774},"380.0,201.0 375.0,192.0 385.0,192.0",[737,796],{"x":745,"y":797,"width":747,"height":748,"rx":749,"fill":798,"stroke":799,"style":752},"206.0","#fdefd8","var(--gold,#b4740a)",[754,801,804],{"x":756,"y":802,"style":803},"231.5","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","− Excess discount",[754,806,808],{"x":756,"y":807,"style":763},"251.0","beyond the policy",[42,810],{"x1":756,"y1":811,"x2":756,"y2":812,"stroke":769,"style":752},"270.0","292.0",[771,814],{"points":815,"fill":774},"380.0,296.0 375.0,287.0 385.0,287.0",[737,817],{"x":745,"y":818,"width":747,"height":748,"rx":749,"fill":819,"stroke":820,"style":752},"301.0","#d9f4f1","var(--teal,#0f9488)",[754,822,825],{"x":756,"y":823,"style":824},"326.5","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Approved exceptions",[754,827,829],{"x":756,"y":828,"style":763},"346.0","manager signed off",[42,831],{"x1":756,"y1":832,"x2":756,"y2":833,"stroke":769,"style":752},"365.0","387.0",[771,835],{"points":836,"fill":774},"380.0,391.0 375.0,382.0 385.0,382.0",[737,838],{"x":745,"y":839,"width":747,"height":748,"rx":749,"fill":840,"stroke":841,"style":752},"396.0","#fce9e9","var(--danger,#dc2626)",[754,843,846],{"x":756,"y":844,"style":845},"421.5","font-size:13.5px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","Unapproved = leakage",[754,848,850],{"x":756,"y":849,"style":763},"441.0","margin lost",[718,852,727,858,727,861,727,864,727,869,727,875,727,881,727,887,727,892,727,896,727,900,727,904,727,907,727,912,727,916,727,919,727,922,727,925],{"viewBox":853,"role":721,"ariaLabelledBy":854,"xmlns":725,"style":857},"4 0 752 199",[855,856],"disc-categories-t","disc-categories-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[729,859,860],{"id":855},"Approved exception or leakage",[733,862,863],{"id":856},"An approved exception is a discount beyond policy that a manager signed off, usually to win or keep a deal; it is a pricing decision to review, not a leak. Unapproved excess discount had no sign-off and is the leakage the report is about.",[737,865],{"x":866,"y":339,"width":867,"height":868,"fill":742},"4","752","199",[737,870],{"x":871,"y":746,"width":872,"height":873,"rx":874,"fill":819,"stroke":820,"style":752},"20.0","351.0","167.0","14",[754,876,880],{"x":877,"y":878,"style":879},"195.5","44.0","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Approved exception",[42,882],{"x1":883,"y1":884,"x2":885,"y2":884,"stroke":820,"style":886},"36.0","57.0","355.0","stroke-width:1px",[754,888,891],{"x":877,"y":889,"style":890},"77.0","font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","beyond policy, signed off",[754,893,895],{"x":877,"y":894,"style":890},"103.0","a deliberate decision",[754,897,899],{"x":877,"y":898,"style":890},"129.0","review: was it worth it?",[754,901,903],{"x":877,"y":902,"style":890},"155.0","30 lines, 20,376",[737,905],{"x":906,"y":746,"width":872,"height":873,"rx":874,"fill":840,"stroke":841,"style":752},"389.0",[754,908,911],{"x":909,"y":878,"style":910},"564.5","font-size:14px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","Unapproved",[42,913],{"x1":914,"y1":884,"x2":915,"y2":884,"stroke":841,"style":886},"405.0","724.0",[754,917,918],{"x":909,"y":889,"style":890},"beyond policy, no sign-off",[754,920,921],{"x":909,"y":894,"style":890},"margin lost without a decision",[754,923,924],{"x":909,"y":898,"style":890},"recover and prevent",[754,926,927],{"x":909,"y":902,"style":890},"63 lines, 58,911",[25,929,931],{"id":930},"step-2-who-and-where","Step 2 — Who and where",[30,933,935],{"className":67,"code":934,"language":69,"meta":35,"style":35},"unapproved = df[df[\"Category\"] == \"Unapproved\"]\nby_rep = df.groupby(\"Rep\").agg(lines=(\"Invoice\", \"size\"), over_policy=(\"Excess\", lambda s: (s > 0).mean()))\nby_rep[\"Unapproved leakage\"] = unapproved.groupby(\"Rep\")[\"Leakage\"].sum()\nby_rep = by_rep.fillna(0).sort_values(\"Unapproved leakage\", ascending=False).round(3)\nprint(by_rep.to_string())\n\nby_customer = (unapproved.groupby([\"Customer\", \"Tier\", \"Rep\"])[\"Leakage\"].sum()\n               .sort_values(ascending=False).head(5).round(0))\nprint(by_customer.to_string())\n",[37,936,937,958,1010,1035,1068,1075,1079,1106,1129],{"__ignoreMap":35},[40,938,939,942,944,947,949,951,953,956],{"class":42,"line":43},[40,940,941],{"class":80},"unapproved ",[40,943,103],{"class":76},[40,945,946],{"class":80}," df[df[",[40,948,409],{"class":50},[40,950,274],{"class":80},[40,952,448],{"class":76},[40,954,955],{"class":50}," \"Unapproved\"",[40,957,301],{"class":80},[40,959,960,963,965,967,970,972,974,976,978,980,982,984,986,989,991,993,995,997,1000,1003,1005,1007],{"class":42,"line":90},[40,961,962],{"class":80},"by_rep ",[40,964,103],{"class":76},[40,966,518],{"class":80},[40,968,969],{"class":50},"\"Rep\"",[40,971,523],{"class":80},[40,973,526],{"class":115},[40,975,103],{"class":76},[40,977,531],{"class":80},[40,979,534],{"class":50},[40,981,112],{"class":80},[40,983,539],{"class":50},[40,985,454],{"class":80},[40,987,988],{"class":115},"over_policy",[40,990,103],{"class":76},[40,992,531],{"class":80},[40,994,309],{"class":50},[40,996,112],{"class":80},[40,998,999],{"class":76},"lambda",[40,1001,1002],{"class":80}," s: (s ",[40,1004,429],{"class":76},[40,1006,432],{"class":121},[40,1008,1009],{"class":80},").mean()))\n",[40,1011,1012,1015,1018,1020,1022,1025,1027,1030,1032],{"class":42,"line":97},[40,1013,1014],{"class":80},"by_rep[",[40,1016,1017],{"class":50},"\"Unapproved leakage\"",[40,1019,274],{"class":80},[40,1021,103],{"class":76},[40,1023,1024],{"class":80}," unapproved.groupby(",[40,1026,969],{"class":50},[40,1028,1029],{"class":80},")[",[40,1031,377],{"class":50},[40,1033,1034],{"class":80},"].sum()\n",[40,1036,1037,1039,1041,1044,1046,1049,1051,1053,1056,1058,1060,1063,1066],{"class":42,"line":128},[40,1038,962],{"class":80},[40,1040,103],{"class":76},[40,1042,1043],{"class":80}," by_rep.fillna(",[40,1045,339],{"class":121},[40,1047,1048],{"class":80},").sort_values(",[40,1050,1017],{"class":50},[40,1052,112],{"class":80},[40,1054,1055],{"class":115},"ascending",[40,1057,103],{"class":76},[40,1059,122],{"class":121},[40,1061,1062],{"class":80},").round(",[40,1064,1065],{"class":121},"3",[40,1067,125],{"class":80},[40,1069,1070,1072],{"class":42,"line":143},[40,1071,583],{"class":121},[40,1073,1074],{"class":80},"(by_rep.to_string())\n",[40,1076,1077],{"class":42,"line":158},[40,1078,94],{"emptyLinePlaceholder":93},[40,1080,1081,1084,1086,1089,1091,1093,1095,1097,1099,1102,1104],{"class":42,"line":173},[40,1082,1083],{"class":80},"by_customer ",[40,1085,103],{"class":76},[40,1087,1088],{"class":80}," (unapproved.groupby([",[40,1090,194],{"class":50},[40,1092,112],{"class":80},[40,1094,245],{"class":50},[40,1096,112],{"class":80},[40,1098,969],{"class":50},[40,1100,1101],{"class":80},"])[",[40,1103,377],{"class":50},[40,1105,1034],{"class":80},[40,1107,1108,1111,1113,1115,1117,1120,1123,1125,1127],{"class":42,"line":178},[40,1109,1110],{"class":80},"               .sort_values(",[40,1112,1055],{"class":115},[40,1114,103],{"class":76},[40,1116,122],{"class":121},[40,1118,1119],{"class":80},").head(",[40,1121,1122],{"class":121},"5",[40,1124,1062],{"class":80},[40,1126,339],{"class":121},[40,1128,262],{"class":80},[40,1130,1131,1133],{"class":42,"line":209},[40,1132,583],{"class":121},[40,1134,1135],{"class":80},"(by_customer.to_string())\n",[10,1137,1138],{},"Leakage is rarely spread evenly. Grouping by sales rep shows each person's share of lines over policy and the unapproved leakage on their accounts; here Dev discounts beyond policy on 22% of lines and Ben on 13%, against 2–3% for Ana and Caro, and between them Dev and Ben account for 51,000 of the 59,000 unapproved leakage. Four of the five customers with the most leakage are Dev's accounts, led by a bronze-tier customer whose unapproved discounts cost 7,026 in the quarter. The customer view lists the accounts where most of the money goes, which is where a pricing conversation will recover the most. Neither view is a verdict on its own — a rep with the largest accounts will naturally show more leakage in currency — which is why the share of lines over policy sits next to the amount.",[25,1140,1142],{"id":1141},"step-3-how-consistent-is-pricing","Step 3 — How consistent is pricing?",[30,1144,1146],{"className":67,"code":1145,"language":69,"meta":35,"style":35},"spread = (df.groupby([\"Product\", \"Tier\"])[\"Discount\"]\n            .agg(lines=\"size\", lowest=\"min\", median=\"median\", highest=\"max\")\n            .query(\"lines >= 10\"))\nspread[\"Range\"] = spread[\"highest\"] - spread[\"lowest\"]\nprint(spread.sort_values(\"Range\", ascending=False).head(6).round(3).to_string())\n",[37,1147,1148,1170,1213,1223,1252],{"__ignoreMap":35},[40,1149,1150,1153,1155,1158,1160,1162,1164,1166,1168],{"class":42,"line":43},[40,1151,1152],{"class":80},"spread ",[40,1154,103],{"class":76},[40,1156,1157],{"class":80}," (df.groupby([",[40,1159,219],{"class":50},[40,1161,112],{"class":80},[40,1163,245],{"class":50},[40,1165,1101],{"class":80},[40,1167,271],{"class":50},[40,1169,301],{"class":80},[40,1171,1172,1175,1177,1179,1181,1183,1186,1188,1191,1193,1196,1198,1201,1203,1206,1208,1211],{"class":42,"line":90},[40,1173,1174],{"class":80},"            .agg(",[40,1176,526],{"class":115},[40,1178,103],{"class":76},[40,1180,539],{"class":50},[40,1182,112],{"class":80},[40,1184,1185],{"class":115},"lowest",[40,1187,103],{"class":76},[40,1189,1190],{"class":50},"\"min\"",[40,1192,112],{"class":80},[40,1194,1195],{"class":115},"median",[40,1197,103],{"class":76},[40,1199,1200],{"class":50},"\"median\"",[40,1202,112],{"class":80},[40,1204,1205],{"class":115},"highest",[40,1207,103],{"class":76},[40,1209,1210],{"class":50},"\"max\"",[40,1212,125],{"class":80},[40,1214,1215,1218,1221],{"class":42,"line":97},[40,1216,1217],{"class":80},"            .query(",[40,1219,1220],{"class":50},"\"lines >= 10\"",[40,1222,262],{"class":80},[40,1224,1225,1228,1231,1233,1235,1238,1241,1243,1245,1247,1250],{"class":42,"line":128},[40,1226,1227],{"class":80},"spread[",[40,1229,1230],{"class":50},"\"Range\"",[40,1232,274],{"class":80},[40,1234,103],{"class":76},[40,1236,1237],{"class":80}," spread[",[40,1239,1240],{"class":50},"\"highest\"",[40,1242,274],{"class":80},[40,1244,323],{"class":76},[40,1246,1237],{"class":80},[40,1248,1249],{"class":50},"\"lowest\"",[40,1251,301],{"class":80},[40,1253,1254,1256,1259,1261,1263,1265,1267,1269,1271,1274,1276,1278],{"class":42,"line":143},[40,1255,583],{"class":121},[40,1257,1258],{"class":80},"(spread.sort_values(",[40,1260,1230],{"class":50},[40,1262,112],{"class":80},[40,1264,1055],{"class":115},[40,1266,103],{"class":76},[40,1268,122],{"class":121},[40,1270,1119],{"class":80},[40,1272,1273],{"class":121},"6",[40,1275,1062],{"class":80},[40,1277,1065],{"class":121},[40,1279,1280],{"class":80},").to_string())\n",[10,1282,1283],{},"Even within policy, two customers of the same tier can pay very different prices for the same product. The spread of discounts for each product and tier — lowest, median, highest and the range between them — shows how consistent pricing really is. A wide range means the price depends more on who negotiated than on the policy, and it is the pattern customers notice when they compare notes. The widest here is product P12 for silver customers: across 26 lines the discount ranges from 8.8% to 28%, with a median of 13.2% — the same product, the same tier, and some customers paying a fifth less than others. Limiting the view to combinations with at least ten invoice lines keeps one-off deals from dominating it.",[718,1285,727,1291,727,1294,727,1297,727,1302,727,1307,727,1311,727,1317,727,1321,727,1325,727,1329,727,1333,727,1337,727,1341,727,1345,727,1348,727,1351,727,1355,727,1357,727,1360,727,1362,727,1365,727,1367,727,1370,727,1373,727,1376,727,1378,727,1382,727,1384,727,1387,727,1389,727,1392,727,1394,727,1397,727,1400,727,1403,727,1405,727,1408,727,1410,727,1413,727,1415,727,1419,727,1421,727,1424,727,1427,727,1430,727,1432,727,1435,727,1437,727,1440,727,1442,727,1445,727,1447,727,1450,727,1453,727,1455,727,1458,727,1460,727,1463,727,1465,727,1468],{"viewBox":1286,"role":721,"ariaLabelledBy":1287,"xmlns":725,"style":1290},"120 0 519 196",[1288,1289],"disc-rep-t","disc-rep-d","width:100%;max-width:519px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[729,1292,1293],{"id":1288},"Leakage by sales rep",[733,1295,1296],{"id":1289},"Dev gives discounts beyond policy on 22.3 percent of lines, with 28,470 of unapproved leakage. Ben is next at 12.6 percent and 22,467. Caro and Ana are rarely over policy, with about 5,000 and 2,900.",[737,1298],{"x":1299,"y":339,"width":1300,"height":1301,"fill":742},"120","519","196",[737,1303],{"x":1304,"y":746,"width":1305,"height":1306,"rx":339,"fill":750,"stroke":751,"style":886},"137.0","32.0","24.0",[737,1308],{"x":1309,"y":746,"width":1310,"height":1306,"rx":339,"fill":750,"stroke":751,"style":886},"169.0","53.5",[754,1312,1316],{"x":1313,"y":1314,"style":1315},"195.7","33.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[737,1318],{"x":1319,"y":746,"width":1320,"height":1306,"rx":339,"fill":750,"stroke":751,"style":886},"222.5","128.3",[754,1322,1324],{"x":1323,"y":1314,"style":1315},"286.6","B",[737,1326],{"x":1327,"y":746,"width":1328,"height":1306,"rx":339,"fill":750,"stroke":751,"style":886},"350.8","151.5",[754,1330,1332],{"x":1331,"y":1314,"style":1315},"426.5","C",[737,1334],{"x":1335,"y":746,"width":1336,"height":1306,"rx":339,"fill":750,"stroke":751,"style":886},"502.3","120.7",[754,1338,1340],{"x":1339,"y":1314,"style":1315},"562.6","D",[737,1342],{"x":1304,"y":1343,"width":1305,"height":1344,"rx":339,"fill":750,"stroke":751,"style":886},"40.0","28.0",[754,1346,678],{"x":1347,"y":748,"style":1315},"153.0",[737,1349],{"x":1309,"y":1343,"width":1310,"height":1344,"rx":339,"fill":778,"stroke":769,"style":1350},"stroke-width:1.5px",[754,1352,1354],{"x":1313,"y":748,"style":1353},"font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Rep",[737,1356],{"x":1319,"y":1343,"width":1320,"height":1344,"rx":339,"fill":778,"stroke":769,"style":1350},[754,1358,1359],{"x":1323,"y":748,"style":1353},"Lines over policy",[737,1361],{"x":1327,"y":1343,"width":1328,"height":1344,"rx":339,"fill":778,"stroke":769,"style":1350},[754,1363,1364],{"x":1331,"y":748,"style":1353},"Unapproved leakage",[737,1366],{"x":1335,"y":1343,"width":1336,"height":1344,"rx":339,"fill":778,"stroke":769,"style":1350},[754,1368,1369],{"x":1339,"y":748,"style":1353},"Note",[737,1371],{"x":1304,"y":1372,"width":1305,"height":1344,"rx":339,"fill":750,"stroke":751,"style":886},"68.0",[754,1374,399],{"x":1347,"y":1375,"style":1315},"87.0",[737,1377],{"x":1309,"y":1372,"width":1310,"height":1344,"rx":339,"fill":840,"stroke":841,"style":1350},[754,1379,1381],{"x":1313,"y":1375,"style":1380},"font-size:12px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","Dev",[737,1383],{"x":1319,"y":1372,"width":1320,"height":1344,"rx":339,"fill":840,"stroke":841,"style":1350},[754,1385,1386],{"x":1323,"y":1375,"style":1380},"22.3%",[737,1388],{"x":1327,"y":1372,"width":1328,"height":1344,"rx":339,"fill":840,"stroke":841,"style":1350},[754,1390,1391],{"x":1331,"y":1375,"style":1380},"28,470",[737,1393],{"x":1335,"y":1372,"width":1336,"height":1344,"rx":339,"fill":840,"stroke":841,"style":1350},[754,1395,1396],{"x":1339,"y":1375,"style":1380},"review deals",[737,1398],{"x":1304,"y":1399,"width":1305,"height":1344,"rx":339,"fill":750,"stroke":751,"style":886},"96.0",[754,1401,1065],{"x":1347,"y":1402,"style":1315},"115.0",[737,1404],{"x":1309,"y":1399,"width":1310,"height":1344,"rx":339,"fill":742,"stroke":751,"style":886},[754,1406,1407],{"x":1313,"y":1402,"style":890},"Ben",[737,1409],{"x":1319,"y":1399,"width":1320,"height":1344,"rx":339,"fill":742,"stroke":751,"style":886},[754,1411,1412],{"x":1323,"y":1402,"style":890},"12.6%",[737,1414],{"x":1327,"y":1399,"width":1328,"height":1344,"rx":339,"fill":798,"stroke":799,"style":1350},[754,1416,1418],{"x":1331,"y":1402,"style":1417},"font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","22,467",[737,1420],{"x":1335,"y":1399,"width":1336,"height":1344,"rx":339,"fill":742,"stroke":751,"style":886},[754,1422,1423],{"x":1339,"y":1402,"style":890},"coach on policy",[737,1425],{"x":1304,"y":1426,"width":1305,"height":1344,"rx":339,"fill":750,"stroke":751,"style":886},"124.0",[754,1428,866],{"x":1347,"y":1429,"style":1315},"143.0",[737,1431],{"x":1309,"y":1426,"width":1310,"height":1344,"rx":339,"fill":742,"stroke":751,"style":886},[754,1433,1434],{"x":1313,"y":1429,"style":890},"Caro",[737,1436],{"x":1319,"y":1426,"width":1320,"height":1344,"rx":339,"fill":742,"stroke":751,"style":886},[754,1438,1439],{"x":1323,"y":1429,"style":890},"2.8%",[737,1441],{"x":1327,"y":1426,"width":1328,"height":1344,"rx":339,"fill":742,"stroke":751,"style":886},[754,1443,1444],{"x":1331,"y":1429,"style":890},"5,057",[737,1446],{"x":1335,"y":1426,"width":1336,"height":1344,"rx":339,"fill":742,"stroke":751,"style":886},[737,1448],{"x":1304,"y":1449,"width":1305,"height":1344,"rx":339,"fill":750,"stroke":751,"style":886},"152.0",[754,1451,1122],{"x":1347,"y":1452,"style":1315},"171.0",[737,1454],{"x":1309,"y":1449,"width":1310,"height":1344,"rx":339,"fill":742,"stroke":751,"style":886},[754,1456,1457],{"x":1313,"y":1452,"style":890},"Ana",[737,1459],{"x":1319,"y":1449,"width":1320,"height":1344,"rx":339,"fill":742,"stroke":751,"style":886},[754,1461,1462],{"x":1323,"y":1452,"style":890},"1.9%",[737,1464],{"x":1327,"y":1449,"width":1328,"height":1344,"rx":339,"fill":742,"stroke":751,"style":886},[754,1466,1467],{"x":1331,"y":1452,"style":890},"2,917",[737,1469],{"x":1335,"y":1449,"width":1336,"height":1344,"rx":339,"fill":742,"stroke":751,"style":886},[25,1471,1473],{"id":1472},"step-4-the-report","Step 4 — The report",[30,1475,1477],{"className":67,"code":1476,"language":69,"meta":35,"style":35},"with pd.ExcelWriter(\"discount_leakage_2026-Q3.xlsx\") as xw:\n    summary.reset_index().to_excel(xw, sheet_name=\"Summary\", index=False)\n    by_rep.reset_index().to_excel(xw, sheet_name=\"By rep\", index=False)\n    by_customer.reset_index().to_excel(xw, sheet_name=\"Top customers\", index=False)\n    spread.reset_index().to_excel(xw, sheet_name=\"Discount spread\", index=False)\n    unapproved.sort_values(\"Leakage\", ascending=False)[\n        [\"Invoice\", \"Customer\", \"Rep\", \"Product\", \"Tier\", \"Group\", \"Qty\", \"List price\", \"Unit price\",\n         \"Discount\", \"Max discount\", \"Leakage\"]].to_excel(xw, sheet_name=\"Unapproved lines\", index=False)\nprint(\"written\")\n",[37,1478,1479,1497,1521,1543,1565,1587,1605,1647,1680],{"__ignoreMap":35},[40,1480,1481,1484,1487,1490,1492,1494],{"class":42,"line":43},[40,1482,1483],{"class":76},"with",[40,1485,1486],{"class":80}," pd.ExcelWriter(",[40,1488,1489],{"class":50},"\"discount_leakage_2026-Q3.xlsx\"",[40,1491,435],{"class":80},[40,1493,84],{"class":76},[40,1495,1496],{"class":80}," xw:\n",[40,1498,1499,1502,1505,1507,1510,1512,1515,1517,1519],{"class":42,"line":90},[40,1500,1501],{"class":80},"    summary.reset_index().to_excel(xw, ",[40,1503,1504],{"class":115},"sheet_name",[40,1506,103],{"class":76},[40,1508,1509],{"class":50},"\"Summary\"",[40,1511,112],{"class":80},[40,1513,1514],{"class":115},"index",[40,1516,103],{"class":76},[40,1518,122],{"class":121},[40,1520,125],{"class":80},[40,1522,1523,1526,1528,1530,1533,1535,1537,1539,1541],{"class":42,"line":97},[40,1524,1525],{"class":80},"    by_rep.reset_index().to_excel(xw, ",[40,1527,1504],{"class":115},[40,1529,103],{"class":76},[40,1531,1532],{"class":50},"\"By rep\"",[40,1534,112],{"class":80},[40,1536,1514],{"class":115},[40,1538,103],{"class":76},[40,1540,122],{"class":121},[40,1542,125],{"class":80},[40,1544,1545,1548,1550,1552,1555,1557,1559,1561,1563],{"class":42,"line":128},[40,1546,1547],{"class":80},"    by_customer.reset_index().to_excel(xw, ",[40,1549,1504],{"class":115},[40,1551,103],{"class":76},[40,1553,1554],{"class":50},"\"Top customers\"",[40,1556,112],{"class":80},[40,1558,1514],{"class":115},[40,1560,103],{"class":76},[40,1562,122],{"class":121},[40,1564,125],{"class":80},[40,1566,1567,1570,1572,1574,1577,1579,1581,1583,1585],{"class":42,"line":143},[40,1568,1569],{"class":80},"    spread.reset_index().to_excel(xw, ",[40,1571,1504],{"class":115},[40,1573,103],{"class":76},[40,1575,1576],{"class":50},"\"Discount spread\"",[40,1578,112],{"class":80},[40,1580,1514],{"class":115},[40,1582,103],{"class":76},[40,1584,122],{"class":121},[40,1586,125],{"class":80},[40,1588,1589,1592,1594,1596,1598,1600,1602],{"class":42,"line":158},[40,1590,1591],{"class":80},"    unapproved.sort_values(",[40,1593,377],{"class":50},[40,1595,112],{"class":80},[40,1597,1055],{"class":115},[40,1599,103],{"class":76},[40,1601,122],{"class":121},[40,1603,1604],{"class":80},")[\n",[40,1606,1607,1610,1612,1614,1616,1618,1620,1622,1624,1626,1628,1630,1632,1634,1636,1638,1640,1642,1644],{"class":42,"line":173},[40,1608,1609],{"class":80},"        [",[40,1611,534],{"class":50},[40,1613,112],{"class":80},[40,1615,194],{"class":50},[40,1617,112],{"class":80},[40,1619,969],{"class":50},[40,1621,112],{"class":80},[40,1623,219],{"class":50},[40,1625,112],{"class":80},[40,1627,245],{"class":50},[40,1629,112],{"class":80},[40,1631,250],{"class":50},[40,1633,112],{"class":80},[40,1635,367],{"class":50},[40,1637,112],{"class":80},[40,1639,298],{"class":50},[40,1641,112],{"class":80},[40,1643,288],{"class":50},[40,1645,1646],{"class":80},",\n",[40,1648,1649,1652,1654,1656,1658,1660,1663,1665,1667,1670,1672,1674,1676,1678],{"class":42,"line":178},[40,1650,1651],{"class":50},"         \"Discount\"",[40,1653,112],{"class":80},[40,1655,328],{"class":50},[40,1657,112],{"class":80},[40,1659,377],{"class":50},[40,1661,1662],{"class":80},"]].to_excel(xw, ",[40,1664,1504],{"class":115},[40,1666,103],{"class":76},[40,1668,1669],{"class":50},"\"Unapproved lines\"",[40,1671,112],{"class":80},[40,1673,1514],{"class":115},[40,1675,103],{"class":76},[40,1677,122],{"class":121},[40,1679,125],{"class":80},[40,1681,1682,1684,1686,1689],{"class":42,"line":209},[40,1683,583],{"class":121},[40,1685,531],{"class":80},[40,1687,1688],{"class":50},"\"written\"",[40,1690,125],{"class":80},[10,1692,1693],{},"The workbook opens with the summary — lines, revenue and leakage within policy, as approved exceptions and unapproved — followed by the rep and customer views, the discount spread and the list of unapproved lines with the discount given, the maximum allowed and the money involved, largest first. That list is what sales management works through. Presented this way the report is about recovering margin, not about blame, and the most effective follow-up is usually a change to the process — requiring approval in the quoting tool before a price below the limit can be entered — rather than a hunt through past invoices.",[25,1695,1697],{"id":1696},"looking-at-the-approved-exceptions-too","Looking at the approved exceptions too",[10,1699,1700],{},"Approved exceptions are not leakage, but they deserve their own review once a quarter. Each one was granted for a reason — to win a competitive deal, keep a large customer or clear old stock — and the report can check whether the reason held up: did the customer's volume grow after the extra discount, or did the discount simply become their new normal price? Exceptions that keep being renewed for the same customer are a sign that the customer belongs in a higher tier, or that the policy for that product group is out of date. Either way, the policy table is an input to this report, not a fixed truth, and the leakage numbers are a good prompt to revisit it with the sales and finance leads together. Changing a maximum in the table and rerunning the report shows immediately how much of today's leakage the new policy would have legitimised.",[25,1702,1704],{"id":1703},"common-pitfalls","Common pitfalls",[1706,1707,1708,1716,1722,1728,1734],"ul",{},[1709,1710,1711,1715],"li",{},[1712,1713,1714],"strong",{},"Trusting the discount field."," Derive the discount from the price charged and the list price.",[1709,1717,1718,1721],{},[1712,1719,1720],{},"Silent join failures."," Validate every join; a missing product or tier changes the result.",[1709,1723,1724,1727],{},[1712,1725,1726],{},"Counting approved exceptions as leakage."," Separate them; they are decisions, not leaks.",[1709,1729,1730,1733],{},[1712,1731,1732],{},"Ranking reps by currency alone."," Show the share of lines over policy as well.",[1709,1735,1736,1739],{},[1712,1737,1738],{},"Ignoring dispersion within policy."," A wide range for the same product and tier is a pricing problem too.",[25,1741,1743],{"id":1742},"conclusion","Conclusion",[10,1745,1746],{},"Discount leakage is the revenue lost to discounts beyond policy without approval. Join each invoice line to its customer tier, product group, list price and policy maximum, derive the discount from the price charged, value the excess against the list price, and split it into approved exceptions and unapproved leakage. Break the leakage down by rep and customer, measure the spread of discounts for each product and tier, and give sales management the list of unapproved lines.",[25,1748,1750],{"id":1749},"frequently-asked-questions","Frequently asked questions",[10,1752,1753,1756],{},[1712,1754,1755],{},"How do I calculate discount leakage?","\nFor each invoice line, take the discount given minus the policy maximum, ignore negative values, and multiply by the list value; sum the unapproved lines.",[10,1758,1759,1762],{},[1712,1760,1761],{},"Why calculate the discount from prices instead of using the discount field?","\nSalespeople often type a lower price instead of entering a discount, so the field understates the real discount.",[10,1764,1765,1768,1769,1772],{},[1712,1766,1767],{},"How do I check discounts against a policy matrix in pandas?","\nMerge the invoice lines with the policy table on tier and product group, with ",[37,1770,1771],{},"validate=\"many_to_one\"",", and compare the discount with the maximum.",[10,1774,1775,1778],{},[1712,1776,1777],{},"How do I measure pricing consistency?","\nGroup by product and customer tier and compare the lowest, median and highest discount given.",[25,1780,1782],{"id":1781},"related","Related",[1706,1784,1785,1791,1798,1805],{},[1709,1786,1787,1788,1790],{},"Up one level: ",[19,1789,22],{"href":21}," — finance and operations reports.",[1709,1792,1793,1797],{},[19,1794,1796],{"href":1795},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-price-volume-mix-analysis\u002F","Build a Price-Volume-Mix Analysis"," — price effects on revenue.",[1709,1799,1800,1804],{},[19,1801,1803],{"href":1802},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-customer-profitability-report\u002F","Build a Customer Profitability Report"," — what discounts do to margin by customer.",[1709,1806,1807,1811],{},[19,1808,1810],{"href":1809},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fmodel-the-impact-of-a-price-increase\u002F","Model the Impact of a Price Increase"," — the other side of pricing.",[1813,1814,1815],"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":35,"searchDepth":90,"depth":90,"links":1817},[1818,1819,1820,1821,1822,1823,1824,1825,1826,1827],{"id":27,"depth":90,"text":28},{"id":63,"depth":90,"text":64},{"id":930,"depth":90,"text":931},{"id":1141,"depth":90,"text":1142},{"id":1472,"depth":90,"text":1473},{"id":1696,"depth":90,"text":1697},{"id":1703,"depth":90,"text":1704},{"id":1742,"depth":90,"text":1743},{"id":1749,"depth":90,"text":1750},{"id":1781,"depth":90,"text":1782},"2026-10-02","Find out how much margin is lost to discounts beyond policy with pandas: compare every invoice line's discount with the approved maximum for the customer tier and product group, value the excess, separate approved exceptions from unapproved ones, show who and which customers account for it, measure how widely discounts vary for the same product and tier, and write the findings to Excel.","md",[1832,1834,1836,1838],{"q":1755,"a":1833},"For each invoice line, take the discount given minus the policy maximum, ignore negative values, and multiply by the list value; sum the unapproved lines.",{"q":1761,"a":1835},"Salespeople often type a lower price instead of entering a discount, so the field understates the real discount.",{"q":1767,"a":1837},"Merge the invoice lines with the policy table on tier and product group, with validate=\"many_to_one\", and compare the discount with the maximum.",{"q":1777,"a":1839},"Group by product and customer tier and compare the lowest, median and highest discount given.","discount leakage report python",null,{"breadcrumb":1843},[1844,1846,1849],{"name":1845,"item":293},"Home",{"name":1847,"item":1848},"Automating Reporting Workflows with Python and Excel","\u002Fautomating-reporting-workflows\u002F",{"name":1850,"item":21},"Building Finance and Operations Reports in Excel with Python","\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-discount-leakage-report",{"title":1853,"description":1854},"Build a Discount Leakage Report in Excel with Python","Discount leakage with pandas: discounts against an approved matrix by tier and product group, value of excess discount, approved exceptions, leakage by rep and customer, discount spread and an Excel report.","build-a-discount-leakage-report","automating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-discount-leakage-report\u002Findex",[1858,1859],"report-layouts","pivots-and-summaries","how-to","sr_O11NDvkk4kkXxBqAX4y6CA4IoIf4YtV2lIPxXh-o",[1863,1867],{"title":1864,"path":1865,"stem":1866,"children":-1},"Build a Deferred Revenue Schedule","\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-deferred-revenue-schedule","automating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-deferred-revenue-schedule\u002Findex",{"title":1868,"path":1869,"stem":1870,"children":-1},"Build a Fixed Asset Depreciation Schedule with Python","\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-fixed-asset-depreciation-schedule","automating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-fixed-asset-depreciation-schedule\u002Findex",[1872,1874],{"path":1873,"title":1850},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports",{"path":1875,"title":1847},"\u002Fautomating-reporting-workflows",[],[1878,1882,1885,1888,1891,1894],{"path":1879,"title":1880,"kind":1881},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards","Building Multi-Sheet Excel Dashboards","guide",{"path":1883,"title":1884,"kind":1860},"\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":1886,"title":1887,"kind":1860},"\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":1889,"title":1890,"kind":1860},"\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":1892,"title":1893,"kind":1860},"\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":1895,"title":1896,"kind":1860},"\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",1790972121439]