Check a $2.3 trillion claim in 20 lines of python

An essay says $2.3 trillion of AI backlog sits at four cloud providers. One XBRL concept checks it in 20 lines of Python, if you know which facts not to add up.

Remaining performance obligations at Oracle, Microsoft, Alphabet and Amazon, stacking to $2,363.5 billion

A viral essay put a number on the AI buildout. Checking it took one XBRL concept, four filings and a rule about which facts not to add up.


In August an essay called The Teaser Period argued that the AI boom is financed like a 2/28 mortgage, and its headline number was about $2.3 trillion of contracted backlog sitting at four cloud providers. Numbers like that travel for weeks before anyone checks them, and the reason is usually that checking means opening four revenue notes and hunting for a sentence in each.

It doesn't have to. Backlog, in the filings' language remaining performance obligations, is a tagged number with the same concept at every company, and this is the whole check in Python:

from edgar import Company, set_identity

set_identity("Your Name your.email@example.com")

RPO = "us-gaap:RevenueRemainingPerformanceObligation"
TIMING_AXIS = "us-gaap:RevenueRemainingPerformanceObligationExpectedTimingOfSatisfactionStartDateAxis"


def backlog(ticker):
    filing = Company(ticker).get_filings(form=["10-Q", "10-K"]).latest()
    period = str(filing.period_of_report)
    facts = filing.xbrl().facts.query().by_concept(RPO, exact=True).execute()
    for fact in facts:
        if fact["period_instant"] != period:
            continue  # a prior-period fact carried in the same filing
        if fact.get("dimension") not in (None, TIMING_AXIS):
            continue  # a segment slice of the total, not the total
        return ticker, filing.form, period, fact["numeric_value"] / 1e9


rows = [backlog(t) for t in ["ORCL", "MSFT", "GOOGL", "AMZN"]]
for ticker, form, period, billions in rows:
    print(f"{ticker:6} {form:5} {period}  ${billions:,.1f}B")
print(f"{'Total':25} ${sum(r[3] for r in rows):,.1f}B")
ORCL   10-Q  2026-08-31  $664.0B
MSFT   10-K  2026-06-30  $684.0B
GOOGL  10-Q  2026-06-30  $519.5B
AMZN   10-Q  2026-06-30  $496.0B
Total                     $2,363.5B

The essay's number holds, at $2.36 trillion. The interesting part is the two continue lines. Each one guards against a mistake that produces a plausible total rather than an error, which is the worst kind of mistake to make with a number you are about to repeat.

Remaining performance obligations, from Python's side

Remaining performance obligations, RPO for short, are revenue a company has under contract but has not yet earned. Accounting standard ASC 606 requires the disclosure, so it sits in the revenue note of every 10-K and 10-Q, and the us-gaap taxonomy gives it one concept, RevenueRemainingPerformanceObligation. That uniformity is what makes a cross-company check possible at all: the same query works on Oracle and Amazon, with no text parsing and no per-company regex. If you have not worked with XBRL facts in edgartools before, Extract financial data from SEC filings with edgartools covers the basics this post builds on.

For anyone checking a claim like the essay's, that is the practical point. Whenever a number in an article is an accounting disclosure, there is probably a concept for it, and the concept is faster and more exact than the prose.

Trap one: the segment fact that double-counts

Drop the dimension check and the loop returns whichever fact it meets first, and a sum over all the facts is worse. Here is everything Microsoft and Alphabet tag under that one concept on the balance-sheet date:

MSFT  684.0  (no dimension)
MSFT  678.0  srt:MajorCustomersAxis = msft:CommercialCustomersMember
GOOGL 519.5  (no dimension)
GOOGL 513.9  us-gaap:StatementBusinessSegmentsAxis = goog:GoogleCloudMember

The second row in each pair is a slice of the first: Microsoft's commercial customers, Google Cloud's share of Alphabet. Sum everything and you count about $1.2 trillion twice, and the result still looks like a number a company might report. The safe habit is to read a fact's dimension before you trust it, which execute() makes easy because each fact comes back as a dict with dimension and member keys. One detail to know: to_dataframe() on the same query drops those keys and keeps only an is_dimensioned flag, which is enough to tell you a slice exists and not enough to tell you what it is.

Six RPO facts from four filings, marked keep or skip: the undimensioned totals, Amazon's timing-axis total kept, and the segment and customer slices skipped
Every RevenueRemainingPerformanceObligation fact on the balance-sheet date, and which one is the total.

Trap two: the filter that deletes Amazon

The obvious fix for trap one is to keep only undimensioned facts. Do that and the script prints three companies, a total of $1.87 trillion, and no warning at all. Amazon has disappeared.

Amazon reports no undimensioned RPO fact. Its $496 billion is tagged on RevenueRemainingPerformanceObligationExpectedTimingOfSatisfactionStartDateAxis with a member of 2026-07-01, which says when recognition of the amount begins rather than what part of the company it belongs to. It is the total, dressed as a slice. That is why the rule in the script is "no dimension, or the timing axis" rather than "no dimension", and why the second continue is written as a positive list of axes to accept. A negative list of axes to reject would have missed the next filer's variant just as quietly.

