[{"data":1,"prerenderedAt":2226},["ShallowReactive",2],{"doc:\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fmodel-the-impact-of-a-price-increase":3,"surround:\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fmodel-the-impact-of-a-price-increase":2191,"ancestors:\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fmodel-the-impact-of-a-price-increase":2200,"children:\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fmodel-the-impact-of-a-price-increase":2205,"related:\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fmodel-the-impact-of-a-price-increase":2206},{"id":4,"title":5,"body":6,"dateModified":2157,"datePublished":2157,"description":2158,"extension":2159,"faq":2160,"keyword":2169,"kind":2170,"meta":2171,"navigation":93,"path":2180,"seo":2181,"slug":2184,"stem":2185,"topics":2186,"type":2189,"__hash__":2190},"docs\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fmodel-the-impact-of-a-price-increase\u002Findex.md","Model the Impact of a Price Increase",{"type":7,"value":8,"toc":2146},"minimark",[9,24,29,58,61,65,383,386,390,561,579,765,769,1402,1413,1586,1724,1728,2017,2024,2028,2063,2067,2073,2077,2091,2097,2103,2109,2113,2142],[10,11,12,13,17,18,23],"p",{},"When costs rise, a business has to decide whether to raise its prices, by how much, and on what. The worry is always the same: put prices up and some customers buy less or go elsewhere. The question that settles most of these discussions is surprisingly specific — ",[14,15,16],"em",{},"how much volume could we lose and still make the same gross profit?"," — and the answer is often larger than people expect, because a price increase goes straight to margin while the lost units take their cost with them. This page builds a price increase model with pandas from a year of sales by product and customer: it applies candidate increases per product group, estimates lost volume from simple elasticity assumptions, leaves alone customers whose contracts fix their prices for part of the year, calculates the break-even volume loss for each product, and compares scenarios side by side in a workbook whose assumptions can be changed. 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],{},"Three inputs: products with their group, current price and unit cost; customers, three of whom have contracts that fix their prices until 30 June 2027; and last year's volumes per customer and product. The new prices would take effect on 1 January 2027, and the model covers the year 2027.",[25,62,64],{"id":63},"step-1-the-baseline","Step 1 — The baseline",[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\nproducts = pd.read_excel(\"products.xlsx\").set_index(\"Product\")\ncustomers = pd.read_excel(\"customers.xlsx\", parse_dates=[\"Contract until\"]).set_index(\"Customer\")\nvol = pd.read_excel(\"annual_volumes.xlsx\").join(products, on=\"Product\").join(customers, on=\"Customer\")\n\nvol[\"Revenue\"] = vol[\"Annual units\"] * vol[\"Price\"]\nvol[\"Gross profit\"] = vol[\"Annual units\"] * (vol[\"Price\"] - vol[\"Unit cost\"])\nbase = vol.groupby(\"Group\")[[\"Revenue\", \"Gross profit\"]].sum()\nbase[\"Margin %\"] = (base[\"Gross profit\"] \u002F base[\"Revenue\"] * 100).round(1)\nprint(base.round(0).to_string())\nprint(\"total:\", round(vol[\"Revenue\"].sum()), round(vol[\"Gross profit\"].sum()))\n","python",[37,71,72,88,95,119,155,189,194,227,265,291,335,350],{"__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,116],{"class":42,"line":97},3,[40,99,100],{"class":80},"products ",[40,102,103],{"class":76},"=",[40,105,106],{"class":80}," pd.read_excel(",[40,108,109],{"class":50},"\"products.xlsx\"",[40,111,112],{"class":80},").set_index(",[40,114,115],{"class":50},"\"Product\"",[40,117,118],{"class":80},")\n",[40,120,122,125,127,129,132,135,139,141,144,147,150,153],{"class":42,"line":121},4,[40,123,124],{"class":80},"customers ",[40,126,103],{"class":76},[40,128,106],{"class":80},[40,130,131],{"class":50},"\"customers.xlsx\"",[40,133,134],{"class":80},", ",[40,136,138],{"class":137},"sa561","parse_dates",[40,140,103],{"class":76},[40,142,143],{"class":80},"[",[40,145,146],{"class":50},"\"Contract until\"",[40,148,149],{"class":80},"]).set_index(",[40,151,152],{"class":50},"\"Customer\"",[40,154,118],{"class":80},[40,156,158,161,163,165,168,171,174,176,178,181,183,185,187],{"class":42,"line":157},5,[40,159,160],{"class":80},"vol ",[40,162,103],{"class":76},[40,164,106],{"class":80},[40,166,167],{"class":50},"\"annual_volumes.xlsx\"",[40,169,170],{"class":80},").join(products, ",[40,172,173],{"class":137},"on",[40,175,103],{"class":76},[40,177,115],{"class":50},[40,179,180],{"class":80},").join(customers, ",[40,182,173],{"class":137},[40,184,103],{"class":76},[40,186,152],{"class":50},[40,188,118],{"class":80},[40,190,192],{"class":42,"line":191},6,[40,193,94],{"emptyLinePlaceholder":93},[40,195,197,200,203,206,208,211,214,216,219,221,224],{"class":42,"line":196},7,[40,198,199],{"class":80},"vol[",[40,201,202],{"class":50},"\"Revenue\"",[40,204,205],{"class":80},"] ",[40,207,103],{"class":76},[40,209,210],{"class":80}," vol[",[40,212,213],{"class":50},"\"Annual units\"",[40,215,205],{"class":80},[40,217,218],{"class":76},"*",[40,220,210],{"class":80},[40,222,223],{"class":50},"\"Price\"",[40,225,226],{"class":80},"]\n",[40,228,230,232,235,237,239,241,243,245,247,250,252,254,257,259,262],{"class":42,"line":229},8,[40,231,199],{"class":80},[40,233,234],{"class":50},"\"Gross profit\"",[40,236,205],{"class":80},[40,238,103],{"class":76},[40,240,210],{"class":80},[40,242,213],{"class":50},[40,244,205],{"class":80},[40,246,218],{"class":76},[40,248,249],{"class":80}," (vol[",[40,251,223],{"class":50},[40,253,205],{"class":80},[40,255,256],{"class":76},"-",[40,258,210],{"class":80},[40,260,261],{"class":50},"\"Unit cost\"",[40,263,264],{"class":80},"])\n",[40,266,268,271,273,276,279,282,284,286,288],{"class":42,"line":267},9,[40,269,270],{"class":80},"base ",[40,272,103],{"class":76},[40,274,275],{"class":80}," vol.groupby(",[40,277,278],{"class":50},"\"Group\"",[40,280,281],{"class":80},")[[",[40,283,202],{"class":50},[40,285,134],{"class":80},[40,287,234],{"class":50},[40,289,290],{"class":80},"]].sum()\n",[40,292,294,297,300,302,304,307,309,311,314,317,319,321,323,327,330,333],{"class":42,"line":293},10,[40,295,296],{"class":80},"base[",[40,298,299],{"class":50},"\"Margin %\"",[40,301,205],{"class":80},[40,303,103],{"class":76},[40,305,306],{"class":80}," (base[",[40,308,234],{"class":50},[40,310,205],{"class":80},[40,312,313],{"class":76},"\u002F",[40,315,316],{"class":80}," base[",[40,318,202],{"class":50},[40,320,205],{"class":80},[40,322,218],{"class":76},[40,324,326],{"class":325},"sP0c6"," 100",[40,328,329],{"class":80},").round(",[40,331,332],{"class":325},"1",[40,334,118],{"class":80},[40,336,338,341,344,347],{"class":42,"line":337},11,[40,339,340],{"class":325},"print",[40,342,343],{"class":80},"(base.round(",[40,345,346],{"class":325},"0",[40,348,349],{"class":80},").to_string())\n",[40,351,353,355,358,361,363,366,369,371,374,376,378,380],{"class":42,"line":352},12,[40,354,340],{"class":325},[40,356,357],{"class":80},"(",[40,359,360],{"class":50},"\"total:\"",[40,362,134],{"class":80},[40,364,365],{"class":325},"round",[40,367,368],{"class":80},"(vol[",[40,370,202],{"class":50},[40,372,373],{"class":80},"].sum()), ",[40,375,365],{"class":325},[40,377,368],{"class":80},[40,379,234],{"class":50},[40,381,382],{"class":80},"].sum()))\n",[10,384,385],{},"The baseline assumes next year's volumes repeat last year's at today's prices: revenue and gross profit — revenue less the cost of the goods sold — by product group. Gross margins differ a lot between the groups, from around 30% on power tools to over half on protective equipment, and that difference matters for everything that follows. Across the three groups the business turns over £1.07 million with £445,000 of gross profit, a blended margin of about 41%. The lower a product's margin, the less volume it can afford to lose before a price increase stops paying for itself.",[25,387,389],{"id":388},"step-2-the-break-even-volume-loss","Step 2 — The break-even volume loss",[30,391,393],{"className":67,"code":392,"language":69,"meta":35,"style":35},"products[\"Margin\"] = (products[\"Price\"] - products[\"Unit cost\"]) \u002F products[\"Price\"]\nfor pct in (0.03, 0.05, 0.08):\n    products[f\"Break-even loss at +{pct:.0%}\"] = (pct \u002F (products[\"Margin\"] + pct)).round(3)\nprint(products[[\"Group\", \"Margin\"] + [c for c in products.columns if c.startswith(\"Break-even\")]].round(3).to_string())\n",[37,394,395,432,462,515],{"__ignoreMap":35},[40,396,397,400,403,405,407,410,412,414,416,419,421,424,426,428,430],{"class":42,"line":43},[40,398,399],{"class":80},"products[",[40,401,402],{"class":50},"\"Margin\"",[40,404,205],{"class":80},[40,406,103],{"class":76},[40,408,409],{"class":80}," (products[",[40,411,223],{"class":50},[40,413,205],{"class":80},[40,415,256],{"class":76},[40,417,418],{"class":80}," products[",[40,420,261],{"class":50},[40,422,423],{"class":80},"]) ",[40,425,313],{"class":76},[40,427,418],{"class":80},[40,429,223],{"class":50},[40,431,226],{"class":80},[40,433,434,437,440,443,446,449,451,454,456,459],{"class":42,"line":90},[40,435,436],{"class":76},"for",[40,438,439],{"class":80}," pct ",[40,441,442],{"class":76},"in",[40,444,445],{"class":80}," (",[40,447,448],{"class":325},"0.03",[40,450,134],{"class":80},[40,452,453],{"class":325},"0.05",[40,455,134],{"class":80},[40,457,458],{"class":325},"0.08",[40,460,461],{"class":80},"):\n",[40,463,464,467,470,473,477,480,483,486,489,491,493,496,498,500,502,504,507,510,513],{"class":42,"line":97},[40,465,466],{"class":80},"    products[",[40,468,469],{"class":76},"f",[40,471,472],{"class":50},"\"Break-even loss at +",[40,474,476],{"class":475},"sSjpA","{",[40,478,479],{"class":80},"pct",[40,481,482],{"class":76},":.0%",[40,484,485],{"class":475},"}",[40,487,488],{"class":50},"\"",[40,490,205],{"class":80},[40,492,103],{"class":76},[40,494,495],{"class":80}," (pct ",[40,497,313],{"class":76},[40,499,409],{"class":80},[40,501,402],{"class":50},[40,503,205],{"class":80},[40,505,506],{"class":76},"+",[40,508,509],{"class":80}," pct)).round(",[40,511,512],{"class":325},"3",[40,514,118],{"class":80},[40,516,517,519,522,524,526,528,530,532,535,537,540,542,545,548,551,554,557,559],{"class":42,"line":121},[40,518,340],{"class":325},[40,520,521],{"class":80},"(products[[",[40,523,278],{"class":50},[40,525,134],{"class":80},[40,527,402],{"class":50},[40,529,205],{"class":80},[40,531,506],{"class":76},[40,533,534],{"class":80}," [c ",[40,536,436],{"class":76},[40,538,539],{"class":80}," c ",[40,541,442],{"class":76},[40,543,544],{"class":80}," products.columns ",[40,546,547],{"class":76},"if",[40,549,550],{"class":80}," c.startswith(",[40,552,553],{"class":50},"\"Break-even\"",[40,555,556],{"class":80},")]].round(",[40,558,512],{"class":325},[40,560,349],{"class":80},[10,562,563,564,566,567,570,571,574,575,578],{},"For a price increase of ",[14,565,10],{}," (as a fraction of the old price) on a product with gross margin ",[14,568,569],{},"m",", gross profit is unchanged if volume falls by ",[14,572,573],{},"p ÷ (m + p)",". The formula comes from setting old gross profit equal to new: the new margin per unit is ",[14,576,577],{},"m + p"," in units of the old price, so volume can shrink by that ratio and still earn the same total. On a product with a 30% margin, a 5% increase breaks even at a volume loss of 14%; on one with a 55% margin, the same increase breaks even at 8% loss. Those are thresholds, not forecasts — they say how many customers would have to walk away before the increase was a mistake — and they are often the most useful numbers in a pricing discussion, because they turn a vague fear into a specific question about customers: would more than one in seven of our drill buyers really walk away over five per cent?",[580,581,589,590,589,594,589,598,589,605,589,614,589,618,589,625,589,629,589,633,589,637,589,641,589,645,589,649,589,653,589,657,589,662,589,666,589,668,589,671,589,673,589,676,589,678,589,681,589,684,589,688,589,690,589,694,589,696,589,699,589,703,589,707,589,709,589,712,589,715,589,718,589,720,589,723,589,725,589,728,589,730,589,733,589,735,589,738,589,741,589,745,589,747,589,750,589,752,589,755,589,757,589,760,589,762],"svg",{"viewBox":582,"role":583,"ariaLabelledBy":584,"xmlns":587,"style":588},"223 0 314 168","img",[585,586],"pi-breakeven-t","pi-breakeven-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:314px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[591,592,593],"title",{"id":585},"Volume that can be lost before gross profit falls",[595,596,597],"desc",{"id":586},"The break-even volume loss is the price increase divided by the margin plus the increase. A low-margin power tool can lose about 14 percent of volume on a 5 percent increase, while high-margin protective equipment breaks even at about 8 to 9 percent.",[599,600],"rect",{"x":601,"y":346,"width":602,"height":603,"fill":604},"223","314","168","#ffffff",[599,606],{"x":607,"y":608,"width":609,"height":610,"rx":346,"fill":611,"stroke":612,"style":613},"239.3","16.0","32.0","24.0","#e7ebef","var(--line,#cdd5e6)","stroke-width:1px",[599,615],{"x":616,"y":608,"width":617,"height":610,"rx":346,"fill":611,"stroke":612,"style":613},"271.3","67.4",[619,620,624],"text",{"x":621,"y":622,"style":623},"305.1","33.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[599,626],{"x":627,"y":608,"width":628,"height":610,"rx":346,"fill":611,"stroke":612,"style":613},"338.8","56.8",[619,630,632],{"x":631,"y":622,"style":623},"367.2","B",[599,634],{"x":635,"y":608,"width":636,"height":610,"rx":346,"fill":611,"stroke":612,"style":613},"395.6","63.1",[619,638,640],{"x":639,"y":622,"style":623},"427.2","C",[599,642],{"x":643,"y":608,"width":644,"height":610,"rx":346,"fill":611,"stroke":612,"style":613},"458.7","62.0",[619,646,648],{"x":647,"y":622,"style":623},"489.7","D",[599,650],{"x":607,"y":651,"width":609,"height":652,"rx":346,"fill":611,"stroke":612,"style":613},"40.0","28.0",[619,654,332],{"x":655,"y":656,"style":623},"255.3","59.0",[599,658],{"x":616,"y":651,"width":617,"height":652,"rx":346,"fill":659,"stroke":660,"style":661},"#f0f4ff","var(--brand,#5b5cf0)","stroke-width:1.5px",[619,663,665],{"x":621,"y":656,"style":664},"font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Margin",[599,667],{"x":627,"y":651,"width":628,"height":652,"rx":346,"fill":659,"stroke":660,"style":661},[619,669,670],{"x":631,"y":656,"style":664},"+3%",[599,672],{"x":635,"y":651,"width":636,"height":652,"rx":346,"fill":659,"stroke":660,"style":661},[619,674,675],{"x":639,"y":656,"style":664},"+5%",[599,677],{"x":643,"y":651,"width":644,"height":652,"rx":346,"fill":659,"stroke":660,"style":661},[619,679,680],{"x":647,"y":656,"style":664},"+8%",[599,682],{"x":607,"y":683,"width":609,"height":652,"rx":346,"fill":611,"stroke":612,"style":613},"68.0",[619,685,687],{"x":655,"y":686,"style":623},"87.0","2",[599,689],{"x":616,"y":683,"width":617,"height":652,"rx":346,"fill":604,"stroke":612,"style":613},[619,691,693],{"x":621,"y":686,"style":692},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","30%",[599,695],{"x":627,"y":683,"width":628,"height":652,"rx":346,"fill":604,"stroke":612,"style":613},[619,697,698],{"x":631,"y":686,"style":692},"9.1%",[599,700],{"x":635,"y":683,"width":636,"height":652,"rx":346,"fill":701,"stroke":702,"style":661},"#d9f4f1","var(--teal,#0f9488)",[619,704,706],{"x":639,"y":686,"style":705},"font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","14.3%",[599,708],{"x":643,"y":683,"width":644,"height":652,"rx":346,"fill":604,"stroke":612,"style":613},[619,710,711],{"x":647,"y":686,"style":692},"21.1%",[599,713],{"x":607,"y":714,"width":609,"height":652,"rx":346,"fill":611,"stroke":612,"style":613},"96.0",[619,716,512],{"x":655,"y":717,"style":623},"115.0",[599,719],{"x":616,"y":714,"width":617,"height":652,"rx":346,"fill":604,"stroke":612,"style":613},[619,721,722],{"x":621,"y":717,"style":692},"40%",[599,724],{"x":627,"y":714,"width":628,"height":652,"rx":346,"fill":604,"stroke":612,"style":613},[619,726,727],{"x":631,"y":717,"style":692},"7.0%",[599,729],{"x":635,"y":714,"width":636,"height":652,"rx":346,"fill":604,"stroke":612,"style":613},[619,731,732],{"x":639,"y":717,"style":692},"11.1%",[599,734],{"x":643,"y":714,"width":644,"height":652,"rx":346,"fill":604,"stroke":612,"style":613},[619,736,737],{"x":647,"y":717,"style":692},"16.7%",[599,739],{"x":607,"y":740,"width":609,"height":652,"rx":346,"fill":611,"stroke":612,"style":613},"124.0",[619,742,744],{"x":655,"y":743,"style":623},"143.0","4",[599,746],{"x":616,"y":740,"width":617,"height":652,"rx":346,"fill":604,"stroke":612,"style":613},[619,748,749],{"x":621,"y":743,"style":692},"55%",[599,751],{"x":627,"y":740,"width":628,"height":652,"rx":346,"fill":604,"stroke":612,"style":613},[619,753,754],{"x":631,"y":743,"style":692},"5.2%",[599,756],{"x":635,"y":740,"width":636,"height":652,"rx":346,"fill":604,"stroke":612,"style":613},[619,758,759],{"x":639,"y":743,"style":692},"8.3%",[599,761],{"x":643,"y":740,"width":644,"height":652,"rx":346,"fill":604,"stroke":612,"style":613},[619,763,764],{"x":647,"y":743,"style":692},"12.7%",[25,766,768],{"id":767},"step-3-scenarios-with-elasticity-and-contract-locks","Step 3 — Scenarios with elasticity and contract locks",[30,770,772],{"className":67,"code":771,"language":69,"meta":35,"style":35},"ELASTICITY = {\"Fixings\": -0.8, \"Power tools\": -1.6, \"PPE\": -0.5}    # % volume change per 1% price change\nSTART = pd.Timestamp(\"2027-01-01\")\nYEAR_END = pd.Timestamp(\"2027-12-31\")\n\ndef scenario(increases):\n    v = vol.copy()\n    v[\"Increase\"] = v[\"Group\"].map(increases).fillna(0)\n    locked_days = (v[\"Contract until\"].clip(upper=YEAR_END) - START).dt.days.clip(lower=0).fillna(0) + v[\"Contract until\"].notna()\n    v[\"Share at new price\"] = 1 - locked_days \u002F 365\n    v[\"Volume change\"] = v[\"Increase\"] * v[\"Group\"].map(ELASTICITY) * v[\"Share at new price\"]\n    v[\"New units\"] = v[\"Annual units\"] * (1 + v[\"Volume change\"])\n    v[\"New price\"] = v[\"Price\"] * (1 + v[\"Increase\"] * v[\"Share at new price\"])       # average price over the year\n    v[\"Revenue\"] = v[\"New units\"] * v[\"New price\"]\n    v[\"Gross profit\"] = v[\"New units\"] * (v[\"New price\"] - v[\"Unit cost\"])\n    return v.groupby(\"Group\")[[\"Revenue\", \"Gross profit\"]].sum()\n\nSCENARIOS = {\"Flat 5%\": {\"Fixings\": 0.05, \"Power tools\": 0.05, \"PPE\": 0.05},\n             \"Targeted\": {\"Fixings\": 0.06, \"Power tools\": 0.02, \"PPE\": 0.08},\n             \"Flat 8%\": {\"Fixings\": 0.08, \"Power tools\": 0.08, \"PPE\": 0.08}}\nresults = {\"Baseline\": base[[\"Revenue\", \"Gross profit\"]]} | {name: scenario(inc) for name, inc in SCENARIOS.items()}\nsummary = pd.DataFrame({name: r.sum() for name, r in results.items()}).T.round(0)\nsummary[\"GP change\"] = summary[\"Gross profit\"] - summary.loc[\"Baseline\", \"Gross profit\"]\nprint(summary.to_string())\n",[37,773,774,827,842,856,860,872,882,906,962,987,1025,1057,1100,1125,1158,1179,1184,1225,1259,1292,1336,1361,1394],{"__ignoreMap":35},[40,775,776,779,782,785,788,791,793,796,798,801,803,805,808,810,813,815,817,820,823],{"class":42,"line":43},[40,777,778],{"class":325},"ELASTICITY",[40,780,781],{"class":76}," =",[40,783,784],{"class":80}," {",[40,786,787],{"class":50},"\"Fixings\"",[40,789,790],{"class":80},": ",[40,792,256],{"class":76},[40,794,795],{"class":325},"0.8",[40,797,134],{"class":80},[40,799,800],{"class":50},"\"Power tools\"",[40,802,790],{"class":80},[40,804,256],{"class":76},[40,806,807],{"class":325},"1.6",[40,809,134],{"class":80},[40,811,812],{"class":50},"\"PPE\"",[40,814,790],{"class":80},[40,816,256],{"class":76},[40,818,819],{"class":325},"0.5",[40,821,822],{"class":80},"}    ",[40,824,826],{"class":825},"s-wDw","# % volume change per 1% price change\n",[40,828,829,832,834,837,840],{"class":42,"line":90},[40,830,831],{"class":325},"START",[40,833,781],{"class":76},[40,835,836],{"class":80}," pd.Timestamp(",[40,838,839],{"class":50},"\"2027-01-01\"",[40,841,118],{"class":80},[40,843,844,847,849,851,854],{"class":42,"line":97},[40,845,846],{"class":325},"YEAR_END",[40,848,781],{"class":76},[40,850,836],{"class":80},[40,852,853],{"class":50},"\"2027-12-31\"",[40,855,118],{"class":80},[40,857,858],{"class":42,"line":121},[40,859,94],{"emptyLinePlaceholder":93},[40,861,862,865,869],{"class":42,"line":157},[40,863,864],{"class":76},"def",[40,866,868],{"class":867},"s_Opv"," scenario",[40,870,871],{"class":80},"(increases):\n",[40,873,874,877,879],{"class":42,"line":191},[40,875,876],{"class":80},"    v ",[40,878,103],{"class":76},[40,880,881],{"class":80}," vol.copy()\n",[40,883,884,887,890,892,894,897,899,902,904],{"class":42,"line":196},[40,885,886],{"class":80},"    v[",[40,888,889],{"class":50},"\"Increase\"",[40,891,205],{"class":80},[40,893,103],{"class":76},[40,895,896],{"class":80}," v[",[40,898,278],{"class":50},[40,900,901],{"class":80},"].map(increases).fillna(",[40,903,346],{"class":325},[40,905,118],{"class":80},[40,907,908,911,913,916,918,921,924,926,928,931,933,936,939,942,944,946,949,951,953,955,957,959],{"class":42,"line":229},[40,909,910],{"class":80},"    locked_days ",[40,912,103],{"class":76},[40,914,915],{"class":80}," (v[",[40,917,146],{"class":50},[40,919,920],{"class":80},"].clip(",[40,922,923],{"class":137},"upper",[40,925,103],{"class":76},[40,927,846],{"class":325},[40,929,930],{"class":80},") ",[40,932,256],{"class":76},[40,934,935],{"class":325}," START",[40,937,938],{"class":80},").dt.days.clip(",[40,940,941],{"class":137},"lower",[40,943,103],{"class":76},[40,945,346],{"class":325},[40,947,948],{"class":80},").fillna(",[40,950,346],{"class":325},[40,952,930],{"class":80},[40,954,506],{"class":76},[40,956,896],{"class":80},[40,958,146],{"class":50},[40,960,961],{"class":80},"].notna()\n",[40,963,964,966,969,971,973,976,979,982,984],{"class":42,"line":267},[40,965,886],{"class":80},[40,967,968],{"class":50},"\"Share at new price\"",[40,970,205],{"class":80},[40,972,103],{"class":76},[40,974,975],{"class":325}," 1",[40,977,978],{"class":76}," -",[40,980,981],{"class":80}," locked_days ",[40,983,313],{"class":76},[40,985,986],{"class":325}," 365\n",[40,988,989,991,994,996,998,1000,1002,1004,1006,1008,1010,1013,1015,1017,1019,1021,1023],{"class":42,"line":293},[40,990,886],{"class":80},[40,992,993],{"class":50},"\"Volume change\"",[40,995,205],{"class":80},[40,997,103],{"class":76},[40,999,896],{"class":80},[40,1001,889],{"class":50},[40,1003,205],{"class":80},[40,1005,218],{"class":76},[40,1007,896],{"class":80},[40,1009,278],{"class":50},[40,1011,1012],{"class":80},"].map(",[40,1014,778],{"class":325},[40,1016,930],{"class":80},[40,1018,218],{"class":76},[40,1020,896],{"class":80},[40,1022,968],{"class":50},[40,1024,226],{"class":80},[40,1026,1027,1029,1032,1034,1036,1038,1040,1042,1044,1046,1048,1051,1053,1055],{"class":42,"line":337},[40,1028,886],{"class":80},[40,1030,1031],{"class":50},"\"New units\"",[40,1033,205],{"class":80},[40,1035,103],{"class":76},[40,1037,896],{"class":80},[40,1039,213],{"class":50},[40,1041,205],{"class":80},[40,1043,218],{"class":76},[40,1045,445],{"class":80},[40,1047,332],{"class":325},[40,1049,1050],{"class":76}," +",[40,1052,896],{"class":80},[40,1054,993],{"class":50},[40,1056,264],{"class":80},[40,1058,1059,1061,1064,1066,1068,1070,1072,1074,1076,1078,1080,1082,1084,1086,1088,1090,1092,1094,1097],{"class":42,"line":352},[40,1060,886],{"class":80},[40,1062,1063],{"class":50},"\"New price\"",[40,1065,205],{"class":80},[40,1067,103],{"class":76},[40,1069,896],{"class":80},[40,1071,223],{"class":50},[40,1073,205],{"class":80},[40,1075,218],{"class":76},[40,1077,445],{"class":80},[40,1079,332],{"class":325},[40,1081,1050],{"class":76},[40,1083,896],{"class":80},[40,1085,889],{"class":50},[40,1087,205],{"class":80},[40,1089,218],{"class":76},[40,1091,896],{"class":80},[40,1093,968],{"class":50},[40,1095,1096],{"class":80},"])       ",[40,1098,1099],{"class":825},"# average price over the year\n",[40,1101,1103,1105,1107,1109,1111,1113,1115,1117,1119,1121,1123],{"class":42,"line":1102},13,[40,1104,886],{"class":80},[40,1106,202],{"class":50},[40,1108,205],{"class":80},[40,1110,103],{"class":76},[40,1112,896],{"class":80},[40,1114,1031],{"class":50},[40,1116,205],{"class":80},[40,1118,218],{"class":76},[40,1120,896],{"class":80},[40,1122,1063],{"class":50},[40,1124,226],{"class":80},[40,1126,1128,1130,1132,1134,1136,1138,1140,1142,1144,1146,1148,1150,1152,1154,1156],{"class":42,"line":1127},14,[40,1129,886],{"class":80},[40,1131,234],{"class":50},[40,1133,205],{"class":80},[40,1135,103],{"class":76},[40,1137,896],{"class":80},[40,1139,1031],{"class":50},[40,1141,205],{"class":80},[40,1143,218],{"class":76},[40,1145,915],{"class":80},[40,1147,1063],{"class":50},[40,1149,205],{"class":80},[40,1151,256],{"class":76},[40,1153,896],{"class":80},[40,1155,261],{"class":50},[40,1157,264],{"class":80},[40,1159,1161,1164,1167,1169,1171,1173,1175,1177],{"class":42,"line":1160},15,[40,1162,1163],{"class":76},"    return",[40,1165,1166],{"class":80}," v.groupby(",[40,1168,278],{"class":50},[40,1170,281],{"class":80},[40,1172,202],{"class":50},[40,1174,134],{"class":80},[40,1176,234],{"class":50},[40,1178,290],{"class":80},[40,1180,1182],{"class":42,"line":1181},16,[40,1183,94],{"emptyLinePlaceholder":93},[40,1185,1187,1190,1192,1194,1197,1200,1202,1204,1206,1208,1210,1212,1214,1216,1218,1220,1222],{"class":42,"line":1186},17,[40,1188,1189],{"class":325},"SCENARIOS",[40,1191,781],{"class":76},[40,1193,784],{"class":80},[40,1195,1196],{"class":50},"\"Flat 5%\"",[40,1198,1199],{"class":80},": {",[40,1201,787],{"class":50},[40,1203,790],{"class":80},[40,1205,453],{"class":325},[40,1207,134],{"class":80},[40,1209,800],{"class":50},[40,1211,790],{"class":80},[40,1213,453],{"class":325},[40,1215,134],{"class":80},[40,1217,812],{"class":50},[40,1219,790],{"class":80},[40,1221,453],{"class":325},[40,1223,1224],{"class":80},"},\n",[40,1226,1228,1231,1233,1235,1237,1240,1242,1244,1246,1249,1251,1253,1255,1257],{"class":42,"line":1227},18,[40,1229,1230],{"class":50},"             \"Targeted\"",[40,1232,1199],{"class":80},[40,1234,787],{"class":50},[40,1236,790],{"class":80},[40,1238,1239],{"class":325},"0.06",[40,1241,134],{"class":80},[40,1243,800],{"class":50},[40,1245,790],{"class":80},[40,1247,1248],{"class":325},"0.02",[40,1250,134],{"class":80},[40,1252,812],{"class":50},[40,1254,790],{"class":80},[40,1256,458],{"class":325},[40,1258,1224],{"class":80},[40,1260,1262,1265,1267,1269,1271,1273,1275,1277,1279,1281,1283,1285,1287,1289],{"class":42,"line":1261},19,[40,1263,1264],{"class":50},"             \"Flat 8%\"",[40,1266,1199],{"class":80},[40,1268,787],{"class":50},[40,1270,790],{"class":80},[40,1272,458],{"class":325},[40,1274,134],{"class":80},[40,1276,800],{"class":50},[40,1278,790],{"class":80},[40,1280,458],{"class":325},[40,1282,134],{"class":80},[40,1284,812],{"class":50},[40,1286,790],{"class":80},[40,1288,458],{"class":325},[40,1290,1291],{"class":80},"}}\n",[40,1293,1295,1298,1300,1302,1305,1308,1310,1312,1314,1317,1320,1323,1325,1328,1330,1333],{"class":42,"line":1294},20,[40,1296,1297],{"class":80},"results ",[40,1299,103],{"class":76},[40,1301,784],{"class":80},[40,1303,1304],{"class":50},"\"Baseline\"",[40,1306,1307],{"class":80},": base[[",[40,1309,202],{"class":50},[40,1311,134],{"class":80},[40,1313,234],{"class":50},[40,1315,1316],{"class":80},"]]} ",[40,1318,1319],{"class":76},"|",[40,1321,1322],{"class":80}," {name: scenario(inc) ",[40,1324,436],{"class":76},[40,1326,1327],{"class":80}," name, inc ",[40,1329,442],{"class":76},[40,1331,1332],{"class":325}," SCENARIOS",[40,1334,1335],{"class":80},".items()}\n",[40,1337,1339,1342,1344,1347,1349,1352,1354,1357,1359],{"class":42,"line":1338},21,[40,1340,1341],{"class":80},"summary ",[40,1343,103],{"class":76},[40,1345,1346],{"class":80}," pd.DataFrame({name: r.sum() ",[40,1348,436],{"class":76},[40,1350,1351],{"class":80}," name, r ",[40,1353,442],{"class":76},[40,1355,1356],{"class":80}," results.items()}).T.round(",[40,1358,346],{"class":325},[40,1360,118],{"class":80},[40,1362,1364,1367,1370,1372,1374,1377,1379,1381,1383,1386,1388,1390,1392],{"class":42,"line":1363},22,[40,1365,1366],{"class":80},"summary[",[40,1368,1369],{"class":50},"\"GP change\"",[40,1371,205],{"class":80},[40,1373,103],{"class":76},[40,1375,1376],{"class":80}," summary[",[40,1378,234],{"class":50},[40,1380,205],{"class":80},[40,1382,256],{"class":76},[40,1384,1385],{"class":80}," summary.loc[",[40,1387,1304],{"class":50},[40,1389,134],{"class":80},[40,1391,234],{"class":50},[40,1393,226],{"class":80},[40,1395,1397,1399],{"class":42,"line":1396},23,[40,1398,340],{"class":325},[40,1400,1401],{"class":80},"(summary.to_string())\n",[10,1403,1404,1405,1408,1409,1412],{},"Each scenario is a set of increases by product group. Lost volume comes from an ",[14,1406,1407],{},"elasticity"," per group — the percentage change in volume for a one per cent change in price. The numbers here are assumptions to be agreed with the sales team: commodity fixings are fairly insensitive, power tools face online competition and are sensitive, safety equipment is a small, necessary purchase that customers rarely shop around for. A linear elasticity is a simplification, reasonable for increases of a few per cent and unreliable for large ones. Customers whose contracts fix prices until June pay the old price for half the year, so both their increase and their volume response are scaled by the share of the year at the new price. The scenarios can then be compared on gross profit, which is what the increase is for. The ",[14,1410,1411],{},"targeted"," scenario — higher increases where customers are less sensitive and lower ones where they are more — earns £30,635 more gross profit than the baseline, slightly more than a flat 5% at a similar average increase, and it is the only scenario in which revenue also grows. A flat 8% earns the most gross profit on paper, £42,911 more, but it does so by shrinking revenue and relies most heavily on the elasticity assumptions holding at a larger increase, which is exactly where a linear elasticity is least trustworthy.",[580,1414,589,1420,589,1423,589,1426,589,1431,589,1434,589,1438,589,1441,589,1445,589,1448,589,1452,589,1455,589,1459,589,1462,589,1464,589,1467,589,1469,589,1472,589,1474,589,1477,589,1479,589,1482,589,1484,589,1487,589,1489,589,1491,589,1493,589,1496,589,1498,589,1501,589,1503,589,1506,589,1508,589,1511,589,1513,589,1515,589,1517,589,1520,589,1522,589,1525,589,1527,589,1530,589,1532,589,1535,589,1537,589,1539,589,1541,589,1544,589,1546,589,1549,589,1551,589,1554,589,1556,589,1559,589,1562,589,1566,589,1568,589,1571,589,1573,589,1576,589,1578,589,1581,589,1583],{"viewBox":1415,"role":583,"ariaLabelledBy":1416,"xmlns":587,"style":1419},"169 0 421 196",[1417,1418],"pi-results-t","pi-results-d","width:100%;max-width:421px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[591,1421,1422],{"id":1417},"The three scenarios against the baseline",[595,1424,1425],{"id":1418},"The baseline has revenue of 1,074,290 pounds and gross profit of 445,131. A flat 5 percent increase adds 28,398 of gross profit with slightly lower revenue. The targeted scenario adds 30,635 with higher revenue. A flat 8 percent adds 42,911 but cuts revenue to 1,064,221.",[599,1427],{"x":1428,"y":346,"width":1429,"height":1430,"fill":604},"169","421","196",[599,1432],{"x":1433,"y":608,"width":609,"height":610,"rx":346,"fill":611,"stroke":612,"style":613},"186.0",[599,1435],{"x":1436,"y":608,"width":1437,"height":610,"rx":346,"fill":611,"stroke":612,"style":613},"218.0","80.4",[619,1439,624],{"x":1440,"y":622,"style":623},"258.2",[599,1442],{"x":1443,"y":608,"width":1444,"height":610,"rx":346,"fill":611,"stroke":612,"style":613},"298.4","87.9",[619,1446,632],{"x":1447,"y":622,"style":623},"342.3",[599,1449],{"x":1450,"y":608,"width":1451,"height":610,"rx":346,"fill":611,"stroke":612,"style":613},"386.3","97.2",[619,1453,640],{"x":1454,"y":622,"style":623},"434.9",[599,1456],{"x":1457,"y":608,"width":1458,"height":610,"rx":346,"fill":611,"stroke":612,"style":613},"483.5","90.5",[619,1460,648],{"x":1461,"y":622,"style":623},"528.7",[599,1463],{"x":1433,"y":651,"width":609,"height":652,"rx":346,"fill":611,"stroke":612,"style":613},[619,1465,332],{"x":1466,"y":656,"style":623},"202.0",[599,1468],{"x":1436,"y":651,"width":1437,"height":652,"rx":346,"fill":659,"stroke":660,"style":661},[619,1470,1471],{"x":1440,"y":656,"style":664},"Scenario",[599,1473],{"x":1443,"y":651,"width":1444,"height":652,"rx":346,"fill":659,"stroke":660,"style":661},[619,1475,1476],{"x":1447,"y":656,"style":664},"Revenue",[599,1478],{"x":1450,"y":651,"width":1451,"height":652,"rx":346,"fill":659,"stroke":660,"style":661},[619,1480,1481],{"x":1454,"y":656,"style":664},"Gross profit",[599,1483],{"x":1457,"y":651,"width":1458,"height":652,"rx":346,"fill":659,"stroke":660,"style":661},[619,1485,1486],{"x":1461,"y":656,"style":664},"GP change",[599,1488],{"x":1433,"y":683,"width":609,"height":652,"rx":346,"fill":611,"stroke":612,"style":613},[619,1490,687],{"x":1466,"y":686,"style":623},[599,1492],{"x":1436,"y":683,"width":1437,"height":652,"rx":346,"fill":604,"stroke":612,"style":613},[619,1494,1495],{"x":1440,"y":686,"style":692},"Baseline",[599,1497],{"x":1443,"y":683,"width":1444,"height":652,"rx":346,"fill":604,"stroke":612,"style":613},[619,1499,1500],{"x":1447,"y":686,"style":692},"1,074,290",[599,1502],{"x":1450,"y":683,"width":1451,"height":652,"rx":346,"fill":604,"stroke":612,"style":613},[619,1504,1505],{"x":1454,"y":686,"style":692},"445,131",[599,1507],{"x":1457,"y":683,"width":1458,"height":652,"rx":346,"fill":604,"stroke":612,"style":613},[619,1509,1510],{"x":1461,"y":686,"style":692},"—",[599,1512],{"x":1433,"y":714,"width":609,"height":652,"rx":346,"fill":611,"stroke":612,"style":613},[619,1514,512],{"x":1466,"y":717,"style":623},[599,1516],{"x":1436,"y":714,"width":1437,"height":652,"rx":346,"fill":604,"stroke":612,"style":613},[619,1518,1519],{"x":1440,"y":717,"style":692},"Flat 5%",[599,1521],{"x":1443,"y":714,"width":1444,"height":652,"rx":346,"fill":604,"stroke":612,"style":613},[619,1523,1524],{"x":1447,"y":717,"style":692},"1,069,576",[599,1526],{"x":1450,"y":714,"width":1451,"height":652,"rx":346,"fill":604,"stroke":612,"style":613},[619,1528,1529],{"x":1454,"y":717,"style":692},"473,529",[599,1531],{"x":1457,"y":714,"width":1458,"height":652,"rx":346,"fill":604,"stroke":612,"style":613},[619,1533,1534],{"x":1461,"y":717,"style":692},"+28,398",[599,1536],{"x":1433,"y":740,"width":609,"height":652,"rx":346,"fill":611,"stroke":612,"style":613},[619,1538,744],{"x":1466,"y":743,"style":623},[599,1540],{"x":1436,"y":740,"width":1437,"height":652,"rx":346,"fill":701,"stroke":702,"style":661},[619,1542,1543],{"x":1440,"y":743,"style":705},"Targeted",[599,1545],{"x":1443,"y":740,"width":1444,"height":652,"rx":346,"fill":701,"stroke":702,"style":661},[619,1547,1548],{"x":1447,"y":743,"style":705},"1,080,606",[599,1550],{"x":1450,"y":740,"width":1451,"height":652,"rx":346,"fill":701,"stroke":702,"style":661},[619,1552,1553],{"x":1454,"y":743,"style":705},"475,766",[599,1555],{"x":1457,"y":740,"width":1458,"height":652,"rx":346,"fill":701,"stroke":702,"style":661},[619,1557,1558],{"x":1461,"y":743,"style":705},"+30,635",[599,1560],{"x":1433,"y":1561,"width":609,"height":652,"rx":346,"fill":611,"stroke":612,"style":613},"152.0",[619,1563,1565],{"x":1466,"y":1564,"style":623},"171.0","5",[599,1567],{"x":1436,"y":1561,"width":1437,"height":652,"rx":346,"fill":604,"stroke":612,"style":613},[619,1569,1570],{"x":1440,"y":1564,"style":692},"Flat 8%",[599,1572],{"x":1443,"y":1561,"width":1444,"height":652,"rx":346,"fill":604,"stroke":612,"style":613},[619,1574,1575],{"x":1447,"y":1564,"style":692},"1,064,221",[599,1577],{"x":1450,"y":1561,"width":1451,"height":652,"rx":346,"fill":604,"stroke":612,"style":613},[619,1579,1580],{"x":1454,"y":1564,"style":692},"488,042",[599,1582],{"x":1457,"y":1561,"width":1458,"height":652,"rx":346,"fill":604,"stroke":612,"style":613},[619,1584,1585],{"x":1461,"y":1564,"style":692},"+42,911",[580,1587,589,1593,589,1596,589,1599,589,1603,589,1610,589,1616,589,1620,589,1625,589,1630,589,1635,589,1640,589,1646,589,1649,589,1653,589,1656,589,1660,589,1663,589,1666,589,1670,589,1673,589,1676,589,1678,589,1682,589,1685,589,1688,589,1693,589,1696,589,1699,589,1701,589,1705,589,1708,589,1711,589,1715,589,1718,589,1721],{"viewBox":1588,"role":583,"ariaLabelledBy":1589,"xmlns":587,"style":1592},"4 0 752 126",[1590,1591],"pi-scenario-t","pi-scenario-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[591,1594,1595],{"id":1590},"How one scenario is calculated",[595,1597,1598],{"id":1591},"Each customer and product line gets the increase for its group, reduced by the share of the year still under a fixed-price contract. Volume changes by the increase times the group's elasticity. New units times the new average price gives revenue, and new units times price less unit cost gives gross profit.",[599,1600],{"x":744,"y":346,"width":1601,"height":1602,"fill":604},"752","126",[599,1604],{"x":1605,"y":608,"width":1606,"height":1607,"rx":1608,"fill":659,"stroke":660,"style":1609},"20.0","115.2","94.0","12","stroke-width:2px",[619,1611,1615],{"x":1612,"y":1613,"style":1614},"77.6","50.2","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Increase by",[619,1617,1619],{"x":1612,"y":1618,"style":1614},"67.8","group",[619,1621,1624],{"x":1612,"y":1622,"style":1623},"87.2","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","e.g. PPE +8%",[42,1626],{"x1":1627,"y1":1628,"x2":1629,"y2":1628,"stroke":660,"style":1609},"140.2","63.0","162.2",[1631,1632],"polygon",{"points":1633,"fill":1634},"166.2,63.0 157.2,68.0 157.2,58.0","#5b5cf0",[599,1636],{"x":1637,"y":608,"width":1606,"height":1607,"rx":1608,"fill":1638,"stroke":1639,"style":1609},"171.2","#fdefd8","var(--gold,#b4740a)",[619,1641,1645],{"x":1642,"y":1643,"style":1644},"228.8","41.5","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","× share at",[619,1647,1648],{"x":1642,"y":656,"style":1644},"new price",[619,1650,1652],{"x":1642,"y":1651,"style":1623},"78.5","contracts",[619,1654,1655],{"x":1642,"y":714,"style":1623},"locked to June",[42,1657],{"x1":1658,"y1":1628,"x2":1659,"y2":1628,"stroke":660,"style":1609},"291.4","313.4",[1631,1661],{"points":1662,"fill":1634},"317.4,63.0 308.4,68.0 308.4,58.0",[599,1664],{"x":1665,"y":608,"width":1606,"height":1607,"rx":1608,"fill":1638,"stroke":1639,"style":1609},"322.4",[619,1667,1669],{"x":1668,"y":1643,"style":1644},"380.0","Volume",[619,1671,1672],{"x":1668,"y":656,"style":1644},"change",[619,1674,1675],{"x":1668,"y":1651,"style":1623},"increase ×",[619,1677,1407],{"x":1668,"y":714,"style":1623},[42,1679],{"x1":1680,"y1":1628,"x2":1681,"y2":1628,"stroke":660,"style":1609},"442.6","464.6",[1631,1683],{"points":1684,"fill":1634},"468.6,63.0 459.6,68.0 459.6,58.0",[599,1686],{"x":1687,"y":608,"width":1606,"height":1607,"rx":1608,"fill":701,"stroke":702,"style":1609},"473.6",[619,1689,1692],{"x":1690,"y":1643,"style":1691},"531.2","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Revenue and",[619,1694,1695],{"x":1690,"y":656,"style":1691},"GP",[619,1697,1698],{"x":1690,"y":1651,"style":1623},"new units ×",[619,1700,1648],{"x":1690,"y":714,"style":1623},[42,1702],{"x1":1703,"y1":1628,"x2":1704,"y2":1628,"stroke":660,"style":1609},"593.8","615.8",[1631,1706],{"points":1707,"fill":1634},"619.8,63.0 610.8,68.0 610.8,58.0",[599,1709],{"x":1710,"y":608,"width":1606,"height":1607,"rx":1608,"fill":659,"stroke":660,"style":1609},"624.8",[619,1712,1714],{"x":1713,"y":1643,"style":1614},"682.4","Compare",[619,1716,1717],{"x":1713,"y":656,"style":1614},"scenarios",[619,1719,1720],{"x":1713,"y":1651,"style":1623},"GP change vs",[619,1722,1723],{"x":1713,"y":714,"style":1623},"baseline",[25,1725,1727],{"id":1726},"step-4-an-excel-model-people-can-change","Step 4 — An Excel model people can change",[30,1729,1731],{"className":67,"code":1730,"language":69,"meta":35,"style":35},"from openpyxl import load_workbook\n\npath = \"price_increase_model_2027.xlsx\"\nwith pd.ExcelWriter(path) as xw:\n    summary.to_excel(xw, sheet_name=\"Scenarios\")\n    pd.DataFrame(SCENARIOS).T.to_excel(xw, sheet_name=\"Increases\")\n    pd.Series(ELASTICITY, name=\"Elasticity\").to_frame().to_excel(xw, sheet_name=\"Elasticity\")\n    products.to_excel(xw, sheet_name=\"Break-even\")\n    pd.concat({n: r for n, r in results.items()}, names=[\"Scenario\"]).round(0).to_excel(xw, sheet_name=\"By group\")\nwb = load_workbook(path)\nfor name in (\"Increases\", \"Break-even\"):\n    for row in wb[name].iter_rows(min_row=2, min_col=2):\n        for c in row:\n            if isinstance(c.value, float) and abs(c.value) \u003C 1:\n                c.number_format = \"0.0%\"\nwb.save(path)\nprint(wb.sheetnames)\n",[37,1732,1733,1746,1750,1760,1773,1788,1807,1835,1848,1890,1900,1919,1950,1962,1995,2005,2010],{"__ignoreMap":35},[40,1734,1735,1738,1741,1743],{"class":42,"line":43},[40,1736,1737],{"class":76},"from",[40,1739,1740],{"class":80}," openpyxl ",[40,1742,77],{"class":76},[40,1744,1745],{"class":80}," load_workbook\n",[40,1747,1748],{"class":42,"line":90},[40,1749,94],{"emptyLinePlaceholder":93},[40,1751,1752,1755,1757],{"class":42,"line":97},[40,1753,1754],{"class":80},"path ",[40,1756,103],{"class":76},[40,1758,1759],{"class":50}," \"price_increase_model_2027.xlsx\"\n",[40,1761,1762,1765,1768,1770],{"class":42,"line":121},[40,1763,1764],{"class":76},"with",[40,1766,1767],{"class":80}," pd.ExcelWriter(path) ",[40,1769,84],{"class":76},[40,1771,1772],{"class":80}," xw:\n",[40,1774,1775,1778,1781,1783,1786],{"class":42,"line":157},[40,1776,1777],{"class":80},"    summary.to_excel(xw, ",[40,1779,1780],{"class":137},"sheet_name",[40,1782,103],{"class":76},[40,1784,1785],{"class":50},"\"Scenarios\"",[40,1787,118],{"class":80},[40,1789,1790,1793,1795,1798,1800,1802,1805],{"class":42,"line":191},[40,1791,1792],{"class":80},"    pd.DataFrame(",[40,1794,1189],{"class":325},[40,1796,1797],{"class":80},").T.to_excel(xw, ",[40,1799,1780],{"class":137},[40,1801,103],{"class":76},[40,1803,1804],{"class":50},"\"Increases\"",[40,1806,118],{"class":80},[40,1808,1809,1812,1814,1816,1819,1821,1824,1827,1829,1831,1833],{"class":42,"line":196},[40,1810,1811],{"class":80},"    pd.Series(",[40,1813,778],{"class":325},[40,1815,134],{"class":80},[40,1817,1818],{"class":137},"name",[40,1820,103],{"class":76},[40,1822,1823],{"class":50},"\"Elasticity\"",[40,1825,1826],{"class":80},").to_frame().to_excel(xw, ",[40,1828,1780],{"class":137},[40,1830,103],{"class":76},[40,1832,1823],{"class":50},[40,1834,118],{"class":80},[40,1836,1837,1840,1842,1844,1846],{"class":42,"line":229},[40,1838,1839],{"class":80},"    products.to_excel(xw, ",[40,1841,1780],{"class":137},[40,1843,103],{"class":76},[40,1845,553],{"class":50},[40,1847,118],{"class":80},[40,1849,1850,1853,1855,1858,1860,1863,1866,1868,1870,1873,1876,1878,1881,1883,1885,1888],{"class":42,"line":267},[40,1851,1852],{"class":80},"    pd.concat({n: r ",[40,1854,436],{"class":76},[40,1856,1857],{"class":80}," n, r ",[40,1859,442],{"class":76},[40,1861,1862],{"class":80}," results.items()}, ",[40,1864,1865],{"class":137},"names",[40,1867,103],{"class":76},[40,1869,143],{"class":80},[40,1871,1872],{"class":50},"\"Scenario\"",[40,1874,1875],{"class":80},"]).round(",[40,1877,346],{"class":325},[40,1879,1880],{"class":80},").to_excel(xw, ",[40,1882,1780],{"class":137},[40,1884,103],{"class":76},[40,1886,1887],{"class":50},"\"By group\"",[40,1889,118],{"class":80},[40,1891,1892,1895,1897],{"class":42,"line":293},[40,1893,1894],{"class":80},"wb ",[40,1896,103],{"class":76},[40,1898,1899],{"class":80}," load_workbook(path)\n",[40,1901,1902,1904,1907,1909,1911,1913,1915,1917],{"class":42,"line":337},[40,1903,436],{"class":76},[40,1905,1906],{"class":80}," name ",[40,1908,442],{"class":76},[40,1910,445],{"class":80},[40,1912,1804],{"class":50},[40,1914,134],{"class":80},[40,1916,553],{"class":50},[40,1918,461],{"class":80},[40,1920,1921,1924,1927,1929,1932,1935,1937,1939,1941,1944,1946,1948],{"class":42,"line":352},[40,1922,1923],{"class":76},"    for",[40,1925,1926],{"class":80}," row ",[40,1928,442],{"class":76},[40,1930,1931],{"class":80}," wb[name].iter_rows(",[40,1933,1934],{"class":137},"min_row",[40,1936,103],{"class":76},[40,1938,687],{"class":325},[40,1940,134],{"class":80},[40,1942,1943],{"class":137},"min_col",[40,1945,103],{"class":76},[40,1947,687],{"class":325},[40,1949,461],{"class":80},[40,1951,1952,1955,1957,1959],{"class":42,"line":1102},[40,1953,1954],{"class":76},"        for",[40,1956,539],{"class":80},[40,1958,442],{"class":76},[40,1960,1961],{"class":80}," row:\n",[40,1963,1964,1967,1970,1973,1976,1978,1981,1984,1987,1990,1992],{"class":42,"line":1127},[40,1965,1966],{"class":76},"            if",[40,1968,1969],{"class":325}," isinstance",[40,1971,1972],{"class":80},"(c.value, ",[40,1974,1975],{"class":325},"float",[40,1977,930],{"class":80},[40,1979,1980],{"class":76},"and",[40,1982,1983],{"class":325}," abs",[40,1985,1986],{"class":80},"(c.value) ",[40,1988,1989],{"class":76},"\u003C",[40,1991,975],{"class":325},[40,1993,1994],{"class":80},":\n",[40,1996,1997,2000,2002],{"class":42,"line":1160},[40,1998,1999],{"class":80},"                c.number_format ",[40,2001,103],{"class":76},[40,2003,2004],{"class":50}," \"0.0%\"\n",[40,2006,2007],{"class":42,"line":1181},[40,2008,2009],{"class":80},"wb.save(path)\n",[40,2011,2012,2014],{"class":42,"line":1186},[40,2013,340],{"class":325},[40,2015,2016],{"class":80},"(wb.sheetnames)\n",[10,2018,2019,2020,23],{},"The workbook keeps the assumptions — the increases per scenario and the elasticities — on their own sheets next to the results, so a reader can see exactly what each scenario assumed. For a model that sales and finance will argue over, the next step is to read the assumptions back from those sheets at the start of the script, so a changed elasticity in the workbook and a rerun give a new set of results without anyone touching the code. Once a scenario is agreed, the new prices go into the dated price list with their effective date, as in ",[19,2021,2023],{"href":2022},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fupdate-a-price-list-with-effective-dates\u002F","Update a Price List with Effective Dates",[25,2025,2027],{"id":2026},"common-pitfalls","Common pitfalls",[2029,2030,2031,2039,2045,2051,2057],"ul",{},[2032,2033,2034,2038],"li",{},[2035,2036,2037],"strong",{},"Ignoring the break-even volume loss."," It is the clearest single number for the decision.",[2032,2040,2041,2044],{},[2035,2042,2043],{},"One elasticity for everything."," Customers react differently by product group; set it per group.",[2032,2046,2047,2050],{},[2035,2048,2049],{},"Treating elasticities as facts."," They are assumptions; show them and test a range.",[2032,2052,2053,2056],{},[2035,2054,2055],{},"Forgetting contract price locks."," Customers on fixed prices keep the old price until their contract ends.",[2032,2058,2059,2062],{},[2035,2060,2061],{},"Comparing scenarios on revenue."," Compare on gross profit; revenue can rise while profit falls.",[25,2064,2066],{"id":2065},"conclusion","Conclusion",[10,2068,2069,2070,2072],{},"To model a price increase, start from a baseline of revenue and gross profit by product group, calculate each product's break-even volume loss as ",[14,2071,573],{},", and run scenarios that apply increases by group, reduce volume through agreed elasticity assumptions, and keep contract customers on their old prices until their contracts end. Comparing scenarios on gross profit, with the assumptions visible next to the results in Excel, gives sales and finance a shared basis for the decision.",[25,2074,2076],{"id":2075},"frequently-asked-questions","Frequently asked questions",[10,2078,2079,2082,2083,2085,2086,2088,2089,23],{},[2035,2080,2081],{},"How much volume can I lose after a price increase?","\nFor an increase ",[14,2084,10],{}," on a product with gross margin ",[14,2087,569],{},", gross profit stays the same if volume falls by ",[14,2090,573],{},[10,2092,2093,2096],{},[2035,2094,2095],{},"What is price elasticity in a pricing model?","\nThe percentage change in volume for a one per cent change in price; a value of −1.5 means a 2% increase loses 3% of volume.",[10,2098,2099,2102],{},[2035,2100,2101],{},"How do I handle customers on fixed-price contracts?","\nKeep them at the old price until the contract ends and apply the increase only to the rest of the year.",[10,2104,2105,2108],{},[2035,2106,2107],{},"Should price scenarios be compared on revenue or profit?","\nOn gross profit, because the purpose of an increase is to protect margin, and revenue can rise while profit falls.",[25,2110,2112],{"id":2111},"related","Related",[2029,2114,2115,2121,2128,2135],{},[2032,2116,2117,2118,2120],{},"Up one level: ",[19,2119,22],{"href":21}," — finance reports from Python.",[2032,2122,2123,2127],{},[19,2124,2126],{"href":2125},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-price-volume-mix-analysis\u002F","Build a Price-Volume-Mix Analysis"," — measuring the effect afterwards.",[2032,2129,2130,2134],{},[19,2131,2133],{"href":2132},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-break-even-analysis-in-excel-with-python\u002F","Build a Break-Even Analysis in Excel with Python"," — the same idea for fixed costs.",[2032,2136,2137,2141],{},[19,2138,2140],{"href":2139},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-discount-leakage-report\u002F","Build a Discount Leakage Report"," — protecting price after the increase.",[2143,2144,2145],"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}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}",{"title":35,"searchDepth":90,"depth":90,"links":2147},[2148,2149,2150,2151,2152,2153,2154,2155,2156],{"id":27,"depth":90,"text":28},{"id":63,"depth":90,"text":64},{"id":388,"depth":90,"text":389},{"id":767,"depth":90,"text":768},{"id":1726,"depth":90,"text":1727},{"id":2026,"depth":90,"text":2027},{"id":2065,"depth":90,"text":2066},{"id":2075,"depth":90,"text":2076},{"id":2111,"depth":90,"text":2112},"2026-10-02","Estimate what a price increase will do to revenue and gross profit with pandas before announcing it: apply candidate increases per product group, model lost volume with simple elasticity assumptions, respect customer contracts that fix prices until a date, calculate the break-even volume loss for each product, compare scenarios side by side and write an Excel workbook whose assumptions sales and finance can change.","md",[2161,2163,2165,2167],{"q":2081,"a":2162},"For an increase *p* on a product with gross margin *m*, gross profit stays the same if volume falls by *p ÷ (m + p)*.",{"q":2095,"a":2164},"The percentage change in volume for a one per cent change in price; a value of −1.5 means a 2% increase loses 3% of volume.",{"q":2101,"a":2166},"Keep them at the old price until the contract ends and apply the increase only to the rest of the year.",{"q":2107,"a":2168},"On gross profit, because the purpose of an increase is to protect margin, and revenue can rise while profit falls.","price increase impact model python",null,{"breadcrumb":2172},[2173,2175,2178],{"name":2174,"item":313},"Home",{"name":2176,"item":2177},"Automating Reporting Workflows with Python and Excel","\u002Fautomating-reporting-workflows\u002F",{"name":2179,"item":21},"Building Finance and Operations Reports in Excel with Python","\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fmodel-the-impact-of-a-price-increase",{"title":2182,"description":2183},"Model the Impact of a Price Increase with Python and Excel","Price increase scenarios with pandas: increases by product group, volume loss from elasticity assumptions, contract price locks, break-even volume loss, revenue and gross profit by scenario and an editable Excel model.","model-the-impact-of-a-price-increase","automating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fmodel-the-impact-of-a-price-increase\u002Findex",[2187,2188],"report-layouts","pandas","how-to","bDeUmMiRiehRPnjYzdSr8YanfhT0AFiAgZAwJ1fa8ug",[2192,2196],{"title":2193,"path":2194,"stem":2195,"children":-1},"Generate a Staff Rota in Excel with Python","\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fgenerate-a-staff-rota-in-excel-with-python","automating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fgenerate-a-staff-rota-in-excel-with-python\u002Findex",{"title":2197,"path":2198,"stem":2199,"children":-1},"Reconcile a Stock Count Against System Inventory","\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Freconcile-a-stock-count-against-system-inventory","automating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Freconcile-a-stock-count-against-system-inventory\u002Findex",[2201,2203],{"path":2202,"title":2179},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports",{"path":2204,"title":2176},"\u002Fautomating-reporting-workflows",[],[2207,2211,2214,2217,2220,2223],{"path":2208,"title":2209,"kind":2210},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards","Building Multi-Sheet Excel Dashboards","guide",{"path":2212,"title":2213,"kind":2189},"\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":2215,"title":2216,"kind":2189},"\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":2218,"title":2219,"kind":2189},"\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":2221,"title":2222,"kind":2189},"\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":2224,"title":2225,"kind":2189},"\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",1790972128039]