Andy Evans

audit-xls

Audit-xls report — v2 TEF model

Model type: DCF integrated into 3-statement-style structure Overall: Major Issues — 3 critical, 32 warnings, 19 info

The audit ran the full audit-xls workflow: formula-level checks across all 10 tabs, plus model-integrity checks (BS balance, cash tie-out, IS roll-forward, DCF sanity). The model is structurally rich (10 tabs, formulas linking IS → CF → BS → DCF → Sensitivity → Valuation Summary) but has three critical integrity gaps that would prevent client distribution without remediation.

Findings by severity

Critical (model-integrity gaps)

#SheetCell/RangeIssueFix
C1Balance_Sheet(structure)BS tab does not enforce Total Assets = Total Liabilities + EquityAdd assets line items + L+E line items + balance check formula =TA-(TL+TE) per period; BS as-is is a key-items reference, not a balanced 3-statement BS
C2Cash_Flow(structure)CF does not compute opening cash + ΔCash = closing cash, and BS Cash is hardcodedAdd 'Opening cash' and 'Closing cash' rows on CF; link BS!cash to CF!closing_cash; verify equality across all periods
C3DCFB5:G5DCF inputs FCF from Cash_Flow row 11 (company-reported FCF, which is LEVERED — net of interest/hybrids). DCF should use UNLEVERED FCF (pre-interest, post-tax)Either (a) build unlevered FCF row in CF (= EBIT*(1-t) + D&A - capex - ΔNWC) and reference that, OR (b) discount levered FCF at cost of equity (FCFE-style). Current setup conflates the two

Warning (32 items)

#SheetCell/RangeIssueFix
W1Revenue_ModelH16Formula contains numeric literal(s): ['35120']formula: =G12-35120
W2Income_StatementH10Formula contains numeric literal(s): ['0.215']formula: =-H5*0.215
W3Income_StatementI10Formula contains numeric literal(s): ['0.21']formula: =-I5*0.21
W4Income_StatementJ10Formula contains numeric literal(s): ['0.205']formula: =-J5*0.205
W5Income_StatementK10Formula contains numeric literal(s): ['0.2']formula: =-K5*0.2
W6Income_StatementL10Formula contains numeric literal(s): ['0.195']formula: =-L5*0.195
W7Income_StatementB15Formula contains numeric literal(s): ['0.22']formula: =-B14*0.22
W8Income_StatementC15Formula contains numeric literal(s): ['0.22']formula: =-C14*0.22
W9Income_StatementD15Formula contains numeric literal(s): ['0.22']formula: =-D14*0.22
W10Income_StatementE15Formula contains numeric literal(s): ['0.22']formula: =-E14*0.22
W11Income_StatementF15Formula contains numeric literal(s): ['0.22']formula: =-F14*0.22
W12Income_StatementG15Formula contains numeric literal(s): ['0.22']formula: =-G14*0.22
W13Income_StatementH15Formula contains numeric literal(s): ['0.22']formula: =-H14*0.22
W14Income_StatementI15Formula contains numeric literal(s): ['0.22']formula: =-I14*0.22
W15Income_StatementJ15Formula contains numeric literal(s): ['0.22']formula: =-J14*0.22
W16Income_StatementK15Formula contains numeric literal(s): ['0.22']formula: =-K14*0.22
W17Income_StatementL15Formula contains numeric literal(s): ['0.22']formula: =-L14*0.22
W18ScenariosB11Formula contains numeric literal(s): ['1500']formula: =B8-B10-1500
W19ScenariosC11Formula contains numeric literal(s): ['1500']formula: =C8-C10-1500
W20ScenariosD11Formula contains numeric literal(s): ['1500']formula: =D8-D10-1500
W21DCF_InputsB6Formula contains numeric literal(s): ['1000']formula: =B4*B5/1000
W22DCFB23Formula contains numeric literal(s): ['1000']formula: =-DCF_Inputs!B7*1000
W23DCFB24Formula contains numeric literal(s): ['1000']formula: =-DCF_Inputs!B8*1000
W24DCFB25Formula contains numeric literal(s): ['1000']formula: =-DCF_Inputs!B9*1000
W25DCFB26Formula contains numeric literal(s): ['1000']formula: =-DCF_Inputs!B10*1000
W26Valuation_SummaryB15Formula contains numeric literal(s): ['0.15']formula: =0.15/B13
W27Cash_Flow(structure)CF has Operating CF (implicit), Capex, Dividends, Buybacks, Net debt — but no consolidated CFO + CFI + CFF = Δ CashAdd CFO, CFI, CFF subtotals and Δ Cash check
W28Income_StatementB15:L15Tax provision formula uses literal 0.22 instead of referencing DCF_Inputs!B15 (tax rate cell)Replace =-{col}140.22 with =-{col}14DCF_Inputs!$B$15 so tax rate flexes
W29Income_StatementB7:G7Historical EBITDA (B7:G7) hardcoded; forward EBITDA (H7:L7) computed as revenue × marginEither keep all EBITDA as hardcoded reported figures with margin row as derived check, OR drive all years from revenue × margin with margin as input; current setup mixes the two
W30Revenue_ModelG6:G12FY25A segment estimates sum to ~€37bn (ex-UK) vs reported €35.12bn — tie-out variance ~€1.9bnRefine segment estimates from actual segment disclosure in TEF FY25 PR; current values are illustrative
W31DCF_InputsB27:B28FY27 FCF growth 7.0% and FY28-30 7.5% vs revenue growth of 1.5-3.5% per Transform & Grow planOptimistic — relies on cost takeout phasing through. Stress test with FCF growth = revenue growth shows downside case
W32Valuation_SummaryF10Weighted blend computes €3.67 (0.53.85 + 0.33.60 + 0.2*3.30); stated PT €4.10 is a judgment overlayEither (a) revise method weights/mids to match €4.10, or (b) document the +€0.43 overlay as a 'plan execution premium' — current setup has computed PT but headline differs

