[{"data":1,"prerenderedAt":2139},["ShallowReactive",2],{"doc:\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-deferred-revenue-schedule":3,"surround:\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-deferred-revenue-schedule":2104,"ancestors:\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-deferred-revenue-schedule":2113,"children:\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-deferred-revenue-schedule":2118,"related:\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-deferred-revenue-schedule":2119},{"id":4,"title":5,"body":6,"dateModified":2070,"datePublished":2070,"description":2071,"extension":2072,"faq":2073,"keyword":2082,"kind":2083,"meta":2084,"navigation":99,"path":2093,"seo":2094,"slug":2097,"stem":2098,"topics":2099,"type":2102,"__hash__":2103},"docs\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-deferred-revenue-schedule\u002Findex.md","Build a Deferred Revenue Schedule",{"type":7,"value":8,"toc":2058},"minimark",[9,24,29,58,64,68,71,248,255,259,262,699,706,836,840,843,1093,1104,1108,1111,1378,1381,1461,1465,1468,1809,1821,1949,1953,1988,1992,1995,1999,2005,2011,2017,2023,2027,2054],[10,11,12,13,17,18,23],"p",{},"A customer pays 12,000 in March for a year's subscription starting on 15 March. The cash is in the bank, but the revenue is not earned yet: it is earned day by day as the service is delivered, and until then the unearned part sits on the balance sheet as ",[14,15,16],"em",{},"deferred revenue",". Every month, finance must work out how much of each prepaid contract to recognise and what remains deferred. With a handful of contracts that is a spreadsheet; with hundreds, each starting on a different day and some with odd lengths, the spreadsheet becomes fragile — one dragged formula off by a row and the month's revenue is wrong. pandas builds the whole schedule from the contract list in a few lines, with daily precision and a built-in reconciliation. It belongs to ",[19,20,22],"a",{"href":21},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002F","Building Finance and Operations Reports",".",[25,26,28],"h2",{"id":27},"prerequisites","Prerequisites",[30,31,36],"pre",{"className":32,"code":33,"language":34,"meta":35,"style":35},"language-bash shiki shiki-themes github-light github-dark-high-contrast","pip install pandas openpyxl\n","bash","",[37,38,39],"code",{"__ignoreMap":35},[40,41,44,48,52,55],"span",{"class":42,"line":43},"line",1,[40,45,47],{"class":46},"sMTad","pip",[40,49,51],{"class":50},"srMev"," install",[40,53,54],{"class":50}," pandas",[40,56,57],{"class":50}," openpyxl\n",[10,59,60,63],{},[37,61,62],{},"contracts.xlsx"," lists five prepaid contracts with their invoice date, amount and service period — a one-year subscription starting mid-January, a six-month contract, a one-year contract from 15 March, a quarterly contract and a two-year contract from 10 September. The schedule covers January 2026 to December 2027, and month-end reporting is for September 2026.",[25,65,67],{"id":66},"step-1-daily-revenue-for-each-contract","Step 1 — Daily revenue for each contract",[10,69,70],{},"The fairest and most common straight-line method spreads the amount evenly over every day of the service period, so a contract starting on the 15th earns about half a month in its first month:",[30,72,76],{"className":73,"code":74,"language":75,"meta":35,"style":35},"language-python shiki shiki-themes github-light github-dark-high-contrast","import pandas as pd\n\ncontracts = pd.read_excel(\"contracts.xlsx\")\ncontracts[\"Days\"] = (contracts[\"Service end\"] - contracts[\"Service start\"]).dt.days + 1\ncontracts[\"Per day\"] = contracts[\"Amount\"] \u002F contracts[\"Days\"]\nprint(contracts[[\"Contract\", \"Service start\", \"Service end\", \"Days\", \"Amount\", \"Per day\"]].round({\"Per day\": 4}).to_string(index=False))\n","python",[37,77,78,94,101,119,160,189],{"__ignoreMap":35},[40,79,80,84,88,91],{"class":42,"line":43},[40,81,83],{"class":82},"s-kum","import",[40,85,87],{"class":86},"skGVy"," pandas ",[40,89,90],{"class":82},"as",[40,92,93],{"class":86}," pd\n",[40,95,97],{"class":42,"line":96},2,[40,98,100],{"emptyLinePlaceholder":99},true,"\n",[40,102,104,107,110,113,116],{"class":42,"line":103},3,[40,105,106],{"class":86},"contracts ",[40,108,109],{"class":82},"=",[40,111,112],{"class":86}," pd.read_excel(",[40,114,115],{"class":50},"\"contracts.xlsx\"",[40,117,118],{"class":86},")\n",[40,120,122,125,128,131,133,136,139,141,144,147,150,153,156],{"class":42,"line":121},4,[40,123,124],{"class":86},"contracts[",[40,126,127],{"class":50},"\"Days\"",[40,129,130],{"class":86},"] ",[40,132,109],{"class":82},[40,134,135],{"class":86}," (contracts[",[40,137,138],{"class":50},"\"Service end\"",[40,140,130],{"class":86},[40,142,143],{"class":82},"-",[40,145,146],{"class":86}," contracts[",[40,148,149],{"class":50},"\"Service start\"",[40,151,152],{"class":86},"]).dt.days ",[40,154,155],{"class":82},"+",[40,157,159],{"class":158},"sP0c6"," 1\n",[40,161,163,165,168,170,172,174,177,179,182,184,186],{"class":42,"line":162},5,[40,164,124],{"class":86},[40,166,167],{"class":50},"\"Per day\"",[40,169,130],{"class":86},[40,171,109],{"class":82},[40,173,146],{"class":86},[40,175,176],{"class":50},"\"Amount\"",[40,178,130],{"class":86},[40,180,181],{"class":82},"\u002F",[40,183,146],{"class":86},[40,185,127],{"class":50},[40,187,188],{"class":86},"]\n",[40,190,192,195,198,201,204,206,208,210,212,214,216,218,220,222,225,227,230,233,236,240,242,245],{"class":42,"line":191},6,[40,193,194],{"class":158},"print",[40,196,197],{"class":86},"(contracts[[",[40,199,200],{"class":50},"\"Contract\"",[40,202,203],{"class":86},", ",[40,205,149],{"class":50},[40,207,203],{"class":86},[40,209,138],{"class":50},[40,211,203],{"class":86},[40,213,127],{"class":50},[40,215,203],{"class":86},[40,217,176],{"class":50},[40,219,203],{"class":86},[40,221,167],{"class":50},[40,223,224],{"class":86},"]].round({",[40,226,167],{"class":50},[40,228,229],{"class":86},": ",[40,231,232],{"class":158},"4",[40,234,235],{"class":86},"}).to_string(",[40,237,239],{"class":238},"sa561","index",[40,241,109],{"class":82},[40,243,244],{"class":158},"False",[40,246,247],{"class":86},"))\n",[10,249,250,251,254],{},"Other recognition patterns exist — usage-based, milestone-based, or equal monthly amounts regardless of days — and the contract terms decide which applies; straight-line daily is the usual default for subscriptions and support contracts, and the structure below works for the others by changing how each month's share is computed. The ",[37,252,253],{},"+ 1"," counts both the first and last day: 15 January 2026 to 14 January 2027 is 365 days. Contract C-1003 covers 365 days for 9,125, which is exactly 25 a day; the two-year C-1005 runs over 2027–2028 and includes 29 February 2028, so it has 731 days. Daily rates are kept unrounded here; rounding happens once, per month, in the next step, so the parts add back to the invoice.",[25,256,258],{"id":257},"step-2-spread-the-days-into-months","Step 2 — Spread the days into months",[10,260,261],{},"For each contract and month, count the service days that fall in the month and multiply by the daily rate. Building the overlap with dates rather than loops keeps it fast for thousands of contracts:",[30,263,265],{"className":73,"code":264,"language":75,"meta":35,"style":35},"import numpy as np\n\nmonths = pd.period_range(\"2026-01\", \"2028-09\", freq=\"M\")\nm_start = months.to_timestamp(how=\"start\")\nm_end = months.to_timestamp(how=\"end\").normalize()\n\ndef schedule_row(c):\n    start = m_start.where(m_start > c[\"Service start\"], c[\"Service start\"])\n    end = m_end.where(m_end \u003C c[\"Service end\"], c[\"Service end\"])\n    days = np.maximum((end - start).days + 1, 0)\n    return pd.Series(np.round(days * c[\"Per day\"], 2), index=months.astype(str))\n\nsched = contracts.set_index(\"Contract\").apply(schedule_row, axis=1)\n# put any rounding difference in the final month so each contract totals its invoice exactly\ndiff = (contracts.set_index(\"Contract\")[\"Amount\"] - sched.sum(axis=1)).round(2)\nlast_month = sched.apply(lambda r: r[r > 0].index[-1], axis=1)\nfor c, m in last_month.items():\n    sched.loc[c, m] = round(sched.loc[c, m] + diff[c], 2)\nprint(sched.loc[:, \"2026-01\":\"2026-04\"].to_string())\nprint(\"contract totals equal invoices:\", (sched.sum(axis=1).round(2) == contracts.set_index(\"Contract\")[\"Amount\"]).all())\n",[37,266,267,279,283,313,333,352,356,369,396,420,448,485,490,516,523,561,600,615,638,657],{"__ignoreMap":35},[40,268,269,271,274,276],{"class":42,"line":43},[40,270,83],{"class":82},[40,272,273],{"class":86}," numpy ",[40,275,90],{"class":82},[40,277,278],{"class":86}," np\n",[40,280,281],{"class":42,"line":96},[40,282,100],{"emptyLinePlaceholder":99},[40,284,285,288,290,293,296,298,301,303,306,308,311],{"class":42,"line":103},[40,286,287],{"class":86},"months ",[40,289,109],{"class":82},[40,291,292],{"class":86}," pd.period_range(",[40,294,295],{"class":50},"\"2026-01\"",[40,297,203],{"class":86},[40,299,300],{"class":50},"\"2028-09\"",[40,302,203],{"class":86},[40,304,305],{"class":238},"freq",[40,307,109],{"class":82},[40,309,310],{"class":50},"\"M\"",[40,312,118],{"class":86},[40,314,315,318,320,323,326,328,331],{"class":42,"line":121},[40,316,317],{"class":86},"m_start ",[40,319,109],{"class":82},[40,321,322],{"class":86}," months.to_timestamp(",[40,324,325],{"class":238},"how",[40,327,109],{"class":82},[40,329,330],{"class":50},"\"start\"",[40,332,118],{"class":86},[40,334,335,338,340,342,344,346,349],{"class":42,"line":162},[40,336,337],{"class":86},"m_end ",[40,339,109],{"class":82},[40,341,322],{"class":86},[40,343,325],{"class":238},[40,345,109],{"class":82},[40,347,348],{"class":50},"\"end\"",[40,350,351],{"class":86},").normalize()\n",[40,353,354],{"class":42,"line":191},[40,355,100],{"emptyLinePlaceholder":99},[40,357,359,362,366],{"class":42,"line":358},7,[40,360,361],{"class":82},"def",[40,363,365],{"class":364},"s_Opv"," schedule_row",[40,367,368],{"class":86},"(c):\n",[40,370,372,375,377,380,383,386,388,391,393],{"class":42,"line":371},8,[40,373,374],{"class":86},"    start ",[40,376,109],{"class":82},[40,378,379],{"class":86}," m_start.where(m_start ",[40,381,382],{"class":82},">",[40,384,385],{"class":86}," c[",[40,387,149],{"class":50},[40,389,390],{"class":86},"], c[",[40,392,149],{"class":50},[40,394,395],{"class":86},"])\n",[40,397,399,402,404,407,410,412,414,416,418],{"class":42,"line":398},9,[40,400,401],{"class":86},"    end ",[40,403,109],{"class":82},[40,405,406],{"class":86}," m_end.where(m_end ",[40,408,409],{"class":82},"\u003C",[40,411,385],{"class":86},[40,413,138],{"class":50},[40,415,390],{"class":86},[40,417,138],{"class":50},[40,419,395],{"class":86},[40,421,423,426,428,431,433,436,438,441,443,446],{"class":42,"line":422},10,[40,424,425],{"class":86},"    days ",[40,427,109],{"class":82},[40,429,430],{"class":86}," np.maximum((end ",[40,432,143],{"class":82},[40,434,435],{"class":86}," start).days ",[40,437,155],{"class":82},[40,439,440],{"class":158}," 1",[40,442,203],{"class":86},[40,444,445],{"class":158},"0",[40,447,118],{"class":86},[40,449,451,454,457,460,462,464,467,470,473,475,477,480,483],{"class":42,"line":450},11,[40,452,453],{"class":82},"    return",[40,455,456],{"class":86}," pd.Series(np.round(days ",[40,458,459],{"class":82},"*",[40,461,385],{"class":86},[40,463,167],{"class":50},[40,465,466],{"class":86},"], ",[40,468,469],{"class":158},"2",[40,471,472],{"class":86},"), ",[40,474,239],{"class":238},[40,476,109],{"class":82},[40,478,479],{"class":86},"months.astype(",[40,481,482],{"class":158},"str",[40,484,247],{"class":86},[40,486,488],{"class":42,"line":487},12,[40,489,100],{"emptyLinePlaceholder":99},[40,491,493,496,498,501,503,506,509,511,514],{"class":42,"line":492},13,[40,494,495],{"class":86},"sched ",[40,497,109],{"class":82},[40,499,500],{"class":86}," contracts.set_index(",[40,502,200],{"class":50},[40,504,505],{"class":86},").apply(schedule_row, ",[40,507,508],{"class":238},"axis",[40,510,109],{"class":82},[40,512,513],{"class":158},"1",[40,515,118],{"class":86},[40,517,519],{"class":42,"line":518},14,[40,520,522],{"class":521},"s-wDw","# put any rounding difference in the final month so each contract totals its invoice exactly\n",[40,524,526,529,531,534,536,539,541,543,545,548,550,552,554,557,559],{"class":42,"line":525},15,[40,527,528],{"class":86},"diff ",[40,530,109],{"class":82},[40,532,533],{"class":86}," (contracts.set_index(",[40,535,200],{"class":50},[40,537,538],{"class":86},")[",[40,540,176],{"class":50},[40,542,130],{"class":86},[40,544,143],{"class":82},[40,546,547],{"class":86}," sched.sum(",[40,549,508],{"class":238},[40,551,109],{"class":82},[40,553,513],{"class":158},[40,555,556],{"class":86},")).round(",[40,558,469],{"class":158},[40,560,118],{"class":86},[40,562,564,567,569,572,575,578,580,583,586,588,590,592,594,596,598],{"class":42,"line":563},16,[40,565,566],{"class":86},"last_month ",[40,568,109],{"class":82},[40,570,571],{"class":86}," sched.apply(",[40,573,574],{"class":82},"lambda",[40,576,577],{"class":86}," r: r[r ",[40,579,382],{"class":82},[40,581,582],{"class":158}," 0",[40,584,585],{"class":86},"].index[",[40,587,143],{"class":82},[40,589,513],{"class":158},[40,591,466],{"class":86},[40,593,508],{"class":238},[40,595,109],{"class":82},[40,597,513],{"class":158},[40,599,118],{"class":86},[40,601,603,606,609,612],{"class":42,"line":602},17,[40,604,605],{"class":82},"for",[40,607,608],{"class":86}," c, m ",[40,610,611],{"class":82},"in",[40,613,614],{"class":86}," last_month.items():\n",[40,616,618,621,623,626,629,631,634,636],{"class":42,"line":617},18,[40,619,620],{"class":86},"    sched.loc[c, m] ",[40,622,109],{"class":82},[40,624,625],{"class":158}," round",[40,627,628],{"class":86},"(sched.loc[c, m] ",[40,630,155],{"class":82},[40,632,633],{"class":86}," diff[c], ",[40,635,469],{"class":158},[40,637,118],{"class":86},[40,639,641,643,646,648,651,654],{"class":42,"line":640},19,[40,642,194],{"class":158},[40,644,645],{"class":86},"(sched.loc[:, ",[40,647,295],{"class":50},[40,649,650],{"class":86},":",[40,652,653],{"class":50},"\"2026-04\"",[40,655,656],{"class":86},"].to_string())\n",[40,658,660,662,665,668,671,673,675,677,680,682,685,688,690,692,694,696],{"class":42,"line":659},20,[40,661,194],{"class":158},[40,663,664],{"class":86},"(",[40,666,667],{"class":50},"\"contract totals equal invoices:\"",[40,669,670],{"class":86},", (sched.sum(",[40,672,508],{"class":238},[40,674,109],{"class":82},[40,676,513],{"class":158},[40,678,679],{"class":86},").round(",[40,681,469],{"class":158},[40,683,684],{"class":86},") ",[40,686,687],{"class":82},"==",[40,689,500],{"class":86},[40,691,200],{"class":50},[40,693,538],{"class":86},[40,695,176],{"class":50},[40,697,698],{"class":86},"]).all())\n",[10,700,701,702,705],{},"For each month, the overlap starts at the later of the month start and the service start and ends at the earlier of the month end and the service end; a negative day count means no overlap and is clipped to zero. C-1001 earns 17 days in January (15–31 January) and a full month after that. ",[37,703,704],{},"np.maximum(..., 0)"," does the clipping element-wise across all months at once. Laying the months out as columns in this way also makes the schedule easy to read in Excel, where accountants expect one row per contract and one column per period. Rounding each month to cents leaves a few cents of difference over a contract's life; the code puts that difference in the contract's last month so every contract recognises exactly its invoiced amount — auditors check this.",[707,708,716,717,716,721,716,725,716,731,716,738,716,748,716,755,716,760,716,764,716,768,716,774,716,780,716,784,716,786,716,788,716,793,716,798,716,801,716,803,716,805,716,808,716,812,716,815,716,817,716,819,716,824,716,829,716,832,716,834],"svg",{"viewBox":709,"role":710,"ariaLabelledBy":711,"xmlns":714,"style":715},"4 0 752 211","img",[712,713],"dr-c1001-t","dr-c1001-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  ",[718,719,720],"title",{"id":712},"How contract C-1001 is recognised",[722,723,724],"desc",{"id":713},"The 12,000 one-year contract starts on 15 January 2026 at about 32.88 a day. January recognises 17 days, about 559. Each following full month recognises its days, about 920 to 1,019. The contract ends on 14 January 2027 with 14 days recognised.",[726,727],"rect",{"x":232,"y":445,"width":728,"height":729,"fill":730},"752","211","#ffffff",[42,732],{"x1":733,"y1":734,"x2":735,"y2":734,"stroke":736,"style":737},"76.0","105.5","684.0","var(--muted,#5b6780)","stroke-width:2.5px",[726,739],{"x":740,"y":741,"width":742,"height":743,"rx":744,"fill":745,"stroke":746,"style":747},"20.0","16.0","136.0","57.5","12","#f0f4ff","var(--brand,#5b5cf0)","stroke-width:2px",[749,750,754],"text",{"x":751,"y":752,"style":753},"88.0","40.5","font-size:12.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","15 Jan 2026",[749,756,759],{"x":751,"y":757,"style":758},"59.0","font-size:11px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","start · 32.88\u002Fday",[42,761],{"x1":751,"y1":762,"x2":751,"y2":734,"stroke":736,"style":763},"73.5","stroke-width:1.5px",[765,766],"circle",{"cx":751,"cy":734,"r":767,"fill":746,"stroke":730,"style":747},"7.0",[726,769],{"x":770,"y":771,"width":742,"height":743,"rx":744,"fill":772,"stroke":773,"style":747},"166.0","137.5","#d9f4f1","var(--teal,#0f9488)",[749,775,779],{"x":776,"y":777,"style":778},"234.0","162.0","font-size:12.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Jan 2026",[749,781,783],{"x":776,"y":782,"style":758},"180.5","17 days · 558.90",[42,785],{"x1":776,"y1":734,"x2":776,"y2":771,"stroke":736,"style":763},[765,787],{"cx":776,"cy":734,"r":767,"fill":773,"stroke":730,"style":747},[726,789],{"x":790,"y":741,"width":742,"height":743,"rx":744,"fill":791,"stroke":792,"style":747},"312.0","#e7ebef","var(--line,#cdd5e6)",[749,794,797],{"x":795,"y":752,"style":796},"380.0","font-size:12.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Feb–Dec 2026",[749,799,800],{"x":795,"y":757,"style":758},"full months",[42,802],{"x1":795,"y1":762,"x2":795,"y2":734,"stroke":736,"style":763},[765,804],{"cx":795,"cy":734,"r":767,"fill":736,"stroke":730,"style":747},[726,806],{"x":807,"y":771,"width":742,"height":743,"rx":744,"fill":772,"stroke":773,"style":747},"458.0",[749,809,811],{"x":810,"y":777,"style":778},"526.0","Jan 2027",[749,813,814],{"x":810,"y":782,"style":758},"14 days · rest",[42,816],{"x1":810,"y1":734,"x2":810,"y2":771,"stroke":736,"style":763},[765,818],{"cx":810,"cy":734,"r":767,"fill":773,"stroke":730,"style":747},[726,820],{"x":821,"y":741,"width":742,"height":743,"rx":744,"fill":822,"stroke":823,"style":747},"604.0","#fdefd8","var(--gold,#b4740a)",[749,825,828],{"x":826,"y":752,"style":827},"672.0","font-size:12.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","14 Jan 2027",[749,830,831],{"x":826,"y":757,"style":758},"fully recognised",[42,833],{"x1":826,"y1":762,"x2":826,"y2":734,"stroke":736,"style":763},[765,835],{"cx":826,"cy":734,"r":767,"fill":823,"stroke":730,"style":747},[25,837,839],{"id":838},"step-3-deferred-balance-at-each-month-end","Step 3 — Deferred balance at each month end",[10,841,842],{},"Deferred revenue at a month end is everything invoiced so far minus everything recognised so far. Invoices are recorded in the month they are issued, which may be before the service starts:",[30,844,846],{"className":73,"code":845,"language":75,"meta":35,"style":35},"invoiced = (contracts.assign(Month=contracts[\"Invoice date\"].dt.to_period(\"M\").astype(str))\n                     .pivot_table(index=\"Contract\", columns=\"Month\", values=\"Amount\", aggfunc=\"sum\")\n                     .reindex(columns=sched.columns, fill_value=0).fillna(0))\n\nroll = pd.DataFrame({\n    \"Invoiced\": invoiced.sum(),\n    \"Recognised\": sched.sum(),\n})\nroll[\"Deferred (closing)\"] = (roll[\"Invoiced\"] - roll[\"Recognised\"]).cumsum().round(2)\nroll[\"Deferred (opening)\"] = roll[\"Deferred (closing)\"].shift(fill_value=0)\nroll = roll[[\"Deferred (opening)\", \"Invoiced\", \"Recognised\", \"Deferred (closing)\"]]\nprint(roll.loc[\"2026-06\":\"2026-10\"].round(2).to_string())\n",[37,847,848,880,922,948,952,962,970,978,983,1018,1044,1070],{"__ignoreMap":35},[40,849,850,853,855,858,861,863,865,868,871,873,876,878],{"class":42,"line":43},[40,851,852],{"class":86},"invoiced ",[40,854,109],{"class":82},[40,856,857],{"class":86}," (contracts.assign(",[40,859,860],{"class":238},"Month",[40,862,109],{"class":82},[40,864,124],{"class":86},[40,866,867],{"class":50},"\"Invoice date\"",[40,869,870],{"class":86},"].dt.to_period(",[40,872,310],{"class":50},[40,874,875],{"class":86},").astype(",[40,877,482],{"class":158},[40,879,247],{"class":86},[40,881,882,885,887,889,891,893,896,898,901,903,906,908,910,912,915,917,920],{"class":42,"line":96},[40,883,884],{"class":86},"                     .pivot_table(",[40,886,239],{"class":238},[40,888,109],{"class":82},[40,890,200],{"class":50},[40,892,203],{"class":86},[40,894,895],{"class":238},"columns",[40,897,109],{"class":82},[40,899,900],{"class":50},"\"Month\"",[40,902,203],{"class":86},[40,904,905],{"class":238},"values",[40,907,109],{"class":82},[40,909,176],{"class":50},[40,911,203],{"class":86},[40,913,914],{"class":238},"aggfunc",[40,916,109],{"class":82},[40,918,919],{"class":50},"\"sum\"",[40,921,118],{"class":86},[40,923,924,927,929,931,934,937,939,941,944,946],{"class":42,"line":103},[40,925,926],{"class":86},"                     .reindex(",[40,928,895],{"class":238},[40,930,109],{"class":82},[40,932,933],{"class":86},"sched.columns, ",[40,935,936],{"class":238},"fill_value",[40,938,109],{"class":82},[40,940,445],{"class":158},[40,942,943],{"class":86},").fillna(",[40,945,445],{"class":158},[40,947,247],{"class":86},[40,949,950],{"class":42,"line":121},[40,951,100],{"emptyLinePlaceholder":99},[40,953,954,957,959],{"class":42,"line":162},[40,955,956],{"class":86},"roll ",[40,958,109],{"class":82},[40,960,961],{"class":86}," pd.DataFrame({\n",[40,963,964,967],{"class":42,"line":191},[40,965,966],{"class":50},"    \"Invoiced\"",[40,968,969],{"class":86},": invoiced.sum(),\n",[40,971,972,975],{"class":42,"line":358},[40,973,974],{"class":50},"    \"Recognised\"",[40,976,977],{"class":86},": sched.sum(),\n",[40,979,980],{"class":42,"line":371},[40,981,982],{"class":86},"})\n",[40,984,985,988,991,993,995,998,1001,1003,1005,1008,1011,1014,1016],{"class":42,"line":398},[40,986,987],{"class":86},"roll[",[40,989,990],{"class":50},"\"Deferred (closing)\"",[40,992,130],{"class":86},[40,994,109],{"class":82},[40,996,997],{"class":86}," (roll[",[40,999,1000],{"class":50},"\"Invoiced\"",[40,1002,130],{"class":86},[40,1004,143],{"class":82},[40,1006,1007],{"class":86}," roll[",[40,1009,1010],{"class":50},"\"Recognised\"",[40,1012,1013],{"class":86},"]).cumsum().round(",[40,1015,469],{"class":158},[40,1017,118],{"class":86},[40,1019,1020,1022,1025,1027,1029,1031,1033,1036,1038,1040,1042],{"class":42,"line":422},[40,1021,987],{"class":86},[40,1023,1024],{"class":50},"\"Deferred (opening)\"",[40,1026,130],{"class":86},[40,1028,109],{"class":82},[40,1030,1007],{"class":86},[40,1032,990],{"class":50},[40,1034,1035],{"class":86},"].shift(",[40,1037,936],{"class":238},[40,1039,109],{"class":82},[40,1041,445],{"class":158},[40,1043,118],{"class":86},[40,1045,1046,1048,1050,1053,1055,1057,1059,1061,1063,1065,1067],{"class":42,"line":450},[40,1047,956],{"class":86},[40,1049,109],{"class":82},[40,1051,1052],{"class":86}," roll[[",[40,1054,1024],{"class":50},[40,1056,203],{"class":86},[40,1058,1000],{"class":50},[40,1060,203],{"class":86},[40,1062,1010],{"class":50},[40,1064,203],{"class":86},[40,1066,990],{"class":50},[40,1068,1069],{"class":86},"]]\n",[40,1071,1072,1074,1077,1080,1082,1085,1088,1090],{"class":42,"line":487},[40,1073,194],{"class":158},[40,1075,1076],{"class":86},"(roll.loc[",[40,1078,1079],{"class":50},"\"2026-06\"",[40,1081,650],{"class":86},[40,1083,1084],{"class":50},"\"2026-10\"",[40,1086,1087],{"class":86},"].round(",[40,1089,469],{"class":158},[40,1091,1092],{"class":86},").to_string())\n",[10,1094,1095,1096,1099,1100,23],{},"This is the deferred revenue ",[14,1097,1098],{},"roll-forward",": opening balance, plus new invoices, minus revenue recognised, equals closing balance. In September 2026 the two-year 24,000 contract is invoiced, so the balance jumps, while recognition continues on the others. The closing balance for September is the figure that must agree with the deferred revenue account in the general ledger — see ",[19,1101,1103],{"href":1102},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-trial-balance-from-a-general-ledger\u002F","Build a Trial Balance from a General Ledger",[25,1105,1107],{"id":1106},"step-4-the-months-journal-and-the-checks","Step 4 — The month's journal and the checks",[10,1109,1110],{},"Each month, one journal moves the recognised amount from the deferred revenue liability to revenue. Generate it from the schedule, and check that the schedule reconciles before posting:",[30,1112,1114],{"className":73,"code":1113,"language":75,"meta":35,"style":35},"month = \"2026-09\"\njournal = pd.DataFrame([\n    {\"Account\": \"2400 Deferred revenue\", \"Debit\": round(sched[month].sum(), 2), \"Credit\": 0.0},\n    {\"Account\": \"4100 Subscription revenue\", \"Debit\": 0.0, \"Credit\": round(sched[month].sum(), 2)},\n])\nprint(journal.to_string(index=False))\nprint(\"by contract:\", sched[month][sched[month] > 0].round(2).to_dict())\n\ntotal_invoiced = contracts[\"Amount\"].sum()\nrecognised_to_date = sched.loc[:, :month].sum().sum()\ndeferred_now = roll.loc[month, \"Deferred (closing)\"]\nprint(f\"invoiced {total_invoiced:,.2f} = recognised to date {recognised_to_date:,.2f} + deferred {deferred_now:,.2f}:\",\n      round(recognised_to_date + deferred_now, 2) == round(total_invoiced, 2))\n",[37,1115,1116,1126,1136,1177,1211,1215,1230,1253,1257,1271,1281,1295,1350],{"__ignoreMap":35},[40,1117,1118,1121,1123],{"class":42,"line":43},[40,1119,1120],{"class":86},"month ",[40,1122,109],{"class":82},[40,1124,1125],{"class":50}," \"2026-09\"\n",[40,1127,1128,1131,1133],{"class":42,"line":96},[40,1129,1130],{"class":86},"journal ",[40,1132,109],{"class":82},[40,1134,1135],{"class":86}," pd.DataFrame([\n",[40,1137,1138,1141,1144,1146,1149,1151,1154,1156,1159,1162,1164,1166,1169,1171,1174],{"class":42,"line":103},[40,1139,1140],{"class":86},"    {",[40,1142,1143],{"class":50},"\"Account\"",[40,1145,229],{"class":86},[40,1147,1148],{"class":50},"\"2400 Deferred revenue\"",[40,1150,203],{"class":86},[40,1152,1153],{"class":50},"\"Debit\"",[40,1155,229],{"class":86},[40,1157,1158],{"class":158},"round",[40,1160,1161],{"class":86},"(sched[month].sum(), ",[40,1163,469],{"class":158},[40,1165,472],{"class":86},[40,1167,1168],{"class":50},"\"Credit\"",[40,1170,229],{"class":86},[40,1172,1173],{"class":158},"0.0",[40,1175,1176],{"class":86},"},\n",[40,1178,1179,1181,1183,1185,1188,1190,1192,1194,1196,1198,1200,1202,1204,1206,1208],{"class":42,"line":121},[40,1180,1140],{"class":86},[40,1182,1143],{"class":50},[40,1184,229],{"class":86},[40,1186,1187],{"class":50},"\"4100 Subscription revenue\"",[40,1189,203],{"class":86},[40,1191,1153],{"class":50},[40,1193,229],{"class":86},[40,1195,1173],{"class":158},[40,1197,203],{"class":86},[40,1199,1168],{"class":50},[40,1201,229],{"class":86},[40,1203,1158],{"class":158},[40,1205,1161],{"class":86},[40,1207,469],{"class":158},[40,1209,1210],{"class":86},")},\n",[40,1212,1213],{"class":42,"line":162},[40,1214,395],{"class":86},[40,1216,1217,1219,1222,1224,1226,1228],{"class":42,"line":191},[40,1218,194],{"class":158},[40,1220,1221],{"class":86},"(journal.to_string(",[40,1223,239],{"class":238},[40,1225,109],{"class":82},[40,1227,244],{"class":158},[40,1229,247],{"class":86},[40,1231,1232,1234,1236,1239,1242,1244,1246,1248,1250],{"class":42,"line":358},[40,1233,194],{"class":158},[40,1235,664],{"class":86},[40,1237,1238],{"class":50},"\"by contract:\"",[40,1240,1241],{"class":86},", sched[month][sched[month] ",[40,1243,382],{"class":82},[40,1245,582],{"class":158},[40,1247,1087],{"class":86},[40,1249,469],{"class":158},[40,1251,1252],{"class":86},").to_dict())\n",[40,1254,1255],{"class":42,"line":371},[40,1256,100],{"emptyLinePlaceholder":99},[40,1258,1259,1262,1264,1266,1268],{"class":42,"line":398},[40,1260,1261],{"class":86},"total_invoiced ",[40,1263,109],{"class":82},[40,1265,146],{"class":86},[40,1267,176],{"class":50},[40,1269,1270],{"class":86},"].sum()\n",[40,1272,1273,1276,1278],{"class":42,"line":422},[40,1274,1275],{"class":86},"recognised_to_date ",[40,1277,109],{"class":82},[40,1279,1280],{"class":86}," sched.loc[:, :month].sum().sum()\n",[40,1282,1283,1286,1288,1291,1293],{"class":42,"line":450},[40,1284,1285],{"class":86},"deferred_now ",[40,1287,109],{"class":82},[40,1289,1290],{"class":86}," roll.loc[month, ",[40,1292,990],{"class":50},[40,1294,188],{"class":86},[40,1296,1297,1299,1301,1304,1307,1311,1314,1317,1320,1323,1325,1328,1330,1332,1335,1337,1340,1342,1344,1347],{"class":42,"line":487},[40,1298,194],{"class":158},[40,1300,664],{"class":86},[40,1302,1303],{"class":82},"f",[40,1305,1306],{"class":50},"\"invoiced ",[40,1308,1310],{"class":1309},"sSjpA","{",[40,1312,1313],{"class":86},"total_invoiced",[40,1315,1316],{"class":82},":,.2f",[40,1318,1319],{"class":1309},"}",[40,1321,1322],{"class":50}," = recognised to date ",[40,1324,1310],{"class":1309},[40,1326,1327],{"class":86},"recognised_to_date",[40,1329,1316],{"class":82},[40,1331,1319],{"class":1309},[40,1333,1334],{"class":50}," + deferred ",[40,1336,1310],{"class":1309},[40,1338,1339],{"class":86},"deferred_now",[40,1341,1316],{"class":82},[40,1343,1319],{"class":1309},[40,1345,1346],{"class":50},":\"",[40,1348,1349],{"class":86},",\n",[40,1351,1352,1355,1358,1360,1363,1365,1367,1369,1371,1374,1376],{"class":42,"line":492},[40,1353,1354],{"class":158},"      round",[40,1356,1357],{"class":86},"(recognised_to_date ",[40,1359,155],{"class":82},[40,1361,1362],{"class":86}," deferred_now, ",[40,1364,469],{"class":158},[40,1366,684],{"class":86},[40,1368,687],{"class":82},[40,1370,625],{"class":158},[40,1372,1373],{"class":86},"(total_invoiced, ",[40,1375,469],{"class":158},[40,1377,247],{"class":86},[10,1379,1380],{},"The journal debits deferred revenue and credits revenue for the month's total, with a by-contract breakdown for the audit file. The final check is the identity that keeps the schedule honest: everything invoiced to date equals what has been recognised plus what is still deferred. C-1002 finished at the end of August and recognises nothing; C-1004 recognises its final month, 30 days at 1,800 ÷ 92 a day; and C-1005, which started on 10 September, recognises 21 days.",[707,1382,716,1388,716,1391,716,1394,716,1398,716,1404,716,1409,716,1413,716,1418,716,1421,716,1425,716,1428,716,1432,716,1435,716,1439,716,1442,716,1447,716,1449,716,1453,716,1456],{"viewBox":1383,"role":710,"ariaLabelledBy":1384,"xmlns":714,"style":1387},"4 0 727 180",[1385,1386],"dr-sept-t","dr-sept-d","width:100%;max-width:727px;height:auto;display:block;margin:1.5rem auto;font-family:Inter,ui-sans-serif,system-ui,sans-serif",[718,1389,1390],{"id":1385},"September 2026 revenue recognised by contract",[722,1392,1393],{"id":1386},"Revenue recognised in September 2026. The one-year contracts C-1001 and C-1003 each recognise a full month, about 986 and 750. The quarterly contract C-1004 recognises its last month, 586.96. The new two-year contract C-1005 recognises 21 days, about 689.",[726,1395],{"x":232,"y":445,"width":1396,"height":1397,"fill":730},"727","180",[726,1399],{"x":740,"y":741,"width":1400,"height":1401,"rx":1402,"fill":791,"stroke":792,"style":1403},"181.2","28.0","7","stroke-width:1px",[749,1405,1408],{"x":1406,"y":1407,"style":796},"110.6","34.5","C-1001 Oakridge Farms",[726,1410],{"x":1411,"y":741,"width":1412,"height":1401,"rx":1402,"fill":745,"stroke":746,"style":763},"211.2","377.9",[749,1414,1417],{"x":1415,"y":1407,"style":1416},"597.1","font-size:12px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:start","986.30 · full month",[726,1419],{"x":740,"y":1420,"width":1400,"height":1401,"rx":1402,"fill":791,"stroke":792,"style":1403},"56.0",[749,1422,1424],{"x":1406,"y":1423,"style":796},"74.5","C-1003 Zenith Logistics",[726,1426],{"x":1411,"y":1420,"width":1427,"height":1401,"rx":1402,"fill":745,"stroke":746,"style":763},"287.4",[749,1429,1431],{"x":1430,"y":1423,"style":1416},"506.6","750.00 · 30 days × 25",[726,1433],{"x":740,"y":1434,"width":1400,"height":1401,"rx":1402,"fill":791,"stroke":792,"style":1403},"96.0",[749,1436,1438],{"x":1406,"y":1437,"style":796},"114.5","C-1004 Haddad & Sons",[726,1440],{"x":1411,"y":1434,"width":1441,"height":1401,"rx":1402,"fill":772,"stroke":773,"style":763},"224.9",[749,1443,1446],{"x":1444,"y":1437,"style":1445},"444.1","font-size:12px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:start","586.96 · final month",[726,1448],{"x":740,"y":742,"width":1400,"height":1401,"rx":1402,"fill":791,"stroke":792,"style":1403},[749,1450,1452],{"x":1406,"y":1451,"style":796},"154.5","C-1005 Lindqvist AB",[726,1454],{"x":1411,"y":742,"width":1455,"height":1401,"rx":1402,"fill":822,"stroke":823,"style":763},"264.2",[749,1457,1460],{"x":1458,"y":1451,"style":1459},"483.4","font-size:12px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:start","689.47 · from 10 Sep",[25,1462,1464],{"id":1463},"step-5-write-the-schedule-to-excel","Step 5 — Write the schedule to Excel",[10,1466,1467],{},"The workbook carries the roll-forward, the contract-by-month schedule and the journal:",[30,1469,1471],{"className":73,"code":1470,"language":75,"meta":35,"style":35},"from openpyxl.styles import Font\n\nwith pd.ExcelWriter(\"deferred_revenue_2026-09.xlsx\", engine=\"openpyxl\") as xw:\n    roll.round(2).to_excel(xw, sheet_name=\"Roll-forward\")\n    out = contracts.set_index(\"Contract\")[[\"Customer\", \"Amount\", \"Service start\", \"Service end\"]].join(sched)\n    out.to_excel(xw, sheet_name=\"Schedule\")\n    journal.to_excel(xw, sheet_name=f\"Journal {month}\", index=False)\n    for ws in xw.book.worksheets:\n        for c in ws[1]:\n            c.font = Font(bold=True)\n        ws.freeze_panes = \"B2\" if ws.title != \"Schedule\" else \"F2\"\n    ws = xw.sheets[\"Schedule\"]\n    for row in ws.iter_rows(min_row=2, min_col=3):\n        for c in row:\n            if isinstance(c.value, float):\n                c.number_format = \"#,##0.00;-#,##0.00;\\\"\\\"\"\nprint(out.shape[1] - 4, \"monthly columns written\")\n",[37,1472,1473,1486,1490,1518,1538,1570,1584,1618,1631,1649,1669,1697,1711,1743,1754,1770,1786],{"__ignoreMap":35},[40,1474,1475,1478,1481,1483],{"class":42,"line":43},[40,1476,1477],{"class":82},"from",[40,1479,1480],{"class":86}," openpyxl.styles ",[40,1482,83],{"class":82},[40,1484,1485],{"class":86}," Font\n",[40,1487,1488],{"class":42,"line":96},[40,1489,100],{"emptyLinePlaceholder":99},[40,1491,1492,1495,1498,1501,1503,1506,1508,1511,1513,1515],{"class":42,"line":103},[40,1493,1494],{"class":82},"with",[40,1496,1497],{"class":86}," pd.ExcelWriter(",[40,1499,1500],{"class":50},"\"deferred_revenue_2026-09.xlsx\"",[40,1502,203],{"class":86},[40,1504,1505],{"class":238},"engine",[40,1507,109],{"class":82},[40,1509,1510],{"class":50},"\"openpyxl\"",[40,1512,684],{"class":86},[40,1514,90],{"class":82},[40,1516,1517],{"class":86}," xw:\n",[40,1519,1520,1523,1525,1528,1531,1533,1536],{"class":42,"line":121},[40,1521,1522],{"class":86},"    roll.round(",[40,1524,469],{"class":158},[40,1526,1527],{"class":86},").to_excel(xw, ",[40,1529,1530],{"class":238},"sheet_name",[40,1532,109],{"class":82},[40,1534,1535],{"class":50},"\"Roll-forward\"",[40,1537,118],{"class":86},[40,1539,1540,1543,1545,1547,1549,1552,1555,1557,1559,1561,1563,1565,1567],{"class":42,"line":162},[40,1541,1542],{"class":86},"    out ",[40,1544,109],{"class":82},[40,1546,500],{"class":86},[40,1548,200],{"class":50},[40,1550,1551],{"class":86},")[[",[40,1553,1554],{"class":50},"\"Customer\"",[40,1556,203],{"class":86},[40,1558,176],{"class":50},[40,1560,203],{"class":86},[40,1562,149],{"class":50},[40,1564,203],{"class":86},[40,1566,138],{"class":50},[40,1568,1569],{"class":86},"]].join(sched)\n",[40,1571,1572,1575,1577,1579,1582],{"class":42,"line":191},[40,1573,1574],{"class":86},"    out.to_excel(xw, ",[40,1576,1530],{"class":238},[40,1578,109],{"class":82},[40,1580,1581],{"class":50},"\"Schedule\"",[40,1583,118],{"class":86},[40,1585,1586,1589,1591,1593,1595,1598,1600,1603,1605,1608,1610,1612,1614,1616],{"class":42,"line":358},[40,1587,1588],{"class":86},"    journal.to_excel(xw, ",[40,1590,1530],{"class":238},[40,1592,109],{"class":82},[40,1594,1303],{"class":82},[40,1596,1597],{"class":50},"\"Journal ",[40,1599,1310],{"class":1309},[40,1601,1602],{"class":86},"month",[40,1604,1319],{"class":1309},[40,1606,1607],{"class":50},"\"",[40,1609,203],{"class":86},[40,1611,239],{"class":238},[40,1613,109],{"class":82},[40,1615,244],{"class":158},[40,1617,118],{"class":86},[40,1619,1620,1623,1626,1628],{"class":42,"line":371},[40,1621,1622],{"class":82},"    for",[40,1624,1625],{"class":86}," ws ",[40,1627,611],{"class":82},[40,1629,1630],{"class":86}," xw.book.worksheets:\n",[40,1632,1633,1636,1639,1641,1644,1646],{"class":42,"line":398},[40,1634,1635],{"class":82},"        for",[40,1637,1638],{"class":86}," c ",[40,1640,611],{"class":82},[40,1642,1643],{"class":86}," ws[",[40,1645,513],{"class":158},[40,1647,1648],{"class":86},"]:\n",[40,1650,1651,1654,1656,1659,1662,1664,1667],{"class":42,"line":422},[40,1652,1653],{"class":86},"            c.font ",[40,1655,109],{"class":82},[40,1657,1658],{"class":86}," Font(",[40,1660,1661],{"class":238},"bold",[40,1663,109],{"class":82},[40,1665,1666],{"class":158},"True",[40,1668,118],{"class":86},[40,1670,1671,1674,1676,1679,1682,1685,1688,1691,1694],{"class":42,"line":450},[40,1672,1673],{"class":86},"        ws.freeze_panes ",[40,1675,109],{"class":82},[40,1677,1678],{"class":50}," \"B2\"",[40,1680,1681],{"class":82}," if",[40,1683,1684],{"class":86}," ws.title ",[40,1686,1687],{"class":82},"!=",[40,1689,1690],{"class":50}," \"Schedule\"",[40,1692,1693],{"class":82}," else",[40,1695,1696],{"class":50}," \"F2\"\n",[40,1698,1699,1702,1704,1707,1709],{"class":42,"line":487},[40,1700,1701],{"class":86},"    ws ",[40,1703,109],{"class":82},[40,1705,1706],{"class":86}," xw.sheets[",[40,1708,1581],{"class":50},[40,1710,188],{"class":86},[40,1712,1713,1715,1718,1720,1723,1726,1728,1730,1732,1735,1737,1740],{"class":42,"line":492},[40,1714,1622],{"class":82},[40,1716,1717],{"class":86}," row ",[40,1719,611],{"class":82},[40,1721,1722],{"class":86}," ws.iter_rows(",[40,1724,1725],{"class":238},"min_row",[40,1727,109],{"class":82},[40,1729,469],{"class":158},[40,1731,203],{"class":86},[40,1733,1734],{"class":238},"min_col",[40,1736,109],{"class":82},[40,1738,1739],{"class":158},"3",[40,1741,1742],{"class":86},"):\n",[40,1744,1745,1747,1749,1751],{"class":42,"line":518},[40,1746,1635],{"class":82},[40,1748,1638],{"class":86},[40,1750,611],{"class":82},[40,1752,1753],{"class":86}," row:\n",[40,1755,1756,1759,1762,1765,1768],{"class":42,"line":525},[40,1757,1758],{"class":82},"            if",[40,1760,1761],{"class":158}," isinstance",[40,1763,1764],{"class":86},"(c.value, ",[40,1766,1767],{"class":158},"float",[40,1769,1742],{"class":86},[40,1771,1772,1775,1777,1780,1783],{"class":42,"line":563},[40,1773,1774],{"class":86},"                c.number_format ",[40,1776,109],{"class":82},[40,1778,1779],{"class":50}," \"#,##0.00;-#,##0.00;",[40,1781,1782],{"class":1309},"\\\"\\\"",[40,1784,1785],{"class":50},"\"\n",[40,1787,1788,1790,1793,1795,1797,1799,1802,1804,1807],{"class":42,"line":602},[40,1789,194],{"class":158},[40,1791,1792],{"class":86},"(out.shape[",[40,1794,513],{"class":158},[40,1796,130],{"class":86},[40,1798,143],{"class":82},[40,1800,1801],{"class":158}," 4",[40,1803,203],{"class":86},[40,1805,1806],{"class":50},"\"monthly columns written\"",[40,1808,118],{"class":86},[10,1810,1811,1812,1815,1816,1820],{},"The schedule sheet freezes the contract details on the left so the monthly columns scroll past them, and the number format's third section (",[37,1813,1814],{},"\"\"",") hides zeros, so each contract's recognition shows as a band across the months it covers — a quick visual check for start and end dates. The roll-forward goes first because it is what the month-end reviewer signs off. Adding the totals as formulas, or a chart of the deferred balance over time, follows the same patterns as ",[19,1817,1819],{"href":1818},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-fixed-asset-depreciation-schedule\u002F","Build a Fixed Asset Depreciation Schedule",", whose contract-by-month structure is the same.",[707,1822,716,1827,716,1830,716,1833,716,1836,716,1840,716,1851,716,1857,716,1862,716,1867,716,1872,716,1875,716,1882,716,1887,716,1890,716,1894,716,1897,716,1900,716,1907,716,1913,716,1917,716,1921,716,1925,716,1928,716,1931,716,1938,716,1943,716,1946],{"viewBox":1823,"role":710,"ariaLabelledBy":1824,"xmlns":714,"style":715},"4 0 752 142",[1825,1826],"dr-flow-t","dr-flow-d",[718,1828,1829],{"id":1825},"From contracts to month-end figures",[722,1831,1832],{"id":1826},"Each contract's amount becomes a daily rate over its service days, the days are spread into months and rounded with the remainder put in the last month, invoices and recognition roll the deferred balance forward, and the month's journal and reconciliation come out of the schedule.",[726,1834],{"x":232,"y":445,"width":728,"height":1835,"fill":730},"142",[726,1837],{"x":740,"y":741,"width":1838,"height":1839,"rx":744,"fill":791,"stroke":792,"style":747},"153.0","110.5",[1841,1842,1844],"g",{"transform":1843},"translate(84.5,37.8) scale(1.000)",[1845,1846],"path",{"d":1847,"fill":1848,"stroke":1849,"style":1850},"M4 5h16v15H4z M4 10h16 M8 3v4 M16 3v4","none","var(--text,#172033)","stroke-width:1.80px;stroke-linecap:round;stroke-linejoin:round",[749,1852,1856],{"x":1853,"y":1854,"style":1855},"96.5","84.2","font-size:13.5px;font-weight:700;fill:var(--text,#172033);text-anchor:middle","Daily rate",[749,1858,1861],{"x":1853,"y":1859,"style":1860},"103.8","font-size:11.5px;font-weight:400;fill:var(--text,#172033);text-anchor:middle","amount ÷ service days",[42,1863],{"x1":1864,"y1":1865,"x2":1866,"y2":1865,"stroke":746,"style":747},"178.0","71.2","200.0",[1868,1869],"polygon",{"points":1870,"fill":1871},"204.0,71.2 195.0,76.2 195.0,66.2","#5b5cf0",[726,1873],{"x":1874,"y":741,"width":1838,"height":1839,"rx":744,"fill":745,"stroke":746,"style":747},"209.0",[1841,1876,1878],{"transform":1877},"translate(273.5,37.8) scale(1.000)",[1845,1879],{"d":1880,"fill":1848,"stroke":1881,"style":1850},"M3 5h18v14H3z M3 10h18 M3 15h18 M9 5v14","var(--brand-strong,#4338ca)",[749,1883,1886],{"x":1884,"y":1854,"style":1885},"285.5","font-size:13.5px;font-weight:700;fill:var(--brand-strong,#4338ca);text-anchor:middle","Monthly schedule",[749,1888,1889],{"x":1884,"y":1859,"style":1860},"overlap days × rate",[42,1891],{"x1":1892,"y1":1865,"x2":1893,"y2":1865,"stroke":746,"style":747},"367.0","389.0",[1868,1895],{"points":1896,"fill":1871},"393.0,71.2 384.0,76.2 384.0,66.2",[726,1898],{"x":1899,"y":741,"width":1838,"height":1839,"rx":744,"fill":772,"stroke":773,"style":747},"398.0",[1841,1901,1903],{"transform":1902},"translate(462.5,29.0) scale(1.000)",[1845,1904],{"d":1905,"fill":1848,"stroke":1906,"style":1850},"M12 3l9 5-9 5-9-5z M3 13l9 5 9-5 M3 17l9 5 9-5","var(--teal-ink,#0b6157)",[749,1908,1912],{"x":1909,"y":1910,"style":1911},"474.5","75.5","font-size:13.5px;font-weight:700;fill:var(--teal-ink,#0b6157);text-anchor:middle","Roll-forward",[749,1914,1916],{"x":1909,"y":1915,"style":1860},"95.0","opening + invoiced −",[749,1918,1920],{"x":1909,"y":1919,"style":1860},"112.5","recognised",[42,1922],{"x1":1923,"y1":1865,"x2":1924,"y2":1865,"stroke":746,"style":747},"556.0","578.0",[1868,1926],{"points":1927,"fill":1871},"582.0,71.2 573.0,76.2 573.0,66.2",[726,1929],{"x":1930,"y":741,"width":1838,"height":1839,"rx":744,"fill":822,"stroke":823,"style":747},"587.0",[1841,1932,1934],{"transform":1933},"translate(651.5,29.0) scale(1.000)",[1845,1935],{"d":1936,"fill":1848,"stroke":1937,"style":1850},"M5 12.5l4.5 4.5L19 7","var(--gold-ink,#7a4e06)",[749,1939,1942],{"x":1940,"y":1910,"style":1941},"663.5","font-size:13.5px;font-weight:700;fill:var(--gold-ink,#7a4e06);text-anchor:middle","Journal + check",[749,1944,1945],{"x":1940,"y":1915,"style":1860},"Dr deferred \u002F Cr",[749,1947,1948],{"x":1940,"y":1919,"style":1860},"revenue",[25,1950,1952],{"id":1951},"common-pitfalls","Common pitfalls",[1954,1955,1956,1964,1970,1976,1982],"ul",{},[1957,1958,1959,1963],"li",{},[1960,1961,1962],"strong",{},"Off-by-one service days."," Count both the start and end day.",[1957,1965,1966,1969],{},[1960,1967,1968],{},"Equal monthly amounts for mid-month starts."," Use daily pro-rata so partial months are fair.",[1957,1971,1972,1975],{},[1960,1973,1974],{},"Rounding drift."," Round per month and put the remainder in the final month so totals match invoices.",[1957,1977,1978,1981],{},[1960,1979,1980],{},"Invoices recorded in the service month."," Deferred revenue starts when the invoice is issued.",[1957,1983,1984,1987],{},[1960,1985,1986],{},"No reconciliation."," Check invoiced equals recognised plus deferred every month.",[25,1989,1991],{"id":1990},"conclusion","Conclusion",[10,1993,1994],{},"A deferred revenue schedule is daily straight-line recognition made systematic: compute each contract's daily rate over its service days, count the days each month overlaps the service period, round per month with any remainder in the final month, and roll the deferred balance forward from invoices and recognition. The month's journal, the by-contract breakdown and the check that invoiced equals recognised plus deferred all come from the same schedule, which then goes to Excel for review.",[25,1996,1998],{"id":1997},"frequently-asked-questions","Frequently asked questions",[10,2000,2001,2004],{},[1960,2002,2003],{},"How do I calculate deferred revenue in Python?","\nFor each month, deferred revenue is the cumulative amount invoiced minus the cumulative revenue recognised. Build a contract-by-month recognition schedule and take the running difference.",[10,2006,2007,2010],{},[1960,2008,2009],{},"How do I recognise revenue for a contract that starts mid-month?","\nDivide the contract amount by its number of service days and multiply by the days of service in each month, so the first and last months get a partial share.",[10,2012,2013,2016],{},[1960,2014,2015],{},"How do I stop rounding differences in a revenue schedule?","\nRound each monthly amount to cents and add the remaining difference to the contract's final month, so every contract recognises exactly its invoiced amount.",[10,2018,2019,2022],{},[1960,2020,2021],{},"What journal records monthly revenue recognition?","\nDebit the deferred revenue liability and credit revenue for the total recognised in the month, with a breakdown by contract kept as support.",[25,2024,2026],{"id":2025},"related","Related",[1954,2028,2029,2035,2040,2047],{},[1957,2030,2031,2032,2034],{},"Up one level: ",[19,2033,22],{"href":21}," — finance and operations reports in Excel.",[1957,2036,2037,2039],{},[19,2038,1819],{"href":1818}," — a schedule with the same shape.",[1957,2041,2042,2046],{},[19,2043,2045],{"href":2044},"\u002Fadvanced-data-transformation-and-cleaning\u002Fworking-with-dates-and-times-in-excel-data\u002Fbuild-a-date-table-for-excel-reports-with-pandas\u002F","Build a Date Table for Excel Reports with pandas"," — month boundaries and calendars.",[1957,2048,2049,2053],{},[19,2050,2052],{"href":2051},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-profit-and-loss-statement-from-a-general-ledger\u002F","Build a Profit and Loss Statement from a General Ledger"," — where recognised revenue appears.",[2055,2056,2057],"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 .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 .s-wDw, html code.shiki .s-wDw{--shiki-default:#6A737D;--shiki-dark:#BDC4CC}html pre.shiki code .sSjpA, html code.shiki .sSjpA{--shiki-default:#005CC5;--shiki-dark:#FF9492}",{"title":35,"searchDepth":96,"depth":96,"links":2059},[2060,2061,2062,2063,2064,2065,2066,2067,2068,2069],{"id":27,"depth":96,"text":28},{"id":66,"depth":96,"text":67},{"id":257,"depth":96,"text":258},{"id":838,"depth":96,"text":839},{"id":1106,"depth":96,"text":1107},{"id":1463,"depth":96,"text":1464},{"id":1951,"depth":96,"text":1952},{"id":1990,"depth":96,"text":1991},{"id":1997,"depth":96,"text":1998},{"id":2025,"depth":96,"text":2026},"2026-10-02","Spread prepaid subscription and contract revenue over the service period with pandas: daily pro-rata recognition for contracts that start mid-month, a contract-by-month schedule, the deferred revenue balance at each month end, the monthly journal, a check that invoiced equals recognised plus deferred, and the schedule written to Excel.","md",[2074,2076,2078,2080],{"q":2003,"a":2075},"For each month, deferred revenue is the cumulative amount invoiced minus the cumulative revenue recognised. Build a contract-by-month recognition schedule and take the running difference.",{"q":2009,"a":2077},"Divide the contract amount by its number of service days and multiply by the days of service in each month, so the first and last months get a partial share.",{"q":2015,"a":2079},"Round each monthly amount to cents and add the remaining difference to the contract's final month, so every contract recognises exactly its invoiced amount.",{"q":2021,"a":2081},"Debit the deferred revenue liability and credit revenue for the total recognised in the month, with a breakdown by contract kept as support.","build a deferred revenue schedule",null,{"breadcrumb":2085},[2086,2088,2091],{"name":2087,"item":181},"Home",{"name":2089,"item":2090},"Automating Reporting Workflows with Python and Excel","\u002Fautomating-reporting-workflows\u002F",{"name":2092,"item":21},"Building Finance and Operations Reports in Excel with Python","\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-deferred-revenue-schedule",{"title":2095,"description":2096},"Build a Deferred Revenue Schedule in Excel with Python","A revenue recognition schedule from invoiced contracts: daily pro-rata by month, contract × month matrix, deferred balance roll-forward, monthly journals, reconciliation checks and an Excel layout.","build-a-deferred-revenue-schedule","automating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-deferred-revenue-schedule\u002Findex",[2100,2101],"report-layouts","dates-and-times","how-to","RMzhpY1_A0wmpuzDuBj5xhT7AUj4fpmKaQhkr2sNzdM",[2105,2109],{"title":2106,"path":2107,"stem":2108,"children":-1},"Build a Customer Profitability Report","\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-customer-profitability-report","automating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-customer-profitability-report\u002Findex",{"title":2110,"path":2111,"stem":2112,"children":-1},"Build a Discount Leakage Report","\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-discount-leakage-report","automating-reporting-workflows\u002Fbuilding-finance-and-operations-reports\u002Fbuild-a-discount-leakage-report\u002Findex",[2114,2116],{"path":2115,"title":2092},"\u002Fautomating-reporting-workflows\u002Fbuilding-finance-and-operations-reports",{"path":2117,"title":2089},"\u002Fautomating-reporting-workflows",[],[2120,2124,2127,2130,2133,2136],{"path":2121,"title":2122,"kind":2123},"\u002Fautomating-reporting-workflows\u002Fbuilding-multi-sheet-excel-dashboards","Building Multi-Sheet Excel Dashboards","guide",{"path":2125,"title":2126,"kind":2102},"\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":2128,"title":2129,"kind":2102},"\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":2131,"title":2132,"kind":2102},"\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":2134,"title":2135,"kind":2102},"\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":2137,"title":2138,"kind":2102},"\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",1790972121410]