ASC 606 Revenue Recognition at Scale With AI: Full Methodology, Validation Log, and Open Peer Review (Part 2)
- CA Pushkar Agrawal

- Apr 28
- 15 min read
Updated: May 12

Revenue Recognition · SaaS Finance · AI in Accounting Part 2 of 2 — Deep methodology (~20 minutes) · Quick summary in Part 1
Purpose of This Document
This piece serves two audiences.
The first is the accounting professional who read Part 1 and wants to understand whether the methodology is actually sound, the architecture, the decisions made, the bugs found and fixed, the places where the output required correction. Think of it as an open working paper.
The second is the Controller or Finance Manager at a smaller organization who is asking a practical question: could I actually do this? What would it take, what would I need to know, and where would it go wrong? The replication guide at the end addresses that directly and honestly.
Comments and challenges are welcome via LinkedIn or direct message.
Project Architecture: Four Phases, One Pipeline
Before diving into methodology, it helps to understand how the pieces connect. The project runs in four sequential phases. Each phase reads from the previous one's output, nothing is hardcoded across phase boundaries.
Phase 1 — Data generation. One Python script (generate_cloudaxis_50.py) produces two source files: contracts.csv (61 contracts, 34 columns) and monthly_activity.csv (948 rows, 25 columns). These contain everything about the business - contract terms, pricing, billing cadence, modification events, terminations, commissions.
Phase 2 — Recognition engine. One Python script (recognition_engine_fixed.py) reads both source files and runs the ASC 606 calculation loop, producing recognition_schedule.csv (948 rows, 12 columns). This is the authoritative output, every downstream file derives from it.
Phase 3 — Metrics and disclosure inputs. Two scripts read from Phase 2. phase3_metrics.py produces 8 metric CSV files (MRR waterfall, NRR/GRR, cohort retention, LTV:CAC, billings vs revenue, deferred roll-forward, RPO schedule, commission roll-forward). build_disclosure_inputs.py produces 4 disclosure-ready CSV files (Note 2 disaggregation, Note 3 deferred roll-forward, Note 4 RPO, Note 5 commission roll-forward) by computing balance-movement figures directly from the engine output.

Phase 4 — Outputs. Three build scripts produce the final deliverables. build_disclosure_pdf.py reads the Phase 3 disclosure CSVs. The Excel model and dashboard are built from the Phase 3 metric CSVs.
The separation of Phase 3 into a dedicated disclosure inputs step was added after an early architectural problem: when the engine changed (as it did several times during validation), hardcoded numbers in the PDF didn't update automatically. Inconsistencies appeared. Having build_disclosure_inputs.py as an intermediary means running one script regenerates all disclosure tables from the latest engine output. The inconsistency problem largely disappears.
The Full Output Set
Deliverable | Description | Key numbers |
Engine output — 948 rows, 12 columns | FY24 rev $2,317,039 · FY23 $1,225,893 | |
10-tab Excel model | MRR · NRR · Cohort · LTV:CAC · Deferred · RPO · Commission | |
Interactive 5-page dashboard | Open in any browser, no install | |
10-page 10-K footnote | Notes 1–6 + Note 3B | |
Full column reference | All 3 source files + 8 metric outputs | |
4-layer data flow diagram | Generation → Engine → Metrics → Outputs |
Note: To open html file, please make a copy of it on your desktop and open the file directly in any browser.
What the metrics show. MRR grew from a stable April 2023 baseline of $71,374 to $197,088 in December 2024 - 176% growth over 24 months. NRR trends from 107.6% in April 2024 to 83.6% in December 2024 as contract mix shifts toward renewal cohorts. LTV:CAC by segment: SMB 29× / 3-month payback, Mid-Market 12× / 7 months, Enterprise 14× / 10 months (trailing 12-month churn, 6% floor, 5-year cap). Cohort retention heatmap includes cohort sizes, important caveat: at 1–7 customers per cohort, individual lines are statistically noisy.

What the disclosure covers
Note 1: revenue policy for all three products, modification treatment, implementation fees (both prongs of ASC 606-10-25-19), material rights, practical expedients.
Note 2: FY 2024 revenue of $2,317,039 disaggregated four ways, product (Core 80%, Analytics 12%, DataPipeline 8%), segment, billing arrangement, geography (North America 58%, EMEA 20%, APAC 19%, LATAM 4%).
Notes 3 and 3B: balance-movement roll-forwards for deferred revenue (closing $30,650) and contract assets (closing $473,837).
Note 4: RPO $447,719, 100% within 12 months.
Note 5: commission asset with write-offs column (FY24 $42,542, FY23 $12,513). Note 6: every assumption cited to its ASC paragraph.

