[{"data":1,"prerenderedAt":3022},["ShallowReactive",2],{"doc:\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-one-page-executive-summary-sheet":3,"surround:\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-one-page-executive-summary-sheet":2987,"ancestors:\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-one-page-executive-summary-sheet":2996,"children:\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-one-page-executive-summary-sheet":3001,"related:\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-one-page-executive-summary-sheet":3002},{"id":4,"title":5,"body":6,"dateModified":2954,"datePublished":2954,"description":2955,"extension":2956,"faq":2957,"keyword":2966,"kind":2967,"meta":2968,"navigation":245,"path":2976,"seo":2977,"slug":2980,"stem":2981,"topics":2982,"type":2985,"__hash__":2986},"docs\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-one-page-executive-summary-sheet\u002Findex.md","Build a One-Page Executive Summary Sheet",{"type":7,"value":8,"toc":2940},"minimark",[9,19,24,53,56,60,63,213,217,607,610,614,1090,1097,1101,1918,1926,1930,2426,2434,2438,2535,2583,2593,2733,2737,2740,2814,2818,2857,2861,2864,2868,2885,2891,2897,2903,2907,2936],[10,11,12,13,18],"p",{},"Most people who receive a monthly report read one page of it. An executive summary sheet accepts that and puts everything they need on that page: a handful of headline figures with their comparison to budget and last year, two or three sentences saying what happened, a small chart showing the trend, and the few items that moved the most. Everything else — the detail by region, product and month — lives on the sheets behind it for those who want it. The difference from a dashboard is discipline: the summary is printed, forwarded and read on a phone, so it must fit one page, read top to bottom, and make sense without any interaction. This page builds such a sheet with pandas and openpyxl, writes the commentary from the numbers so it is never out of date, and proves it fits on one page by converting it to PDF. It belongs to ",[14,15,17],"a",{"href":16},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002F","Building Multi-Sheet Excel Dashboards",".",[20,21,23],"h2",{"id":22},"prerequisites","Prerequisites",[25,26,31],"pre",{"className":27,"code":28,"language":29,"meta":30,"style":30},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[32,33,34],"code",{"__ignoreMap":30},[35,36,39,43,47,50],"span",{"class":37,"line":38},"line",1,[35,40,42],{"class":41},"sMTad","pip",[35,44,46],{"class":45},"srMev"," install",[35,48,49],{"class":45}," pandas",[35,51,52],{"class":45}," openpyxl\n",[10,54,55],{},"The input is a monthly fact table with one row per month and region: revenue, gross margin, number of orders, budgeted revenue and revenue for the same month last year. The same approach works for any report whose readers need the headline before the detail.",[20,57,59],{"id":58},"plan-the-page-before-writing-code","Plan the page before writing code",[10,61,62],{},"A summary page that fits on one sheet of A4 in portrait has room for roughly 45 rows of default height and seven or eight normal-width columns, so space is the constraint that shapes everything. Decide the blocks and their order first: title and period at the top, then the headline figures, then the commentary, then the chart, then the movers, then a footer saying where the numbers come from. Each block gets a fixed band of rows, which keeps the layout stable from month to month however the numbers change.",[64,65,73,74,73,78,73,82,73,90,73,100,73,107,73,112,73,117,73,122,73,126,73,131,73,135,73,139,73,142,73,147,73,152,73,156,73,160,73,163,73,168,73,173,73,177,73,181,73,184,73,187,73,191,73,195,73,199,73,202,73,205,73,209],"svg",{"viewBox":66,"role":67,"ariaLabelledBy":68,"xmlns":71,"style":72},"84 0 592 566","img",[69,70],"exec-layout-t","exec-layout-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  ",[75,76,77],"title",{"id":69},"The bands of a one-page summary",[79,80,81],"desc",{"id":70},"The page is read top to bottom. The title and period come first, then four headline figures with their comparisons, three sentences of commentary, a twelve-month trend chart, the biggest movers by region, and a source line at the foot.",[83,84],"rect",{"x":85,"y":86,"width":87,"height":88,"fill":89},"84","0","592","566","#ffffff",[83,91],{"x":92,"y":93,"width":94,"height":95,"rx":96,"fill":97,"stroke":98,"style":99},"100.0","16.0","560.0","59.0","12","#e7ebef","var(--line,#cdd5e6)","stroke-width:2px",[101,102,106],"text",{"x":103,"y":104,"style":105},"380.0","41.5","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Title and period",[101,108,111],{"x":103,"y":109,"style":110},"61.0","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","rows 1–2",[37,113],{"x1":103,"y1":114,"x2":103,"y2":115,"stroke":116,"style":99},"80.0","102.0","var(--brand,#5b5cf0)",[118,119],"polygon",{"points":120,"fill":121},"380.0,106.0 375.0,97.0 385.0,97.0","#5b5cf0",[83,123],{"x":92,"y":124,"width":94,"height":95,"rx":96,"fill":125,"stroke":116,"style":99},"111.0","#f0f4ff",[101,127,130],{"x":103,"y":128,"style":129},"136.5","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Four headline figures",[101,132,134],{"x":103,"y":133,"style":110},"156.0","rows 4–6",[37,136],{"x1":103,"y1":137,"x2":103,"y2":138,"stroke":116,"style":99},"175.0","197.0",[118,140],{"points":141,"fill":121},"380.0,201.0 375.0,192.0 385.0,192.0",[83,143],{"x":92,"y":144,"width":94,"height":95,"rx":96,"fill":145,"stroke":146,"style":99},"206.0","#d9f4f1","var(--teal,#0f9488)",[101,148,151],{"x":103,"y":149,"style":150},"231.5","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Commentary",[101,153,155],{"x":103,"y":154,"style":110},"251.0","rows 8–10",[37,157],{"x1":103,"y1":158,"x2":103,"y2":159,"stroke":116,"style":99},"270.0","292.0",[118,161],{"points":162,"fill":121},"380.0,296.0 375.0,287.0 385.0,287.0",[83,164],{"x":92,"y":165,"width":94,"height":95,"rx":96,"fill":166,"stroke":167,"style":99},"301.0","#fdefd8","var(--gold,#b4740a)",[101,169,172],{"x":103,"y":170,"style":171},"326.5","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Twelve-month trend chart",[101,174,176],{"x":103,"y":175,"style":110},"346.0","rows 12–27",[37,178],{"x1":103,"y1":179,"x2":103,"y2":180,"stroke":116,"style":99},"365.0","387.0",[118,182],{"points":183,"fill":121},"380.0,391.0 375.0,382.0 385.0,382.0",[83,185],{"x":92,"y":186,"width":94,"height":95,"rx":96,"fill":97,"stroke":98,"style":99},"396.0",[101,188,190],{"x":103,"y":189,"style":105},"421.5","Biggest movers",[101,192,194],{"x":103,"y":193,"style":110},"441.0","rows 29–35",[37,196],{"x1":103,"y1":197,"x2":103,"y2":198,"stroke":116,"style":99},"460.0","482.0",[118,200],{"points":201,"fill":121},"380.0,486.0 375.0,477.0 385.0,477.0",[83,203],{"x":92,"y":204,"width":94,"height":95,"rx":96,"fill":97,"stroke":98,"style":99},"491.0",[101,206,208],{"x":103,"y":207,"style":105},"516.5","Source and run time",[101,210,212],{"x":103,"y":211,"style":110},"536.0","last row",[20,214,216],{"id":215},"step-1-calculate-the-headline-figures","Step 1 — Calculate the headline figures",[25,218,222],{"className":219,"code":220,"language":221,"meta":30,"style":30},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\ndf = pd.read_excel(\"monthly_sales.xlsx\")\nmonth = df[\"Month\"].max()\ncur = df[df[\"Month\"] == month]\nprev = df[df[\"Month\"] == month - pd.DateOffset(months=1)]\n\ndef kpis(d):\n    return {\"Revenue\": d[\"Revenue\"].sum(), \"Gross margin %\": d[\"Gross margin\"].sum() \u002F d[\"Revenue\"].sum(),\n            \"Orders\": d[\"Orders\"].sum(), \"Average order\": d[\"Revenue\"].sum() \u002F d[\"Orders\"].sum()}\n\nnow, before = kpis(cur), kpis(prev)\nbudget, last_year = cur[\"Budget revenue\"].sum(), cur[\"Revenue last year\"].sum()\nfor k, v in now.items():\n    print(f\"{k:15} {v:>12,.3f}  previous month {before[k]:>12,.3f}\")\nprint(f\"vs budget {now['Revenue'] \u002F budget - 1:+.1%}   vs last year {now['Revenue'] \u002F last_year - 1:+.1%}\")\n","python",[32,223,224,240,247,265,282,304,342,347,360,402,433,438,449,472,487,541],{"__ignoreMap":30},[35,225,226,230,234,237],{"class":37,"line":38},[35,227,229],{"class":228},"s-kum","import",[35,231,233],{"class":232},"skGVy"," pandas ",[35,235,236],{"class":228},"as",[35,238,239],{"class":232}," pd\n",[35,241,243],{"class":37,"line":242},2,[35,244,246],{"emptyLinePlaceholder":245},true,"\n",[35,248,250,253,256,259,262],{"class":37,"line":249},3,[35,251,252],{"class":232},"df ",[35,254,255],{"class":228},"=",[35,257,258],{"class":232}," pd.read_excel(",[35,260,261],{"class":45},"\"monthly_sales.xlsx\"",[35,263,264],{"class":232},")\n",[35,266,268,271,273,276,279],{"class":37,"line":267},4,[35,269,270],{"class":232},"month ",[35,272,255],{"class":228},[35,274,275],{"class":232}," df[",[35,277,278],{"class":45},"\"Month\"",[35,280,281],{"class":232},"].max()\n",[35,283,285,288,290,293,295,298,301],{"class":37,"line":284},5,[35,286,287],{"class":232},"cur ",[35,289,255],{"class":228},[35,291,292],{"class":232}," df[df[",[35,294,278],{"class":45},[35,296,297],{"class":232},"] ",[35,299,300],{"class":228},"==",[35,302,303],{"class":232}," month]\n",[35,305,307,310,312,314,316,318,320,323,326,329,333,335,339],{"class":37,"line":306},6,[35,308,309],{"class":232},"prev ",[35,311,255],{"class":228},[35,313,292],{"class":232},[35,315,278],{"class":45},[35,317,297],{"class":232},[35,319,300],{"class":228},[35,321,322],{"class":232}," month ",[35,324,325],{"class":228},"-",[35,327,328],{"class":232}," pd.DateOffset(",[35,330,332],{"class":331},"sa561","months",[35,334,255],{"class":228},[35,336,338],{"class":337},"sP0c6","1",[35,340,341],{"class":232},")]\n",[35,343,345],{"class":37,"line":344},7,[35,346,246],{"emptyLinePlaceholder":245},[35,348,350,353,357],{"class":37,"line":349},8,[35,351,352],{"class":228},"def",[35,354,356],{"class":355},"s_Opv"," kpis",[35,358,359],{"class":232},"(d):\n",[35,361,363,366,369,372,375,377,380,383,385,388,391,394,397,399],{"class":37,"line":362},9,[35,364,365],{"class":228},"    return",[35,367,368],{"class":232}," {",[35,370,371],{"class":45},"\"Revenue\"",[35,373,374],{"class":232},": d[",[35,376,371],{"class":45},[35,378,379],{"class":232},"].sum(), ",[35,381,382],{"class":45},"\"Gross margin %\"",[35,384,374],{"class":232},[35,386,387],{"class":45},"\"Gross margin\"",[35,389,390],{"class":232},"].sum() ",[35,392,393],{"class":228},"\u002F",[35,395,396],{"class":232}," d[",[35,398,371],{"class":45},[35,400,401],{"class":232},"].sum(),\n",[35,403,405,408,410,413,415,418,420,422,424,426,428,430],{"class":37,"line":404},10,[35,406,407],{"class":45},"            \"Orders\"",[35,409,374],{"class":232},[35,411,412],{"class":45},"\"Orders\"",[35,414,379],{"class":232},[35,416,417],{"class":45},"\"Average order\"",[35,419,374],{"class":232},[35,421,371],{"class":45},[35,423,390],{"class":232},[35,425,393],{"class":228},[35,427,396],{"class":232},[35,429,412],{"class":45},[35,431,432],{"class":232},"].sum()}\n",[35,434,436],{"class":37,"line":435},11,[35,437,246],{"emptyLinePlaceholder":245},[35,439,441,444,446],{"class":37,"line":440},12,[35,442,443],{"class":232},"now, before ",[35,445,255],{"class":228},[35,447,448],{"class":232}," kpis(cur), kpis(prev)\n",[35,450,452,455,457,460,463,466,469],{"class":37,"line":451},13,[35,453,454],{"class":232},"budget, last_year ",[35,456,255],{"class":228},[35,458,459],{"class":232}," cur[",[35,461,462],{"class":45},"\"Budget revenue\"",[35,464,465],{"class":232},"].sum(), cur[",[35,467,468],{"class":45},"\"Revenue last year\"",[35,470,471],{"class":232},"].sum()\n",[35,473,475,478,481,484],{"class":37,"line":474},14,[35,476,477],{"class":228},"for",[35,479,480],{"class":232}," k, v ",[35,482,483],{"class":228},"in",[35,485,486],{"class":232}," now.items():\n",[35,488,490,493,496,499,502,506,509,512,515,517,520,523,525,528,530,533,535,537,539],{"class":37,"line":489},15,[35,491,492],{"class":337},"    print",[35,494,495],{"class":232},"(",[35,497,498],{"class":228},"f",[35,500,501],{"class":45},"\"",[35,503,505],{"class":504},"sSjpA","{",[35,507,508],{"class":232},"k",[35,510,511],{"class":228},":15",[35,513,514],{"class":504},"}",[35,516,368],{"class":504},[35,518,519],{"class":232},"v",[35,521,522],{"class":228},":>12,.3f",[35,524,514],{"class":504},[35,526,527],{"class":45},"  previous month ",[35,529,505],{"class":504},[35,531,532],{"class":232},"before[k]",[35,534,522],{"class":228},[35,536,514],{"class":504},[35,538,501],{"class":45},[35,540,264],{"class":232},[35,542,544,547,549,551,554,556,559,562,564,566,569,571,574,577,579,582,584,586,588,590,592,595,597,599,601,603,605],{"class":37,"line":543},16,[35,545,546],{"class":337},"print",[35,548,495],{"class":232},[35,550,498],{"class":228},[35,552,553],{"class":45},"\"vs budget ",[35,555,505],{"class":504},[35,557,558],{"class":232},"now[",[35,560,561],{"class":45},"'Revenue'",[35,563,297],{"class":232},[35,565,393],{"class":228},[35,567,568],{"class":232}," budget ",[35,570,325],{"class":228},[35,572,573],{"class":337}," 1",[35,575,576],{"class":228},":+.1%",[35,578,514],{"class":504},[35,580,581],{"class":45},"   vs last year ",[35,583,505],{"class":504},[35,585,558],{"class":232},[35,587,561],{"class":45},[35,589,297],{"class":232},[35,591,393],{"class":228},[35,593,594],{"class":232}," last_year ",[35,596,325],{"class":228},[35,598,573],{"class":337},[35,600,576],{"class":228},[35,602,514],{"class":504},[35,604,501],{"class":45},[35,606,264],{"class":232},[10,608,609],{},"Four figures are enough. Revenue on its own, gross margin as a percentage, the number of orders and the average order value tell the story between them: revenue up with orders flat means bigger orders; revenue up with margin down means discounting. Each is compared with the previous month, and revenue is also compared with budget and with the same month last year, because those are the two questions a senior reader asks first.",[20,611,613],{"id":612},"step-2-write-the-commentary-from-the-numbers","Step 2 — Write the commentary from the numbers",[25,615,617],{"className":219,"code":616,"language":221,"meta":30,"style":30},"by_region = df.pivot_table(index=\"Region\", columns=\"Month\", values=\"Revenue\", aggfunc=\"sum\")\nchange = (by_region[month] - by_region[month - pd.DateOffset(months=1)]).sort_values()\nup, down = change.idxmax(), change.idxmin()\n\ndef pct(a, b):\n    return f\"{a \u002F b - 1:+.1%}\"\n\ndef money(x):\n    return f\"{'+' if x >= 0 else '−'}£{abs(x):,.0f}\"\n\nlines = [\n    f\"Revenue for {month:%B %Y} was £{now['Revenue']:,.0f}, {pct(now['Revenue'], budget)} against budget \"\n    f\"and {pct(now['Revenue'], last_year)} against {month:%B} last year.\",\n    f\"The biggest increase came from {up} ({money(change[up])} on the previous month); \"\n    f\"the biggest fall was in {down} ({money(change[down])}).\",\n    f\"Gross margin was {now['Gross margin %']:.1%} against {before['Gross margin %']:.1%} in the previous month, \"\n    f\"on {now['Orders']:,} orders averaging £{now['Average order']:,.0f}.\",\n]\nprint(\"\\n\".join(lines))\n",[32,618,619,668,696,706,710,720,750,754,764,815,819,829,891,930,957,985,1024,1067,1073],{"__ignoreMap":30},[35,620,621,624,626,629,632,634,637,640,643,645,647,649,652,654,656,658,661,663,666],{"class":37,"line":38},[35,622,623],{"class":232},"by_region ",[35,625,255],{"class":228},[35,627,628],{"class":232}," df.pivot_table(",[35,630,631],{"class":331},"index",[35,633,255],{"class":228},[35,635,636],{"class":45},"\"Region\"",[35,638,639],{"class":232},", ",[35,641,642],{"class":331},"columns",[35,644,255],{"class":228},[35,646,278],{"class":45},[35,648,639],{"class":232},[35,650,651],{"class":331},"values",[35,653,255],{"class":228},[35,655,371],{"class":45},[35,657,639],{"class":232},[35,659,660],{"class":331},"aggfunc",[35,662,255],{"class":228},[35,664,665],{"class":45},"\"sum\"",[35,667,264],{"class":232},[35,669,670,673,675,678,680,683,685,687,689,691,693],{"class":37,"line":242},[35,671,672],{"class":232},"change ",[35,674,255],{"class":228},[35,676,677],{"class":232}," (by_region[month] ",[35,679,325],{"class":228},[35,681,682],{"class":232}," by_region[month ",[35,684,325],{"class":228},[35,686,328],{"class":232},[35,688,332],{"class":331},[35,690,255],{"class":228},[35,692,338],{"class":337},[35,694,695],{"class":232},")]).sort_values()\n",[35,697,698,701,703],{"class":37,"line":249},[35,699,700],{"class":232},"up, down ",[35,702,255],{"class":228},[35,704,705],{"class":232}," change.idxmax(), change.idxmin()\n",[35,707,708],{"class":37,"line":267},[35,709,246],{"emptyLinePlaceholder":245},[35,711,712,714,717],{"class":37,"line":284},[35,713,352],{"class":228},[35,715,716],{"class":355}," pct",[35,718,719],{"class":232},"(a, b):\n",[35,721,722,724,727,729,731,734,736,739,741,743,745,747],{"class":37,"line":306},[35,723,365],{"class":228},[35,725,726],{"class":228}," f",[35,728,501],{"class":45},[35,730,505],{"class":504},[35,732,733],{"class":232},"a ",[35,735,393],{"class":228},[35,737,738],{"class":232}," b ",[35,740,325],{"class":228},[35,742,573],{"class":337},[35,744,576],{"class":228},[35,746,514],{"class":504},[35,748,749],{"class":45},"\"\n",[35,751,752],{"class":37,"line":344},[35,753,246],{"emptyLinePlaceholder":245},[35,755,756,758,761],{"class":37,"line":349},[35,757,352],{"class":228},[35,759,760],{"class":355}," money",[35,762,763],{"class":232},"(x):\n",[35,765,766,768,770,772,774,777,780,783,786,789,792,795,797,800,802,805,808,811,813],{"class":37,"line":362},[35,767,365],{"class":228},[35,769,726],{"class":228},[35,771,501],{"class":45},[35,773,505],{"class":504},[35,775,776],{"class":45},"'+'",[35,778,779],{"class":228}," if",[35,781,782],{"class":232}," x ",[35,784,785],{"class":228},">=",[35,787,788],{"class":337}," 0",[35,790,791],{"class":228}," else",[35,793,794],{"class":45}," '−'",[35,796,514],{"class":504},[35,798,799],{"class":45},"£",[35,801,505],{"class":504},[35,803,804],{"class":337},"abs",[35,806,807],{"class":232},"(x)",[35,809,810],{"class":228},":,.0f",[35,812,514],{"class":504},[35,814,749],{"class":45},[35,816,817],{"class":37,"line":404},[35,818,246],{"emptyLinePlaceholder":245},[35,820,821,824,826],{"class":37,"line":435},[35,822,823],{"class":232},"lines ",[35,825,255],{"class":228},[35,827,828],{"class":232}," [\n",[35,830,831,834,837,839,842,845,848,851,853,856,858,861,863,865,867,870,872,874,876,878,881,883,886,888],{"class":37,"line":440},[35,832,833],{"class":228},"    f",[35,835,836],{"class":45},"\"Revenue for ",[35,838,505],{"class":504},[35,840,841],{"class":232},"month",[35,843,844],{"class":228},":",[35,846,847],{"class":232},"%",[35,849,850],{"class":228},"B ",[35,852,847],{"class":232},[35,854,855],{"class":228},"Y",[35,857,514],{"class":504},[35,859,860],{"class":45}," was £",[35,862,505],{"class":504},[35,864,558],{"class":232},[35,866,561],{"class":45},[35,868,869],{"class":232},"]",[35,871,810],{"class":228},[35,873,514],{"class":504},[35,875,639],{"class":45},[35,877,505],{"class":504},[35,879,880],{"class":232},"pct(now[",[35,882,561],{"class":45},[35,884,885],{"class":232},"], budget)",[35,887,514],{"class":504},[35,889,890],{"class":45}," against budget \"\n",[35,892,893,895,898,900,902,904,907,909,912,914,917,919,922,924,927],{"class":37,"line":451},[35,894,833],{"class":228},[35,896,897],{"class":45},"\"and ",[35,899,505],{"class":504},[35,901,880],{"class":232},[35,903,561],{"class":45},[35,905,906],{"class":232},"], last_year)",[35,908,514],{"class":504},[35,910,911],{"class":45}," against ",[35,913,505],{"class":504},[35,915,916],{"class":232},"month:",[35,918,847],{"class":228},[35,920,921],{"class":232},"B",[35,923,514],{"class":504},[35,925,926],{"class":45}," last year.\"",[35,928,929],{"class":232},",\n",[35,931,932,934,937,939,942,944,947,949,952,954],{"class":37,"line":474},[35,933,833],{"class":228},[35,935,936],{"class":45},"\"The biggest increase came from ",[35,938,505],{"class":504},[35,940,941],{"class":232},"up",[35,943,514],{"class":504},[35,945,946],{"class":45}," (",[35,948,505],{"class":504},[35,950,951],{"class":232},"money(change[up])",[35,953,514],{"class":504},[35,955,956],{"class":45}," on the previous month); \"\n",[35,958,959,961,964,966,969,971,973,975,978,980,983],{"class":37,"line":489},[35,960,833],{"class":228},[35,962,963],{"class":45},"\"the biggest fall was in ",[35,965,505],{"class":504},[35,967,968],{"class":232},"down",[35,970,514],{"class":504},[35,972,946],{"class":45},[35,974,505],{"class":504},[35,976,977],{"class":232},"money(change[down])",[35,979,514],{"class":504},[35,981,982],{"class":45},").\"",[35,984,929],{"class":232},[35,986,987,989,992,994,996,999,1001,1004,1006,1008,1010,1013,1015,1017,1019,1021],{"class":37,"line":543},[35,988,833],{"class":228},[35,990,991],{"class":45},"\"Gross margin was ",[35,993,505],{"class":504},[35,995,558],{"class":232},[35,997,998],{"class":45},"'Gross margin %'",[35,1000,869],{"class":232},[35,1002,1003],{"class":228},":.1%",[35,1005,514],{"class":504},[35,1007,911],{"class":45},[35,1009,505],{"class":504},[35,1011,1012],{"class":232},"before[",[35,1014,998],{"class":45},[35,1016,869],{"class":232},[35,1018,1003],{"class":228},[35,1020,514],{"class":504},[35,1022,1023],{"class":45}," in the previous month, \"\n",[35,1025,1027,1029,1032,1034,1036,1039,1041,1044,1046,1049,1051,1053,1056,1058,1060,1062,1065],{"class":37,"line":1026},17,[35,1028,833],{"class":228},[35,1030,1031],{"class":45},"\"on ",[35,1033,505],{"class":504},[35,1035,558],{"class":232},[35,1037,1038],{"class":45},"'Orders'",[35,1040,869],{"class":232},[35,1042,1043],{"class":228},":,",[35,1045,514],{"class":504},[35,1047,1048],{"class":45}," orders averaging £",[35,1050,505],{"class":504},[35,1052,558],{"class":232},[35,1054,1055],{"class":45},"'Average order'",[35,1057,869],{"class":232},[35,1059,810],{"class":228},[35,1061,514],{"class":504},[35,1063,1064],{"class":45},".\"",[35,1066,929],{"class":232},[35,1068,1070],{"class":37,"line":1069},18,[35,1071,1072],{"class":232},"]\n",[35,1074,1076,1078,1080,1082,1085,1087],{"class":37,"line":1075},19,[35,1077,546],{"class":337},[35,1079,495],{"class":232},[35,1081,501],{"class":45},[35,1083,1084],{"class":504},"\\n",[35,1086,501],{"class":45},[35,1088,1089],{"class":232},".join(lines))\n",[10,1091,1092,1093,18],{},"Commentary written by hand is usually the part of a report that is late, and it is sometimes wrong because it was written against an earlier version of the numbers. Generating the sentences from the same data as the figures means they always agree. The templates are deliberately plain — what happened, compared with what, and why in terms of where — and the reader's knowledge supplies the rest. A finance analyst can still add a sentence of judgement on top; the generated lines are the factual baseline, not a replacement for it. The same variance logic, region by region and line by line, is the subject of ",[14,1094,1096],{"href":1095},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-budget-vs-actual-variance-report\u002F","Build a Budget vs Actual Variance Report in Excel with Python",[20,1098,1100],{"id":1099},"step-3-lay-out-the-sheet","Step 3 — Lay out the sheet",[25,1102,1104],{"className":219,"code":1103,"language":221,"meta":30,"style":30},"from openpyxl import Workbook\nfrom openpyxl.chart import LineChart, Reference\nfrom openpyxl.styles import Alignment, Font, PatternFill\n\nwb = Workbook()\nws = wb.active\nws.title = \"Summary\"\ndetail = wb.create_sheet(\"Detail\")\nfor col in \"ABCDEFGH\":\n    ws.column_dimensions[col].width = 11.5\nws.sheet_view.showGridLines = False\n\nws[\"A1\"] = \"Monthly sales summary\"\nws[\"A1\"].font = Font(size=18, bold=True, color=\"1F3864\")\nws[\"A2\"] = f\"{month:%B %Y} · all regions\"\nws[\"A2\"].font = Font(size=11, color=\"595959\")\n\n# Band 1: four tiles across, each two columns wide (label, value, comparison)\ntiles = [(\"Revenue\", now[\"Revenue\"], \"£#,##0\", pct(now[\"Revenue\"], budget) + \" vs budget\"),\n         (\"Gross margin\", now[\"Gross margin %\"], \"0.0%\",\n          f\"{(now['Gross margin %'] - before['Gross margin %']) * 100:+.1f} pts on month\"),\n         (\"Orders\", now[\"Orders\"], \"#,##0\", pct(now[\"Orders\"], before[\"Orders\"]) + \" on month\"),\n         (\"Average order\", now[\"Average order\"], \"£#,##0\", pct(now[\"Average order\"], before[\"Average order\"]) + \" on month\")]\ntile_fill = PatternFill(\"solid\", fgColor=\"EEF3FA\")\nfor i, (label, value, fmt, note) in enumerate(tiles):\n    c = 1 + i * 2\n    for r, content, font in [(4, label, Font(size=9, color=\"595959\")), (5, value, Font(size=15, bold=True)),\n                             (6, note, Font(size=9, color=\"1F3864\"))]:\n        ws.merge_cells(start_row=r, start_column=c, end_row=r, end_column=c + 1)\n        cell = ws.cell(r, c, content)\n        cell.font, cell.fill = font, tile_fill\n        ws.cell(r, c + 1).fill = tile_fill\n    ws.cell(5, c).number_format = fmt\nws.row_dimensions[5].height = 24\n\n# Band 2: commentary, one merged and wrapped row per sentence\nfor r, text in zip(range(8, 11), lines):\n    ws.merge_cells(start_row=r, start_column=1, end_row=r, end_column=8)\n    ws.cell(r, 1, text).alignment = Alignment(wrap_text=True, vertical=\"top\")\n    ws.row_dimensions[r].height = 30\n",[32,1105,1106,1119,1131,1143,1147,1157,1167,1177,1192,1207,1217,1227,1231,1246,1290,1322,1352,1356,1362,1402,1421,1464,1498,1529,1555,1571,1592,1653,1682,1725,1736,1747,1765,1781,1797,1802,1808,1838,1872,1907],{"__ignoreMap":30},[35,1107,1108,1111,1114,1116],{"class":37,"line":38},[35,1109,1110],{"class":228},"from",[35,1112,1113],{"class":232}," openpyxl ",[35,1115,229],{"class":228},[35,1117,1118],{"class":232}," Workbook\n",[35,1120,1121,1123,1126,1128],{"class":37,"line":242},[35,1122,1110],{"class":228},[35,1124,1125],{"class":232}," openpyxl.chart ",[35,1127,229],{"class":228},[35,1129,1130],{"class":232}," LineChart, Reference\n",[35,1132,1133,1135,1138,1140],{"class":37,"line":249},[35,1134,1110],{"class":228},[35,1136,1137],{"class":232}," openpyxl.styles ",[35,1139,229],{"class":228},[35,1141,1142],{"class":232}," Alignment, Font, PatternFill\n",[35,1144,1145],{"class":37,"line":267},[35,1146,246],{"emptyLinePlaceholder":245},[35,1148,1149,1152,1154],{"class":37,"line":284},[35,1150,1151],{"class":232},"wb ",[35,1153,255],{"class":228},[35,1155,1156],{"class":232}," Workbook()\n",[35,1158,1159,1162,1164],{"class":37,"line":306},[35,1160,1161],{"class":232},"ws ",[35,1163,255],{"class":228},[35,1165,1166],{"class":232}," wb.active\n",[35,1168,1169,1172,1174],{"class":37,"line":344},[35,1170,1171],{"class":232},"ws.title ",[35,1173,255],{"class":228},[35,1175,1176],{"class":45}," \"Summary\"\n",[35,1178,1179,1182,1184,1187,1190],{"class":37,"line":349},[35,1180,1181],{"class":232},"detail ",[35,1183,255],{"class":228},[35,1185,1186],{"class":232}," wb.create_sheet(",[35,1188,1189],{"class":45},"\"Detail\"",[35,1191,264],{"class":232},[35,1193,1194,1196,1199,1201,1204],{"class":37,"line":362},[35,1195,477],{"class":228},[35,1197,1198],{"class":232}," col ",[35,1200,483],{"class":228},[35,1202,1203],{"class":45}," \"ABCDEFGH\"",[35,1205,1206],{"class":232},":\n",[35,1208,1209,1212,1214],{"class":37,"line":404},[35,1210,1211],{"class":232},"    ws.column_dimensions[col].width ",[35,1213,255],{"class":228},[35,1215,1216],{"class":337}," 11.5\n",[35,1218,1219,1222,1224],{"class":37,"line":435},[35,1220,1221],{"class":232},"ws.sheet_view.showGridLines ",[35,1223,255],{"class":228},[35,1225,1226],{"class":337}," False\n",[35,1228,1229],{"class":37,"line":440},[35,1230,246],{"emptyLinePlaceholder":245},[35,1232,1233,1236,1239,1241,1243],{"class":37,"line":451},[35,1234,1235],{"class":232},"ws[",[35,1237,1238],{"class":45},"\"A1\"",[35,1240,297],{"class":232},[35,1242,255],{"class":228},[35,1244,1245],{"class":45}," \"Monthly sales summary\"\n",[35,1247,1248,1250,1252,1255,1257,1260,1263,1265,1268,1270,1273,1275,1278,1280,1283,1285,1288],{"class":37,"line":474},[35,1249,1235],{"class":232},[35,1251,1238],{"class":45},[35,1253,1254],{"class":232},"].font ",[35,1256,255],{"class":228},[35,1258,1259],{"class":232}," Font(",[35,1261,1262],{"class":331},"size",[35,1264,255],{"class":228},[35,1266,1267],{"class":337},"18",[35,1269,639],{"class":232},[35,1271,1272],{"class":331},"bold",[35,1274,255],{"class":228},[35,1276,1277],{"class":337},"True",[35,1279,639],{"class":232},[35,1281,1282],{"class":331},"color",[35,1284,255],{"class":228},[35,1286,1287],{"class":45},"\"1F3864\"",[35,1289,264],{"class":232},[35,1291,1292,1294,1297,1299,1301,1303,1305,1307,1309,1311,1313,1315,1317,1319],{"class":37,"line":489},[35,1293,1235],{"class":232},[35,1295,1296],{"class":45},"\"A2\"",[35,1298,297],{"class":232},[35,1300,255],{"class":228},[35,1302,726],{"class":228},[35,1304,501],{"class":45},[35,1306,505],{"class":504},[35,1308,916],{"class":232},[35,1310,847],{"class":228},[35,1312,850],{"class":232},[35,1314,847],{"class":228},[35,1316,855],{"class":232},[35,1318,514],{"class":504},[35,1320,1321],{"class":45}," · all regions\"\n",[35,1323,1324,1326,1328,1330,1332,1334,1336,1338,1341,1343,1345,1347,1350],{"class":37,"line":543},[35,1325,1235],{"class":232},[35,1327,1296],{"class":45},[35,1329,1254],{"class":232},[35,1331,255],{"class":228},[35,1333,1259],{"class":232},[35,1335,1262],{"class":331},[35,1337,255],{"class":228},[35,1339,1340],{"class":337},"11",[35,1342,639],{"class":232},[35,1344,1282],{"class":331},[35,1346,255],{"class":228},[35,1348,1349],{"class":45},"\"595959\"",[35,1351,264],{"class":232},[35,1353,1354],{"class":37,"line":1026},[35,1355,246],{"emptyLinePlaceholder":245},[35,1357,1358],{"class":37,"line":1069},[35,1359,1361],{"class":1360},"s-wDw","# Band 1: four tiles across, each two columns wide (label, value, comparison)\n",[35,1363,1364,1367,1369,1372,1374,1377,1379,1382,1385,1388,1390,1393,1396,1399],{"class":37,"line":1075},[35,1365,1366],{"class":232},"tiles ",[35,1368,255],{"class":228},[35,1370,1371],{"class":232}," [(",[35,1373,371],{"class":45},[35,1375,1376],{"class":232},", now[",[35,1378,371],{"class":45},[35,1380,1381],{"class":232},"], ",[35,1383,1384],{"class":45},"\"£#,##0\"",[35,1386,1387],{"class":232},", pct(now[",[35,1389,371],{"class":45},[35,1391,1392],{"class":232},"], budget) ",[35,1394,1395],{"class":228},"+",[35,1397,1398],{"class":45}," \" vs budget\"",[35,1400,1401],{"class":232},"),\n",[35,1403,1405,1408,1410,1412,1414,1416,1419],{"class":37,"line":1404},20,[35,1406,1407],{"class":232},"         (",[35,1409,387],{"class":45},[35,1411,1376],{"class":232},[35,1413,382],{"class":45},[35,1415,1381],{"class":232},[35,1417,1418],{"class":45},"\"0.0%\"",[35,1420,929],{"class":232},[35,1422,1424,1427,1429,1431,1434,1436,1438,1440,1443,1445,1448,1451,1454,1457,1459,1462],{"class":37,"line":1423},21,[35,1425,1426],{"class":228},"          f",[35,1428,501],{"class":45},[35,1430,505],{"class":504},[35,1432,1433],{"class":232},"(now[",[35,1435,998],{"class":45},[35,1437,297],{"class":232},[35,1439,325],{"class":228},[35,1441,1442],{"class":232}," before[",[35,1444,998],{"class":45},[35,1446,1447],{"class":232},"]) ",[35,1449,1450],{"class":228},"*",[35,1452,1453],{"class":337}," 100",[35,1455,1456],{"class":228},":+.1f",[35,1458,514],{"class":504},[35,1460,1461],{"class":45}," pts on month\"",[35,1463,1401],{"class":232},[35,1465,1467,1469,1471,1473,1475,1477,1480,1482,1484,1487,1489,1491,1493,1496],{"class":37,"line":1466},22,[35,1468,1407],{"class":232},[35,1470,412],{"class":45},[35,1472,1376],{"class":232},[35,1474,412],{"class":45},[35,1476,1381],{"class":232},[35,1478,1479],{"class":45},"\"#,##0\"",[35,1481,1387],{"class":232},[35,1483,412],{"class":45},[35,1485,1486],{"class":232},"], before[",[35,1488,412],{"class":45},[35,1490,1447],{"class":232},[35,1492,1395],{"class":228},[35,1494,1495],{"class":45}," \" on month\"",[35,1497,1401],{"class":232},[35,1499,1501,1503,1505,1507,1509,1511,1513,1515,1517,1519,1521,1523,1525,1527],{"class":37,"line":1500},23,[35,1502,1407],{"class":232},[35,1504,417],{"class":45},[35,1506,1376],{"class":232},[35,1508,417],{"class":45},[35,1510,1381],{"class":232},[35,1512,1384],{"class":45},[35,1514,1387],{"class":232},[35,1516,417],{"class":45},[35,1518,1486],{"class":232},[35,1520,417],{"class":45},[35,1522,1447],{"class":232},[35,1524,1395],{"class":228},[35,1526,1495],{"class":45},[35,1528,341],{"class":232},[35,1530,1532,1535,1537,1540,1543,1545,1548,1550,1553],{"class":37,"line":1531},24,[35,1533,1534],{"class":232},"tile_fill ",[35,1536,255],{"class":228},[35,1538,1539],{"class":232}," PatternFill(",[35,1541,1542],{"class":45},"\"solid\"",[35,1544,639],{"class":232},[35,1546,1547],{"class":331},"fgColor",[35,1549,255],{"class":228},[35,1551,1552],{"class":45},"\"EEF3FA\"",[35,1554,264],{"class":232},[35,1556,1558,1560,1563,1565,1568],{"class":37,"line":1557},25,[35,1559,477],{"class":228},[35,1561,1562],{"class":232}," i, (label, value, fmt, note) ",[35,1564,483],{"class":228},[35,1566,1567],{"class":337}," enumerate",[35,1569,1570],{"class":232},"(tiles):\n",[35,1572,1574,1577,1579,1581,1584,1587,1589],{"class":37,"line":1573},26,[35,1575,1576],{"class":232},"    c ",[35,1578,255],{"class":228},[35,1580,573],{"class":337},[35,1582,1583],{"class":228}," +",[35,1585,1586],{"class":232}," i ",[35,1588,1450],{"class":228},[35,1590,1591],{"class":337}," 2\n",[35,1593,1595,1598,1601,1603,1605,1608,1611,1613,1615,1618,1620,1622,1624,1626,1629,1632,1635,1637,1639,1642,1644,1646,1648,1650],{"class":37,"line":1594},27,[35,1596,1597],{"class":228},"    for",[35,1599,1600],{"class":232}," r, content, font ",[35,1602,483],{"class":228},[35,1604,1371],{"class":232},[35,1606,1607],{"class":337},"4",[35,1609,1610],{"class":232},", label, Font(",[35,1612,1262],{"class":331},[35,1614,255],{"class":228},[35,1616,1617],{"class":337},"9",[35,1619,639],{"class":232},[35,1621,1282],{"class":331},[35,1623,255],{"class":228},[35,1625,1349],{"class":45},[35,1627,1628],{"class":232},")), (",[35,1630,1631],{"class":337},"5",[35,1633,1634],{"class":232},", value, Font(",[35,1636,1262],{"class":331},[35,1638,255],{"class":228},[35,1640,1641],{"class":337},"15",[35,1643,639],{"class":232},[35,1645,1272],{"class":331},[35,1647,255],{"class":228},[35,1649,1277],{"class":337},[35,1651,1652],{"class":232},")),\n",[35,1654,1656,1659,1662,1665,1667,1669,1671,1673,1675,1677,1679],{"class":37,"line":1655},28,[35,1657,1658],{"class":232},"                             (",[35,1660,1661],{"class":337},"6",[35,1663,1664],{"class":232},", note, Font(",[35,1666,1262],{"class":331},[35,1668,255],{"class":228},[35,1670,1617],{"class":337},[35,1672,639],{"class":232},[35,1674,1282],{"class":331},[35,1676,255],{"class":228},[35,1678,1287],{"class":45},[35,1680,1681],{"class":232},"))]:\n",[35,1683,1685,1688,1691,1693,1696,1699,1701,1704,1707,1709,1711,1714,1716,1719,1721,1723],{"class":37,"line":1684},29,[35,1686,1687],{"class":232},"        ws.merge_cells(",[35,1689,1690],{"class":331},"start_row",[35,1692,255],{"class":228},[35,1694,1695],{"class":232},"r, ",[35,1697,1698],{"class":331},"start_column",[35,1700,255],{"class":228},[35,1702,1703],{"class":232},"c, ",[35,1705,1706],{"class":331},"end_row",[35,1708,255],{"class":228},[35,1710,1695],{"class":232},[35,1712,1713],{"class":331},"end_column",[35,1715,255],{"class":228},[35,1717,1718],{"class":232},"c ",[35,1720,1395],{"class":228},[35,1722,573],{"class":337},[35,1724,264],{"class":232},[35,1726,1728,1731,1733],{"class":37,"line":1727},30,[35,1729,1730],{"class":232},"        cell ",[35,1732,255],{"class":228},[35,1734,1735],{"class":232}," ws.cell(r, c, content)\n",[35,1737,1739,1742,1744],{"class":37,"line":1738},31,[35,1740,1741],{"class":232},"        cell.font, cell.fill ",[35,1743,255],{"class":228},[35,1745,1746],{"class":232}," font, tile_fill\n",[35,1748,1750,1753,1755,1757,1760,1762],{"class":37,"line":1749},32,[35,1751,1752],{"class":232},"        ws.cell(r, c ",[35,1754,1395],{"class":228},[35,1756,573],{"class":337},[35,1758,1759],{"class":232},").fill ",[35,1761,255],{"class":228},[35,1763,1764],{"class":232}," tile_fill\n",[35,1766,1768,1771,1773,1776,1778],{"class":37,"line":1767},33,[35,1769,1770],{"class":232},"    ws.cell(",[35,1772,1631],{"class":337},[35,1774,1775],{"class":232},", c).number_format ",[35,1777,255],{"class":228},[35,1779,1780],{"class":232}," fmt\n",[35,1782,1784,1787,1789,1792,1794],{"class":37,"line":1783},34,[35,1785,1786],{"class":232},"ws.row_dimensions[",[35,1788,1631],{"class":337},[35,1790,1791],{"class":232},"].height ",[35,1793,255],{"class":228},[35,1795,1796],{"class":337}," 24\n",[35,1798,1800],{"class":37,"line":1799},35,[35,1801,246],{"emptyLinePlaceholder":245},[35,1803,1805],{"class":37,"line":1804},36,[35,1806,1807],{"class":1360},"# Band 2: commentary, one merged and wrapped row per sentence\n",[35,1809,1811,1813,1816,1818,1821,1823,1826,1828,1831,1833,1835],{"class":37,"line":1810},37,[35,1812,477],{"class":228},[35,1814,1815],{"class":232}," r, text ",[35,1817,483],{"class":228},[35,1819,1820],{"class":337}," zip",[35,1822,495],{"class":232},[35,1824,1825],{"class":337},"range",[35,1827,495],{"class":232},[35,1829,1830],{"class":337},"8",[35,1832,639],{"class":232},[35,1834,1340],{"class":337},[35,1836,1837],{"class":232},"), lines):\n",[35,1839,1841,1844,1846,1848,1850,1852,1854,1856,1858,1860,1862,1864,1866,1868,1870],{"class":37,"line":1840},38,[35,1842,1843],{"class":232},"    ws.merge_cells(",[35,1845,1690],{"class":331},[35,1847,255],{"class":228},[35,1849,1695],{"class":232},[35,1851,1698],{"class":331},[35,1853,255],{"class":228},[35,1855,338],{"class":337},[35,1857,639],{"class":232},[35,1859,1706],{"class":331},[35,1861,255],{"class":228},[35,1863,1695],{"class":232},[35,1865,1713],{"class":331},[35,1867,255],{"class":228},[35,1869,1830],{"class":337},[35,1871,264],{"class":232},[35,1873,1875,1878,1880,1883,1885,1888,1891,1893,1895,1897,1900,1902,1905],{"class":37,"line":1874},39,[35,1876,1877],{"class":232},"    ws.cell(r, ",[35,1879,338],{"class":337},[35,1881,1882],{"class":232},", text).alignment ",[35,1884,255],{"class":228},[35,1886,1887],{"class":232}," Alignment(",[35,1889,1890],{"class":331},"wrap_text",[35,1892,255],{"class":228},[35,1894,1277],{"class":337},[35,1896,639],{"class":232},[35,1898,1899],{"class":331},"vertical",[35,1901,255],{"class":228},[35,1903,1904],{"class":45},"\"top\"",[35,1906,264],{"class":232},[35,1908,1910,1913,1915],{"class":37,"line":1909},40,[35,1911,1912],{"class":232},"    ws.row_dimensions[r].height ",[35,1914,255],{"class":228},[35,1916,1917],{"class":337}," 30\n",[10,1919,1920,1921,1925],{},"The sheet is eight equal columns wide, and every block is built from them: four tiles of two columns, commentary merged across all eight, the chart spanning the full width. Gridlines are switched off because the summary should look like a page, not a worksheet. Each commentary sentence gets its own merged row with wrapping and a fixed height of two lines — Excel does not grow the height of merged rows to fit their text, so the height has to be set explicitly, and two lines at this width holds a sentence of up to about 180 characters. The tiles follow the pattern in ",[14,1922,1924],{"href":1923},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fadd-a-kpi-summary-block-to-an-excel-dashboard\u002F","Add a KPI Summary Block to an Excel Dashboard",", reduced to one row of four.",[20,1927,1929],{"id":1928},"step-4-chart-movers-and-the-detail-behind-them","Step 4 — Chart, movers and the detail behind them",[25,1931,1933],{"className":219,"code":1932,"language":221,"meta":30,"style":30},"monthly = df.groupby(\"Month\")[[\"Revenue\", \"Budget revenue\", \"Revenue last year\"]].sum()\ndetail.append([\"Month\", \"Revenue\", \"Budget\", \"Last year\"])\nfor m, row in monthly.iterrows():\n    detail.append([m.strftime(\"%b %y\"), *row.round(0).tolist()])\n\nchart = LineChart()\nchart.title = \"Revenue, last 12 months\"\nchart.height, chart.width = 7.5, 17.5\nchart.add_data(Reference(detail, min_col=2, max_col=4, min_row=1, max_row=len(monthly) + 1), titles_from_data=True)\nchart.set_categories(Reference(detail, min_col=1, min_row=2, max_row=len(monthly) + 1))\nchart.y_axis.numFmt = \"£#,##0\"\nchart.x_axis.delete = chart.y_axis.delete = False\nchart.legend.position = \"b\"\nws.add_chart(chart, \"A12\")\n\nws[\"A29\"] = \"Biggest movers on the previous month\"\nws[\"A29\"].font = Font(bold=True, color=\"1F3864\")\nmovers = change.reindex(change.abs().sort_values(ascending=False).index).head(5)\nfor r, (region, delta) in enumerate(movers.items(), start=30):\n    ws.cell(r, 1, region)\n    ws.cell(r, 3, by_region.loc[region, month]).number_format = \"£#,##0\"\n    d = ws.cell(r, 5, delta)\n    d.number_format = '+£#,##0;[Red]-£#,##0'\nws[\"A37\"] = f\"Source: monthly_sales.xlsx · produced {pd.Timestamp.now():%d %b %Y %H:%M}\"\nws[\"A37\"].font = Font(size=8, color=\"808080\")\n",[32,1934,1935,1963,1987,1999,2025,2029,2039,2049,2064,2123,2159,2169,2183,2193,2203,2207,2221,2249,2274,2299,2308,2322,2337,2347,2397],{"__ignoreMap":30},[35,1936,1937,1940,1942,1945,1947,1950,1952,1954,1956,1958,1960],{"class":37,"line":38},[35,1938,1939],{"class":232},"monthly ",[35,1941,255],{"class":228},[35,1943,1944],{"class":232}," df.groupby(",[35,1946,278],{"class":45},[35,1948,1949],{"class":232},")[[",[35,1951,371],{"class":45},[35,1953,639],{"class":232},[35,1955,462],{"class":45},[35,1957,639],{"class":232},[35,1959,468],{"class":45},[35,1961,1962],{"class":232},"]].sum()\n",[35,1964,1965,1968,1970,1972,1974,1976,1979,1981,1984],{"class":37,"line":242},[35,1966,1967],{"class":232},"detail.append([",[35,1969,278],{"class":45},[35,1971,639],{"class":232},[35,1973,371],{"class":45},[35,1975,639],{"class":232},[35,1977,1978],{"class":45},"\"Budget\"",[35,1980,639],{"class":232},[35,1982,1983],{"class":45},"\"Last year\"",[35,1985,1986],{"class":232},"])\n",[35,1988,1989,1991,1994,1996],{"class":37,"line":249},[35,1990,477],{"class":228},[35,1992,1993],{"class":232}," m, row ",[35,1995,483],{"class":228},[35,1997,1998],{"class":232}," monthly.iterrows():\n",[35,2000,2001,2004,2006,2009,2012,2015,2017,2020,2022],{"class":37,"line":267},[35,2002,2003],{"class":232},"    detail.append([m.strftime(",[35,2005,501],{"class":45},[35,2007,2008],{"class":504},"%b",[35,2010,2011],{"class":45}," %y\"",[35,2013,2014],{"class":232},"), ",[35,2016,1450],{"class":228},[35,2018,2019],{"class":232},"row.round(",[35,2021,86],{"class":337},[35,2023,2024],{"class":232},").tolist()])\n",[35,2026,2027],{"class":37,"line":284},[35,2028,246],{"emptyLinePlaceholder":245},[35,2030,2031,2034,2036],{"class":37,"line":306},[35,2032,2033],{"class":232},"chart ",[35,2035,255],{"class":228},[35,2037,2038],{"class":232}," LineChart()\n",[35,2040,2041,2044,2046],{"class":37,"line":344},[35,2042,2043],{"class":232},"chart.title ",[35,2045,255],{"class":228},[35,2047,2048],{"class":45}," \"Revenue, last 12 months\"\n",[35,2050,2051,2054,2056,2059,2061],{"class":37,"line":349},[35,2052,2053],{"class":232},"chart.height, chart.width ",[35,2055,255],{"class":228},[35,2057,2058],{"class":337}," 7.5",[35,2060,639],{"class":232},[35,2062,2063],{"class":337},"17.5\n",[35,2065,2066,2069,2072,2074,2077,2079,2082,2084,2086,2088,2091,2093,2095,2097,2100,2102,2105,2108,2110,2112,2114,2117,2119,2121],{"class":37,"line":362},[35,2067,2068],{"class":232},"chart.add_data(Reference(detail, ",[35,2070,2071],{"class":331},"min_col",[35,2073,255],{"class":228},[35,2075,2076],{"class":337},"2",[35,2078,639],{"class":232},[35,2080,2081],{"class":331},"max_col",[35,2083,255],{"class":228},[35,2085,1607],{"class":337},[35,2087,639],{"class":232},[35,2089,2090],{"class":331},"min_row",[35,2092,255],{"class":228},[35,2094,338],{"class":337},[35,2096,639],{"class":232},[35,2098,2099],{"class":331},"max_row",[35,2101,255],{"class":228},[35,2103,2104],{"class":337},"len",[35,2106,2107],{"class":232},"(monthly) ",[35,2109,1395],{"class":228},[35,2111,573],{"class":337},[35,2113,2014],{"class":232},[35,2115,2116],{"class":331},"titles_from_data",[35,2118,255],{"class":228},[35,2120,1277],{"class":337},[35,2122,264],{"class":232},[35,2124,2125,2128,2130,2132,2134,2136,2138,2140,2142,2144,2146,2148,2150,2152,2154,2156],{"class":37,"line":404},[35,2126,2127],{"class":232},"chart.set_categories(Reference(detail, ",[35,2129,2071],{"class":331},[35,2131,255],{"class":228},[35,2133,338],{"class":337},[35,2135,639],{"class":232},[35,2137,2090],{"class":331},[35,2139,255],{"class":228},[35,2141,2076],{"class":337},[35,2143,639],{"class":232},[35,2145,2099],{"class":331},[35,2147,255],{"class":228},[35,2149,2104],{"class":337},[35,2151,2107],{"class":232},[35,2153,1395],{"class":228},[35,2155,573],{"class":337},[35,2157,2158],{"class":232},"))\n",[35,2160,2161,2164,2166],{"class":37,"line":435},[35,2162,2163],{"class":232},"chart.y_axis.numFmt ",[35,2165,255],{"class":228},[35,2167,2168],{"class":45}," \"£#,##0\"\n",[35,2170,2171,2174,2176,2179,2181],{"class":37,"line":440},[35,2172,2173],{"class":232},"chart.x_axis.delete ",[35,2175,255],{"class":228},[35,2177,2178],{"class":232}," chart.y_axis.delete ",[35,2180,255],{"class":228},[35,2182,1226],{"class":337},[35,2184,2185,2188,2190],{"class":37,"line":451},[35,2186,2187],{"class":232},"chart.legend.position ",[35,2189,255],{"class":228},[35,2191,2192],{"class":45}," \"b\"\n",[35,2194,2195,2198,2201],{"class":37,"line":474},[35,2196,2197],{"class":232},"ws.add_chart(chart, ",[35,2199,2200],{"class":45},"\"A12\"",[35,2202,264],{"class":232},[35,2204,2205],{"class":37,"line":489},[35,2206,246],{"emptyLinePlaceholder":245},[35,2208,2209,2211,2214,2216,2218],{"class":37,"line":543},[35,2210,1235],{"class":232},[35,2212,2213],{"class":45},"\"A29\"",[35,2215,297],{"class":232},[35,2217,255],{"class":228},[35,2219,2220],{"class":45}," \"Biggest movers on the previous month\"\n",[35,2222,2223,2225,2227,2229,2231,2233,2235,2237,2239,2241,2243,2245,2247],{"class":37,"line":1026},[35,2224,1235],{"class":232},[35,2226,2213],{"class":45},[35,2228,1254],{"class":232},[35,2230,255],{"class":228},[35,2232,1259],{"class":232},[35,2234,1272],{"class":331},[35,2236,255],{"class":228},[35,2238,1277],{"class":337},[35,2240,639],{"class":232},[35,2242,1282],{"class":331},[35,2244,255],{"class":228},[35,2246,1287],{"class":45},[35,2248,264],{"class":232},[35,2250,2251,2254,2256,2259,2262,2264,2267,2270,2272],{"class":37,"line":1069},[35,2252,2253],{"class":232},"movers ",[35,2255,255],{"class":228},[35,2257,2258],{"class":232}," change.reindex(change.abs().sort_values(",[35,2260,2261],{"class":331},"ascending",[35,2263,255],{"class":228},[35,2265,2266],{"class":337},"False",[35,2268,2269],{"class":232},").index).head(",[35,2271,1631],{"class":337},[35,2273,264],{"class":232},[35,2275,2276,2278,2281,2283,2285,2288,2291,2293,2296],{"class":37,"line":1075},[35,2277,477],{"class":228},[35,2279,2280],{"class":232}," r, (region, delta) ",[35,2282,483],{"class":228},[35,2284,1567],{"class":337},[35,2286,2287],{"class":232},"(movers.items(), ",[35,2289,2290],{"class":331},"start",[35,2292,255],{"class":228},[35,2294,2295],{"class":337},"30",[35,2297,2298],{"class":232},"):\n",[35,2300,2301,2303,2305],{"class":37,"line":1404},[35,2302,1877],{"class":232},[35,2304,338],{"class":337},[35,2306,2307],{"class":232},", region)\n",[35,2309,2310,2312,2315,2318,2320],{"class":37,"line":1423},[35,2311,1877],{"class":232},[35,2313,2314],{"class":337},"3",[35,2316,2317],{"class":232},", by_region.loc[region, month]).number_format ",[35,2319,255],{"class":228},[35,2321,2168],{"class":45},[35,2323,2324,2327,2329,2332,2334],{"class":37,"line":1466},[35,2325,2326],{"class":232},"    d ",[35,2328,255],{"class":228},[35,2330,2331],{"class":232}," ws.cell(r, ",[35,2333,1631],{"class":337},[35,2335,2336],{"class":232},", delta)\n",[35,2338,2339,2342,2344],{"class":37,"line":1500},[35,2340,2341],{"class":232},"    d.number_format ",[35,2343,255],{"class":228},[35,2345,2346],{"class":45}," '+£#,##0;[Red]-£#,##0'\n",[35,2348,2349,2351,2354,2356,2358,2360,2363,2365,2368,2370,2373,2375,2378,2380,2383,2385,2388,2390,2393,2395],{"class":37,"line":1531},[35,2350,1235],{"class":232},[35,2352,2353],{"class":45},"\"A37\"",[35,2355,297],{"class":232},[35,2357,255],{"class":228},[35,2359,726],{"class":228},[35,2361,2362],{"class":45},"\"Source: monthly_sales.xlsx · produced ",[35,2364,505],{"class":504},[35,2366,2367],{"class":232},"pd.Timestamp.now():",[35,2369,847],{"class":228},[35,2371,2372],{"class":232},"d ",[35,2374,847],{"class":228},[35,2376,2377],{"class":232},"b ",[35,2379,847],{"class":228},[35,2381,2382],{"class":232},"Y ",[35,2384,847],{"class":228},[35,2386,2387],{"class":232},"H:",[35,2389,847],{"class":228},[35,2391,2392],{"class":232},"M",[35,2394,514],{"class":504},[35,2396,749],{"class":45},[35,2398,2399,2401,2403,2405,2407,2409,2411,2413,2415,2417,2419,2421,2424],{"class":37,"line":1557},[35,2400,1235],{"class":232},[35,2402,2353],{"class":45},[35,2404,1254],{"class":232},[35,2406,255],{"class":228},[35,2408,1259],{"class":232},[35,2410,1262],{"class":331},[35,2412,255],{"class":228},[35,2414,1830],{"class":337},[35,2416,639],{"class":232},[35,2418,1282],{"class":331},[35,2420,255],{"class":228},[35,2422,2423],{"class":45},"\"808080\"",[35,2425,264],{"class":232},[10,2427,2428,2429,2433],{},"The chart's data lives on the ",[2430,2431,2432],"em",{},"Detail"," sheet, which also gives readers who want them the actual numbers behind the lines. Three series — actual, budget and last year — are about as many as a small chart can carry before it becomes hard to read. The axes are set explicitly to stay visible, because openpyxl otherwise writes charts whose axes Excel hides. The movers block ranks regions by the size of their change, up or down, so a large fall is as visible as a large rise; the signed number format adds a plus sign to rises and shows falls in red.",[20,2435,2437],{"id":2436},"step-5-make-it-print-on-one-page-and-prove-it","Step 5 — Make it print on one page, and prove it",[25,2439,2441],{"className":219,"code":2440,"language":221,"meta":30,"style":30},"ws.print_area = \"A1:H37\"\nws.page_setup.orientation = \"portrait\"\nws.page_setup.paperSize = ws.PAPERSIZE_A4\nws.page_setup.fitToWidth = ws.page_setup.fitToHeight = 1\nws.sheet_properties.pageSetUpPr.fitToPage = True\nws.page_margins.left = ws.page_margins.right = 0.5\nws.print_options.horizontalCentered = True\nwb.save(\"sales_summary_2026-09.xlsx\")\n",[32,2442,2443,2453,2463,2476,2491,2501,2516,2525],{"__ignoreMap":30},[35,2444,2445,2448,2450],{"class":37,"line":38},[35,2446,2447],{"class":232},"ws.print_area ",[35,2449,255],{"class":228},[35,2451,2452],{"class":45}," \"A1:H37\"\n",[35,2454,2455,2458,2460],{"class":37,"line":242},[35,2456,2457],{"class":232},"ws.page_setup.orientation ",[35,2459,255],{"class":228},[35,2461,2462],{"class":45}," \"portrait\"\n",[35,2464,2465,2468,2470,2473],{"class":37,"line":249},[35,2466,2467],{"class":232},"ws.page_setup.paperSize ",[35,2469,255],{"class":228},[35,2471,2472],{"class":232}," ws.",[35,2474,2475],{"class":337},"PAPERSIZE_A4\n",[35,2477,2478,2481,2483,2486,2488],{"class":37,"line":267},[35,2479,2480],{"class":232},"ws.page_setup.fitToWidth ",[35,2482,255],{"class":228},[35,2484,2485],{"class":232}," ws.page_setup.fitToHeight ",[35,2487,255],{"class":228},[35,2489,2490],{"class":337}," 1\n",[35,2492,2493,2496,2498],{"class":37,"line":284},[35,2494,2495],{"class":232},"ws.sheet_properties.pageSetUpPr.fitToPage ",[35,2497,255],{"class":228},[35,2499,2500],{"class":337}," True\n",[35,2502,2503,2506,2508,2511,2513],{"class":37,"line":306},[35,2504,2505],{"class":232},"ws.page_margins.left ",[35,2507,255],{"class":228},[35,2509,2510],{"class":232}," ws.page_margins.right ",[35,2512,255],{"class":228},[35,2514,2515],{"class":337}," 0.5\n",[35,2517,2518,2521,2523],{"class":37,"line":344},[35,2519,2520],{"class":232},"ws.print_options.horizontalCentered ",[35,2522,255],{"class":228},[35,2524,2500],{"class":337},[35,2526,2527,2530,2533],{"class":37,"line":349},[35,2528,2529],{"class":232},"wb.save(",[35,2531,2532],{"class":45},"\"sales_summary_2026-09.xlsx\"",[35,2534,264],{"class":232},[25,2536,2538],{"className":27,"code":2537,"language":29,"meta":30,"style":30},"soffice --headless --convert-to pdf sales_summary_2026-09.xlsx > \u002Fdev\u002Fnull 2>&1\npdfinfo sales_summary_2026-09.pdf | grep Pages\n",[32,2539,2540,2566],{"__ignoreMap":30},[35,2541,2542,2545,2548,2551,2554,2557,2560,2563],{"class":37,"line":38},[35,2543,2544],{"class":41},"soffice",[35,2546,2547],{"class":337}," --headless",[35,2549,2550],{"class":337}," --convert-to",[35,2552,2553],{"class":45}," pdf",[35,2555,2556],{"class":45}," sales_summary_2026-09.xlsx",[35,2558,2559],{"class":228}," >",[35,2561,2562],{"class":45}," \u002Fdev\u002Fnull",[35,2564,2565],{"class":228}," 2>&1\n",[35,2567,2568,2571,2574,2577,2580],{"class":37,"line":242},[35,2569,2570],{"class":41},"pdfinfo",[35,2572,2573],{"class":45}," sales_summary_2026-09.pdf",[35,2575,2576],{"class":228}," |",[35,2578,2579],{"class":41}," grep",[35,2581,2582],{"class":45}," Pages\n",[10,2584,2585,2586,2588,2589,18],{},"A print area limited to the summary block and fit-to-page in both directions guarantee one page whatever the content does: if a sentence runs long, the page shrinks slightly rather than spilling onto a second sheet. Converting the workbook to PDF with LibreOffice and counting the pages turns \"it should fit\" into a check that can run every month; the ",[2430,2587,2432],{}," sheet adds its own page, so the PDF has two pages in total, and the summary is the first. If the summary ever needs two pages on its own, that is a sign it has stopped being a summary. Printing settings in more depth are in ",[14,2590,2592],{"href":2591},"\u002Fautomating-reporting-workflows\u002Fexporting-excel-reports-to-pdf\u002Ffit-an-excel-sheet-to-one-page-in-pdf\u002F","Fit an Excel Sheet to One Page Wide When Exporting to PDF",[64,2594,73,2600,73,2603,73,2606,73,2611,73,2617,73,2621,73,2627,73,2631,73,2634,73,2638,73,2642,73,2646,73,2650,73,2654,73,2657,73,2659,73,2663,73,2665,73,2668,73,2670,73,2673,73,2675,73,2678,73,2681,73,2684,73,2687,73,2691,73,2693,73,2696,73,2698,73,2701,73,2703,73,2706,73,2709,73,2712,73,2714,73,2718,73,2720,73,2723,73,2725,73,2728,73,2730],{"viewBox":2595,"role":67,"ariaLabelledBy":2596,"xmlns":71,"style":2599},"91 0 577 140",[2597,2598],"exec-tiles-t","exec-tiles-d","width:100%;max-width:577px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[75,2601,2602],{"id":2597},"The headline band for September 2026",[79,2604,2605],{"id":2598},"Four tiles across the top of the page. Revenue of 469,939 pounds, 1.2 percent under budget, gross margin percentage with the change in points, number of orders and average order value, each with the change on the previous month.",[83,2607],{"x":2608,"y":86,"width":2609,"height":2610,"fill":89},"91","577","140",[83,2612],{"x":2613,"y":93,"width":2614,"height":2615,"rx":86,"fill":97,"stroke":98,"style":2616},"107.8","32.0","24.0","stroke-width:1px",[83,2618],{"x":2619,"y":93,"width":2620,"height":2615,"rx":86,"fill":97,"stroke":98,"style":2616},"139.8","126.3",[101,2622,2626],{"x":2623,"y":2624,"style":2625},"202.9","33.0","font-size:11px;font-weight:600;fill:var(--muted,#5b6780);text-anchor:middle","A",[83,2628],{"x":2629,"y":93,"width":2630,"height":2615,"rx":86,"fill":97,"stroke":98,"style":2616},"266.1","135.9",[101,2632,921],{"x":2633,"y":2624,"style":2625},"334.0",[83,2635],{"x":2636,"y":93,"width":2637,"height":2615,"rx":86,"fill":97,"stroke":98,"style":2616},"402.0","127.0",[101,2639,2641],{"x":2640,"y":2624,"style":2625},"465.5","C",[83,2643],{"x":2644,"y":93,"width":2645,"height":2615,"rx":86,"fill":97,"stroke":98,"style":2616},"529.0","123.3",[101,2647,2649],{"x":2648,"y":2624,"style":2625},"590.6","D",[83,2651],{"x":2613,"y":2652,"width":2614,"height":2653,"rx":86,"fill":97,"stroke":98,"style":2616},"40.0","28.0",[101,2655,338],{"x":2656,"y":95,"style":2625},"123.8",[83,2658],{"x":2619,"y":2652,"width":2620,"height":2653,"rx":86,"fill":89,"stroke":98,"style":2616},[101,2660,2662],{"x":2623,"y":95,"style":2661},"font-size:12px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Revenue",[83,2664],{"x":2629,"y":2652,"width":2630,"height":2653,"rx":86,"fill":89,"stroke":98,"style":2616},[101,2666,2667],{"x":2633,"y":95,"style":2661},"Gross margin",[83,2669],{"x":2636,"y":2652,"width":2637,"height":2653,"rx":86,"fill":89,"stroke":98,"style":2616},[101,2671,2672],{"x":2640,"y":95,"style":2661},"Orders",[83,2674],{"x":2644,"y":2652,"width":2645,"height":2653,"rx":86,"fill":89,"stroke":98,"style":2616},[101,2676,2677],{"x":2648,"y":95,"style":2661},"Average order",[83,2679],{"x":2613,"y":2680,"width":2614,"height":2653,"rx":86,"fill":97,"stroke":98,"style":2616},"68.0",[101,2682,2076],{"x":2656,"y":2683,"style":2625},"87.0",[83,2685],{"x":2619,"y":2680,"width":2620,"height":2653,"rx":86,"fill":125,"stroke":116,"style":2686},"stroke-width:1.5px",[101,2688,2690],{"x":2623,"y":2683,"style":2689},"font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","£469,939",[83,2692],{"x":2629,"y":2680,"width":2630,"height":2653,"rx":86,"fill":125,"stroke":116,"style":2686},[101,2694,2695],{"x":2633,"y":2683,"style":2689},"37.0%",[83,2697],{"x":2636,"y":2680,"width":2637,"height":2653,"rx":86,"fill":125,"stroke":116,"style":2686},[101,2699,2700],{"x":2640,"y":2683,"style":2689},"1,153",[83,2702],{"x":2644,"y":2680,"width":2645,"height":2653,"rx":86,"fill":125,"stroke":116,"style":2686},[101,2704,2705],{"x":2648,"y":2683,"style":2689},"£408",[83,2707],{"x":2613,"y":2708,"width":2614,"height":2653,"rx":86,"fill":97,"stroke":98,"style":2616},"96.0",[101,2710,2314],{"x":2656,"y":2711,"style":2625},"115.0",[83,2713],{"x":2619,"y":2708,"width":2620,"height":2653,"rx":86,"fill":89,"stroke":98,"style":2616},[101,2715,2717],{"x":2623,"y":2711,"style":2716},"font-size:12px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","−1.2% vs budget",[83,2719],{"x":2629,"y":2708,"width":2630,"height":2653,"rx":86,"fill":89,"stroke":98,"style":2616},[101,2721,2722],{"x":2633,"y":2711,"style":2716},"−2.4 pts on month",[83,2724],{"x":2636,"y":2708,"width":2637,"height":2653,"rx":86,"fill":89,"stroke":98,"style":2616},[101,2726,2727],{"x":2640,"y":2711,"style":2716},"+3.6% on month",[83,2729],{"x":2644,"y":2708,"width":2645,"height":2653,"rx":86,"fill":89,"stroke":98,"style":2616},[101,2731,2732],{"x":2648,"y":2711,"style":2716},"−1.8% on month",[20,2734,2736],{"id":2735},"what-to-leave-out","What to leave out",[10,2738,2739],{},"The hardest part of a summary page is deciding what is not on it. A useful test is to ask, for each candidate figure, what the reader would do differently if it moved. Figures that would not change anyone's decision belong on the detail sheets. The same goes for precision: revenue to the nearest pound, percentages to one decimal place, never pence on a summary. Limit colour to meaning — red for a fall, the brand colour for headings — and avoid conditional formatting that changes colours from month to month in ways that need a legend. Finally, keep the layout the same every month. Readers learn where to look, and a summary that moves its blocks around makes them search instead of read.",[64,2741,73,2747,73,2750,73,2753,73,2757,73,2763,73,2769,73,2774,73,2778,73,2782,73,2786,73,2790,73,2793,73,2798,73,2802,73,2805,73,2808,73,2811],{"viewBox":2742,"role":67,"ariaLabelledBy":2743,"xmlns":71,"style":2746},"4 0 752 199",[2744,2745],"exec-vs-dash-t","exec-vs-dash-d","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[75,2748,2749],{"id":2744},"Executive summary or dashboard",[79,2751,2752],{"id":2745},"An executive summary is one printed page read top to bottom with written commentary and a few figures. A dashboard is an interactive screen with filters, more charts and no fixed page size.",[83,2754],{"x":1607,"y":86,"width":2755,"height":2756,"fill":89},"752","199",[83,2758],{"x":2759,"y":93,"width":2760,"height":2761,"rx":2762,"fill":125,"stroke":116,"style":99},"20.0","351.0","167.0","14",[101,2764,2768],{"x":2765,"y":2766,"style":2767},"195.5","44.0","font-size:14px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Executive summary",[37,2770],{"x1":2771,"y1":2772,"x2":2773,"y2":2772,"stroke":116,"style":2616},"36.0","57.0","355.0",[101,2775,2777],{"x":2765,"y":2776,"style":2716},"77.0","One page, prints and forwards well",[101,2779,2781],{"x":2765,"y":2780,"style":2716},"103.0","Four figures, three sentences",[101,2783,2785],{"x":2765,"y":2784,"style":2716},"129.0","Read top to bottom",[101,2787,2789],{"x":2765,"y":2788,"style":2716},"155.0","Same layout every month",[83,2791],{"x":2792,"y":93,"width":2760,"height":2761,"rx":2762,"fill":145,"stroke":146,"style":99},"389.0",[101,2794,2797],{"x":2795,"y":2766,"style":2796},"564.5","font-size:14px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Dashboard",[37,2799],{"x1":2800,"y1":2772,"x2":2801,"y2":2772,"stroke":146,"style":2616},"405.0","724.0",[101,2803,2804],{"x":2795,"y":2776,"style":2716},"Screen-sized, interactive",[101,2806,2807],{"x":2795,"y":2780,"style":2716},"Many charts and filters",[101,2809,2810],{"x":2795,"y":2784,"style":2716},"Explored, not read",[101,2812,2813],{"x":2795,"y":2788,"style":2716},"Grows with new questions",[20,2815,2817],{"id":2816},"common-pitfalls","Common pitfalls",[2819,2820,2821,2829,2835,2845,2851],"ul",{},[2822,2823,2824,2828],"li",{},[2825,2826,2827],"strong",{},"Merged rows that hide text."," Set the row height explicitly; merged cells do not auto-fit.",[2822,2830,2831,2834],{},[2825,2832,2833],{},"Hand-written commentary."," It goes stale; generate the factual sentences from the data.",[2822,2836,2837,2840,2841,2844],{},[2825,2838,2839],{},"Hidden chart axes."," Set ",[32,2842,2843],{},"delete = False"," on both axes.",[2822,2846,2847,2850],{},[2825,2848,2849],{},"No print settings."," Set the print area and fit-to-page, then check the PDF page count.",[2822,2852,2853,2856],{},[2825,2854,2855],{},"Too many figures."," Four headline numbers; everything else belongs behind the summary.",[20,2858,2860],{"id":2859},"conclusion","Conclusion",[10,2862,2863],{},"A one-page executive summary is the first sheet most readers see and often the only one. Build it from a few headline figures compared with budget, last year and last month, add three sentences of commentary generated from the same numbers, a twelve-month trend chart and the biggest movers, then fix the print area and fit-to-page and confirm with a PDF conversion that it really is one page.",[20,2865,2867],{"id":2866},"frequently-asked-questions","Frequently asked questions",[10,2869,2870,2873,2874,2877,2878,2881,2882,18],{},[2825,2871,2872],{},"How do I make an Excel sheet print on one page with openpyxl?","\nSet the print area, set ",[32,2875,2876],{},"fitToWidth"," and ",[32,2879,2880],{},"fitToHeight"," to 1 and enable ",[32,2883,2884],{},"sheet_properties.pageSetUpPr.fitToPage",[10,2886,2887,2890],{},[2825,2888,2889],{},"How can I generate report commentary automatically?","\nWrite sentence templates with placeholders and fill them from the same figures as the report, so the words always match the numbers.",[10,2892,2893,2896],{},[2825,2894,2895],{},"Why does my merged commentary cell cut off text?","\nExcel does not auto-fit row height for merged cells; set the row height in openpyxl to fit the longest expected text.",[10,2898,2899,2902],{},[2825,2900,2901],{},"How many figures should an executive summary have?","\nUsually four to six; each one should be something that would change a decision if it moved.",[20,2904,2906],{"id":2905},"related","Related",[2819,2908,2909,2915,2922,2929],{},[2822,2910,2911,2912,2914],{},"Up one level: ",[14,2913,17],{"href":16}," — dashboards over several sheets.",[2822,2916,2917,2921],{},[14,2918,2920],{"href":2919},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fadd-summary-sheet-to-excel-report-python\u002F","Add a Summary Sheet to an Excel Report with Python"," — a summary built from other sheets.",[2822,2923,2924,2928],{},[14,2925,2927],{"href":2926},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Flay-out-a-dashboard-sheet-on-a-grid\u002F","Lay Out a Dashboard Sheet on a Grid"," — grid layout for the blocks.",[2822,2930,2931,2935],{},[14,2932,2934],{"href":2933},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fadd-variance-arrows-and-trend-indicators-to-a-dashboard\u002F","Add Variance Arrows and Trend Indicators to a Dashboard"," — arrows next to the figures.",[2937,2938,2939],"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 .s_Opv, html code.shiki .s_Opv{--shiki-default:#6F42C1;--shiki-dark:#DBB7FF}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}",{"title":30,"searchDepth":242,"depth":242,"links":2941},[2942,2943,2944,2945,2946,2947,2948,2949,2950,2951,2952,2953],{"id":22,"depth":242,"text":23},{"id":58,"depth":242,"text":59},{"id":215,"depth":242,"text":216},{"id":612,"depth":242,"text":613},{"id":1099,"depth":242,"text":1100},{"id":1928,"depth":242,"text":1929},{"id":2436,"depth":242,"text":2437},{"id":2735,"depth":242,"text":2736},{"id":2816,"depth":242,"text":2817},{"id":2859,"depth":242,"text":2860},{"id":2866,"depth":242,"text":2867},{"id":2905,"depth":242,"text":2906},"2026-10-02","Build the first sheet of a monthly Excel report as a one-page executive summary with Python: four headline figures against budget and last year, three sentences of commentary written from the numbers, a small trend chart, the biggest movers, and page setup that prints on exactly one A4 sheet — checked by converting it to PDF.","md",[2958,2960,2962,2964],{"q":2872,"a":2959},"Set the print area, set fitToWidth and fitToHeight to 1 and enable sheet_properties.pageSetUpPr.fitToPage.",{"q":2889,"a":2961},"Write sentence templates with placeholders and fill them from the same figures as the report, so the words always match the numbers.",{"q":2895,"a":2963},"Excel does not auto-fit row height for merged cells; set the row height in openpyxl to fit the longest expected text.",{"q":2901,"a":2965},"Usually four to six; each one should be something that would change a decision if it moved.","executive summary sheet excel python",null,{"breadcrumb":2969},[2970,2972,2975],{"name":2971,"item":393},"Home",{"name":2973,"item":2974},"Automating Reporting Workflows with Python and Excel","\u002Fautomating-reporting-workflows\u002F",{"name":17,"item":16},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-one-page-executive-summary-sheet",{"title":2978,"description":2979},"Build a One-Page Executive Summary Sheet in Excel with Python","A one-page executive summary sheet with openpyxl: headline figures against budget and last year, commentary written from the data, a trend chart, top movers and print settings verified to fit one A4 page.","build-a-one-page-executive-summary-sheet","automating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-one-page-executive-summary-sheet\u002Findex",[2983,2984],"report-layouts","charts-and-images","how-to","BpOOkaGIDROd7QkbmPsyF78k33LrKxBhw9_ns1W3VhQ",[2988,2992],{"title":2989,"path":2990,"stem":2991,"children":-1},"Build a Dashboard Sheet with Charts from Multiple Tabs","\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-dashboard-sheet-with-charts-from-multiple-tabs","automating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-dashboard-sheet-with-charts-from-multiple-tabs\u002Findex",{"title":2993,"path":2994,"stem":2995,"children":-1},"Build a Rolling 12-Month Chart on an Excel Dashboard","\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-rolling-12-month-chart-on-an-excel-dashboard","automating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards\u002Fbuild-a-rolling-12-month-chart-on-an-excel-dashboard\u002Findex",[2997,2999],{"path":2998,"title":17},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards",{"path":3000,"title":2973},"\u002Fautomating-reporting-workflows",[],[3003,3007,3010,3013,3016,3019],{"path":3004,"title":3005,"kind":3006},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports","Building Finance and Operations Reports in Excel with Python","guide",{"path":3008,"title":3009,"kind":2985},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fallocate-shared-costs-across-departments","Allocate Shared Costs Across Departments with Python",{"path":3011,"title":3012,"kind":2985},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-bank-reconciliation-report","Build a Bank Reconciliation Report with Python and Excel",{"path":3014,"title":3015,"kind":2985},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-bill-of-materials-cost-rollup","Build a Bill of Materials Cost Rollup",{"path":3017,"title":3018,"kind":2985},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-break-even-analysis-in-excel-with-python","Build a Break-Even Analysis in Excel with Python",{"path":3020,"title":1096,"kind":3021},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-budget-vs-actual-variance-report","compare",1790972129425]