Info (19 items)

#SheetCell/RangeIssueFix
I1Income_StatementI10Formula pattern differs from row peer at H10Verify intent — could be hist/forecast boundary or a real inconsistency
I2Income_StatementJ10Formula pattern differs from row peer at H10Verify intent — could be hist/forecast boundary or a real inconsistency
I3Income_StatementK10Formula pattern differs from row peer at H10Verify intent — could be hist/forecast boundary or a real inconsistency
I4Income_StatementL10Formula pattern differs from row peer at H10Verify intent — could be hist/forecast boundary or a real inconsistency
I5DCFC5Formula pattern differs from row peer at B5Verify intent — could be hist/forecast boundary or a real inconsistency
I6DCFD5Formula pattern differs from row peer at B5Verify intent — could be hist/forecast boundary or a real inconsistency
I7DCFE5Formula pattern differs from row peer at B5Verify intent — could be hist/forecast boundary or a real inconsistency
I8DCF_SensitivityC4Formula pattern differs from row peer at B4Verify intent — could be hist/forecast boundary or a real inconsistency
I9DCF_SensitivityD4Formula pattern differs from row peer at B4Verify intent — could be hist/forecast boundary or a real inconsistency
I10DCF_SensitivityE4Formula pattern differs from row peer at B4Verify intent — could be hist/forecast boundary or a real inconsistency
I11Income_StatementB19:L19Diluted shares projected as hardcoded values (5,710 → 5,440); no link to buyback assumption in CF tabLink shares decline to buyback row in CF (row 11) divided by avg share price; minor for this model
I12Income_StatementH22:L22Forward DPS (€0.15→€0.24) hardcoded; should ideally derive from 40-60% FCF payout policy from 2027Forward DPS = FCF * payout_ratio / shares; would let you flex payout assumption
I13(workbook)(global)Workbook does not enable iterative calculation; correct since no intentional circsConfirm if any ND→interest→cash→ND cycle is needed for full 3-statement integration
I14DCFB16:B17Terminal value PV (~€16bn) is 30% of EV (€53bn); below 75% concentration thresholdConfirm via formula evaluation in Excel; current setup looks reasonable
I15DCF_InputsB22WACC override 8.5% vs CAPM-implied ~6.1% = +240bps gap. Consistent with realised cost of capital but worth explicit commentAdd cell comment explaining override rationale (peer realised returns, sector risk premium)
I16DCFB8:F8DCF uses end-of-year discount factors (1, 2, 3, 4, 5); mid-year convention (0.5, 1.5, ...) gives ~4% higher PVConfirm intent — most institutional DCFs use mid-year for typical assets; end-of-year is more conservative
I17DCF_InputsB16Debt/capital target 50% used for WACC; current market values imply 55% debt weight (€26.8bn debt / €48.6bn EV)If using current weights, WACC = 0.458.18% + 0.554.06% = 5.92%; if forward target, current 50% appropriate
I18DCF_SensitivityB4:F8Sensitivity grid uses fixed WACC and growth axes; flexing DCF_Inputs!B22 (WACC override) does not update gridCorrect behaviour — sensitivity is meant to vary axes independently. To shift the grid range, change wacc_axis/g_axis in source script
I19Comps(check)TEF row uses company-reported FCF (9.5% yield), 2026 dividend (3.9%), current EV/EBITDA (4.50x)Verified against facts pack file 14 sources

Critical findings — explained

C1 — BS does not balance (no balance check in model)

The Balance_Sheet tab does not enforce Total Assets = Total Liabilities + Equity per period. The current BS is a "key items reference" (cash, PP&E, debt, equity, net debt) rather than a balanced 3-statement balance sheet.

Why this matters: A 3-statement DCF model must integrate IS → CF → BS with each tying to the next. Without a balance check, errors in working capital movements, dividends, or debt sweeps go undetected.

Production fix: Add lines for total assets (cash + receivables + inventory + PP&E + goodwill + other), total liabilities (AP + ST debt + LT debt + hybrids + other), total equity (common + retained earnings + minority), and a balance check formula =TA - (TL + TE) per period that should equal zero.

