[{"data":1,"prerenderedAt":3909},["ShallowReactive",2],{"doc:\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-cash-flow-forecast-report":3,"surround:\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-cash-flow-forecast-report":3874,"ancestors:\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-cash-flow-forecast-report":3883,"children:\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-cash-flow-forecast-report":3888,"related:\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-cash-flow-forecast-report":3889},{"id":4,"title":5,"body":6,"dateModified":3839,"datePublished":3839,"description":3840,"extension":3841,"faq":3842,"keyword":3851,"kind":3852,"meta":3853,"navigation":278,"path":3863,"seo":3864,"slug":3867,"stem":3868,"topics":3869,"type":3872,"__hash__":3873},"docs\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-cash-flow-forecast-report\u002Findex.md","Build a 13-Week Cash Flow Forecast Report in Excel with Python",{"type":7,"value":8,"toc":3823},"minimark",[9,19,24,56,74,243,247,250,482,486,489,849,857,861,864,1025,1029,1032,1265,1268,1272,1275,1413,1416,1420,1423,1864,1867,2076,2080,2083,3269,3425,3428,3432,3442,3478,3532,3684,3691,3695,3703,3707,3742,3746,3757,3761,3767,3773,3779,3785,3789,3819],[10,11,12,13,18],"p",{},"A 13-week cash flow forecast answers the question a profit forecast cannot: will there be enough money in the bank each week of the next quarter? It is built bottom-up from things that already exist — invoices customers owe, bills the business owes, payroll and rent that recur — placed in the week the cash will actually move. Done by hand in Excel it is a weekly chore of copying ledgers and dragging dates. Built in Python it becomes a script that reads the ledger exports, applies the timing rules once, and writes the same report every Monday. This page is part of ",[14,15,17],"a",{"href":16},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002F","Building Finance and Operations Reports",".",[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 xlsxwriter\n","bash","",[32,33,34],"code",{"__ignoreMap":30},[35,36,39,43,47,50,53],"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",[35,54,55],{"class":45}," xlsxwriter\n",[10,57,58,61,62,65,66,69,70,73],{},[32,59,60],{},"ledgers.xlsx"," holds the exports a finance system typically provides: ",[32,63,64],{},"AR",", the open customer invoices with due dates; ",[32,67,68],{},"CustomerHistory",", each customer's average days paid late over the last year; and ",[32,71,72],{},"AP",", the open supplier bills. Recurring commitments that are not in either ledger yet — payroll, rent, a loan repayment — are defined in the script. The forecast starts on Monday 5 October 2026 with 90,000 in the bank.",[75,76,84,85,84,89,84,93,84,101,84,111,84,122,84,129,84,134,84,142,84,149,84,155,84,160,84,163,84,168,84,173,84,180,84,185,84,189,84,192,84,194,84,197,84,203,84,208,84,212,84,215,84,217,84,222,84,229,84,234,84,238,84,241],"svg",{"viewBox":77,"role":78,"ariaLabelledBy":79,"xmlns":82,"style":83},"4 0 752 436","img",[80,81],"cf-sources-t","cf-sources-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","width:100%;max-width:752px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif","\n  ",[86,87,88],"title",{"id":80},"Where each line of the forecast comes from",[90,91,92],"desc",{"id":81},"Four sources feed the weekly forecast. Open receivables shifted by each customer's days late become receipts. Open payables at their due dates become supplier payments. Recurring commitments such as payroll, rent and loan repayments are generated from a schedule, and a run rate estimates receipts from sales not yet invoiced.",[94,95],"rect",{"x":96,"y":97,"width":98,"height":99,"fill":100},"4","0","752","436","#ffffff",[94,102],{"x":103,"y":104,"width":105,"height":106,"rx":107,"fill":108,"stroke":109,"style":110},"520.0","171.5","220.0","93.0","12","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:2px",[112,113,115],"g",{"transform":114},"translate(618.0,184.5) scale(1.000)",[116,117],"path",{"d":118,"fill":119,"stroke":120,"style":121},"M4 4h16v16H4z M4 9h16 M4 14h16 M10 4v16","none","var(--brand-strong,#4338ca)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[123,124,128],"text",{"x":125,"y":126,"style":127},"630.0","231.0","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Weekly forecast",[123,130,133],{"x":125,"y":131,"style":132},"250.5","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","13 weeks",[94,135],{"x":136,"y":137,"width":138,"height":139,"rx":107,"fill":140,"stroke":141,"style":110},"20.0","16.0","250.0","92.0","#d9f4f1","var(--teal,#0f9488)",[112,143,145],{"transform":144},"translate(133.0,29.0) scale(1.000)",[116,146],{"d":147,"fill":119,"stroke":148,"style":121},"M9 11a3.5 3.5 0 1 0 0-7 3.5 3.5 0 0 0 0 7z M2.5 20c0-3.6 2.9-6 6.5-6s6.5 2.4 6.5 6 M16 4.5a3.3 3.3 0 0 1 0 6.3 M18 14.3c2.2.7 3.5 2.8 3.5 5.7","var(--teal-ink,#0b6157)",[123,150,154],{"x":151,"y":152,"style":153},"145.0","75.0","font-size:13px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Open receivables",[123,156,159],{"x":151,"y":157,"style":158},"94.0","font-size:11px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","due date + days late",[116,161],{"d":162,"fill":119,"stroke":109,"style":110},"M274.0,62.0 C394.0,62.0 394.0,218.0 510.0,218.0",[164,165],"polygon",{"points":166,"fill":167},"514.0,218.0 505.0,223.0 505.0,213.0","#5b5cf0",[94,169],{"x":136,"y":170,"width":138,"height":139,"rx":107,"fill":171,"stroke":172,"style":110},"120.0","#fdefd8","var(--gold,#b4740a)",[112,174,176],{"transform":175},"translate(133.0,133.0) scale(1.000)",[116,177],{"d":178,"fill":119,"stroke":179,"style":121},"M6 3h9l4 4v14H6z M15 3v4h4","var(--gold-ink,#7a4e06)",[123,181,184],{"x":151,"y":182,"style":183},"179.0","font-size:13px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Open payables",[123,186,188],{"x":151,"y":187,"style":158},"198.0","at due date",[116,190],{"d":191,"fill":119,"stroke":109,"style":110},"M274.0,166.0 C394.0,166.0 394.0,218.0 510.0,218.0",[164,193],{"points":166,"fill":167},[94,195],{"x":136,"y":196,"width":138,"height":139,"rx":107,"fill":108,"stroke":109,"style":110},"224.0",[112,198,200],{"transform":199},"translate(133.0,237.0) scale(1.000)",[116,201],{"d":202,"fill":119,"stroke":120,"style":121},"M4 5h16v15H4z M4 10h16 M8 3v4 M16 3v4",[123,204,207],{"x":151,"y":205,"style":206},"283.0","font-size:13px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Recurring items",[123,209,211],{"x":151,"y":210,"style":158},"302.0","payroll, rent, loan",[116,213],{"d":214,"fill":119,"stroke":109,"style":110},"M274.0,270.0 C394.0,270.0 394.0,218.0 510.0,218.0",[164,216],{"points":166,"fill":167},[94,218],{"x":136,"y":219,"width":138,"height":139,"rx":107,"fill":220,"stroke":221,"style":110},"328.0","#e7ebef","var(--line,#cdd5e6)",[112,223,225],{"transform":224},"translate(133.0,341.0) scale(1.000)",[116,226],{"d":227,"fill":119,"stroke":228,"style":121},"M4 20h16 M4 20V4 M6 16l4-5 4 3 6-7","var(--text,#172033)",[123,230,233],{"x":151,"y":231,"style":232},"387.0","font-size:13px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Future billings",[123,235,237],{"x":151,"y":236,"style":158},"406.0","run-rate estimate",[116,239],{"d":240,"fill":119,"stroke":109,"style":110},"M274.0,374.0 C394.0,374.0 394.0,218.0 510.0,218.0",[164,242],{"points":166,"fill":167},[20,244,246],{"id":245},"step-1-set-the-horizon-and-the-weeks","Step 1 — Set the horizon and the weeks",[10,248,249],{},"Everything is bucketed into weeks starting on Monday. Define the start, the horizon and a helper that maps any date to the Monday of its week:",[25,251,255],{"className":252,"code":253,"language":254,"meta":30,"style":30},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\nSTART = pd.Timestamp(\"2026-10-05\")        # a Monday\nWEEKS = 13\nOPENING_CASH = 90_000.00\nMIN_CASH = 60_000.00\n\nweeks = pd.date_range(START, periods=WEEKS, freq=\"W-MON\")\nend = weeks[-1] + pd.Timedelta(days=6)\n\ndef week_of(dates):\n    return dates - pd.to_timedelta(dates.dt.weekday, unit=\"D\")\n\nprint(weeks[0].date(), \"to\", end.date())\n","python",[32,256,257,273,280,303,314,325,336,341,379,415,420,433,457,462],{"__ignoreMap":30},[35,258,259,263,267,270],{"class":37,"line":38},[35,260,262],{"class":261},"s-kum","import",[35,264,266],{"class":265},"skGVy"," pandas ",[35,268,269],{"class":261},"as",[35,271,272],{"class":265}," pd\n",[35,274,276],{"class":37,"line":275},2,[35,277,279],{"emptyLinePlaceholder":278},true,"\n",[35,281,283,287,290,293,296,299],{"class":37,"line":282},3,[35,284,286],{"class":285},"sP0c6","START",[35,288,289],{"class":261}," =",[35,291,292],{"class":265}," pd.Timestamp(",[35,294,295],{"class":45},"\"2026-10-05\"",[35,297,298],{"class":265},")        ",[35,300,302],{"class":301},"s-wDw","# a Monday\n",[35,304,306,309,311],{"class":37,"line":305},4,[35,307,308],{"class":285},"WEEKS",[35,310,289],{"class":261},[35,312,313],{"class":285}," 13\n",[35,315,317,320,322],{"class":37,"line":316},5,[35,318,319],{"class":285},"OPENING_CASH",[35,321,289],{"class":261},[35,323,324],{"class":285}," 90_000.00\n",[35,326,328,331,333],{"class":37,"line":327},6,[35,329,330],{"class":285},"MIN_CASH",[35,332,289],{"class":261},[35,334,335],{"class":285}," 60_000.00\n",[35,337,339],{"class":37,"line":338},7,[35,340,279],{"emptyLinePlaceholder":278},[35,342,344,347,350,353,355,358,362,364,366,368,371,373,376],{"class":37,"line":343},8,[35,345,346],{"class":265},"weeks ",[35,348,349],{"class":261},"=",[35,351,352],{"class":265}," pd.date_range(",[35,354,286],{"class":285},[35,356,357],{"class":265},", ",[35,359,361],{"class":360},"sa561","periods",[35,363,349],{"class":261},[35,365,308],{"class":285},[35,367,357],{"class":265},[35,369,370],{"class":360},"freq",[35,372,349],{"class":261},[35,374,375],{"class":45},"\"W-MON\"",[35,377,378],{"class":265},")\n",[35,380,382,385,387,390,393,396,399,402,405,408,410,413],{"class":37,"line":381},9,[35,383,384],{"class":265},"end ",[35,386,349],{"class":261},[35,388,389],{"class":265}," weeks[",[35,391,392],{"class":261},"-",[35,394,395],{"class":285},"1",[35,397,398],{"class":265},"] ",[35,400,401],{"class":261},"+",[35,403,404],{"class":265}," pd.Timedelta(",[35,406,407],{"class":360},"days",[35,409,349],{"class":261},[35,411,412],{"class":285},"6",[35,414,378],{"class":265},[35,416,418],{"class":37,"line":417},10,[35,419,279],{"emptyLinePlaceholder":278},[35,421,423,426,430],{"class":37,"line":422},11,[35,424,425],{"class":261},"def",[35,427,429],{"class":428},"s_Opv"," week_of",[35,431,432],{"class":265},"(dates):\n",[35,434,436,439,442,444,447,450,452,455],{"class":37,"line":435},12,[35,437,438],{"class":261},"    return",[35,440,441],{"class":265}," dates ",[35,443,392],{"class":261},[35,445,446],{"class":265}," pd.to_timedelta(dates.dt.weekday, ",[35,448,449],{"class":360},"unit",[35,451,349],{"class":261},[35,453,454],{"class":45},"\"D\"",[35,456,378],{"class":265},[35,458,460],{"class":37,"line":459},13,[35,461,279],{"emptyLinePlaceholder":278},[35,463,465,468,471,473,476,479],{"class":37,"line":464},14,[35,466,467],{"class":285},"print",[35,469,470],{"class":265},"(weeks[",[35,472,97],{"class":285},[35,474,475],{"class":265},"].date(), ",[35,477,478],{"class":45},"\"to\"",[35,480,481],{"class":265},", end.date())\n",[20,483,485],{"id":484},"step-2-expected-receipts-from-receivables","Step 2 — Expected receipts from receivables",[10,487,488],{},"A due date is when the customer should pay; the forecast needs when they will. Shift each invoice by that customer's average days late. Invoices already overdue are assumed to arrive in the first week at the earliest, rather than in the past:",[25,490,492],{"className":252,"code":491,"language":254,"meta":30,"style":30},"ar = pd.read_excel(\"ledgers.xlsx\", sheet_name=\"AR\", parse_dates=[\"DueDate\"])\nhistory = pd.read_excel(\"ledgers.xlsx\", sheet_name=\"CustomerHistory\")\n\nar = ar.merge(history, on=\"Customer\", how=\"left\", validate=\"many_to_one\")\nar[\"AvgDaysLate\"] = ar[\"AvgDaysLate\"].fillna(30)   # unknown customers: assume a month late\nar[\"Expected\"] = ar[\"DueDate\"] + pd.to_timedelta(ar[\"AvgDaysLate\"], unit=\"D\")\nar[\"Expected\"] = ar[\"Expected\"].clip(lower=START)\nar[\"Week\"] = week_of(ar[\"Expected\"])\n\nbeyond = ar[ar[\"Expected\"] > end]\nprint(f\"{len(ar)} invoices, {ar['Amount'].sum():,.2f} open; \"\n      f\"{len(beyond)} expected after the horizon ({beyond['Amount'].sum():,.2f})\")\nprint(ar.groupby(\"Customer\")[\"Amount\"].sum().round(2).sort_values(ascending=False).to_string())\n",[32,493,494,533,555,559,598,627,662,688,706,710,730,777,814],{"__ignoreMap":30},[35,495,496,499,501,504,507,509,512,514,517,519,522,524,527,530],{"class":37,"line":38},[35,497,498],{"class":265},"ar ",[35,500,349],{"class":261},[35,502,503],{"class":265}," pd.read_excel(",[35,505,506],{"class":45},"\"ledgers.xlsx\"",[35,508,357],{"class":265},[35,510,511],{"class":360},"sheet_name",[35,513,349],{"class":261},[35,515,516],{"class":45},"\"AR\"",[35,518,357],{"class":265},[35,520,521],{"class":360},"parse_dates",[35,523,349],{"class":261},[35,525,526],{"class":265},"[",[35,528,529],{"class":45},"\"DueDate\"",[35,531,532],{"class":265},"])\n",[35,534,535,538,540,542,544,546,548,550,553],{"class":37,"line":275},[35,536,537],{"class":265},"history ",[35,539,349],{"class":261},[35,541,503],{"class":265},[35,543,506],{"class":45},[35,545,357],{"class":265},[35,547,511],{"class":360},[35,549,349],{"class":261},[35,551,552],{"class":45},"\"CustomerHistory\"",[35,554,378],{"class":265},[35,556,557],{"class":37,"line":282},[35,558,279],{"emptyLinePlaceholder":278},[35,560,561,563,565,568,571,573,576,578,581,583,586,588,591,593,596],{"class":37,"line":305},[35,562,498],{"class":265},[35,564,349],{"class":261},[35,566,567],{"class":265}," ar.merge(history, ",[35,569,570],{"class":360},"on",[35,572,349],{"class":261},[35,574,575],{"class":45},"\"Customer\"",[35,577,357],{"class":265},[35,579,580],{"class":360},"how",[35,582,349],{"class":261},[35,584,585],{"class":45},"\"left\"",[35,587,357],{"class":265},[35,589,590],{"class":360},"validate",[35,592,349],{"class":261},[35,594,595],{"class":45},"\"many_to_one\"",[35,597,378],{"class":265},[35,599,600,603,606,608,610,613,615,618,621,624],{"class":37,"line":316},[35,601,602],{"class":265},"ar[",[35,604,605],{"class":45},"\"AvgDaysLate\"",[35,607,398],{"class":265},[35,609,349],{"class":261},[35,611,612],{"class":265}," ar[",[35,614,605],{"class":45},[35,616,617],{"class":265},"].fillna(",[35,619,620],{"class":285},"30",[35,622,623],{"class":265},")   ",[35,625,626],{"class":301},"# unknown customers: assume a month late\n",[35,628,629,631,634,636,638,640,642,644,646,649,651,654,656,658,660],{"class":37,"line":327},[35,630,602],{"class":265},[35,632,633],{"class":45},"\"Expected\"",[35,635,398],{"class":265},[35,637,349],{"class":261},[35,639,612],{"class":265},[35,641,529],{"class":45},[35,643,398],{"class":265},[35,645,401],{"class":261},[35,647,648],{"class":265}," pd.to_timedelta(ar[",[35,650,605],{"class":45},[35,652,653],{"class":265},"], ",[35,655,449],{"class":360},[35,657,349],{"class":261},[35,659,454],{"class":45},[35,661,378],{"class":265},[35,663,664,666,668,670,672,674,676,679,682,684,686],{"class":37,"line":338},[35,665,602],{"class":265},[35,667,633],{"class":45},[35,669,398],{"class":265},[35,671,349],{"class":261},[35,673,612],{"class":265},[35,675,633],{"class":45},[35,677,678],{"class":265},"].clip(",[35,680,681],{"class":360},"lower",[35,683,349],{"class":261},[35,685,286],{"class":285},[35,687,378],{"class":265},[35,689,690,692,695,697,699,702,704],{"class":37,"line":343},[35,691,602],{"class":265},[35,693,694],{"class":45},"\"Week\"",[35,696,398],{"class":265},[35,698,349],{"class":261},[35,700,701],{"class":265}," week_of(ar[",[35,703,633],{"class":45},[35,705,532],{"class":265},[35,707,708],{"class":37,"line":381},[35,709,279],{"emptyLinePlaceholder":278},[35,711,712,715,717,720,722,724,727],{"class":37,"line":417},[35,713,714],{"class":265},"beyond ",[35,716,349],{"class":261},[35,718,719],{"class":265}," ar[ar[",[35,721,633],{"class":45},[35,723,398],{"class":265},[35,725,726],{"class":261},">",[35,728,729],{"class":265}," end]\n",[35,731,732,734,737,740,743,747,750,753,756,759,761,763,766,769,772,774],{"class":37,"line":422},[35,733,467],{"class":285},[35,735,736],{"class":265},"(",[35,738,739],{"class":261},"f",[35,741,742],{"class":45},"\"",[35,744,746],{"class":745},"sSjpA","{",[35,748,749],{"class":285},"len",[35,751,752],{"class":265},"(ar)",[35,754,755],{"class":745},"}",[35,757,758],{"class":45}," invoices, ",[35,760,746],{"class":745},[35,762,602],{"class":265},[35,764,765],{"class":45},"'Amount'",[35,767,768],{"class":265},"].sum()",[35,770,771],{"class":261},":,.2f",[35,773,755],{"class":745},[35,775,776],{"class":45}," open; \"\n",[35,778,779,782,784,786,788,791,793,796,798,801,803,805,807,809,812],{"class":37,"line":435},[35,780,781],{"class":261},"      f",[35,783,742],{"class":45},[35,785,746],{"class":745},[35,787,749],{"class":285},[35,789,790],{"class":265},"(beyond)",[35,792,755],{"class":745},[35,794,795],{"class":45}," expected after the horizon (",[35,797,746],{"class":745},[35,799,800],{"class":265},"beyond[",[35,802,765],{"class":45},[35,804,768],{"class":265},[35,806,771],{"class":261},[35,808,755],{"class":745},[35,810,811],{"class":45},")\"",[35,813,378],{"class":265},[35,815,816,818,821,823,826,829,832,835,838,841,843,846],{"class":37,"line":459},[35,817,467],{"class":285},[35,819,820],{"class":265},"(ar.groupby(",[35,822,575],{"class":45},[35,824,825],{"class":265},")[",[35,827,828],{"class":45},"\"Amount\"",[35,830,831],{"class":265},"].sum().round(",[35,833,834],{"class":285},"2",[35,836,837],{"class":265},").sort_values(",[35,839,840],{"class":360},"ascending",[35,842,349],{"class":261},[35,844,845],{"class":285},"False",[35,847,848],{"class":265},").to_string())\n",[10,850,851,852,856],{},"The fallback for customers with no history is a decision to make visibly and review; 30 days late is deliberately cautious. More careful forecasts use a distribution of payment delays per customer, but the average is where most teams start, and it can be checked each week against what actually arrived. ",[14,853,855],{"href":854},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-an-accounts-receivable-aging-report\u002F","Build an Accounts Receivable Aging Report"," produces the payment history this step depends on.",[20,858,860],{"id":859},"step-3-payments-to-suppliers","Step 3 — Payments to suppliers",[10,862,863],{},"Supplier bills are paid on their due date, or on the next payment run. Here payments go out on Thursdays, so each bill moves to the Thursday on or after its due date, which keeps it in the same Monday-to-Sunday week unless it falls due on Friday to Sunday:",[25,865,867],{"className":252,"code":866,"language":254,"meta":30,"style":30},"ap = pd.read_excel(\"ledgers.xlsx\", sheet_name=\"AP\", parse_dates=[\"DueDate\"])\ndays_to_thursday = (3 - ap[\"DueDate\"].dt.weekday) % 7\nap[\"PayDate\"] = (ap[\"DueDate\"] + pd.to_timedelta(days_to_thursday, unit=\"D\")).clip(lower=START)\nap[\"Week\"] = week_of(ap[\"PayDate\"])\nprint(ap[[\"Bill\", \"DueDate\", \"PayDate\"]].head(4).to_string(index=False))\n",[32,868,869,901,931,972,989],{"__ignoreMap":30},[35,870,871,874,876,878,880,882,884,886,889,891,893,895,897,899],{"class":37,"line":38},[35,872,873],{"class":265},"ap ",[35,875,349],{"class":261},[35,877,503],{"class":265},[35,879,506],{"class":45},[35,881,357],{"class":265},[35,883,511],{"class":360},[35,885,349],{"class":261},[35,887,888],{"class":45},"\"AP\"",[35,890,357],{"class":265},[35,892,521],{"class":360},[35,894,349],{"class":261},[35,896,526],{"class":265},[35,898,529],{"class":45},[35,900,532],{"class":265},[35,902,903,906,908,911,914,917,920,922,925,928],{"class":37,"line":275},[35,904,905],{"class":265},"days_to_thursday ",[35,907,349],{"class":261},[35,909,910],{"class":265}," (",[35,912,913],{"class":285},"3",[35,915,916],{"class":261}," -",[35,918,919],{"class":265}," ap[",[35,921,529],{"class":45},[35,923,924],{"class":265},"].dt.weekday) ",[35,926,927],{"class":261},"%",[35,929,930],{"class":285}," 7\n",[35,932,933,936,939,941,943,946,948,950,952,955,957,959,961,964,966,968,970],{"class":37,"line":282},[35,934,935],{"class":265},"ap[",[35,937,938],{"class":45},"\"PayDate\"",[35,940,398],{"class":265},[35,942,349],{"class":261},[35,944,945],{"class":265}," (ap[",[35,947,529],{"class":45},[35,949,398],{"class":265},[35,951,401],{"class":261},[35,953,954],{"class":265}," pd.to_timedelta(days_to_thursday, ",[35,956,449],{"class":360},[35,958,349],{"class":261},[35,960,454],{"class":45},[35,962,963],{"class":265},")).clip(",[35,965,681],{"class":360},[35,967,349],{"class":261},[35,969,286],{"class":285},[35,971,378],{"class":265},[35,973,974,976,978,980,982,985,987],{"class":37,"line":305},[35,975,935],{"class":265},[35,977,694],{"class":45},[35,979,398],{"class":265},[35,981,349],{"class":261},[35,983,984],{"class":265}," week_of(ap[",[35,986,938],{"class":45},[35,988,532],{"class":265},[35,990,991,993,996,999,1001,1003,1005,1007,1010,1012,1015,1018,1020,1022],{"class":37,"line":316},[35,992,467],{"class":285},[35,994,995],{"class":265},"(ap[[",[35,997,998],{"class":45},"\"Bill\"",[35,1000,357],{"class":265},[35,1002,529],{"class":45},[35,1004,357],{"class":265},[35,1006,938],{"class":45},[35,1008,1009],{"class":265},"]].head(",[35,1011,96],{"class":285},[35,1013,1014],{"class":265},").to_string(",[35,1016,1017],{"class":360},"index",[35,1019,349],{"class":261},[35,1021,845],{"class":285},[35,1023,1024],{"class":265},"))\n",[20,1026,1028],{"id":1027},"step-4-recurring-commitments","Step 4 — Recurring commitments",[10,1030,1031],{},"Payroll, rent and loan repayments are known in advance but not yet billed. Generate them from a small schedule so they are visible and editable in one place:",[25,1033,1035],{"className":252,"code":1034,"language":254,"meta":30,"style":30},"recurring = pd.concat([\n    pd.DataFrame({\"Item\": \"Payroll\", \"Date\": pd.date_range(\"2026-10-09\", end, freq=\"2W-FRI\"), \"Amount\": 46_500.00}),\n    pd.DataFrame({\"Item\": \"Rent\", \"Date\": pd.date_range(\"2026-10-01\", end, freq=\"MS\"), \"Amount\": 18_500.00}),\n    pd.DataFrame({\"Item\": \"Loan repayment\", \"Date\": pd.date_range(\"2026-10-01\", end, freq=\"MS\") + pd.Timedelta(days=14), \"Amount\": 7_250.00}),\n], ignore_index=True)\nrecurring = recurring[recurring[\"Date\"].between(START, end)]\nrecurring[\"Week\"] = week_of(recurring[\"Date\"])\nprint(recurring.groupby(\"Item\")[\"Amount\"].agg([\"count\", \"sum\"]).to_string())\n",[32,1036,1037,1047,1095,1135,1187,1201,1220,1238],{"__ignoreMap":30},[35,1038,1039,1042,1044],{"class":37,"line":38},[35,1040,1041],{"class":265},"recurring ",[35,1043,349],{"class":261},[35,1045,1046],{"class":265}," pd.concat([\n",[35,1048,1049,1052,1055,1058,1061,1063,1066,1069,1072,1075,1077,1079,1082,1085,1087,1089,1092],{"class":37,"line":275},[35,1050,1051],{"class":265},"    pd.DataFrame({",[35,1053,1054],{"class":45},"\"Item\"",[35,1056,1057],{"class":265},": ",[35,1059,1060],{"class":45},"\"Payroll\"",[35,1062,357],{"class":265},[35,1064,1065],{"class":45},"\"Date\"",[35,1067,1068],{"class":265},": pd.date_range(",[35,1070,1071],{"class":45},"\"2026-10-09\"",[35,1073,1074],{"class":265},", end, ",[35,1076,370],{"class":360},[35,1078,349],{"class":261},[35,1080,1081],{"class":45},"\"2W-FRI\"",[35,1083,1084],{"class":265},"), ",[35,1086,828],{"class":45},[35,1088,1057],{"class":265},[35,1090,1091],{"class":285},"46_500.00",[35,1093,1094],{"class":265},"}),\n",[35,1096,1097,1099,1101,1103,1106,1108,1110,1112,1115,1117,1119,1121,1124,1126,1128,1130,1133],{"class":37,"line":282},[35,1098,1051],{"class":265},[35,1100,1054],{"class":45},[35,1102,1057],{"class":265},[35,1104,1105],{"class":45},"\"Rent\"",[35,1107,357],{"class":265},[35,1109,1065],{"class":45},[35,1111,1068],{"class":265},[35,1113,1114],{"class":45},"\"2026-10-01\"",[35,1116,1074],{"class":265},[35,1118,370],{"class":360},[35,1120,349],{"class":261},[35,1122,1123],{"class":45},"\"MS\"",[35,1125,1084],{"class":265},[35,1127,828],{"class":45},[35,1129,1057],{"class":265},[35,1131,1132],{"class":285},"18_500.00",[35,1134,1094],{"class":265},[35,1136,1137,1139,1141,1143,1146,1148,1150,1152,1154,1156,1158,1160,1162,1165,1167,1169,1171,1173,1176,1178,1180,1182,1185],{"class":37,"line":305},[35,1138,1051],{"class":265},[35,1140,1054],{"class":45},[35,1142,1057],{"class":265},[35,1144,1145],{"class":45},"\"Loan repayment\"",[35,1147,357],{"class":265},[35,1149,1065],{"class":45},[35,1151,1068],{"class":265},[35,1153,1114],{"class":45},[35,1155,1074],{"class":265},[35,1157,370],{"class":360},[35,1159,349],{"class":261},[35,1161,1123],{"class":45},[35,1163,1164],{"class":265},") ",[35,1166,401],{"class":261},[35,1168,404],{"class":265},[35,1170,407],{"class":360},[35,1172,349],{"class":261},[35,1174,1175],{"class":285},"14",[35,1177,1084],{"class":265},[35,1179,828],{"class":45},[35,1181,1057],{"class":265},[35,1183,1184],{"class":285},"7_250.00",[35,1186,1094],{"class":265},[35,1188,1189,1191,1194,1196,1199],{"class":37,"line":316},[35,1190,653],{"class":265},[35,1192,1193],{"class":360},"ignore_index",[35,1195,349],{"class":261},[35,1197,1198],{"class":285},"True",[35,1200,378],{"class":265},[35,1202,1203,1205,1207,1210,1212,1215,1217],{"class":37,"line":327},[35,1204,1041],{"class":265},[35,1206,349],{"class":261},[35,1208,1209],{"class":265}," recurring[recurring[",[35,1211,1065],{"class":45},[35,1213,1214],{"class":265},"].between(",[35,1216,286],{"class":285},[35,1218,1219],{"class":265},", end)]\n",[35,1221,1222,1225,1227,1229,1231,1234,1236],{"class":37,"line":338},[35,1223,1224],{"class":265},"recurring[",[35,1226,694],{"class":45},[35,1228,398],{"class":265},[35,1230,349],{"class":261},[35,1232,1233],{"class":265}," week_of(recurring[",[35,1235,1065],{"class":45},[35,1237,532],{"class":265},[35,1239,1240,1242,1245,1247,1249,1251,1254,1257,1259,1262],{"class":37,"line":343},[35,1241,467],{"class":285},[35,1243,1244],{"class":265},"(recurring.groupby(",[35,1246,1054],{"class":45},[35,1248,825],{"class":265},[35,1250,828],{"class":45},[35,1252,1253],{"class":265},"].agg([",[35,1255,1256],{"class":45},"\"count\"",[35,1258,357],{"class":265},[35,1260,1261],{"class":45},"\"sum\"",[35,1263,1264],{"class":265},"]).to_string())\n",[10,1266,1267],{},"October's rent falls on the 1st, before the forecast starts, so it is excluded: it has already left the account and is in the opening balance. The loan line uses the same month starts as rent, shifted by 14 days to the 15th; starting that range on the 15th itself would skip October, because the first month start after 15 October is 1 November.",[20,1269,1271],{"id":1270},"step-5-receipts-from-sales-not-yet-invoiced","Step 5 — Receipts from sales not yet invoiced",[10,1273,1274],{},"Open invoices run out after a few weeks, because next month's sales have not been billed yet. A forecast built only from the ledger therefore always looks bleak at the end. Add an estimate for future billings: here a run rate of 20,000 a week, collected on average 35 days after the week it is billed. Keep it on its own line so nobody mistakes an estimate for an invoice:",[25,1276,1278],{"className":252,"code":1277,"language":254,"meta":30,"style":30},"RUN_RATE = 20_000.00\nCOLLECT_AFTER = pd.Timedelta(days=35)\nnew_sales = pd.DataFrame({\"Date\": weeks + COLLECT_AFTER, \"Amount\": RUN_RATE})\nnew_sales = new_sales[new_sales[\"Date\"] \u003C= end]\nnew_sales[\"Week\"] = week_of(new_sales[\"Date\"])\nprint(f\"{len(new_sales)} weeks of estimated receipts, first on {new_sales['Date'].min().date()}\")\n",[32,1279,1280,1290,1308,1339,1357,1375],{"__ignoreMap":30},[35,1281,1282,1285,1287],{"class":37,"line":38},[35,1283,1284],{"class":285},"RUN_RATE",[35,1286,289],{"class":261},[35,1288,1289],{"class":285}," 20_000.00\n",[35,1291,1292,1295,1297,1299,1301,1303,1306],{"class":37,"line":275},[35,1293,1294],{"class":285},"COLLECT_AFTER",[35,1296,289],{"class":261},[35,1298,404],{"class":265},[35,1300,407],{"class":360},[35,1302,349],{"class":261},[35,1304,1305],{"class":285},"35",[35,1307,378],{"class":265},[35,1309,1310,1313,1315,1318,1320,1323,1325,1328,1330,1332,1334,1336],{"class":37,"line":282},[35,1311,1312],{"class":265},"new_sales ",[35,1314,349],{"class":261},[35,1316,1317],{"class":265}," pd.DataFrame({",[35,1319,1065],{"class":45},[35,1321,1322],{"class":265},": weeks ",[35,1324,401],{"class":261},[35,1326,1327],{"class":285}," COLLECT_AFTER",[35,1329,357],{"class":265},[35,1331,828],{"class":45},[35,1333,1057],{"class":265},[35,1335,1284],{"class":285},[35,1337,1338],{"class":265},"})\n",[35,1340,1341,1343,1345,1348,1350,1352,1355],{"class":37,"line":305},[35,1342,1312],{"class":265},[35,1344,349],{"class":261},[35,1346,1347],{"class":265}," new_sales[new_sales[",[35,1349,1065],{"class":45},[35,1351,398],{"class":265},[35,1353,1354],{"class":261},"\u003C=",[35,1356,729],{"class":265},[35,1358,1359,1362,1364,1366,1368,1371,1373],{"class":37,"line":316},[35,1360,1361],{"class":265},"new_sales[",[35,1363,694],{"class":45},[35,1365,398],{"class":265},[35,1367,349],{"class":261},[35,1369,1370],{"class":265}," week_of(new_sales[",[35,1372,1065],{"class":45},[35,1374,532],{"class":265},[35,1376,1377,1379,1381,1383,1385,1387,1389,1392,1394,1397,1399,1401,1404,1407,1409,1411],{"class":37,"line":327},[35,1378,467],{"class":285},[35,1380,736],{"class":265},[35,1382,739],{"class":261},[35,1384,742],{"class":45},[35,1386,746],{"class":745},[35,1388,749],{"class":285},[35,1390,1391],{"class":265},"(new_sales)",[35,1393,755],{"class":745},[35,1395,1396],{"class":45}," weeks of estimated receipts, first on ",[35,1398,746],{"class":745},[35,1400,1361],{"class":265},[35,1402,1403],{"class":45},"'Date'",[35,1405,1406],{"class":265},"].min().date()",[35,1408,755],{"class":745},[35,1410,742],{"class":45},[35,1412,378],{"class":265},[10,1414,1415],{},"Base the run rate on the last quarter's billings and revisit it monthly; it is the least certain line in the forecast, and the first to adjust when sales change.",[20,1417,1419],{"id":1418},"step-6-bucket-by-week-and-roll-the-balance-forward","Step 6 — Bucket by week and roll the balance forward",[10,1421,1422],{},"Stack the three sources into one table of signed cash movements, pivot to one column per week, and roll the balance forward: each week's closing balance is the next week's opening balance.",[25,1424,1426],{"className":252,"code":1425,"language":254,"meta":30,"style":30},"flows = pd.concat([\n    pd.DataFrame({\"Line\": \"Customer receipts\", \"Week\": ar[\"Week\"], \"Amount\": ar[\"Amount\"]}),\n    pd.DataFrame({\"Line\": \"New sales (estimate)\", \"Week\": new_sales[\"Week\"], \"Amount\": new_sales[\"Amount\"]}),\n    pd.DataFrame({\"Line\": \"Supplier payments\", \"Week\": ap[\"Week\"], \"Amount\": -ap[\"Amount\"]}),\n    pd.DataFrame({\"Line\": recurring[\"Item\"], \"Week\": recurring[\"Week\"], \"Amount\": -recurring[\"Amount\"]}),\n])\nflows = flows[flows[\"Week\"].isin(weeks)]\nlines = [\"Customer receipts\", \"New sales (estimate)\", \"Supplier payments\", \"Payroll\", \"Rent\", \"Loan repayment\"]\ngrid = (flows.pivot_table(index=\"Line\", columns=\"Week\", values=\"Amount\", aggfunc=\"sum\", fill_value=0)\n             .reindex(index=lines, columns=weeks, fill_value=0))\n\nnet = grid.sum()\nclosing = OPENING_CASH + net.cumsum()\nopening = closing.shift(1, fill_value=OPENING_CASH)\nsummary = pd.DataFrame({\"Opening\": opening, \"Net flow\": net, \"Closing\": closing}).round(2)\nsummary.index = summary.index.strftime(\"%d %b\")\nprint(summary.to_string())\nlow = summary[summary[\"Closing\"] \u003C MIN_CASH]\nprint(\"weeks below minimum cash:\", \", \".join(low.index) or \"none\")\n",[32,1427,1428,1437,1469,1499,1533,1566,1570,1584,1619,1673,1700,1704,1714,1730,1752,1784,1807,1815,1838],{"__ignoreMap":30},[35,1429,1430,1433,1435],{"class":37,"line":38},[35,1431,1432],{"class":265},"flows ",[35,1434,349],{"class":261},[35,1436,1046],{"class":265},[35,1438,1439,1441,1444,1446,1449,1451,1453,1456,1458,1460,1462,1464,1466],{"class":37,"line":275},[35,1440,1051],{"class":265},[35,1442,1443],{"class":45},"\"Line\"",[35,1445,1057],{"class":265},[35,1447,1448],{"class":45},"\"Customer receipts\"",[35,1450,357],{"class":265},[35,1452,694],{"class":45},[35,1454,1455],{"class":265},": ar[",[35,1457,694],{"class":45},[35,1459,653],{"class":265},[35,1461,828],{"class":45},[35,1463,1455],{"class":265},[35,1465,828],{"class":45},[35,1467,1468],{"class":265},"]}),\n",[35,1470,1471,1473,1475,1477,1480,1482,1484,1487,1489,1491,1493,1495,1497],{"class":37,"line":282},[35,1472,1051],{"class":265},[35,1474,1443],{"class":45},[35,1476,1057],{"class":265},[35,1478,1479],{"class":45},"\"New sales (estimate)\"",[35,1481,357],{"class":265},[35,1483,694],{"class":45},[35,1485,1486],{"class":265},": new_sales[",[35,1488,694],{"class":45},[35,1490,653],{"class":265},[35,1492,828],{"class":45},[35,1494,1486],{"class":265},[35,1496,828],{"class":45},[35,1498,1468],{"class":265},[35,1500,1501,1503,1505,1507,1510,1512,1514,1517,1519,1521,1523,1525,1527,1529,1531],{"class":37,"line":305},[35,1502,1051],{"class":265},[35,1504,1443],{"class":45},[35,1506,1057],{"class":265},[35,1508,1509],{"class":45},"\"Supplier payments\"",[35,1511,357],{"class":265},[35,1513,694],{"class":45},[35,1515,1516],{"class":265},": ap[",[35,1518,694],{"class":45},[35,1520,653],{"class":265},[35,1522,828],{"class":45},[35,1524,1057],{"class":265},[35,1526,392],{"class":261},[35,1528,935],{"class":265},[35,1530,828],{"class":45},[35,1532,1468],{"class":265},[35,1534,1535,1537,1539,1542,1544,1546,1548,1550,1552,1554,1556,1558,1560,1562,1564],{"class":37,"line":316},[35,1536,1051],{"class":265},[35,1538,1443],{"class":45},[35,1540,1541],{"class":265},": recurring[",[35,1543,1054],{"class":45},[35,1545,653],{"class":265},[35,1547,694],{"class":45},[35,1549,1541],{"class":265},[35,1551,694],{"class":45},[35,1553,653],{"class":265},[35,1555,828],{"class":45},[35,1557,1057],{"class":265},[35,1559,392],{"class":261},[35,1561,1224],{"class":265},[35,1563,828],{"class":45},[35,1565,1468],{"class":265},[35,1567,1568],{"class":37,"line":327},[35,1569,532],{"class":265},[35,1571,1572,1574,1576,1579,1581],{"class":37,"line":338},[35,1573,1432],{"class":265},[35,1575,349],{"class":261},[35,1577,1578],{"class":265}," flows[flows[",[35,1580,694],{"class":45},[35,1582,1583],{"class":265},"].isin(weeks)]\n",[35,1585,1586,1589,1591,1594,1596,1598,1600,1602,1604,1606,1608,1610,1612,1614,1616],{"class":37,"line":343},[35,1587,1588],{"class":265},"lines ",[35,1590,349],{"class":261},[35,1592,1593],{"class":265}," [",[35,1595,1448],{"class":45},[35,1597,357],{"class":265},[35,1599,1479],{"class":45},[35,1601,357],{"class":265},[35,1603,1509],{"class":45},[35,1605,357],{"class":265},[35,1607,1060],{"class":45},[35,1609,357],{"class":265},[35,1611,1105],{"class":45},[35,1613,357],{"class":265},[35,1615,1145],{"class":45},[35,1617,1618],{"class":265},"]\n",[35,1620,1621,1624,1626,1629,1631,1633,1635,1637,1640,1642,1644,1646,1649,1651,1653,1655,1658,1660,1662,1664,1667,1669,1671],{"class":37,"line":381},[35,1622,1623],{"class":265},"grid ",[35,1625,349],{"class":261},[35,1627,1628],{"class":265}," (flows.pivot_table(",[35,1630,1017],{"class":360},[35,1632,349],{"class":261},[35,1634,1443],{"class":45},[35,1636,357],{"class":265},[35,1638,1639],{"class":360},"columns",[35,1641,349],{"class":261},[35,1643,694],{"class":45},[35,1645,357],{"class":265},[35,1647,1648],{"class":360},"values",[35,1650,349],{"class":261},[35,1652,828],{"class":45},[35,1654,357],{"class":265},[35,1656,1657],{"class":360},"aggfunc",[35,1659,349],{"class":261},[35,1661,1261],{"class":45},[35,1663,357],{"class":265},[35,1665,1666],{"class":360},"fill_value",[35,1668,349],{"class":261},[35,1670,97],{"class":285},[35,1672,378],{"class":265},[35,1674,1675,1678,1680,1682,1685,1687,1689,1692,1694,1696,1698],{"class":37,"line":417},[35,1676,1677],{"class":265},"             .reindex(",[35,1679,1017],{"class":360},[35,1681,349],{"class":261},[35,1683,1684],{"class":265},"lines, ",[35,1686,1639],{"class":360},[35,1688,349],{"class":261},[35,1690,1691],{"class":265},"weeks, ",[35,1693,1666],{"class":360},[35,1695,349],{"class":261},[35,1697,97],{"class":285},[35,1699,1024],{"class":265},[35,1701,1702],{"class":37,"line":422},[35,1703,279],{"emptyLinePlaceholder":278},[35,1705,1706,1709,1711],{"class":37,"line":435},[35,1707,1708],{"class":265},"net ",[35,1710,349],{"class":261},[35,1712,1713],{"class":265}," grid.sum()\n",[35,1715,1716,1719,1721,1724,1727],{"class":37,"line":459},[35,1717,1718],{"class":265},"closing ",[35,1720,349],{"class":261},[35,1722,1723],{"class":285}," OPENING_CASH",[35,1725,1726],{"class":261}," +",[35,1728,1729],{"class":265}," net.cumsum()\n",[35,1731,1732,1735,1737,1740,1742,1744,1746,1748,1750],{"class":37,"line":464},[35,1733,1734],{"class":265},"opening ",[35,1736,349],{"class":261},[35,1738,1739],{"class":265}," closing.shift(",[35,1741,395],{"class":285},[35,1743,357],{"class":265},[35,1745,1666],{"class":360},[35,1747,349],{"class":261},[35,1749,319],{"class":285},[35,1751,378],{"class":265},[35,1753,1755,1758,1760,1762,1765,1768,1771,1774,1777,1780,1782],{"class":37,"line":1754},15,[35,1756,1757],{"class":265},"summary ",[35,1759,349],{"class":261},[35,1761,1317],{"class":265},[35,1763,1764],{"class":45},"\"Opening\"",[35,1766,1767],{"class":265},": opening, ",[35,1769,1770],{"class":45},"\"Net flow\"",[35,1772,1773],{"class":265},": net, ",[35,1775,1776],{"class":45},"\"Closing\"",[35,1778,1779],{"class":265},": closing}).round(",[35,1781,834],{"class":285},[35,1783,378],{"class":265},[35,1785,1787,1790,1792,1795,1797,1800,1803,1805],{"class":37,"line":1786},16,[35,1788,1789],{"class":265},"summary.index ",[35,1791,349],{"class":261},[35,1793,1794],{"class":265}," summary.index.strftime(",[35,1796,742],{"class":45},[35,1798,1799],{"class":745},"%d",[35,1801,1802],{"class":745}," %b",[35,1804,742],{"class":45},[35,1806,378],{"class":265},[35,1808,1810,1812],{"class":37,"line":1809},17,[35,1811,467],{"class":285},[35,1813,1814],{"class":265},"(summary.to_string())\n",[35,1816,1818,1821,1823,1826,1828,1830,1833,1836],{"class":37,"line":1817},18,[35,1819,1820],{"class":265},"low ",[35,1822,349],{"class":261},[35,1824,1825],{"class":265}," summary[summary[",[35,1827,1776],{"class":45},[35,1829,398],{"class":265},[35,1831,1832],{"class":261},"\u003C",[35,1834,1835],{"class":285}," MIN_CASH",[35,1837,1618],{"class":265},[35,1839,1841,1843,1845,1848,1850,1853,1856,1859,1862],{"class":37,"line":1840},19,[35,1842,467],{"class":285},[35,1844,736],{"class":265},[35,1846,1847],{"class":45},"\"weeks below minimum cash:\"",[35,1849,357],{"class":265},[35,1851,1852],{"class":45},"\", \"",[35,1854,1855],{"class":265},".join(low.index) ",[35,1857,1858],{"class":261},"or",[35,1860,1861],{"class":45}," \"none\"",[35,1863,378],{"class":265},[10,1865,1866],{},"Four weeks fall below the minimum: the first week, when payroll and a cluster of supplier bills leave before receipts arrive; two more later in October, when the second payroll and then November's rent go out before the larger receipts of mid-November; and the last week of the year, when payroll and rent land together. Those weeks are the reason the report exists: they tell the treasurer when to chase a large receivable, delay a discretionary payment or draw on a credit line — weeks before it becomes urgent.",[75,1868,84,1874,84,1877,84,1880,84,1884,84,1890,84,1896,84,1901,84,1905,84,1911,84,1917,84,1921,84,1925,84,1928,84,1932,84,1935,84,1939,84,1942,84,1946,84,1949,84,1954,84,1958,84,1962,84,1965,84,1969,84,1973,84,1977,84,1981,84,1985,84,1989,84,1993,84,1997,84,2001,84,2005,84,2010,84,2013,84,2016,84,2019,84,2022,84,2025,84,2028,84,2031,84,2034,84,2037,84,2040,84,2043,84,2046,84,2049,84,2052,84,2054,84,2056,84,2058,84,2060,84,2062,84,2064,84,2066,84,2068,84,2070,84,2072,84,2074],{"viewBox":1869,"role":78,"ariaLabelledBy":1870,"xmlns":82,"style":1873},"4 0 748 280",[1871,1872],"cf-balance-t","cf-balance-d","width:100%;max-width:748px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[86,1875,1876],{"id":1871},"Closing cash by week against the minimum",[90,1878,1879],{"id":1872},"A line chart of the forecast closing balance over the 13 weeks from 5 October 2026, with a flat line at the 60,000 minimum. The balance is below the minimum in the weeks of 5, 19 and 26 October, rises to about 183,000 in late November as large receivables arrive, and dips below the minimum again in the last week of December.",[94,1881],{"x":96,"y":97,"width":1882,"height":1883,"fill":100},"748","280",[94,1885],{"x":1886,"y":1887,"width":1888,"height":1888,"rx":913,"fill":108,"stroke":109,"style":1889},"64.0","18.0","14.0","stroke-width:1.5px",[123,1891,1895],{"x":1892,"y":1893,"style":1894},"84.0","29.5","font-size:12px;font-weight:600;fill:var(--text,#172033);text-anchor:start","Closing cash",[94,1897],{"x":1898,"y":1887,"width":1888,"height":1888,"rx":913,"fill":1899,"stroke":1900,"style":1889},"191.4","#fce9e9","var(--danger,#dc2626)",[123,1902,1904],{"x":1903,"y":1893,"style":1894},"211.4","Minimum cash",[37,1906],{"x1":1907,"y1":1908,"x2":1909,"y2":1908,"stroke":221,"style":1910},"60.0","242.0","736.0","stroke-width:1px",[123,1912,1916],{"x":1913,"y":1914,"style":1915},"34.0","246.0","font-size:11px;font-weight:400;fill:var(--muted,#5b6780);text-anchor:middle","0k",[37,1918],{"x1":1907,"y1":1919,"x2":1909,"y2":1919,"stroke":221,"style":1920},"194.5","stroke-width:1px;stroke-dasharray:5 4",[123,1922,1924],{"x":1913,"y":1923,"style":1915},"198.5","50k",[37,1926],{"x1":1907,"y1":1927,"x2":1909,"y2":1927,"stroke":221,"style":1920},"147.0",[123,1929,1931],{"x":1913,"y":1930,"style":1915},"151.0","100k",[37,1933],{"x1":1907,"y1":1934,"x2":1909,"y2":1934,"stroke":221,"style":1920},"99.5",[123,1936,1938],{"x":1913,"y":1937,"style":1915},"103.5","150k",[37,1940],{"x1":1907,"y1":1941,"x2":1909,"y2":1941,"stroke":221,"style":1920},"52.0",[123,1943,1945],{"x":1913,"y":1944,"style":1915},"56.0","200k",[37,1947],{"x1":1907,"y1":1908,"x2":1909,"y2":1908,"stroke":1948,"style":1889},"var(--muted,#5b6780)",[123,1950,1953],{"x":1951,"y":1952,"style":132},"86.0","260.0","05 Oct",[123,1955,1957],{"x":1956,"y":1952,"style":132},"138.0","12 Oct",[123,1959,1961],{"x":1960,"y":1952,"style":132},"190.0","19 Oct",[123,1963,1964],{"x":1908,"y":1952,"style":132},"26 Oct",[123,1966,1968],{"x":1967,"y":1952,"style":132},"294.0","02 Nov",[123,1970,1972],{"x":1971,"y":1952,"style":132},"346.0","09 Nov",[123,1974,1976],{"x":1975,"y":1952,"style":132},"398.0","16 Nov",[123,1978,1980],{"x":1979,"y":1952,"style":132},"450.0","23 Nov",[123,1982,1984],{"x":1983,"y":1952,"style":132},"502.0","30 Nov",[123,1986,1988],{"x":1987,"y":1952,"style":132},"554.0","07 Dec",[123,1990,1992],{"x":1991,"y":1952,"style":132},"606.0","14 Dec",[123,1994,1996],{"x":1995,"y":1952,"style":132},"658.0","21 Dec",[123,1998,2000],{"x":1999,"y":1952,"style":132},"710.0","28 Dec",[116,2002],{"d":2003,"fill":119,"stroke":109,"style":2004},"M86.0,211.2 L138.0,183.6 L190.0,197.4 L242.0,202.3 L294.0,184.5 L346.0,176.9 L398.0,88.8 L450.0,68.4 L502.0,131.5 L554.0,120.9 L606.0,180.1 L658.0,161.1 L710.0,187.6","stroke-width:2.5px;stroke-linejoin:round;stroke-linecap:round",[2006,2007],"circle",{"cx":1951,"cy":2008,"r":2009,"fill":100,"stroke":109,"style":110},"211.2","4.0",[2006,2011],{"cx":1956,"cy":2012,"r":2009,"fill":100,"stroke":109,"style":110},"183.6",[2006,2014],{"cx":1960,"cy":2015,"r":2009,"fill":100,"stroke":109,"style":110},"197.4",[2006,2017],{"cx":1908,"cy":2018,"r":2009,"fill":100,"stroke":109,"style":110},"202.3",[2006,2020],{"cx":1967,"cy":2021,"r":2009,"fill":100,"stroke":109,"style":110},"184.5",[2006,2023],{"cx":1971,"cy":2024,"r":2009,"fill":100,"stroke":109,"style":110},"176.9",[2006,2026],{"cx":1975,"cy":2027,"r":2009,"fill":100,"stroke":109,"style":110},"88.8",[2006,2029],{"cx":1979,"cy":2030,"r":2009,"fill":100,"stroke":109,"style":110},"68.4",[2006,2032],{"cx":1983,"cy":2033,"r":2009,"fill":100,"stroke":109,"style":110},"131.5",[2006,2035],{"cx":1987,"cy":2036,"r":2009,"fill":100,"stroke":109,"style":110},"120.9",[2006,2038],{"cx":1991,"cy":2039,"r":2009,"fill":100,"stroke":109,"style":110},"180.1",[2006,2041],{"cx":1995,"cy":2042,"r":2009,"fill":100,"stroke":109,"style":110},"161.1",[2006,2044],{"cx":1999,"cy":2045,"r":2009,"fill":100,"stroke":109,"style":110},"187.6",[116,2047],{"d":2048,"fill":119,"stroke":1900,"style":2004},"M86.0,185.0 L138.0,185.0 L190.0,185.0 L242.0,185.0 L294.0,185.0 L346.0,185.0 L398.0,185.0 L450.0,185.0 L502.0,185.0 L554.0,185.0 L606.0,185.0 L658.0,185.0 L710.0,185.0",[2006,2050],{"cx":1951,"cy":2051,"r":2009,"fill":100,"stroke":1900,"style":110},"185.0",[2006,2053],{"cx":1956,"cy":2051,"r":2009,"fill":100,"stroke":1900,"style":110},[2006,2055],{"cx":1960,"cy":2051,"r":2009,"fill":100,"stroke":1900,"style":110},[2006,2057],{"cx":1908,"cy":2051,"r":2009,"fill":100,"stroke":1900,"style":110},[2006,2059],{"cx":1967,"cy":2051,"r":2009,"fill":100,"stroke":1900,"style":110},[2006,2061],{"cx":1971,"cy":2051,"r":2009,"fill":100,"stroke":1900,"style":110},[2006,2063],{"cx":1975,"cy":2051,"r":2009,"fill":100,"stroke":1900,"style":110},[2006,2065],{"cx":1979,"cy":2051,"r":2009,"fill":100,"stroke":1900,"style":110},[2006,2067],{"cx":1983,"cy":2051,"r":2009,"fill":100,"stroke":1900,"style":110},[2006,2069],{"cx":1987,"cy":2051,"r":2009,"fill":100,"stroke":1900,"style":110},[2006,2071],{"cx":1991,"cy":2051,"r":2009,"fill":100,"stroke":1900,"style":110},[2006,2073],{"cx":1995,"cy":2051,"r":2009,"fill":100,"stroke":1900,"style":110},[2006,2075],{"cx":1999,"cy":2051,"r":2009,"fill":100,"stroke":1900,"style":110},[20,2077,2079],{"id":2078},"step-7-write-the-report-with-live-balance-formulas","Step 7 — Write the report with live balance formulas",[10,2081,2082],{},"The reader will want to try changes: move a receipt a week later, add a one-off payment. Write the flows as values and the balances as formulas, so the workbook recalculates when a cell is edited. Weeks go across, lines go down, the way treasury teams read a cash forecast:",[25,2084,2086],{"className":252,"code":2085,"language":254,"meta":30,"style":30},"import xlsxwriter\nfrom xlsxwriter.utility import xl_rowcol_to_cell\n\nwb = xlsxwriter.Workbook(\"cash_forecast.xlsx\")\nws = wb.add_worksheet(\"13-week forecast\")\nmoney = wb.add_format({\"num_format\": \"#,##0;[Red]-#,##0\"})\nbold_money = wb.add_format({\"num_format\": \"#,##0;[Red]-#,##0\", \"bold\": True, \"top\": 1})\nhead = wb.add_format({\"bold\": True, \"bg_color\": \"#EEF2FF\", \"align\": \"center\"})\nlabel = wb.add_format({\"bold\": True})\nlow_fill = wb.add_format({\"bg_color\": \"#FDE2E2\", \"font_color\": \"#9B1C1C\"})\n\nws.write(0, 0, \"Cash flow forecast from 5 October 2026\", label)\nws.write(1, 0, \"Week starting\", head)\nfor c, wk in enumerate(weeks, start=1):\n    ws.write(1, c, wk.strftime(\"%d %b\"), head)\n\nws.write(2, 0, \"Opening balance\", label)\nfor r, line in enumerate(lines, start=3):\n    ws.write(r, 0, line)\n    for c, wk in enumerate(weeks, start=1):\n        ws.write_number(r, c, float(grid.loc[line, wk]), money)\nnet_row, close_row = 3 + len(lines), 4 + len(lines)\nws.write(net_row, 0, \"Net cash flow\", label)\nws.write(close_row, 0, \"Closing balance\", label)\nws.write(close_row + 2, 0, \"Minimum cash\")\nws.write_number(close_row + 2, 1, MIN_CASH, money)\n\nfor c in range(1, WEEKS + 1):\n    opening_ref = \"=\" + xl_rowcol_to_cell(close_row, c - 1) if c > 1 else OPENING_CASH\n    if c == 1:\n        ws.write_number(2, c, OPENING_CASH, money)\n    else:\n        ws.write_formula(2, c, opening_ref, money)\n    first, last = xl_rowcol_to_cell(3, c), xl_rowcol_to_cell(net_row - 1, c)\n    ws.write_formula(net_row, c, f\"=SUM({first}:{last})\", bold_money)\n    ws.write_formula(close_row, c, f\"={xl_rowcol_to_cell(2, c)}+{xl_rowcol_to_cell(net_row, c)}\", bold_money)\n\nws.conditional_format(close_row, 1, close_row, WEEKS, {\n    \"type\": \"cell\", \"criteria\": \"\u003C\", \"value\": f\"$B${close_row + 3}\", \"format\": low_fill})\nws.set_column(0, 0, 20)\nws.set_column(1, WEEKS, 10)\nws.freeze_panes(2, 1)\n\nchart = wb.add_chart({\"type\": \"line\"})\nchart.add_series({\"name\": \"Closing balance\",\n                  \"categories\": [\"13-week forecast\", 1, 1, 1, WEEKS],\n                  \"values\": [\"13-week forecast\", close_row, 1, close_row, WEEKS]})\nchart.set_title({\"name\": \"Closing cash by week\"})\nchart.set_legend({\"none\": True})\nws.insert_chart(close_row + 4, 1, chart, {\"x_scale\": 1.6})\n\ndetail = wb.add_worksheet(\"Receipts detail\")\ncols = [\"Invoice\", \"Customer\", \"DueDate\", \"AvgDaysLate\", \"Expected\", \"Amount\"]\ndetail.write_row(0, 0, cols, head)\ndatefmt = wb.add_format({\"num_format\": \"yyyy-mm-dd\"})\nfor r, row in enumerate(ar.sort_values(\"Expected\")[cols].itertuples(index=False), start=1):\n    detail.write(r, 0, row.Invoice)\n    detail.write(r, 1, row.Customer)\n    detail.write_datetime(r, 2, row.DueDate.to_pydatetime(), datefmt)\n    detail.write_number(r, 3, row.AvgDaysLate)\n    detail.write_datetime(r, 4, row.Expected.to_pydatetime(), datefmt)\n    detail.write_number(r, 5, row.Amount, money)\ndetail.set_column(0, 5, 14)\nwb.close()\nprint(\"wrote cash_forecast.xlsx\")\n",[32,2087,2088,2094,2107,2111,2126,2141,2161,2196,2233,2250,2278,2282,2301,2319,2346,2367,2371,2388,2410,2420,2442,2454,2482,2497,2512,2534,2555,2560,2588,2625,2641,2656,2664,2675,2698,2731,2767,2772,2788,2842,2861,2879,2893,2898,2919,2935,2965,2986,3001,3016,3044,3049,3064,3099,3114,3133,3169,3180,3190,3201,3212,3222,3233,3251,3257],{"__ignoreMap":30},[35,2089,2090,2092],{"class":37,"line":38},[35,2091,262],{"class":261},[35,2093,55],{"class":265},[35,2095,2096,2099,2102,2104],{"class":37,"line":275},[35,2097,2098],{"class":261},"from",[35,2100,2101],{"class":265}," xlsxwriter.utility ",[35,2103,262],{"class":261},[35,2105,2106],{"class":265}," xl_rowcol_to_cell\n",[35,2108,2109],{"class":37,"line":282},[35,2110,279],{"emptyLinePlaceholder":278},[35,2112,2113,2116,2118,2121,2124],{"class":37,"line":305},[35,2114,2115],{"class":265},"wb ",[35,2117,349],{"class":261},[35,2119,2120],{"class":265}," xlsxwriter.Workbook(",[35,2122,2123],{"class":45},"\"cash_forecast.xlsx\"",[35,2125,378],{"class":265},[35,2127,2128,2131,2133,2136,2139],{"class":37,"line":316},[35,2129,2130],{"class":265},"ws ",[35,2132,349],{"class":261},[35,2134,2135],{"class":265}," wb.add_worksheet(",[35,2137,2138],{"class":45},"\"13-week forecast\"",[35,2140,378],{"class":265},[35,2142,2143,2146,2148,2151,2154,2156,2159],{"class":37,"line":327},[35,2144,2145],{"class":265},"money ",[35,2147,349],{"class":261},[35,2149,2150],{"class":265}," wb.add_format({",[35,2152,2153],{"class":45},"\"num_format\"",[35,2155,1057],{"class":265},[35,2157,2158],{"class":45},"\"#,##0;[Red]-#,##0\"",[35,2160,1338],{"class":265},[35,2162,2163,2166,2168,2170,2172,2174,2176,2178,2181,2183,2185,2187,2190,2192,2194],{"class":37,"line":338},[35,2164,2165],{"class":265},"bold_money ",[35,2167,349],{"class":261},[35,2169,2150],{"class":265},[35,2171,2153],{"class":45},[35,2173,1057],{"class":265},[35,2175,2158],{"class":45},[35,2177,357],{"class":265},[35,2179,2180],{"class":45},"\"bold\"",[35,2182,1057],{"class":265},[35,2184,1198],{"class":285},[35,2186,357],{"class":265},[35,2188,2189],{"class":45},"\"top\"",[35,2191,1057],{"class":265},[35,2193,395],{"class":285},[35,2195,1338],{"class":265},[35,2197,2198,2201,2203,2205,2207,2209,2211,2213,2216,2218,2221,2223,2226,2228,2231],{"class":37,"line":343},[35,2199,2200],{"class":265},"head ",[35,2202,349],{"class":261},[35,2204,2150],{"class":265},[35,2206,2180],{"class":45},[35,2208,1057],{"class":265},[35,2210,1198],{"class":285},[35,2212,357],{"class":265},[35,2214,2215],{"class":45},"\"bg_color\"",[35,2217,1057],{"class":265},[35,2219,2220],{"class":45},"\"#EEF2FF\"",[35,2222,357],{"class":265},[35,2224,2225],{"class":45},"\"align\"",[35,2227,1057],{"class":265},[35,2229,2230],{"class":45},"\"center\"",[35,2232,1338],{"class":265},[35,2234,2235,2238,2240,2242,2244,2246,2248],{"class":37,"line":381},[35,2236,2237],{"class":265},"label ",[35,2239,349],{"class":261},[35,2241,2150],{"class":265},[35,2243,2180],{"class":45},[35,2245,1057],{"class":265},[35,2247,1198],{"class":285},[35,2249,1338],{"class":265},[35,2251,2252,2255,2257,2259,2261,2263,2266,2268,2271,2273,2276],{"class":37,"line":417},[35,2253,2254],{"class":265},"low_fill ",[35,2256,349],{"class":261},[35,2258,2150],{"class":265},[35,2260,2215],{"class":45},[35,2262,1057],{"class":265},[35,2264,2265],{"class":45},"\"#FDE2E2\"",[35,2267,357],{"class":265},[35,2269,2270],{"class":45},"\"font_color\"",[35,2272,1057],{"class":265},[35,2274,2275],{"class":45},"\"#9B1C1C\"",[35,2277,1338],{"class":265},[35,2279,2280],{"class":37,"line":422},[35,2281,279],{"emptyLinePlaceholder":278},[35,2283,2284,2287,2289,2291,2293,2295,2298],{"class":37,"line":435},[35,2285,2286],{"class":265},"ws.write(",[35,2288,97],{"class":285},[35,2290,357],{"class":265},[35,2292,97],{"class":285},[35,2294,357],{"class":265},[35,2296,2297],{"class":45},"\"Cash flow forecast from 5 October 2026\"",[35,2299,2300],{"class":265},", label)\n",[35,2302,2303,2305,2307,2309,2311,2313,2316],{"class":37,"line":459},[35,2304,2286],{"class":265},[35,2306,395],{"class":285},[35,2308,357],{"class":265},[35,2310,97],{"class":285},[35,2312,357],{"class":265},[35,2314,2315],{"class":45},"\"Week starting\"",[35,2317,2318],{"class":265},", head)\n",[35,2320,2321,2324,2327,2330,2333,2336,2339,2341,2343],{"class":37,"line":464},[35,2322,2323],{"class":261},"for",[35,2325,2326],{"class":265}," c, wk ",[35,2328,2329],{"class":261},"in",[35,2331,2332],{"class":285}," enumerate",[35,2334,2335],{"class":265},"(weeks, ",[35,2337,2338],{"class":360},"start",[35,2340,349],{"class":261},[35,2342,395],{"class":285},[35,2344,2345],{"class":265},"):\n",[35,2347,2348,2351,2353,2356,2358,2360,2362,2364],{"class":37,"line":1754},[35,2349,2350],{"class":265},"    ws.write(",[35,2352,395],{"class":285},[35,2354,2355],{"class":265},", c, wk.strftime(",[35,2357,742],{"class":45},[35,2359,1799],{"class":745},[35,2361,1802],{"class":745},[35,2363,742],{"class":45},[35,2365,2366],{"class":265},"), head)\n",[35,2368,2369],{"class":37,"line":1786},[35,2370,279],{"emptyLinePlaceholder":278},[35,2372,2373,2375,2377,2379,2381,2383,2386],{"class":37,"line":1809},[35,2374,2286],{"class":265},[35,2376,834],{"class":285},[35,2378,357],{"class":265},[35,2380,97],{"class":285},[35,2382,357],{"class":265},[35,2384,2385],{"class":45},"\"Opening balance\"",[35,2387,2300],{"class":265},[35,2389,2390,2392,2395,2397,2399,2402,2404,2406,2408],{"class":37,"line":1817},[35,2391,2323],{"class":261},[35,2393,2394],{"class":265}," r, line ",[35,2396,2329],{"class":261},[35,2398,2332],{"class":285},[35,2400,2401],{"class":265},"(lines, ",[35,2403,2338],{"class":360},[35,2405,349],{"class":261},[35,2407,913],{"class":285},[35,2409,2345],{"class":265},[35,2411,2412,2415,2417],{"class":37,"line":1840},[35,2413,2414],{"class":265},"    ws.write(r, ",[35,2416,97],{"class":285},[35,2418,2419],{"class":265},", line)\n",[35,2421,2423,2426,2428,2430,2432,2434,2436,2438,2440],{"class":37,"line":2422},20,[35,2424,2425],{"class":261},"    for",[35,2427,2326],{"class":265},[35,2429,2329],{"class":261},[35,2431,2332],{"class":285},[35,2433,2335],{"class":265},[35,2435,2338],{"class":360},[35,2437,349],{"class":261},[35,2439,395],{"class":285},[35,2441,2345],{"class":265},[35,2443,2445,2448,2451],{"class":37,"line":2444},21,[35,2446,2447],{"class":265},"        ws.write_number(r, c, ",[35,2449,2450],{"class":285},"float",[35,2452,2453],{"class":265},"(grid.loc[line, wk]), money)\n",[35,2455,2457,2460,2462,2465,2467,2470,2473,2475,2477,2479],{"class":37,"line":2456},22,[35,2458,2459],{"class":265},"net_row, close_row ",[35,2461,349],{"class":261},[35,2463,2464],{"class":285}," 3",[35,2466,1726],{"class":261},[35,2468,2469],{"class":285}," len",[35,2471,2472],{"class":265},"(lines), ",[35,2474,96],{"class":285},[35,2476,1726],{"class":261},[35,2478,2469],{"class":285},[35,2480,2481],{"class":265},"(lines)\n",[35,2483,2485,2488,2490,2492,2495],{"class":37,"line":2484},23,[35,2486,2487],{"class":265},"ws.write(net_row, ",[35,2489,97],{"class":285},[35,2491,357],{"class":265},[35,2493,2494],{"class":45},"\"Net cash flow\"",[35,2496,2300],{"class":265},[35,2498,2500,2503,2505,2507,2510],{"class":37,"line":2499},24,[35,2501,2502],{"class":265},"ws.write(close_row, ",[35,2504,97],{"class":285},[35,2506,357],{"class":265},[35,2508,2509],{"class":45},"\"Closing balance\"",[35,2511,2300],{"class":265},[35,2513,2515,2518,2520,2523,2525,2527,2529,2532],{"class":37,"line":2514},25,[35,2516,2517],{"class":265},"ws.write(close_row ",[35,2519,401],{"class":261},[35,2521,2522],{"class":285}," 2",[35,2524,357],{"class":265},[35,2526,97],{"class":285},[35,2528,357],{"class":265},[35,2530,2531],{"class":45},"\"Minimum cash\"",[35,2533,378],{"class":265},[35,2535,2537,2540,2542,2544,2546,2548,2550,2552],{"class":37,"line":2536},26,[35,2538,2539],{"class":265},"ws.write_number(close_row ",[35,2541,401],{"class":261},[35,2543,2522],{"class":285},[35,2545,357],{"class":265},[35,2547,395],{"class":285},[35,2549,357],{"class":265},[35,2551,330],{"class":285},[35,2553,2554],{"class":265},", money)\n",[35,2556,2558],{"class":37,"line":2557},27,[35,2559,279],{"emptyLinePlaceholder":278},[35,2561,2563,2565,2568,2570,2573,2575,2577,2579,2581,2583,2586],{"class":37,"line":2562},28,[35,2564,2323],{"class":261},[35,2566,2567],{"class":265}," c ",[35,2569,2329],{"class":261},[35,2571,2572],{"class":285}," range",[35,2574,736],{"class":265},[35,2576,395],{"class":285},[35,2578,357],{"class":265},[35,2580,308],{"class":285},[35,2582,1726],{"class":261},[35,2584,2585],{"class":285}," 1",[35,2587,2345],{"class":265},[35,2589,2591,2594,2596,2599,2601,2604,2606,2608,2610,2613,2615,2617,2619,2622],{"class":37,"line":2590},29,[35,2592,2593],{"class":265},"    opening_ref ",[35,2595,349],{"class":261},[35,2597,2598],{"class":45}," \"=\"",[35,2600,1726],{"class":261},[35,2602,2603],{"class":265}," xl_rowcol_to_cell(close_row, c ",[35,2605,392],{"class":261},[35,2607,2585],{"class":285},[35,2609,1164],{"class":265},[35,2611,2612],{"class":261},"if",[35,2614,2567],{"class":265},[35,2616,726],{"class":261},[35,2618,2585],{"class":285},[35,2620,2621],{"class":261}," else",[35,2623,2624],{"class":285}," OPENING_CASH\n",[35,2626,2628,2631,2633,2636,2638],{"class":37,"line":2627},30,[35,2629,2630],{"class":261},"    if",[35,2632,2567],{"class":265},[35,2634,2635],{"class":261},"==",[35,2637,2585],{"class":285},[35,2639,2640],{"class":265},":\n",[35,2642,2644,2647,2649,2652,2654],{"class":37,"line":2643},31,[35,2645,2646],{"class":265},"        ws.write_number(",[35,2648,834],{"class":285},[35,2650,2651],{"class":265},", c, ",[35,2653,319],{"class":285},[35,2655,2554],{"class":265},[35,2657,2659,2662],{"class":37,"line":2658},32,[35,2660,2661],{"class":261},"    else",[35,2663,2640],{"class":265},[35,2665,2667,2670,2672],{"class":37,"line":2666},33,[35,2668,2669],{"class":265},"        ws.write_formula(",[35,2671,834],{"class":285},[35,2673,2674],{"class":265},", c, opening_ref, money)\n",[35,2676,2678,2681,2683,2686,2688,2691,2693,2695],{"class":37,"line":2677},34,[35,2679,2680],{"class":265},"    first, last ",[35,2682,349],{"class":261},[35,2684,2685],{"class":265}," xl_rowcol_to_cell(",[35,2687,913],{"class":285},[35,2689,2690],{"class":265},", c), xl_rowcol_to_cell(net_row ",[35,2692,392],{"class":261},[35,2694,2585],{"class":285},[35,2696,2697],{"class":265},", c)\n",[35,2699,2701,2704,2706,2709,2711,2714,2716,2719,2721,2724,2726,2728],{"class":37,"line":2700},35,[35,2702,2703],{"class":265},"    ws.write_formula(net_row, c, ",[35,2705,739],{"class":261},[35,2707,2708],{"class":45},"\"=SUM(",[35,2710,746],{"class":745},[35,2712,2713],{"class":265},"first",[35,2715,755],{"class":745},[35,2717,2718],{"class":45},":",[35,2720,746],{"class":745},[35,2722,2723],{"class":265},"last",[35,2725,755],{"class":745},[35,2727,811],{"class":45},[35,2729,2730],{"class":265},", bold_money)\n",[35,2732,2734,2737,2739,2742,2744,2747,2749,2752,2754,2756,2758,2761,2763,2765],{"class":37,"line":2733},36,[35,2735,2736],{"class":265},"    ws.write_formula(close_row, c, ",[35,2738,739],{"class":261},[35,2740,2741],{"class":45},"\"=",[35,2743,746],{"class":745},[35,2745,2746],{"class":265},"xl_rowcol_to_cell(",[35,2748,834],{"class":285},[35,2750,2751],{"class":265},", c)",[35,2753,755],{"class":745},[35,2755,401],{"class":45},[35,2757,746],{"class":745},[35,2759,2760],{"class":265},"xl_rowcol_to_cell(net_row, c)",[35,2762,755],{"class":745},[35,2764,742],{"class":45},[35,2766,2730],{"class":265},[35,2768,2770],{"class":37,"line":2769},37,[35,2771,279],{"emptyLinePlaceholder":278},[35,2773,2775,2778,2780,2783,2785],{"class":37,"line":2774},38,[35,2776,2777],{"class":265},"ws.conditional_format(close_row, ",[35,2779,395],{"class":285},[35,2781,2782],{"class":265},", close_row, ",[35,2784,308],{"class":285},[35,2786,2787],{"class":265},", {\n",[35,2789,2791,2794,2796,2799,2801,2804,2806,2809,2811,2814,2816,2818,2821,2823,2826,2828,2830,2832,2834,2836,2839],{"class":37,"line":2790},39,[35,2792,2793],{"class":45},"    \"type\"",[35,2795,1057],{"class":265},[35,2797,2798],{"class":45},"\"cell\"",[35,2800,357],{"class":265},[35,2802,2803],{"class":45},"\"criteria\"",[35,2805,1057],{"class":265},[35,2807,2808],{"class":45},"\"\u003C\"",[35,2810,357],{"class":265},[35,2812,2813],{"class":45},"\"value\"",[35,2815,1057],{"class":265},[35,2817,739],{"class":261},[35,2819,2820],{"class":45},"\"$B$",[35,2822,746],{"class":745},[35,2824,2825],{"class":265},"close_row ",[35,2827,401],{"class":261},[35,2829,2464],{"class":285},[35,2831,755],{"class":745},[35,2833,742],{"class":45},[35,2835,357],{"class":265},[35,2837,2838],{"class":45},"\"format\"",[35,2840,2841],{"class":265},": low_fill})\n",[35,2843,2845,2848,2850,2852,2854,2856,2859],{"class":37,"line":2844},40,[35,2846,2847],{"class":265},"ws.set_column(",[35,2849,97],{"class":285},[35,2851,357],{"class":265},[35,2853,97],{"class":285},[35,2855,357],{"class":265},[35,2857,2858],{"class":285},"20",[35,2860,378],{"class":265},[35,2862,2864,2866,2868,2870,2872,2874,2877],{"class":37,"line":2863},41,[35,2865,2847],{"class":265},[35,2867,395],{"class":285},[35,2869,357],{"class":265},[35,2871,308],{"class":285},[35,2873,357],{"class":265},[35,2875,2876],{"class":285},"10",[35,2878,378],{"class":265},[35,2880,2882,2885,2887,2889,2891],{"class":37,"line":2881},42,[35,2883,2884],{"class":265},"ws.freeze_panes(",[35,2886,834],{"class":285},[35,2888,357],{"class":265},[35,2890,395],{"class":285},[35,2892,378],{"class":265},[35,2894,2896],{"class":37,"line":2895},43,[35,2897,279],{"emptyLinePlaceholder":278},[35,2899,2901,2904,2906,2909,2912,2914,2917],{"class":37,"line":2900},44,[35,2902,2903],{"class":265},"chart ",[35,2905,349],{"class":261},[35,2907,2908],{"class":265}," wb.add_chart({",[35,2910,2911],{"class":45},"\"type\"",[35,2913,1057],{"class":265},[35,2915,2916],{"class":45},"\"line\"",[35,2918,1338],{"class":265},[35,2920,2922,2925,2928,2930,2932],{"class":37,"line":2921},45,[35,2923,2924],{"class":265},"chart.add_series({",[35,2926,2927],{"class":45},"\"name\"",[35,2929,1057],{"class":265},[35,2931,2509],{"class":45},[35,2933,2934],{"class":265},",\n",[35,2936,2938,2941,2944,2946,2948,2950,2952,2954,2956,2958,2960,2962],{"class":37,"line":2937},46,[35,2939,2940],{"class":45},"                  \"categories\"",[35,2942,2943],{"class":265},": [",[35,2945,2138],{"class":45},[35,2947,357],{"class":265},[35,2949,395],{"class":285},[35,2951,357],{"class":265},[35,2953,395],{"class":285},[35,2955,357],{"class":265},[35,2957,395],{"class":285},[35,2959,357],{"class":265},[35,2961,308],{"class":285},[35,2963,2964],{"class":265},"],\n",[35,2966,2968,2971,2973,2975,2977,2979,2981,2983],{"class":37,"line":2967},47,[35,2969,2970],{"class":45},"                  \"values\"",[35,2972,2943],{"class":265},[35,2974,2138],{"class":45},[35,2976,2782],{"class":265},[35,2978,395],{"class":285},[35,2980,2782],{"class":265},[35,2982,308],{"class":285},[35,2984,2985],{"class":265},"]})\n",[35,2987,2989,2992,2994,2996,2999],{"class":37,"line":2988},48,[35,2990,2991],{"class":265},"chart.set_title({",[35,2993,2927],{"class":45},[35,2995,1057],{"class":265},[35,2997,2998],{"class":45},"\"Closing cash by week\"",[35,3000,1338],{"class":265},[35,3002,3004,3007,3010,3012,3014],{"class":37,"line":3003},49,[35,3005,3006],{"class":265},"chart.set_legend({",[35,3008,3009],{"class":45},"\"none\"",[35,3011,1057],{"class":265},[35,3013,1198],{"class":285},[35,3015,1338],{"class":265},[35,3017,3019,3022,3024,3027,3029,3031,3034,3037,3039,3042],{"class":37,"line":3018},50,[35,3020,3021],{"class":265},"ws.insert_chart(close_row ",[35,3023,401],{"class":261},[35,3025,3026],{"class":285}," 4",[35,3028,357],{"class":265},[35,3030,395],{"class":285},[35,3032,3033],{"class":265},", chart, {",[35,3035,3036],{"class":45},"\"x_scale\"",[35,3038,1057],{"class":265},[35,3040,3041],{"class":285},"1.6",[35,3043,1338],{"class":265},[35,3045,3047],{"class":37,"line":3046},51,[35,3048,279],{"emptyLinePlaceholder":278},[35,3050,3052,3055,3057,3059,3062],{"class":37,"line":3051},52,[35,3053,3054],{"class":265},"detail ",[35,3056,349],{"class":261},[35,3058,2135],{"class":265},[35,3060,3061],{"class":45},"\"Receipts detail\"",[35,3063,378],{"class":265},[35,3065,3067,3070,3072,3074,3077,3079,3081,3083,3085,3087,3089,3091,3093,3095,3097],{"class":37,"line":3066},53,[35,3068,3069],{"class":265},"cols ",[35,3071,349],{"class":261},[35,3073,1593],{"class":265},[35,3075,3076],{"class":45},"\"Invoice\"",[35,3078,357],{"class":265},[35,3080,575],{"class":45},[35,3082,357],{"class":265},[35,3084,529],{"class":45},[35,3086,357],{"class":265},[35,3088,605],{"class":45},[35,3090,357],{"class":265},[35,3092,633],{"class":45},[35,3094,357],{"class":265},[35,3096,828],{"class":45},[35,3098,1618],{"class":265},[35,3100,3102,3105,3107,3109,3111],{"class":37,"line":3101},54,[35,3103,3104],{"class":265},"detail.write_row(",[35,3106,97],{"class":285},[35,3108,357],{"class":265},[35,3110,97],{"class":285},[35,3112,3113],{"class":265},", cols, head)\n",[35,3115,3117,3120,3122,3124,3126,3128,3131],{"class":37,"line":3116},55,[35,3118,3119],{"class":265},"datefmt ",[35,3121,349],{"class":261},[35,3123,2150],{"class":265},[35,3125,2153],{"class":45},[35,3127,1057],{"class":265},[35,3129,3130],{"class":45},"\"yyyy-mm-dd\"",[35,3132,1338],{"class":265},[35,3134,3136,3138,3141,3143,3145,3148,3150,3153,3155,3157,3159,3161,3163,3165,3167],{"class":37,"line":3135},56,[35,3137,2323],{"class":261},[35,3139,3140],{"class":265}," r, row ",[35,3142,2329],{"class":261},[35,3144,2332],{"class":285},[35,3146,3147],{"class":265},"(ar.sort_values(",[35,3149,633],{"class":45},[35,3151,3152],{"class":265},")[cols].itertuples(",[35,3154,1017],{"class":360},[35,3156,349],{"class":261},[35,3158,845],{"class":285},[35,3160,1084],{"class":265},[35,3162,2338],{"class":360},[35,3164,349],{"class":261},[35,3166,395],{"class":285},[35,3168,2345],{"class":265},[35,3170,3172,3175,3177],{"class":37,"line":3171},57,[35,3173,3174],{"class":265},"    detail.write(r, ",[35,3176,97],{"class":285},[35,3178,3179],{"class":265},", row.Invoice)\n",[35,3181,3183,3185,3187],{"class":37,"line":3182},58,[35,3184,3174],{"class":265},[35,3186,395],{"class":285},[35,3188,3189],{"class":265},", row.Customer)\n",[35,3191,3193,3196,3198],{"class":37,"line":3192},59,[35,3194,3195],{"class":265},"    detail.write_datetime(r, ",[35,3197,834],{"class":285},[35,3199,3200],{"class":265},", row.DueDate.to_pydatetime(), datefmt)\n",[35,3202,3204,3207,3209],{"class":37,"line":3203},60,[35,3205,3206],{"class":265},"    detail.write_number(r, ",[35,3208,913],{"class":285},[35,3210,3211],{"class":265},", row.AvgDaysLate)\n",[35,3213,3215,3217,3219],{"class":37,"line":3214},61,[35,3216,3195],{"class":265},[35,3218,96],{"class":285},[35,3220,3221],{"class":265},", row.Expected.to_pydatetime(), datefmt)\n",[35,3223,3225,3227,3230],{"class":37,"line":3224},62,[35,3226,3206],{"class":265},[35,3228,3229],{"class":285},"5",[35,3231,3232],{"class":265},", row.Amount, money)\n",[35,3234,3236,3239,3241,3243,3245,3247,3249],{"class":37,"line":3235},63,[35,3237,3238],{"class":265},"detail.set_column(",[35,3240,97],{"class":285},[35,3242,357],{"class":265},[35,3244,3229],{"class":285},[35,3246,357],{"class":265},[35,3248,1175],{"class":285},[35,3250,378],{"class":265},[35,3252,3254],{"class":37,"line":3253},64,[35,3255,3256],{"class":265},"wb.close()\n",[35,3258,3260,3262,3264,3267],{"class":37,"line":3259},65,[35,3261,467],{"class":285},[35,3263,736],{"class":265},[35,3265,3266],{"class":45},"\"wrote cash_forecast.xlsx\"",[35,3268,378],{"class":265},[75,3270,84,3276,84,3279,84,3282,84,3287,84,3292,84,3298,84,3302,84,3305,84,3309,84,3312,84,3316,84,3319,84,3322,84,3326,84,3328,84,3331,84,3333,84,3337,84,3339,84,3342,84,3345,84,3349,84,3351,84,3354,84,3356,84,3358,84,3360,84,3362,84,3365,84,3369,84,3371,84,3373,84,3375,84,3377,84,3379,84,3381,84,3384,84,3387,84,3389,84,3392,84,3394,84,3397,84,3399,84,3402,84,3405,84,3409,84,3411,84,3415,84,3417,84,3420,84,3422],{"viewBox":3271,"role":78,"ariaLabelledBy":3272,"xmlns":82,"style":3275},"144 0 471 200",[3273,3274],"cf-layout-t","cf-layout-d","width:100%;max-width:471px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[86,3277,3278],{"id":3273},"The forecast sheet",[90,3280,3281],{"id":3274},"The first columns of the forecast sheet. Weeks run across from 5 October; lines run down from the opening balance through customer receipts, new sales, supplier payments, payroll, rent and loan repayment, to net cash flow and the closing balance. The opening, net and closing rows are formulas and the closing cell for 5 October is shaded red because it is below the minimum.",[94,3283],{"x":3284,"y":97,"width":3285,"height":3286,"fill":100},"144","471","200",[94,3288],{"x":3289,"y":137,"width":3290,"height":3291,"rx":97,"fill":108,"stroke":109,"style":1889},"160.6","130.2","28.0",[123,3293,3297],{"x":3294,"y":3295,"style":3296},"225.7","35.0","font-size:11px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Week starting",[94,3299],{"x":3300,"y":137,"width":3301,"height":3291,"rx":97,"fill":108,"stroke":109,"style":1889},"290.8","101.8",[123,3303,1953],{"x":3304,"y":3295,"style":3296},"341.7",[94,3306],{"x":3307,"y":137,"width":3308,"height":3291,"rx":97,"fill":108,"stroke":109,"style":1889},"392.6","103.6",[123,3310,1957],{"x":3311,"y":3295,"style":3296},"444.4",[94,3313],{"x":3314,"y":137,"width":3315,"height":3291,"rx":97,"fill":108,"stroke":109,"style":1889},"496.2","103.2",[123,3317,1961],{"x":3318,"y":3295,"style":3296},"547.8",[94,3320],{"x":3289,"y":3321,"width":3290,"height":3291,"rx":97,"fill":100,"stroke":221,"style":1910},"44.0",[123,3323,3325],{"x":3294,"y":3324,"style":158},"63.0","Opening balance",[94,3327],{"x":3300,"y":3321,"width":3301,"height":3291,"rx":97,"fill":100,"stroke":221,"style":1910},[123,3329,3330],{"x":3304,"y":3324,"style":158},"90,000",[94,3332],{"x":3307,"y":3321,"width":3308,"height":3291,"rx":97,"fill":140,"stroke":141,"style":1889},[123,3334,3336],{"x":3311,"y":3324,"style":3335},"font-size:11px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","=B11",[94,3338],{"x":3314,"y":3321,"width":3315,"height":3291,"rx":97,"fill":140,"stroke":141,"style":1889},[123,3340,3341],{"x":3318,"y":3324,"style":3335},"=C11",[94,3343],{"x":3289,"y":3344,"width":3290,"height":3291,"rx":97,"fill":100,"stroke":221,"style":1910},"72.0",[123,3346,3348],{"x":3294,"y":3347,"style":158},"91.0","Customer receipts",[94,3350],{"x":3300,"y":3344,"width":3301,"height":3291,"rx":97,"fill":100,"stroke":221,"style":1910},[123,3352,3353],{"x":3304,"y":3347,"style":158},"…",[94,3355],{"x":3307,"y":3344,"width":3308,"height":3291,"rx":97,"fill":100,"stroke":221,"style":1910},[123,3357,3353],{"x":3311,"y":3347,"style":158},[94,3359],{"x":3314,"y":3344,"width":3315,"height":3291,"rx":97,"fill":100,"stroke":221,"style":1910},[123,3361,3353],{"x":3318,"y":3347,"style":158},[94,3363],{"x":3289,"y":3364,"width":3290,"height":3291,"rx":97,"fill":100,"stroke":221,"style":1910},"100.0",[123,3366,3368],{"x":3294,"y":3367,"style":158},"119.0","Supplier payments",[94,3370],{"x":3300,"y":3364,"width":3301,"height":3291,"rx":97,"fill":100,"stroke":221,"style":1910},[123,3372,3353],{"x":3304,"y":3367,"style":158},[94,3374],{"x":3307,"y":3364,"width":3308,"height":3291,"rx":97,"fill":100,"stroke":221,"style":1910},[123,3376,3353],{"x":3311,"y":3367,"style":158},[94,3378],{"x":3314,"y":3364,"width":3315,"height":3291,"rx":97,"fill":100,"stroke":221,"style":1910},[123,3380,3353],{"x":3318,"y":3367,"style":158},[94,3382],{"x":3289,"y":3383,"width":3290,"height":3291,"rx":97,"fill":100,"stroke":221,"style":1910},"128.0",[123,3385,3386],{"x":3294,"y":1927,"style":158},"Net cash flow",[94,3388],{"x":3300,"y":3383,"width":3301,"height":3291,"rx":97,"fill":100,"stroke":221,"style":1910},[123,3390,3391],{"x":3304,"y":1927,"style":158},"=SUM(B4:B9)",[94,3393],{"x":3307,"y":3383,"width":3308,"height":3291,"rx":97,"fill":100,"stroke":221,"style":1910},[123,3395,3396],{"x":3311,"y":1927,"style":158},"=SUM(C4:C9)",[94,3398],{"x":3314,"y":3383,"width":3315,"height":3291,"rx":97,"fill":100,"stroke":221,"style":1910},[123,3400,3401],{"x":3318,"y":1927,"style":158},"=SUM(D4:D9)",[94,3403],{"x":3289,"y":3404,"width":3290,"height":3291,"rx":97,"fill":100,"stroke":221,"style":1910},"156.0",[123,3406,3408],{"x":3294,"y":3407,"style":158},"175.0","Closing balance",[94,3410],{"x":3300,"y":3404,"width":3301,"height":3291,"rx":97,"fill":1899,"stroke":1900,"style":1889},[123,3412,3414],{"x":3304,"y":3407,"style":3413},"font-size:11px;font-weight:700;fill:var(--accent-ink,#be185d);text-anchor:middle","=B3+B10",[94,3416],{"x":3307,"y":3404,"width":3308,"height":3291,"rx":97,"fill":100,"stroke":221,"style":1910},[123,3418,3419],{"x":3311,"y":3407,"style":158},"=C3+C10",[94,3421],{"x":3314,"y":3404,"width":3315,"height":3291,"rx":97,"fill":1899,"stroke":1900,"style":1889},[123,3423,3424],{"x":3318,"y":3407,"style":3413},"=D3+D10",[10,3426,3427],{},"The receipts detail sheet is what makes the forecast arguable: when someone asks why a week looks thin, the answer is a list of invoices and the dates they are expected. Supplier payments deserve the same sheet in a production version.",[20,3429,3431],{"id":3430},"step-8-check-the-formulas-agree-with-pandas","Step 8 — Check the formulas agree with pandas",[10,3433,3434,3435,3437,3438,3441],{},"Recalculate the file with LibreOffice and compare the closing balances Excel would show with the ones pandas computed. xlsxwriter stores ",[32,3436,97],{}," as the cached result of every formula and LibreOffice trusts cached results in ",[32,3439,3440],{},".xlsx"," files by default, so save a copy through openpyxl first, which drops the cached values and forces a recalculation:",[25,3443,3445],{"className":252,"code":3444,"language":254,"meta":30,"style":30},"from openpyxl import load_workbook\n\nload_workbook(\"cash_forecast.xlsx\").save(\"cash_forecast_nocache.xlsx\")\n",[32,3446,3447,3459,3463],{"__ignoreMap":30},[35,3448,3449,3451,3454,3456],{"class":37,"line":38},[35,3450,2098],{"class":261},[35,3452,3453],{"class":265}," openpyxl ",[35,3455,262],{"class":261},[35,3457,3458],{"class":265}," load_workbook\n",[35,3460,3461],{"class":37,"line":275},[35,3462,279],{"emptyLinePlaceholder":278},[35,3464,3465,3468,3470,3473,3476],{"class":37,"line":282},[35,3466,3467],{"class":265},"load_workbook(",[35,3469,2123],{"class":45},[35,3471,3472],{"class":265},").save(",[35,3474,3475],{"class":45},"\"cash_forecast_nocache.xlsx\"",[35,3477,378],{"class":265},[25,3479,3481],{"className":27,"code":3480,"language":29,"meta":30,"style":30},"soffice -env:UserInstallation=file:\u002F\u002F$PWD\u002Flo_profile --headless --convert-to xlsx --outdir recalculated cash_forecast_nocache.xlsx > \u002Fdev\u002Fnull 2>&1\nls recalculated\n",[32,3482,3483,3524],{"__ignoreMap":30},[35,3484,3485,3488,3491,3494,3497,3500,3503,3506,3509,3512,3515,3518,3521],{"class":37,"line":38},[35,3486,3487],{"class":41},"soffice",[35,3489,3490],{"class":285}," -env:UserInstallation=file:\u002F\u002F",[35,3492,3493],{"class":265},"$PWD",[35,3495,3496],{"class":285},"\u002Flo_profile",[35,3498,3499],{"class":285}," --headless",[35,3501,3502],{"class":285}," --convert-to",[35,3504,3505],{"class":45}," xlsx",[35,3507,3508],{"class":285}," --outdir",[35,3510,3511],{"class":45}," recalculated",[35,3513,3514],{"class":45}," cash_forecast_nocache.xlsx",[35,3516,3517],{"class":261}," >",[35,3519,3520],{"class":45}," \u002Fdev\u002Fnull",[35,3522,3523],{"class":261}," 2>&1\n",[35,3525,3526,3529],{"class":37,"line":275},[35,3527,3528],{"class":41},"ls",[35,3530,3531],{"class":45}," recalculated\n",[25,3533,3535],{"className":252,"code":3534,"language":254,"meta":30,"style":30},"ws = load_workbook(\"recalculated\u002Fcash_forecast_nocache.xlsx\", data_only=True)[\"13-week forecast\"]\nexcel_closing = [ws.cell(row=close_row + 1, column=c).value for c in range(2, WEEKS + 2)]\ndiff = max(abs(a - b) for a, b in zip(excel_closing, summary[\"Closing\"]))\nprint(f\"largest difference between workbook and pandas: {diff:.6f}\")\n",[32,3536,3537,3564,3618,3659],{"__ignoreMap":30},[35,3538,3539,3541,3543,3546,3549,3551,3554,3556,3558,3560,3562],{"class":37,"line":38},[35,3540,2130],{"class":265},[35,3542,349],{"class":261},[35,3544,3545],{"class":265}," load_workbook(",[35,3547,3548],{"class":45},"\"recalculated\u002Fcash_forecast_nocache.xlsx\"",[35,3550,357],{"class":265},[35,3552,3553],{"class":360},"data_only",[35,3555,349],{"class":261},[35,3557,1198],{"class":285},[35,3559,825],{"class":265},[35,3561,2138],{"class":45},[35,3563,1618],{"class":265},[35,3565,3566,3569,3571,3574,3577,3579,3581,3583,3585,3587,3590,3592,3595,3597,3599,3601,3603,3605,3607,3609,3611,3613,3615],{"class":37,"line":275},[35,3567,3568],{"class":265},"excel_closing ",[35,3570,349],{"class":261},[35,3572,3573],{"class":265}," [ws.cell(",[35,3575,3576],{"class":360},"row",[35,3578,349],{"class":261},[35,3580,2825],{"class":265},[35,3582,401],{"class":261},[35,3584,2585],{"class":285},[35,3586,357],{"class":265},[35,3588,3589],{"class":360},"column",[35,3591,349],{"class":261},[35,3593,3594],{"class":265},"c).value ",[35,3596,2323],{"class":261},[35,3598,2567],{"class":265},[35,3600,2329],{"class":261},[35,3602,2572],{"class":285},[35,3604,736],{"class":265},[35,3606,834],{"class":285},[35,3608,357],{"class":265},[35,3610,308],{"class":285},[35,3612,1726],{"class":261},[35,3614,2522],{"class":285},[35,3616,3617],{"class":265},")]\n",[35,3619,3620,3623,3625,3628,3630,3633,3636,3638,3641,3643,3646,3648,3651,3654,3656],{"class":37,"line":282},[35,3621,3622],{"class":265},"diff ",[35,3624,349],{"class":261},[35,3626,3627],{"class":285}," max",[35,3629,736],{"class":265},[35,3631,3632],{"class":285},"abs",[35,3634,3635],{"class":265},"(a ",[35,3637,392],{"class":261},[35,3639,3640],{"class":265}," b) ",[35,3642,2323],{"class":261},[35,3644,3645],{"class":265}," a, b ",[35,3647,2329],{"class":261},[35,3649,3650],{"class":285}," zip",[35,3652,3653],{"class":265},"(excel_closing, summary[",[35,3655,1776],{"class":45},[35,3657,3658],{"class":265},"]))\n",[35,3660,3661,3663,3665,3667,3670,3672,3675,3678,3680,3682],{"class":37,"line":305},[35,3662,467],{"class":285},[35,3664,736],{"class":265},[35,3666,739],{"class":261},[35,3668,3669],{"class":45},"\"largest difference between workbook and pandas: ",[35,3671,746],{"class":745},[35,3673,3674],{"class":265},"diff",[35,3676,3677],{"class":261},":.6f",[35,3679,755],{"class":745},[35,3681,742],{"class":45},[35,3683,378],{"class":265},[10,3685,3686,3690],{},[14,3687,3689],{"href":3688},"\u002Fgetting-started-with-python-excel-automation\u002Fworking-with-excel-formulas-in-python\u002Frecalculate-excel-formulas-without-excel-in-python\u002F","Recalculate Excel Formulas Without Excel in Python"," explains the LibreOffice step and the alternatives.",[20,3692,3694],{"id":3693},"keeping-the-forecast-honest","Keeping the forecast honest",[10,3696,3697,3698,3702],{},"A forecast earns trust by being compared with what happened. Each Monday, before rolling forward, keep last week's forecast and set it beside the actual bank movements: receipts forecast against received, payments forecast against paid. The differences show which assumption is wrong. A customer whose average days late is stale, a supplier paid early, a payroll that grew. Save each week's output with its date in the name so the comparison is always possible; ",[14,3699,3701],{"href":3700},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-bank-reconciliation-report\u002F","Build a Bank Reconciliation Report"," provides the actual side.",[20,3704,3706],{"id":3705},"common-pitfalls","Common pitfalls",[3708,3709,3710,3718,3724,3730,3736],"ul",{},[3711,3712,3713,3717],"li",{},[3714,3715,3716],"strong",{},"Forecasting receipts on due dates."," Customers pay late, and some always do; use their history, or the forecast is optimistic every week.",[3711,3719,3720,3723],{},[3714,3721,3722],{},"Past-dated flows."," Overdue invoices and bills land before the start date; clip them into week one or they vanish from the forecast.",[3711,3725,3726,3729],{},[3714,3727,3728],{},"Double counting."," A payroll that is already in AP as a bill must not also be generated as recurring.",[3711,3731,3732,3735],{},[3714,3733,3734],{},"Items beyond the horizon."," Report how much falls after week 13; it is still cash to come.",[3711,3737,3738,3741],{},[3714,3739,3740],{},"Hard-coded balances."," Write closing balances as formulas so edits flow through the remaining weeks.",[20,3743,3745],{"id":3744},"conclusion","Conclusion",[10,3747,3748,3749,3752,3753,3756],{},"Build the forecast from four sources: receivables shifted by each customer's days late, payables moved to the next payment run, recurring commitments generated from a schedule, and a clearly labelled estimate for sales not yet invoiced. Bucket everything into Monday-starting weeks with ",[32,3750,3751],{},"pivot_table",", roll the balance forward with ",[32,3754,3755],{},"cumsum",", and flag weeks below minimum cash. Write flows as values and balances as formulas, recalculate once to confirm the workbook matches pandas, and compare each week's forecast with the actuals.",[20,3758,3760],{"id":3759},"frequently-asked-questions","Frequently asked questions",[10,3762,3763,3766],{},[3714,3764,3765],{},"Why 13 weeks?","\nThirteen weeks is one quarter: long enough to see a shortfall coming and arrange financing, short enough that the receipts and payments are mostly known invoices rather than guesses.",[10,3768,3769,3772],{},[3714,3770,3771],{},"How do I estimate when customers will pay?","\nUse each customer's average days late from their payment history, and review it against actual receipts every week. Customers without history get a cautious default.",[10,3774,3775,3778],{},[3714,3776,3777],{},"Should the forecast use formulas or values?","\nWrite the individual flows as values from Python and the opening, net and closing balances as formulas. The reader can then move a receipt or add a payment and see the effect on every later week.",[10,3780,3781,3784],{},[3714,3782,3783],{},"How is this different from a budget?","\nA budget is about profit and is usually monthly; a cash forecast is about the bank balance, uses actual invoice timing, and is usually weekly.",[20,3786,3788],{"id":3787},"related","Related",[3708,3790,3791,3797,3802,3809,3814],{},[3711,3792,3793,3794,3796],{},"Up one level: ",[14,3795,17],{"href":16}," — the rest of the finance reports.",[3711,3798,3799,3801],{},[14,3800,855],{"href":854}," — where payment history comes from.",[3711,3803,3804,3808],{},[14,3805,3807],{"href":3806},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-budget-vs-actual-variance-report\u002F","Build a Budget vs Actual Variance Report"," — the profit view beside the cash view.",[3711,3810,3811,3813],{},[14,3812,3701],{"href":3700}," — checking forecasts against the bank.",[3711,3815,3816,3818],{},[14,3817,3689],{"href":3688}," — getting results into formula cells.",[3820,3821,3822],"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 .sP0c6, html code.shiki .sP0c6{--shiki-default:#005CC5;--shiki-dark:#91CBFF}html pre.shiki code .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .sa561, html code.shiki .sa561{--shiki-default:#E36209;--shiki-dark:#FFB757}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}",{"title":30,"searchDepth":275,"depth":275,"links":3824},[3825,3826,3827,3828,3829,3830,3831,3832,3833,3834,3835,3836,3837,3838],{"id":22,"depth":275,"text":23},{"id":245,"depth":275,"text":246},{"id":484,"depth":275,"text":485},{"id":859,"depth":275,"text":860},{"id":1027,"depth":275,"text":1028},{"id":1270,"depth":275,"text":1271},{"id":1418,"depth":275,"text":1419},{"id":2078,"depth":275,"text":2079},{"id":3430,"depth":275,"text":3431},{"id":3693,"depth":275,"text":3694},{"id":3705,"depth":275,"text":3706},{"id":3744,"depth":275,"text":3745},{"id":3759,"depth":275,"text":3760},{"id":3787,"depth":275,"text":3788},"2026-09-29","Turn open receivables, payables and recurring commitments into a rolling 13-week cash forecast: expected receipt dates from each customer's payment history, weekly buckets, opening and closing balances, a minimum-cash warning, and an Excel report with live balance formulas and a chart.","md",[3843,3845,3847,3849],{"q":3765,"a":3844},"Thirteen weeks is one quarter: long enough to see a shortfall coming and arrange financing, short enough that the receipts and payments are mostly known invoices rather than guesses.",{"q":3771,"a":3846},"Use each customer's average days late from their payment history, and review it against actual receipts every week. Customers without history get a cautious default.",{"q":3777,"a":3848},"Write the individual flows as values from Python and the opening, net and closing balances as formulas. The reader can then move a receipt or add a payment and see the effect on every later week.",{"q":3783,"a":3850},"A budget is about profit and is usually monthly; a cash forecast is about the bank balance, uses actual invoice timing, and is usually weekly.","build a 13-week cash flow forecast in excel",null,{"breadcrumb":3854},[3855,3858,3861],{"name":3856,"item":3857},"Home","\u002F",{"name":3859,"item":3860},"Automating Reporting Workflows with Python and Excel","\u002Fautomating-reporting-workflows\u002F",{"name":3862,"item":16},"Building Finance and Operations Reports in Excel with Python","\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-cash-flow-forecast-report",{"title":3865,"description":3866},"13-Week Cash Flow Forecast in Excel with Python and pandas","Build a weekly cash flow forecast with pandas: shift AR due dates by customer days late, schedule AP and recurring payroll and rent, bucket by week, roll the balance forward, flag weeks below minimum cash, write Excel with formulas.","build-a-cash-flow-forecast-report","automating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-cash-flow-forecast-report\u002Findex",[3870,3871],"report-layouts","dates-and-times","how-to","fZV1QqvZ2PAm7kIIPLMzC3OIItWfGxKVrnF0bO5wDB8",[3875,3879],{"title":3876,"path":3877,"stem":3878,"children":-1},"Build a Capacity Planning Report","\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-capacity-planning-report","automating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-capacity-planning-report\u002Findex",{"title":3880,"path":3881,"stem":3882,"children":-1},"Build a Cash Position Report from Bank Statements","\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-cash-position-report-from-bank-statements","automating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-cash-position-report-from-bank-statements\u002Findex",[3884,3886],{"path":3885,"title":3862},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports",{"path":3887,"title":3859},"\u002Fautomating-reporting-workflows",[],[3890,3894,3897,3900,3903,3906],{"path":3891,"title":3892,"kind":3893},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards","Building Multi-Sheet Excel Dashboards","guide",{"path":3895,"title":3896,"kind":3872},"\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":3898,"title":3899,"kind":3872},"\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":3901,"title":3902,"kind":3872},"\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":3904,"title":3905,"kind":3872},"\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":3907,"title":3908,"kind":3872},"\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",1790972121224]