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.
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.

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.
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.