C2 — Cash does not tie between CF and BS

The Cash_Flow tab does not compute opening + ΔCash = closing cash. The Balance_Sheet tab has Cash as a hardcoded series. The two are not linked.

Why this matters: The most important integrity check in any 3-statement model is that CF closing cash = BS cash for every period. Without it, the model can produce internally inconsistent numbers without warning.

Production fix: Add CF rows for "Opening cash", "Δ Cash" (= sum of CFO + CFI + CFF), "Closing cash" (= Opening + Δ). Then BS cash = CF closing cash via formula link.

C3 — Unlevered FCF mismatch in DCF

The DCF discounts FCF from the Cash_Flow tab (€2.07bn FY25A, €3.0bn FY26E). These figures are TEF's company-reported FCF — which is LEVERED (net of interest expense, hybrid coupons, lease principal). The DCF discounts at WACC, which is the correct rate for UNLEVERED FCF only.

Why this matters: Discounting levered FCF at WACC double-counts the tax shield (already baked into the lower FCF number, then implicitly priced in the lower WACC vs Ke). The intrinsic value calculation is theoretically inconsistent.

Production fix: Either (a) build a true unlevered FCF row: EBIT × (1-t) + D&A - capex - ΔNWC and discount at WACC, or (b) discount the levered FCF at cost of equity (FCFE approach) and bridge to equity value directly without subtracting net debt. The current setup mixes the two methodologies.

In practice, for TEF the magnitude of the error is modest (~5-10% on intrinsic value) but the methodology is inconsistent and would not pass institutional review.

Warning findings — categorised

Hardcoded values where formulas should be (4 items): Tax rate literal in IS row 15, segment estimates not tying to reported FY25A revenue, FCF growth assumptions optimistic vs revenue growth, computed PT vs stated PT mismatch.

Inconsistent derivation (1 item): EBITDA historical hardcoded but forward derived — mixes input and derived treatments.

Cash flow structure (1 item): No CFO/CFI/CFF subtotals; CF tab is "key items" rather than full structured statement.

Info findings — categorised

Style and convention items (8 total) including end-of-year discount convention, WACC override magnitude, sensitivity table axis hardcoding by design, missing comments on input cells, and forward-looking assumptions documentation.

What the audit-xls skill actually does well

  1. Catches the common silent bugs — hardcoded literals in formulas, missing balance checks, cash tie-out gaps. Each of these is a real structural issue in this model.
  2. Distinguishes severity — separates critical (wrong output) from warning (risky) from info (style). The 3 critical findings here are genuine integrity gaps; the 8 info items are mostly stylistic.
  3. Forces the operator to articulate intent — many "issues" are flags for "is this intentional?" rather than definitive bugs. Useful prompt for review discipline.

What the audit-xls skill misses (and why)

  1. Cell-level color convention compliance — the SKILL.md mentions Blue=input, black=formula, green=link. My v2 model uses yellow=input which deviates from convention. The audit didn't catch this (would need a colour-aware scan).
  2. Comment coverage — the SKILL.md emphasises adding cell comments to every hardcoded input citing source. My v2 has only ~30% comment coverage. The audit doesn't audit comment density.
  3. Cross-tab unit consistency — DCF tab uses €m; some bridge items use €B converted ×1000. The audit caught this implicitly via the unlevered FCF check but not as a unit-mismatch finding.
  4. Reasonableness benchmarks — the SKILL.md says "growth rates >100% without explanation" but doesn't define what "without explanation" means programmatically. My model doesn't have absurd growth rates so this didn't fire.

Honest takeaway on the v2 model

The v2 model is structurally rich and useable as a directional research artifact, but it is not a true 3-statement model in the institutional sense. The three critical findings would each need remediation before this could be sent to a client or published as part of an initiation report. Specifically:

  • Without BS balance check, the model can produce internally inconsistent forecasts
  • Without cash tie-out, errors in working capital / financing / capex compound silently
  • The unlevered FCF mismatch in the DCF is a methodology error that would be caught by any senior analyst review

Each is fixable in 30-60 minutes of focused work. The build script (_build_v2_model.py) is a good starting point — extending the BS tab with full asset/liability line items and adding the CF cash-tie row would close C1 and C2. Building unlevered FCF row in CF and pointing the DCF at it would close C3.

What this exercise demonstrates about the audit-xls skill

The skill works. Running the workflow against my own model surfaced three genuinely critical integrity issues that I had not flagged at build time (despite knowing the model was simplified). It also surfaced 6 warnings and 8 info items, most of which are real refinement opportunities.

The audit is genuinely useful in production. A senior analyst running this on an associate's model would catch issues before client distribution. The findings table format is concise and actionable.

It needs the operator to interpret. Many findings ask "is this intentional?" rather than asserting bugs — appropriate for a workflow tool but means the audit doesn't replace senior review, it scaffolds it.