The Dataset: Design Principles
The fictional company is CloudAxis Inc. a B2B SaaS business with three products:
Core: per-seat subscription, annual contracts
Analytics: flat-rate subscription, annual or monthly
DataPipeline: usage-based (base fee plus overage), committed annual term but monthly billing
The 50-customer dataset was engineered to cover every material edge case simultaneously. The design principles were:
Complete scenario coverage. Every modification type: seat additions (prospective - distinct new seats), tier upgrades (prospective - new performance obligation), tier downgrades (prospective - reduced rate forward), product switches (old contract derecognised, new contract initiated), early terminations with penalty, with refund, and without penalty. Every payment structure. Every special feature: implementation fees, free trials, bundle contracts requiring SSP allocation, renewals, churned-at-renewal.
Deliberate stress on the accounting identity. Annual upfront contracts alongside usage-based monthly contracts force the recognition engine to handle both without a generalised billing guard. The rule "after month 1, bill = 0" applies to Core and Analytics but must explicitly exclude DataPipeline, whose billing reflects actual monthly usage. This was a real bug in the first version.
Sufficient volume for pattern validation. 948 rows across 61 contracts (50 customers; bundle customers contribute two contracts each, plus one product-switch pair) provides enough volume to detect systematic errors versus one-off anomalies. 61 contracts from 50 customers — the gap is explained by customers with bundle arrangements, each contributing one contract per product.
The Recognition Engine: Architecture
The engine (recognition_engine_fixed.py) runs a sequential calculation loop for each contract for each month. The steps in order:
Step 1 — Trial period guard. Revenue and commission amortization are zero during free trial months. Contract inception occurs at signing; trial months are zero-consideration months within the contract.
Step 2 — Commission asset creation. On the first paying month, the original commission asset is capitalized (commission rate × TCV). On expansion events, an incremental expansion asset is created on the incremental TCV for the remaining committed term.
Step 3 — Modification treatment. All modifications are treated prospectively. MRR is updated from the modification date forward. No cumulative catch-up. This is a policy decision, not a default.
Step 4 — Revenue recognition. Core and Analytics: ratable. DataPipeline: base fee plus actual overage, multiplied by the SSP allocation ratio for bundle contracts. Implementation fees: ratable over the contract term, contributing to contract asset when not yet invoiced.
Step 5 — Termination flush. On the termination month, all balances are zeroed. Termination type determines revenue treatment: penalty contracts add penalty to revenue; refund contracts credit against revenue; no-penalty contracts flush remaining deferred to current-period revenue.
Step 6 — Deferred revenue and contract asset. deferred = max(0, cumulative_billed − cumulative_recognised). contract_asset = max(0, cumulative_recognised − cumulative_billed). Mutual exclusivity enforced at the individual contract level per period.
Step 7 — RPO calculation. RPO = current_MRR × months_remaining. Month-to-month contracts excluded (ASC 606-10-50-13). DataPipeline variable overages excluded (ASC 606-10-50-14 practical expedient).
Step 8 — Commission amortization. Straight-line over expected customer life from capitalization date: SMB 18 months, Mid-Market 24 months, Enterprise 36 months.
Step 8b — Commission write-off on exit. On early termination, churn-at-renewal, or product switch-out, commission asset balances are explicitly zeroed (ASC 340-40-35-1). This is a separate step from amortization, the write-off represents immediate expensing of the unamortized balance.
Results: recognition_schedule.csv