The same filing also carries a $38.0 billion RPO fact dated March 31, which is the other reason for the period check: filings repeat prior-period facts as context, and "the latest filing" is not the same as "the latest date".

If you build screens over XBRL, this is the trap that matters. A filter that silently drops a quarter of your universe produces a smaller, still-plausible number, and the only defence is to assert that every ticker you asked about came back.

When the backlog becomes revenue

The timing axis is also where the essay's argument lives. Its claim is that the backlog is back-loaded, that most of it turns into billing in 2027 and 2028, and several filers tag the schedule. Oracle's is three facts on the same start-date axis:

from edgar import Company, set_identity

set_identity("Your Name your.email@example.com")

xbrl = Company("ORCL").get_filings(form="10-Q").latest().xbrl()


def facts(concept):
    # a fresh query each time: filters on one query object accumulate
    return xbrl.facts.query().by_concept(concept, exact=True).execute()


window = {f["member"]: f["value"] for f in facts("us-gaap:RevenueRemainingPerformanceObligationExpectedTimingOfSatisfactionPeriod1")}
for share in sorted(facts("us-gaap:RevenueRemainingPerformanceObligationPercentage"), key=lambda f: f["member"]):
    print(f"from {share['member']}  {share['numeric_value']:>4.0%}  over {window[share['member']]}")
from 2026-09-01   13%  over P12M
from 2027-09-01   37%  over P2Y
from 2029-09-01   34%  over P2Y

Thirteen percent of Oracle's $664 billion becomes revenue in the next twelve months and 37 percent in the two years after that, with the untagged 16 percent later still. The comment in the helper is the third trap, and the one that caught me while writing this: a FactQuery accumulates filters, so reusing one object for two by_concept calls asks for facts that are both concepts at once, and you get an empty result rather than an error.

Who the backlog is owed by

The essay's sharpest claim is that the backlog is concentrated in a few AI labs. Most filers do not say, but the same dimension habit finds the one that does. Amazon tags named customers on srt:MajorCustomersAxis:

from edgar import Company, set_identity

set_identity("Your Name your.email@example.com")

xbrl = Company("AMZN").get_filings(form="10-Q").latest().xbrl()
for fact in xbrl.facts.query().by_concept("RemainingPerformanceObligation").execute():
    if fact.get("dimension") == "srt:MajorCustomersAxis":
        print(f"{fact['member']:28} {fact['concept'].split(':')[1]:62} {fact['value']}")
amzn:OpenAIGroupPBCMember    RevenueRemainingPerformanceObligation                          38000000000.0
amzn:OpenAIGroupPBCMember    RevenueRemainingPerformanceObligationAmountPeriodIncreaseDecrease 100000000000.0
amzn:OpenAIGroupPBCMember    RevenueRemainingPerformanceObligationExpectedTimingOfSatisfactionPeriod1 P8Y
amzn:AnthropicMember         RevenueRemainingPerformanceObligationAmountPeriodIncreaseDecrease 100000000000.0
amzn:AnthropicMember         RevenueRemainingPerformanceObligationExpectedTimingOfSatisfactionPeriod1 P10Y

OpenAI's existing $38.0 billion AWS commitment grew by $100.0 billion over eight years, and Anthropic's by $100.0 billion over ten, both as tagged facts rather than press-release prose. Run the same loop over the other three and Oracle tags no customer at all, Alphabet only its Cloud segment, and Microsoft uses the same customer axis for "commercial customers", which is a class of customer rather than a name. The concentration question is answerable for one of the four, and the code tells you which one in a single pass.

πŸ“Š
The hosted version of this check, with the lease commitments and cash flow the essay skips, is written up for non-coders at edgar.tools, with every figure cited to its filing.

Where the script stops

Two things this script cannot do. It reads the latest filing only, so a year-over-year comparison needs the prior filing's fact, which is one more get_filings() call and the same period check. And RPO is only half of the essay's argument: the other half is the leases the cloud providers have signed but not started, and that disclosure is a sentence in a text block with no clean numeric concept at most filers. What 32,000 SEC filings taught me about XBRL mappings goes into why some disclosures never make it into a tagged number, and that one is a good example.

For your own screens, the lesson generalises past backlog. When a concept comes back with more than one fact, the dimension is the difference between a total and a slice, and the filer, not the taxonomy, decides which axis carries the total.


GitHub - dgunning/edgartools: Read and analyze SEC EDGAR filings in Python. 10-K, 8-K, XBRL financials, Form 3/4/5, 13F, ADV β€” clean API, well-typed, MIT-licensed.
Read and analyze SEC EDGAR filings in Python. 10-K, 8-K, XBRL financials, Form 3/4/5, 13F, ADV β€” clean API, well-typed, MIT-licensed. - dgunning/edgartools

Subscribe to EdgarTools

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe