# /// script
# dependencies = [
#     "marimo",
#     "matplotlib==3.11.1",
# ]
# requires-python = ">=3.12"
# ///

import marimo

__generated_with = "0.24.0"
app = marimo.App(width="medium")


@app.cell(hide_code=True)
def imports():
    import marimo as mo
    import math
    import json
    from pathlib import Path
    import matplotlib.pyplot as plt
    from matplotlib.ticker import FuncFormatter

    return FuncFormatter, Path, json, math, mo, plt


@app.cell(hide_code=True)
def introduction(mo):
    mo.md("""
    # Microsoft: value after the AI build-out
    **Valuation date:** 8 September 2026. **Financial baseline:** FY26 ended 30 June, reported 29 July.
    The $493.95 regular-session close is from Microsoft Investor Relations' Refinitiv widget (8 September, 16:00 ET).

    This is an analyst-assumption model, not management guidance or a forecast with calibrated probabilities.
    All financial quantities are USD billions; share counts are billions. Ten forward annual periods use the latest fiscal-year revenue as a run-rate baseline. Cash flows are placed at each year end; no precision is claimed for the fiscal/calendar stub or intervening balance-sheet movements.

    Our base estimate is about **$427/share** at a **9%** nominal dollar discount rate. A 20% margin-of-safety rule gives $341, rounded down to a **$340 entry threshold**. That threshold applies only while the operating thesis remains intact. It is not an order, a guaranteed floor, or a predicted trading path.
    """)
    return


@app.cell
def inputs():
    # USD billions, shares billions. Reported FY26 values, not model assumptions.
    reported = {
        "period_end": "2026-06-30", "price_date": "2026-09-08 16:00 America/New_York",
        "price": 493.95, "revenue": {"Productivity": 139.996, "Cloud": 137.791, "Personal": 54.052},
        "operating_income": 155.237, "cfo": 182.935, "cash_capex": 115.948,
        "da_and_other": 38.534, "sbc": 12.405, "finance_lease_additions": 24.608,
        "finance_lease_principal": 3.101, "cash_and_short_investments": 76.843,
        "debt_face": 46.136, "finance_lease_liability": 66.594, "shares_diluted": 7.453,
        "equity_and_other_investments": 36.348,
        "sources": {
            "release": "https://www.microsoft.com/en-us/Investor/earnings/FY-2026-Q4/press-release-webcast",
            "filing": "https://www.sec.gov/Archives/edgar/data/789019/000119312526323660/msft-20260630.htm",
            "call": "https://www.microsoft.com/en-us/investor/events/fy-2026/earnings-fy-2026-q4"
        }
    }
    # Assumptions are subjective. These are scenarios, not confidence intervals.
    common = {"tax": .20, "wacc": .09, "terminal_growth": .035, "terminal_roic": .20,
              "operating_cash_reserve": 20.0, "nwc_per_incremental_revenue": .05}
    scenarios = {
        "Downside": {
            "growth": {"Productivity": [.10,.09,.08,.07,.06,.055,.05,.045,.04,.035],
                       "Cloud": [.24,.20,.16,.13,.10,.08,.07,.06,.05,.04],
                       "Personal": [-.04,0,.01,.02,.02,.02,.02,.02,.02,.02]},
            "margin": [.455,.44,.43,.42,.42,.42,.42,.42,.42,.42],
            "capex": [.52,.48,.43,.38,.33,.29,.27,.25,.24,.23],
            "da": [.14,.17,.19,.20,.20,.19,.18,.17,.16,.15],
            "terminal_roic": .15},
        "Base": {
            "growth": {"Productivity": [.13,.12,.11,.10,.09,.08,.07,.06,.05,.04],
                       "Cloud": [.28,.25,.22,.19,.16,.14,.12,.10,.08,.06],
                       "Personal": [-.02,.02,.03,.03,.03,.03,.03,.03,.03,.03]},
            "margin": [.46,.455,.45,.445,.445,.445,.445,.445,.445,.445],
            "capex": [.50,.45,.39,.33,.28,.25,.23,.22,.21,.20],
            "da": [.14,.17,.19,.20,.20,.19,.18,.17,.16,.14],
            "terminal_roic": .20},
        "Upside": {
            "growth": {"Productivity": [.15,.14,.13,.12,.11,.10,.09,.08,.07,.06],
                       "Cloud": [.32,.29,.26,.23,.20,.18,.16,.14,.12,.10],
                       "Personal": [0,.03,.04,.04,.04,.04,.04,.04,.04,.04]},
            "margin": [.465,.465,.47,.47,.47,.47,.47,.47,.47,.47],
            "capex": [.50,.45,.39,.33,.28,.25,.23,.21,.19,.18],
            "da": [.14,.17,.19,.20,.20,.19,.18,.17,.16,.14],
            "terminal_roic": .25}
    }

    return common, reported, scenarios


@app.cell
def valuation(common, mo, reported, scenarios):
    def forecast(case, cloud_shift=0.0, reinvestment_shift=0.0):
        segments = dict(reported["revenue"])
        rows = []
        for i in range(10):
            previous = sum(segments.values())
            for segment in segments:
                growth = case["growth"][segment][i] + (cloud_shift if segment == "Cloud" else 0)
                segments[segment] *= 1 + growth
            revenue = sum(segments.values())
            ebit = revenue * case["margin"][i]
            nopat = ebit * (1 - common["tax"])
            da = revenue * case["da"][i]
            capex = revenue * (case["capex"][i] + reinvestment_shift)
            delta_nwc = (revenue - previous) * common["nwc_per_incremental_revenue"]
            fcff = nopat + da - capex - delta_nwc
            rows.append({"year": i+1, **segments, "revenue": revenue, "ebit": ebit,
                         "nopat": nopat, "da": da, "capex": capex, "delta_nwc": delta_nwc, "fcff": fcff})
        return rows

    def value(case, wacc=None, terminal_growth=None, cloud_shift=0.0, reinvestment_shift=0.0):
        wacc = common["wacc"] if wacc is None else wacc
        growth = common["terminal_growth"] if terminal_growth is None else terminal_growth
        if wacc <= growth:
            raise ValueError("Discount rate must exceed perpetual growth")
        rows = forecast(case, cloud_shift, reinvestment_shift)
        terminal_fcff = rows[-1]["nopat"] * (1 + growth) * (1 - growth/case["terminal_roic"])
        pv_terminal = terminal_fcff / (wacc - growth) / (1 + wacc)**10
        pv_explicit = sum(row["fcff"]/(1+wacc)**row["year"] for row in rows)
        enterprise = pv_explicit + pv_terminal
        bridge = reported["cash_and_short_investments"] - common["operating_cash_reserve"] - reported["debt_face"] - reported["finance_lease_liability"]
        equity = enterprise + bridge
        return {"per_share": equity/reported["shares_diluted"], "enterprise": enterprise, "equity": equity,
                "bridge": bridge, "terminal_share": pv_terminal/enterprise, "terminal_fcff": terminal_fcff,
                "pv_explicit": pv_explicit, "pv_terminal": pv_terminal, "rows": rows}

    def solve(fn, target, lo, hi):
        flo = fn(lo) - target
        if flo * (fn(hi)-target) > 0:
            raise ValueError("Root is not bracketed")
        for _ in range(80):
            mid = (lo + hi)/2
            if (fn(mid)-target) * flo > 0:
                lo = mid
            else:
                hi = mid
        return (lo + hi)/2

    results = {name: value(case) for name, case in scenarios.items()}
    reverse_cloud_shift = solve(lambda shift: value(scenarios["Base"], cloud_shift=shift)["per_share"], reported["price"], -.1,.20)
    implied_discount = solve(lambda rate: value(scenarios["Base"], wacc=rate)["per_share"], reported["price"], .04,.2)
    sensitivity = [{"wacc": w, "g": g, "value": value(scenarios["Base"], wacc=w, terminal_growth=g)["per_share"]}
                   for w in [.08,.09,.10] for g in [.025,.035,.045]]
    mo.ui.table([{ "Scenario": name, "Value": round(result["per_share"],2),
     "Year 5 revenue": round(result["rows"][4]["revenue"],1), "Year 5 FCFF": round(result["rows"][4]["fcff"],1),
     "Year 10 FCFF": round(result["rows"][-1]["fcff"],1), "Terminal %": round(100*result["terminal_share"],1)} for name,result in results.items()])

    return implied_discount, results, reverse_cloud_shift, sensitivity, value


@app.cell(hide_code=True)
def controls(mo):
    discount_control = mo.ui.slider(start=7, stop=12, step=.25, value=9, label="Discount rate (%)")
    growth_control = mo.ui.slider(start=2, stop=5, step=.25, value=3.5, label="Perpetual growth (%)")
    cloud_control = mo.ui.slider(start=-5, stop=5, step=.5, value=0, label="Annual cloud growth shift (percentage points)")
    mo.vstack([mo.md("## Test the base case"), discount_control, growth_control, cloud_control])
    return cloud_control, discount_control, growth_control


@app.cell(hide_code=True)
def interactive(
    cloud_control,
    discount_control,
    growth_control,
    mo,
    scenarios,
    value,
):
    interactive_value = value(scenarios["Base"], wacc=discount_control.value/100,
                              terminal_growth=growth_control.value/100, cloud_shift=cloud_control.value/100)
    mo.md(f"Under your assumptions: **${interactive_value['per_share']:,.0f}/share**; "
          f"20% margin-of-safety threshold **${.8*interactive_value['per_share']:,.0f}**. "
          f"Terminal value contributes **{100*interactive_value['terminal_share']:.1f}%** of enterprise value.")
    return


@app.cell(hide_code=True)
def method(mo):
    mo.md("""
    ## Model conventions and limits
    **Enterprise DCF:** FCFF = EBIT × (1 − 20% tax) + depreciation/amortization − capital expenditure − incremental operating working capital. GAAP-like operating margins include stock compensation; there is no SBC add-back and no assumed buyback-driven share reduction. Current dilution is approximated by FY26's 7.453B weighted diluted shares.

    **Leases:** Capital expenditure includes cash PP&E and new finance-leased asset investment. Finance lease debt is deducted in the equity bridge; principal payments are not also subtracted from FCFF. Operating rent remains in projected operating margins; operating lease liabilities are therefore not deducted again. Future lease starts must fit either forecast capital investment or rent. This is an aggregate approximation, not a contract-by-contract lease forecast. FY26 had $26.7B of PP&E purchases in payables: our forward cash capex budgets must absorb their settlement; they are not separately deducted as debt.

    **Equity bridge:** add $76.843B cash/short investments, retain $20B operating liquidity, subtract $46.136B face-value debt and $66.594B finance lease liabilities. Face debt is conservative versus the reported $36.5B estimated market value. No separate premium for OpenAI or other strategic investments. Adding all $36.348B equity/other investments at book would add $4.88/share before tax and overlap; book is not an exit valuation.

    **Terminal reinvestment:** next-year NOPAT × (1 − perpetual growth / terminal return on invested capital). This charges for growth instead of assuming perpetual growth is free. ROIC is 15%/20%/25% in downside/base/upside. It is a terminal assumption, not a measured Azure return. A 9% discount rate and 3.5% perpetual nominal growth apply to all three cases. The 9% rate is our valuation hurdle, not an estimated CAPM/WACC fact; sensitivity is essential. No scenario probabilities are assigned.

    **Assumption rationale:** base consolidated revenue growth starts at 16.8%, near FY27 Q1 company guidance of 16–17%, but our annual path is not guidance. Intelligent Cloud growth fades from 28% to 6%; Productivity from 13% to 4%; Personal Computing recovers from −2% to 3%. Growth and margin forecasts already include Copilot/cloud adoption, so no extra AI business value is added. Operating margin fades from 46% to 44.5%, reflecting a larger share of lower-margin cloud sales and operating rent. Tax of 20% follows FY27 guidance. Initial $194B investment is our forward-year budget, compared with company guidance for over $50B in FY27 Q1 and growing FY27 spending; the $175B company figure is calendar 2026, a different period.

    D&A rises with installation of the asset fleet, from 14% of revenue to 20% in years 4–5 before fading to 14%; modeled investment falls from 50% of sales to 20%. Absolute base investment stays close to $190–205B while revenue almost triples over ten years. This is the principal efficiency bet, not a disclosed maintenance/growth split. A fleet replacement schedule is unavailable; faster hardware obsolescence or higher rent can invalidate the cash recovery. Working-capital funding is 5% of incremental revenue, including operating liquidity needs. No future acquisition program is modeled: acquisition-funded growth or new strategic investments would require additional cash deductions.

    ## Sources
    - [Microsoft FY26 release and timestamped quote](https://www.microsoft.com/en-us/Investor/earnings/FY-2026-Q4/press-release-webcast)
    - [FY26 10-K: financial statements and Notes 6, 10, 13, 15](https://www.sec.gov/Archives/edgar/data/789019/000119312526323660/msft-20260630.htm)
    - [29 July call: FY27 guidance, lease reclassification, RPO](https://www.microsoft.com/en-us/investor/events/fy-2026/earnings-fy-2026-q4)

    Public general research; readers decide suitability themselves. Intrinsic value is an uncertain estimate that changes with assumptions, not a price the market must return to.
    """)
    return


@app.cell(hide_code=True)
def exports(
    Path,
    common,
    implied_discount,
    json,
    math,
    mo,
    reported,
    results,
    reverse_cloud_shift,
    scenarios,
    sensitivity,
):
    base = Path(mo.notebook_location())
    export = {"reported": reported, "common": common, "scenarios": scenarios}
    (base / "inputs.json").write_text(json.dumps(export, indent=2)+"\n")
    (base / "results.json").write_text(json.dumps({"scenarios": results, "sensitivity": sensitivity,
        "reverse_cloud_shift": reverse_cloud_shift, "implied_discount": implied_discount,
        "entry_threshold": math.floor(.8*results["Base"]["per_share"]/10)*10}, indent=2)+"\n")
    print("Saved inputs.json and results.json alongside the notebook")
    return (base,)


@app.cell(hide_code=True)
def figures(FuncFormatter, base, mo, plt, reported, results):
    plt.rcParams.update({"font.family":"DejaVu Sans", "font.size":12,
        "figure.facecolor":"white", "axes.facecolor":"white", "text.color":"#182739",
        "axes.spines.top":False,"axes.spines.right":False,"axes.spines.left":False,
        "axes.spines.bottom":False,"xtick.color":"#526174","ytick.color":"#182739",
        "svg.fonttype":"path"})
    for _compact in [False,True]:
        _fig,_ax=plt.subplots(figsize=(4.5,3.5) if _compact else (9,3.5),layout="constrained")
        _names=["Downside","Base","Upside"]
        _vals=[results[n]["per_share"] for n in _names]
        _ax.barh(range(3),_vals,color=["#aebac6","#234d7b","#7893aa"],height=.42)
        for _i,_v in enumerate(_vals):
            _ax.text(_v,_i+.27,f"${_v:.0f}",va="bottom",ha="center",fontsize=11 if _compact else 14,fontweight="bold")
        _ax.axvline(reported["price"],color="#a5663c",lw=1.6,ls=(0,(4,3)))
        _ax.text(reported["price"],2.65,"Market $493.95",ha="center",fontsize=10 if _compact else 12,color="#8d522c")
        _ax.set_yticks(range(3),_names,fontsize=11 if _compact else 13)
        _ax.set_xlim(0,690);_ax.set_ylim(-.5,3)
        _ax.set_xticks([0,200,400,600]);_ax.xaxis.set_major_formatter(FuncFormatter(lambda x,p:f"${x:.0f}"))
        _ax.tick_params(length=0,pad=8);_ax.grid(axis="x",color="#e4e8ec",lw=.6);_ax.set_axisbelow(True)
        _suffix="-mobile" if _compact else ""
        _fig.savefig(base/f"valuation{_suffix}.svg",bbox_inches="tight")
        _fig.savefig(base/f"valuation{_suffix}.png",dpi=150,bbox_inches="tight");plt.close(_fig)

        _fig,_ax=plt.subplots(figsize=(4.5,3.8) if _compact else (9,3.5),layout="constrained")
        _rows=results["Base"]["rows"]
        _x=[r["year"] for r in _rows]
        _ax.plot(_x,[r["nopat"] for r in _rows],color="#7893aa",lw=2,label="After-tax operating profit")
        _ax.plot(_x,[r["capex"]-r["da"]+r["delta_nwc"] for r in _rows],color="#a5663c",lw=2,label="Net reinvestment")
        _ax.plot(_x,[r["fcff"] for r in _rows],color="#234d7b",lw=2.7,label="Cash flow to the firm")
        _ax.scatter([5,10],[_rows[4]["fcff"],_rows[9]["fcff"]],color="#234d7b",s=22,zorder=4)
        for _i in [4,9]:
            _ax.annotate(f"${_rows[_i]['fcff']:.0f}B",(_i+1,_rows[_i]["fcff"]),xytext=(0,-22 if _i==9 else -25),textcoords="offset points",ha="center",fontsize=10 if _compact else 12,color="#234d7b")
        _ax.set_xlim(.7,10.5);_ax.set_ylim(-10,390)
        _ax.set_xticks([1,3,5,7,10],["Year 1","3","5","7","10"])
        _ax.set_yticks([0,100,200,300]);_ax.yaxis.set_major_formatter(FuncFormatter(lambda x,p:f"${x:.0f}B"))
        _ax.tick_params(length=0,pad=8,labelsize=10 if _compact else 11)
        _ax.grid(axis="y",color="#e4e8ec",lw=.7);_ax.set_axisbelow(True)
        _ax.legend(loc="upper left",frameon=False,fontsize=9 if _compact else 11)
        _fig.savefig(base/f"cash-recovery{_suffix}.svg",bbox_inches="tight")
        _fig.savefig(base/f"cash-recovery{_suffix}.png",dpi=150,bbox_inches="tight");plt.close(_fig)
    for _path in base.glob("*.svg"):
        _path.write_text("\n".join(line.rstrip() for line in _path.read_text().splitlines())+"\n")
    mo.image(base/"valuation.svg")
    return


@app.cell(hide_code=True)
def annual_table(mo, results):
    mo.ui.table(results["Base"]["rows"],label="Base forecast: all annual cash-flow inputs, USD billions")
    return


if __name__ == "__main__":
    app.run()