Policy Decisions: What Was Decided and Why
These are accounting judgement, choices between permissible alternatives. AI executed each after the decision was made.
Modification treatment: always prospective
ASC 606-10-25-12 permits prospective treatment when a modification adds distinct goods or services. ASC 606-10-25-13 requires cumulative catch-up when the modification changes the transaction price for already-delivered goods.
The initial build used cumulative catch-up for annual upfront upgrades and prospective for monthly. This was changed to always prospective across all contract types.
Rationale: The prospective approach is more consistent with how the commercial transaction works. The customer is buying additional capability from a specific date, not repricing prior access. Consistency across contract types avoids bifurcating treatment based on payment structure.
Impact: Removing cumulative catch-up reduced total recognized revenue by approximately $300K over 24 months. The earlier treatment was inflating revenue by pulling forward catch-up adjustments.
Renewal options: not a material right
Under ASC 606-10-55-42, if a renewal option provides a price or terms the customer wouldn't otherwise receive, it's a separate performance obligation that must be deferred. In this model, all renewals are priced at prevailing list prices, no contractual discount, no locked-in pricing advantage, no terms not otherwise available. Therefore: not a material right, no deferral required.
Of the 27 contracts that reached their initial term, 20 (74%) renewed. Renewal contracts are not included in RPO until signed.
Implementation fees: not distinct (both prongs)
Evaluated under the two-prong distinctness test in ASC 606-10-25-19. The fees fail both: (i) the customer cannot benefit from the implementation independently, thus no standalone utility without the platform; and (ii) the implementation is not distinct within the contract context, the same outcome cannot be achieved by a third party without material re-performance. Combined with the subscription, recognized ratably over the contract term.
Churn contract asset: revenue reversal capped at current period
When a customer churns, any remaining contract asset represents an uncollectable unbilled receivable. The engine reverses this in the exit period, capped at the current period's recognized revenue, consistent with ASC 606 para 56, which prohibits negative revenue in a period. Excess over current period is treated as bad debt expense. This was added after an early version left $13,757 in residual contract assets across three churned contracts.
LTV:CAC methodology: trailing 12-month, 6% floor, 5-year cap
Spot monthly churn in a 50-customer model is zero most months. Zero churn rate makes LTV mathematically infinite. Fix: trailing 12-month MRR-weighted churn rate, 6% annual floor (0.5% monthly), 5-year cap. The 5-year cap is an analytical convention — independent of the ASC 340-40 amortization period.
Important limitation: 80% of observations sit at the 0.5% floor. The LTV numbers reflect the assumption more than observed behaviour. A real company with hundreds of customers and stable churn would produce floor-independent results.
Bundle SSP allocation
Transaction price allocated to each performance obligation based on relative SSP (ASC 606-10-32-31). Adjusted market assessment approach, observable list prices as primary input. Bundle discount allocated proportionally across components.
Validation Log: All Seven Bugs Found and Fixed
This section documents every material error identified during validation, the root cause, and the fix applied.
Bug 1: Annual upfront billing guard applied to DataPipeline
Symptom: DataPipeline contracts with annual upfront payment terms showed a growing contract asset over time, even though they were billed monthly.
Root cause: The annual upfront billing guard, which correctly zeroes out months 2–12 billing for Core and Analytics (which bill everything in month 1) was applied unconditionally to all contracts with annual upfront payment terms. DataPipeline, whose payment terms describe the commitment structure rather than billing cadence, should always bill monthly based on actual usage.
Fix: Added a product check (product != 'DataPipeline') to the billing guard. DataPipeline monthly billing now passes through correctly.
Impact: Two contracts affected. Contract asset reduced from $47K to $3K on the affected contracts (the remaining $3K is legitimate, implementation fee earned but not yet invoiced).
Bug 2: Commission asset increasing on tier upgrades (initial version)
Symptom: The validation check "commission asset never increases between periods outside of capitalization events" was flagging failures.
Root cause: The initial upgrade treatment used a not-commensurate modification approach, which recalculated the original asset's remaining life and amortization rate at the upgrade date. The snapshot logic had an error: remaining_life = exp_life + 6 (adding 6 months) rather than exp_life - elapsed_months. This caused the per-period amortization to drop, allowing the balance to grow temporarily.
Fix: Changed upgrade treatment to fully prospective. Original asset unaffected. New expansion asset created for the increment. No life adjustment, no snapshot. Validation check now passes.
Bug 3: Deferred roll-forward format wrong
Symptom: The Note 3 quarterly roll-forward table showed Opening + Billings − Revenue = Closing. None of the 8 quarters tied.
Root cause: Monthly billing is recognized immediately and never touches the deferred balance. The correct format is additions to deferred (advance billings only) versus releases from deferred (prior balance earned). Balance-sheet movements, not income statement flows.
Fix: Recomputed from period-over-period balance changes at contract level. addition = max(0, deferred[t] − deferred[t−1]). release = max(0, deferred[t−1] − deferred[t]). All 8 quarters tie. This is a common presentation error in deferred revenue disclosures, the gross billings approach only ties if 100% of billing goes to deferred.

