You generate a complete institutional-grade CRE underwriting model. Output is real code (Python with openpyxl for Excel output) plus the Excel workbook itself plus a markdown investment memo — not a static spreadsheet template.
The pain you solve: CBRE 2025 research found 62% of CRE acquisitions analysts spend most of their time on data entry — copying numbers from Offering Memorandums into Excel. Cap rate validation alone takes ~3 hours per deal. This skill generates the model AND the data-extraction scaffold so the underwriter spends time on judgment, not typing.
Self-storage — economic vs physical occupancy, ECRI cadence
Mixed-use — segmented proforma per use type, combined exit
Capital stack assumption. All-cash, single mortgage, A/B note, mezz, preferred equity — drives Phase 4 (Waterfall).
Output format. Excel workbook (openpyxl), Python module with API, OR both (recommend both — Python for repeatability, Excel for LP delivery).
Inputs available. OM PDF? Rent roll CSV? T-12 spreadsheet? Loan term sheet? If only narrative description, generate with sample values clearly marked as placeholder.
Recovery:
If asset type unclear, default to multifamily (the most common deal type — 40%+ of US CRE transaction volume).
If inputs are PDFs/photos, scaffold an extraction module using pdfplumber + a structured prompt to extract rent roll line items — but mark the extraction stage as REQUIRES_REVIEW.
gp_co_invest_pct, lp_pref_rate (default 8.0%), promote_tiers (e.g., 70/30 to 8% IRR, 60/40 to 15%, 50/50 above)
VALIDATION: Schema validates against a sample multifamily deal (10-unit, $1.5M purchase) without errors. All derived fields recompute correctly from primary fields.
FALLBACK: If user has a custom field, add via extra_fields: dict rather than hardcoding.
Generate calc.py with these formulas (cite each so the user can audit):
# Cap Rate = NOI / Purchase Price
# Source: Appraisal Institute, "The Appraisal of Real Estate" 15th ed.
# Cash-on-Cash = (NOI - Debt Service) / Total Equity Invested
# Year 1; should be > LP pref to make sense for value-add deals
# DSCR = NOI / Annual Debt Service
# Lender minimum typically 1.20x-1.25x (multifamily), 1.30x+ (other)
# Debt Yield = NOI / Loan Amount
# Lender minimum typically 7.5-9% — cap-rate-independent stress test
# Loan Constant = Annual Debt Service / Loan Amount
# For amortizing loan: use PMT formula
# Annual Debt Service:
# IO period: loan_amount * interest_rate
# Amortizing: numpy_financial.pmt(rate/12, am_months, -loan) * 12
# Unlevered IRR: numpy_financial.irr([- total_basis, ncf_yr1, ..., ncf_yrN + sale_proceeds])
# Levered IRR: numpy_financial.irr([- total_equity, cfat_yr1, ..., cfat_yrN + net_sale_to_equity])
# Equity Multiple = Sum(Distributions to Equity) / Total Equity Invested
# Terminal Value = Year_N+1_NOI / Exit Cap Rate
# Net Sale Proceeds = Terminal Value - Cost of Sale - Loan Balance at Exit
The engine MUST:
Use numpy_financial for IRR/PMT/NPV (NOT the pure-numpy versions — they're deprecated).
Compute LEVERED and UNLEVERED separately. Many junior models conflate these.
Compute YEAR-1 stabilized AND T-12 actual AND stabilized AT EXIT NOI. The cap rate at sale uses Year_N+1 NOI, not Year_N.
Handle a value-add scenario where NOI grows non-linearly (e.g., rent bumps after renovation).
Compute debt sizing test: if loan_amount is None, size to MIN(LTV constraint, DSCR constraint, Debt Yield constraint).
VALIDATION: Run engine against the textbook example (50 units, $7.5M purchase, 6% cap, 65% LTV, 5.5% interest 30am IO 24, 7-year hold, exit at 6.5% cap) and confirm Levered IRR matches the worked example within 10 bps.
If GP/LP partnership is configured, generate the waterfall.
Standard CRE waterfall (American or European — default European, which is simpler and LP-friendly):
Tier 1: Return of Capital — 100% to LP until LP has received back original equity
Tier 2: Preferred Return — 100% to LP until LP IRR = preferred rate (typically 8%)
Tier 3: First Promote — 70/30 (LP/GP) until LP IRR = 12% (or configured threshold)
Tier 4: Second Promote — 60/40 until LP IRR = 18%
Tier 5: Final Promote — 50/50 above
Output per LP and per GP:
Equity invested, distributions received, levered IRR, equity multiple, % of total profit
VALIDATION: Sum of (LP + GP) distributions = total distributable cash flow. GP carry only kicks in after LP IRR hurdle met.
FALLBACK: If single-investor deal, skip this phase entirely.
Complete: All 6 phases present? Both levered and unlevered IRR computed? Waterfall if applicable?
Robust: Handles divide-by-zero (cap rate when NOI < 0), partial first year, IO period, value-add NOI ramp?
Clean: Excel output formatted with proper number formats ($, %, x for multipliers)? Tabs labeled? Print-area set?
CRE-credible: Would a CRE acquisitions associate at JLL/CBRE/Cushman recognize the conventions and the formulas? (Killer dimension — wrong cap rate calculation = no trust ever.)
If any < 4:
Most common gap: using current-year NOI instead of forward-year NOI for the exit valuation. Fix and re-run sensitivity.
Never use Year_N NOI for exit valuation. Always Year_N+1 NOI / exit cap.
Never confuse levered and unlevered IRR. Both ship; both labeled.
Never use deprecated numpy.irr. Use numpy_financial.irr.
Never hardcode market rents — they come from the user's rent roll or comp set.
Never imply the model gives a buy/sell recommendation. It presents math; humans decide.
If the user has ARGUS, generate an export-to-ARGUS schema rather than a competing model.
1---2name: cre-underwriting3description: Generate an institutional-grade commercial real estate underwriting model — input schema (T-12 income, T-3 trailing, rent roll, debt terms, exit assumptions), calc engine (Cap Rate, NOI, Cash-on-Cash, IRR, DSCR, Debt Yield, ROI, Equity Multiple, levered & unlevered returns).4---56# Commercial Real Estate Underwriting Generator78You generate a complete institutional-grade CRE underwriting model. Output is real code (Python with openpyxl for Excel output) plus the Excel workbook itself plus a markdown investment memo — not a static spreadsheet template.910The pain you solve: CBRE 2025 research found 62% of CRE acquisitions analysts spend most of their time on data entry — copying numbers from Offering Memorandums into Excel. Cap rate validation alone takes ~3 hours per deal. This skill generates the model AND the data-extraction scaffold so the underwriter spends time on judgment, not typing.1112============================================================13=== PRE-FLIGHT ===14============================================================1516Gather and verify before generating:1718- [ ] **Asset type identified.** The model differs significantly:19 - **Multifamily** — unit mix, in-place vs market rent, T-12 with rent roll, loss-to-lease, vacancy, concessions20 - **Office** — rent roll with WALT, TI/LC reserves, vacancy assumption from CoStar comps21 - **Retail** — anchor vs in-line tenants, % rent clauses, CAM recoveries22 - **Industrial** — flat NNN structure, expansion options, build-to-suit credit23 - **Hospitality** — RevPAR / ADR / Occupancy, FF&E reserve24 - **Self-storage** — economic vs physical occupancy, ECRI cadence25 - **Mixed-use** — segmented proforma per use type, combined exit26- [ ] **Capital stack assumption.** All-cash, single mortgage, A/B note, mezz, preferred equity — drives Phase 4 (Waterfall).27- [ ] **Output format.** Excel workbook (openpyxl), Python module with API, OR both (recommend both — Python for repeatability, Excel for LP delivery).28- [ ] **Inputs available.** OM PDF? Rent roll CSV? T-12 spreadsheet? Loan term sheet? If only narrative description, generate with sample values clearly marked as placeholder.2930Recovery:3132- If asset type unclear, default to multifamily (the most common deal type — 40%+ of US CRE transaction volume).33- If inputs are PDFs/photos, scaffold an extraction module using `pdfplumber` + a structured prompt to extract rent roll line items — but mark the extraction stage as REQUIRES_REVIEW.3435============================================================36=== PHASE 1: INPUT SCHEMA ===37============================================================3839Generate `inputs.py` defining the deal inputs as a strict Pydantic schema. Fields by section:4041**Property**4243- name, address, asset_type, year_built, year_renovated, sq_ft (NRA), unit_count, parking_count, submarket4445**Acquisition**4647- purchase_price, closing_costs_pct (default 1.5%), due_diligence_costs, financing_costs, capex_at_close, working_capital, total_basis (derived)4849**Income (T-12 actual + Y1 underwritten)**5051- gross_potential_rent, vacancy_pct (physical), credit_loss_pct, concessions, other_income (parking, fees, RUBS, laundry), effective_gross_income (derived)5253**Operating Expenses (Y1 underwritten)**5455- real_estate_taxes (post-reassessment if relevant), insurance, utilities, repairs_maintenance, marketing, payroll, mgmt_fee_pct, replacement_reserves_per_unit, total_opex (derived), expense_ratio (derived)5657**Net Operating Income** (derived: EGI − OpEx)5859**Debt**6061- ltv_pct OR loan_amount (mutually exclusive), interest_rate, amortization_years, term_years, io_period_years (default 0), origination_fee_pct, dscr_required_min (default 1.20x), debt_yield_required_min (default 8.0%)6263**Exit**6465- hold_period_years (default 5 or 7), exit_cap_rate (typically +25-75 bps over entry cap), cost_of_sale_pct (default 2.0%), terminal_value (derived)6667**Growth Assumptions (10-year vectors)**6869- rent_growth_pct[], expense_growth_pct[], other_income_growth_pct[]7071**Partnership** (if syndication)7273- gp_co_invest_pct, lp_pref_rate (default 8.0%), promote_tiers (e.g., 70/30 to 8% IRR, 60/40 to 15%, 50/50 above)7475VALIDATION: Schema validates against a sample multifamily deal (10-unit, $1.5M purchase) without errors. All derived fields recompute correctly from primary fields.7677FALLBACK: If user has a custom field, add via `extra_fields: dict` rather than hardcoding.7879============================================================80=== PHASE 2: CORE CALCULATION ENGINE ===81============================================================8283Generate `calc.py` with these formulas (cite each so the user can audit):8485```python86# Cap Rate = NOI / Purchase Price87# Source: Appraisal Institute, "The Appraisal of Real Estate" 15th ed.8889# Cash-on-Cash = (NOI - Debt Service) / Total Equity Invested90# Year 1; should be > LP pref to make sense for value-add deals9192# DSCR = NOI / Annual Debt Service93# Lender minimum typically 1.20x-1.25x (multifamily), 1.30x+ (other)9495# Debt Yield = NOI / Loan Amount96# Lender minimum typically 7.5-9% — cap-rate-independent stress test9798# Loan Constant = Annual Debt Service / Loan Amount99# For amortizing loan: use PMT formula100101# Annual Debt Service:102# IO period: loan_amount * interest_rate103# Amortizing: numpy_financial.pmt(rate/12, am_months, -loan) * 12104105# Unlevered IRR: numpy_financial.irr([- total_basis, ncf_yr1, ..., ncf_yrN + sale_proceeds])106# Levered IRR: numpy_financial.irr([- total_equity, cfat_yr1, ..., cfat_yrN + net_sale_to_equity])107108# Equity Multiple = Sum(Distributions to Equity) / Total Equity Invested109110# Terminal Value = Year_N+1_NOI / Exit Cap Rate111# Net Sale Proceeds = Terminal Value - Cost of Sale - Loan Balance at Exit112```113114The engine MUST:115116- Use `numpy_financial` for IRR/PMT/NPV (NOT the pure-numpy versions — they're deprecated).117- Compute LEVERED and UNLEVERED separately. Many junior models conflate these.118- Compute YEAR-1 stabilized AND T-12 actual AND stabilized AT EXIT NOI. The cap rate at sale uses Year_N+1 NOI, not Year_N.119- Handle a value-add scenario where NOI grows non-linearly (e.g., rent bumps after renovation).120- Compute breakeven occupancy: `Breakeven_Occ = (OpEx + Debt Service) / GPR`.121- Compute debt sizing test: if `loan_amount` is None, size to MIN(LTV constraint, DSCR constraint, Debt Yield constraint).122123VALIDATION: Run engine against the textbook example (50 units, $7.5M purchase, 6% cap, 65% LTV, 5.5% interest 30am IO 24, 7-year hold, exit at 6.5% cap) and confirm Levered IRR matches the worked example within 10 bps.124125============================================================126=== PHASE 3: 10-YEAR PROFORMA ===127============================================================128129Generate the full 10-year cash flow waterfall:130131| Line | Year 1 | Year 2 | ... | Year N (exit) |132| ----------------------------- | ------ | ------ | --- | ------------- |133| Gross Potential Rent | 1.20M | grown | | |134| (-) Vacancy | (60K) | | | |135| (-) Concessions | (10K) | | | |136| (+) Other Income | 80K | | | |137| **Effective Gross Income** | 1.21M | | | |138| (-) Operating Expenses | (480K) | | | |139| **Net Operating Income** | 730K | | | |140| (-) Capital Reserves | (15K) | | | |141| **NOI after Reserves** | 715K | | | |142| (-) Debt Service | (450K) | | | |143| **Cash Flow After Debt** | 265K | | | |144| (+) Sale Proceeds net of debt | | | | + 5.2M |145| **Cash Flow to Equity** | 265K | | | 5.46M |146147Plus a Sources & Uses table at acquisition and a Sources & Uses at exit.148149VALIDATION: Row totals reconcile (EGI − OpEx = NOI). Year N+1 NOI used for exit valuation, not Year N.150151============================================================152=== PHASE 4: WATERFALL (for syndication deals) ===153============================================================154155If GP/LP partnership is configured, generate the waterfall.156157Standard CRE waterfall (American or European — default European, which is simpler and LP-friendly):158159```160Tier 1: Return of Capital — 100% to LP until LP has received back original equity161Tier 2: Preferred Return — 100% to LP until LP IRR = preferred rate (typically 8%)162Tier 3: First Promote — 70/30 (LP/GP) until LP IRR = 12% (or configured threshold)163Tier 4: Second Promote — 60/40 until LP IRR = 18%164Tier 5: Final Promote — 50/50 above165```166167Output per LP and per GP:168169- Equity invested, distributions received, levered IRR, equity multiple, % of total profit170171VALIDATION: Sum of (LP + GP) distributions = total distributable cash flow. GP carry only kicks in after LP IRR hurdle met.172173FALLBACK: If single-investor deal, skip this phase entirely.174175============================================================176=== PHASE 5: SENSITIVITY TABLES ===177============================================================178179Generate three 2D sensitivities (the deal-killers):1801811. **Exit Cap × Rent Growth** → Levered IRR1822. **Entry Cap × Loan Constant** → Cash-on-Cash Year 11833. **Vacancy × OpEx Growth** → DSCR Year 1184185Each output as both a pandas DataFrame heatmap AND an Excel sheet with conditional formatting.186187VALIDATION: Center cell of each sensitivity equals the base-case output.188189============================================================190=== PHASE 6: INVESTMENT MEMO ===191============================================================192193Generate `memo.md` (markdown) with these sections:1941951. **Executive Summary** (3 sentences: asset, basis per unit, headline returns)1962. **Returns Summary Table** (Y1 cap, stabilized cap, Y1 CoC, levered IRR, equity multiple, DSCR Y1)1973. **Sources & Uses** at acquisition1984. **Capital Stack diagram** (text-based)1995. **Underwriting Assumptions Highlights** (rent growth, expense growth, exit cap)2006. **Sensitivity Summary** (best case / base case / downside)2017. **Risks & Mitigants** (3-5 items, populated from heuristics: high LTV → refi risk; aggressive rent growth → stabilization risk; etc.)2028. **Recommendation** (with a clearly-marked placeholder for the underwriter — model doesn't recommend, it presents)203204VALIDATION: Memo renders without dangling markdown. All numbers tie to the proforma.205206FALLBACK: If user wants PDF, add a step to convert via `pandoc` or `weasyprint`.207208============================================================209=== SELF-REVIEW ===210============================================================211212Score 1–5:213214- **Complete**: All 6 phases present? Both levered and unlevered IRR computed? Waterfall if applicable?215- **Robust**: Handles divide-by-zero (cap rate when NOI < 0), partial first year, IO period, value-add NOI ramp?216- **Clean**: Excel output formatted with proper number formats ($, %, x for multipliers)? Tabs labeled? Print-area set?217- **CRE-credible**: Would a CRE acquisitions associate at JLL/CBRE/Cushman recognize the conventions and the formulas? (Killer dimension — wrong cap rate calculation = no trust ever.)218219If any < 4:220221- Most common gap: using current-year NOI instead of forward-year NOI for the exit valuation. Fix and re-run sensitivity.222223============================================================224=== LEARNINGS CAPTURE ===225============================================================226227Append to `~/.claude/skills/cre-underwriting/LEARNINGS.md`:228229## <YYYY-MM-DD> — <asset type, deal size, capital stack>230231- **What worked:** <pattern that produced clean output>232- **What was awkward:** <retry or manual fix needed>233- **Suggested patch:** <concrete improvement>234- **Verdict:** [Smooth / Minor friction / Major friction]235236============================================================237=== STRICT RULES ===238============================================================239240- Never use Year_N NOI for exit valuation. Always Year_N+1 NOI / exit cap.241- Never confuse levered and unlevered IRR. Both ship; both labeled.242- Never use deprecated `numpy.irr`. Use `numpy_financial.irr`.243- Never hardcode market rents — they come from the user's rent roll or comp set.244- Never imply the model gives a buy/sell recommendation. It presents math; humans decide.245- If the user has ARGUS, generate an export-to-ARGUS schema rather than a competing model.
Run npx skillmds@latest add tinh2/cre-underwriting in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
Generate an institutional-grade commercial real estate underwriting model — input schema (T-12 income, T-3 trailing, rent roll, debt terms, exit assumptions), calc engine (Cap Rate, NOI, Cash-on-Cash, IRR, DSCR, Debt Yield, ROI, Equity Multiple, levered & unlevered returns). It is listed under AI & ML on SkillMD.
This skill has not completed SkillMD's automated safety review yet. Independent scanners report: SkillSpector: PASS, Skill Scanner: PASS. Capability flags: docs only. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free, and the skill stays under its author's original license.
tinh2 (@tinh2) published this skill. Their other Agent Skills are listed on their SkillMD profile.