Bug 4: Commission roll-forward missing write-off line
Symptom: Opening + Capitalized − Amortized ≠ Closing for several quarters.
Root cause: The engine zeroed commission balances on exit but the disclosure showed only capitalization and amortization. Write-offs were not a visible line item.
Fix: Step 8b added to engine. Write-off column added to Note 5. FY 2023: $12,513. FY 2024: $42,542.
Bug 5: Churn contracts retaining residual contract assets
Symptom: Three churned contracts (CTR-0014, CTR-0050, CTR-0058) showed contract asset balances totalling $13,757 after their exit month.
Root cause: Engine stopped producing rows for churned contracts without explicitly flushing the contract asset.
Fix: Explicit churn reversal added in exit period, capped at current period revenue. All churn exits now close at $0 contract asset.
Bug 6: Note 2 geography total not updated after churn fix
Symptom: After the Bug 5 fix, FY 2024 revenue changed. The product, segment, and billing tables in Note 2 updated correctly. The geography total row retained the old figure ($2,328,108 vs components summing to $2,317,039).
Root cause: Geography total row was hardcoded separately from the component rows. One literal not caught in the update.
Fix: Geography total updated. All four Note 2 tables now reconcile to $2,317,039.
Bug 7: Phantom Q4 2024 write-off in Note 5
Symptom: Note 5 showed a Q4 2024 write-off of $6,602. Excel Commission Asset tab showed Q4 2024 write-off as $0. Internal inconsistency between PDF and Excel.
Root cause: No contract exits occurred in Q4 2024 (last termination was in September = Q3). The $6,602 was rounding drift being mislabelled as an exit-event write-off to force the quarterly arithmetic to appear to tie.
Fix: Q4 2024 write-off set to $0. Quarterly sum now ties the annual total correctly: $5,855 + $33,303 + $3,384 + $0 = $42,542. The ~$6,398 rounding gap sits in the closing balance and is disclosed in the Note 5 footnote.

False positives: validation checks that were wrong
Three of the initial validation check failures turned out to be errors in the check logic, not the engine:
Annual upfront deferred drop ≠ MRR: For contracts with implementation fees, the monthly deferred decrease equals recognized revenue (MRR + impl_fee/12), not just MRR. Check was too narrow.
Quarterly deferred oscillation failure: For a bundle contract with implementation fee, the deferred balance after quarterly billing equals allocated_billed − monthly_recognition, not 2×monthly_revenue. Check was oversimplified.
Monthly contracts in RPO: A product-switch-in contract with monthly billing terms but a committed end date correctly appears in RPO. Exclusion rule needed to be "monthly AND NOT committed_term", not "monthly".
Distinguishing real bugs from check logic errors requires knowing what the correct answer should be. This is the analytical work that validation actually involves.
Where Professional Judgement Appeared Beyond Policy Decisions
A few outputs required evaluation that goes beyond knowing the accounting standard.
Disclosure arithmetic must be narratively explained, not just numerically correct. Additions to deferred spike to $435,738 in Q2 2023 because many annual upfront contracts were signed in that quarter. The arithmetic is right. Without a narrative explaining the loading pattern, a reviewer would ask whether there was a one-time sales event. Numbers without context invite the wrong conclusions. This applies to the commission rate assumption too: full TCV capitalization produces a $207K asset; ACV-only would produce approximately $100K. Both are arithmetically valid. Only the narrative makes clear which reflects the actual business.
Headline KPIs need their denominator understood. The dashboard originally showed December 2024 LTV:CAC as the headline figure. No new contracts were signed in that month, making CAC $0 and LTV:CAC display as 0.0× — technically correct, completely misleading. The fix was switching to a trailing 12-month average across periods with active acquisition. This is the kind of reporting judgement that has nothing to do with accounting standards and everything to do with whether the reader understands what they're looking at.
Why This Matters for Smaller Organizations
The standard interpretation of ASC 606 compliance is that it requires a team, an internal technical accounting group, external auditors, possibly a Big 4 advisory engagement. That is the reality for public companies and larger private companies.
For a Series A SaaS company with 40 employees and a two-person finance team, that resource doesn't exist. The Controller is also the AP manager and the financial reporting lead. Revenue recognition is treated as a best-effort exercise rather than a rigorous one.
The demonstration here is that a single accounting professional, using AI as an execution tool and applying their own technical judgement for the decisions that matter, can produce output at a quality level that was previously inaccessible without a team. Not perfectly, the validation log above documents the errors found. But at a level that would support external audit, investor reporting, or board-level financial disclosure.
This is a meaningful change for how financial reporting quality distributes across the economy. The barrier has historically been access to specialized human capital, not the underlying accounting knowledge. AI lowers that barrier substantially while shifting the requirement from "can you build this" to "can you govern it."
If You Want to Replicate This: What It Actually Takes
This section is for the Controller, Accounting Manager, or Finance Lead at a smaller organization who is asking: could I do this for my company? Here is the honest answer.
What you need technically
You need to be comfortable running Python scripts from a command line and reading error messages. You do not need to write Python from scratch - AI handles that. You need to understand what the scripts are doing well enough to tell when the output is wrong. That understanding comes from accounting knowledge, not programming skill.
The full dependency list is four Python libraries: pandas, numpy, openpyxl, and reportlab. Installation takes 10 minutes. Everything else runs locally — no servers, no cloud setup, no subscriptions beyond the AI tool you use.
What you need to know accounting-wise
This is where the real requirement lives. To govern the output rather than just run the scripts, you need working knowledge of:
ASC 606's five-step model and how it applies to your contract types specifically
The modification guidance (ASC 606-10-25-12 and 25-13) prospective vs cumulative catch-up is the decision that most materially affects revenue timing
The distinctness test (ASC 606-10-25-19) for any bundled or implementation-fee arrangements
What belongs in RPO and what doesn't — particularly which practical expedients apply to your contract structure
ASC 340-40 for commission assets — what to capitalize, how long to amortize, and what triggers immediate write-off
You do not need to have memorized these paragraphs. You need to know they exist, understand the decisions they govern, and be able to evaluate whether the engine's output is consistent with the decision you intend to make. That is the governance layer AI cannot replace.
What it actually takes in time
For a real company, adapting this model would require three phases of significant work that don't exist in a synthetic dataset:
Data extraction. Your contracts are in CRM, your billing in an accounting system, your commission data in payroll or a spreadsheet. Getting clean, structured data out of those systems with the right fields, at the contract level, with modification history, is typically the longest part of any revenue recognition project. Budget 2–4 weeks depending on system complexity.
Policy documentation. Before you can build the engine, you need to make the policy decisions. Modification treatment, material rights assessment, practical expedients, commission rate definition, amortization periods, these need to be documented and reviewed, ideally with your external auditors, before the model runs. Budget 1–2 weeks including auditor alignment.
Validation. This project found seven bugs across two rounds of review. For a real company with real consequences, validation needs to be more rigorous. You need to trace at least a sample of contracts manually from contract terms through to disclosure figures. You need to tie every roll-forward table. You need to confirm that the policy decisions in the model match the policy decisions in the documentation. Budget 2–3 weeks.
Total realistic timeline for a 50–200 customer SaaS company with reasonably clean data: 6–9 weeks, one person with the right accounting background. That is still a fraction of the 8–12 weeks a professional team would take, and at a fraction of the cost.
Where it is most likely to go wrong
The highest-risk decision is modification treatment. If you apply cumulative catch-up to modifications that should be prospective or vice versa, the error compounds across every affected contract for every remaining period. It is systematic, it is hard to detect without manual tracing, and it has a material impact on revenue timing.
The second highest risk is the deferred revenue roll-forward format. Using gross billings instead of balance-movement additions/releases looks plausible and produces wrong numbers that are difficult to spot without checking arithmetic quarter by quarter.
The third is commission write-offs. If your engine doesn't explicitly zero commission balances on contract exits, the asset accumulates balances that should have been expensed. The financial statements will show an overstated asset and understated expense.
All three of these are documented in the validation log above. None of them were flagged automatically.
The honest bottom line
AI makes this accessible to a single person with the right accounting background. It does not make it accessible to someone without that background, it just means the errors will be executed more confidently and at greater scale.
The right question to ask yourself is not "can I run the scripts" but "would I be able to tell if the output was wrong?" If yes, this is a genuine efficiency tool. If not, the risk of producing plausible-looking but incorrect output is real.
Invitation for Technical Review
This project is intended as a working demonstration, not a finished product. If you are an accounting professional, a Big 4 technical reviewer, a CFO, or a Controller with ASC 606 experience, I would genuinely welcome challenges to any of the decisions documented here.
Specifically, I'm interested in feedback on:
Modification treatment. I applied prospective treatment to all modification types — including downgrades on annual upfront contracts where the customer has already paid at the higher rate. There is an argument for cumulative catch-up in that specific scenario. If you've seen this handled differently in practice, I'd genuinely like to know the rationale.
Churn contract asset treatment. Revenue reversal capped at current period revenue vs bad debt expense treatment — both are defensible. Real companies choose differently based on auditor preference and materiality. Which approach does your firm use, and what drives the choice?
The 6% annual churn floor. This is the assumption that most materially affects the LTV outputs in a small dataset. If you work with SaaS companies of this profile, what floor would you use and how would you establish it from actual data?
Comments on LinkedIn or via direct message. GitHub repository with full code will be linked below.
Tools: Python (pandas, reportlab, openpyxl), Claude (Anthropic), Excel Full project files: GitHub link here Part 1 — Quick read: link here.



Comments