# Real Estate Property Analysis

> Use when underwriting property. Cap rate, NOI, IRR, DSCR.

- Skill: `loopyluci/real-estate-property-analysis` (Agent Skill)
- Install (CLI): `npx skillmds@latest add loopyluci/real-estate-property-analysis`
- Raw SKILL.md: https://api.skillmd.com/api/skills/loopyluci/real-estate-property-analysis/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- License: MIT
- Author: LoopyLuci (https://skillmd.com/u/loopyluci)
- Updated: 2026-09-08
- Page: https://skillmd.com/skills/loopyluci/real-estate-property-analysis

---


# Real Estate Property Analysis

Comprehensive financial underwriting for income-producing real estate investments. Calculate all key performance metrics, generate sensitivity tables, and produce professional PDF reports.

## When to Use

- Evaluating a potential acquisition (single-family, multi-family, commercial)
- Running the numbers on a deal presented by an agent or wholesaler
- Comparing multiple investment opportunities side-by-side
- Preparing a detailed investment memo for partners or lenders
- Stress-testing a deal under different assumptions (vacancy, rate, rent growth)

## Key Financial Metrics

| Metric | Formula | Target Range |
|---|---|---|
| **Cap Rate** | NOI ÷ Purchase Price | 4-12% (market-dependent) |
| **Cash-on-Cash Return** | Pre-Tax Cash Flow ÷ Total Cash Invested | 8%+ |
| **NOI** | Effective Gross Income - Total Operating Expenses | Positive |
| **GRM (Gross Rent Multiplier)** | Price ÷ Gross Annual Rent | 4-12 |
| **Debt Service Coverage Ratio (DSCR)** | NOI ÷ Annual Debt Service | 1.25+ (conventional) |
| **Break-Even Occupancy** | (OpEx + Debt Service) ÷ EGI at 100% occupancy | < 85% |
| **IRR** | Annualized total return (time-weighted) | 12-20% (target dependent) |
| **Equity Multiple** | Total Cash Returned ÷ Equity Invested | 2.0x+ over hold period |

## Workflow

### 1. Gather Property Data

Collect these inputs from the user, MLS, or public records:

```
Purchase Price:
Down Payment %:
Closing Costs:
Loan Amount:
Interest Rate:
Amortization (years):
Gross Annual Rent:
Other Income (laundry, parking, storage, etc.):
Vacancy Rate (%):
Property Management (% of EGI):
Property Taxes (annual):
Insurance (annual):
HOA/Condo Fees (annual):
Maintenance/Repairs (% of EGI or $ amount):
Utilities (annual):
CapEx Reserve (% of EGI or $ amount):
Other OpEx (legal, accounting, marketing):
Appreciation Rate (annual, projection):
Rent Growth Rate (annual, projection):
Selling Costs (% at exit):
Hold Period (years):
```

### 2. Calculate Base Case

Run the core calculations:

**Income:**
```
PGI (Potential Gross Income) = Gross Annual Rent + Other Income
V&C Allowance = PGI × Vacancy Rate
EGI (Effective Gross Income) = PGI - V&C Allowance
```

**Operating Expenses:**
```
Property Management = EGI × Mgmt %
Taxes (fixed annual)
Insurance (fixed annual)
HOA (fixed annual)
Maintenance = EGI × Maint %
Utilities (fixed annual)
CapEx Reserve = EGI × CapEx %
Other (fixed annual)
Total OpEx = sum of all above
```

**Net Operating Income:**
```
NOI = EGI - Total OpEx
```

**Debt Service:**
```
Monthly P&I = PMT(rate/12, term×12, -loan amount)
Annual Debt Service = Monthly P&I × 12
DSCR = NOI ÷ Annual Debt Service
```

**Cash Flow:**
```
Pre-Tax Cash Flow = NOI - Annual Debt Service
Total Cash Invested = (Down Payment + Closing Costs)
Cash-on-Cash Return = Pre-Tax Cash Flow ÷ Total Cash Invested × 100
```

**Valuation Metrics:**
```
Cap Rate = NOI ÷ Purchase Price × 100
GRM = Purchase Price ÷ Gross Annual Rent
Break-Even Occupancy = (OpEx + Debt Service) ÷ PGI × 100
```

### 3. Python Financial Model

Save as `analyze_deal.py`:

```python
#!/usr/bin/env python3
def pmt(rate, nper, pv):
    if rate == 0:
        return pv / nper
    r = rate / 12
    n = nper * 12
    return r * pv * (1 + r)**n / ((1 + r)**n - 1)

def analyze_property(**kwargs):
    p = {
        'purchase_price': 500000,
        'down_payment_pct': 20,
        'closing_costs': 10000,
        'interest_rate': 6.5,
        'amort_years': 30,
        'gross_annual_rent': 60000,
        'other_annual_income': 2400,
        'vacancy_rate': 5,
        'mgmt_pct': 8,
        'property_taxes': 6000,
        'insurance': 1800,
        'hoa_fees': 0,
        'maintenance_pct': 5,
        'utilities': 1200,
        'capex_pct': 5,
        'other_opex': 500,
        'appreciation_rate': 3,
        'rent_growth_rate': 2,
        'selling_costs_pct': 8,
        'hold_years': 5,
    }
    p.update(kwargs)

    pgi = p['gross_annual_rent'] + p['other_annual_income']
    vacancy_allowance = pgi * p['vacancy_rate'] / 100
    egi = pgi - vacancy_allowance

    mgmt = egi * p['mgmt_pct'] / 100
    maintenance = egi * p['maintenance_pct'] / 100
    capex = egi * p['capex_pct'] / 100
    total_opex = (mgmt + p['property_taxes'] + p['insurance'] +
                  p['hoa_fees'] + maintenance + p['utilities'] +
                  capex + p['other_opex'])

    noi = egi - total_opex
    loan_amount = p['purchase_price'] * (1 - p['down_payment_pct'] / 100)
    monthly_pmt = pmt(p['interest_rate'] / 100, p['amort_years'], loan_amount)
    annual_debt = monthly_pmt * 12
    pre_tax_cf = noi - annual_debt
    total_cash = (p['purchase_price'] * p['down_payment_pct'] / 100) + p['closing_costs']
    coc_return = (pre_tax_cf / total_cash) * 100 if total_cash else 0

    cap_rate = (noi / p['purchase_price']) * 100
    grm = p['purchase_price'] / p['gross_annual_rent'] if p['gross_annual_rent'] else 0
    dscr = noi / annual_debt if annual_debt else 0
    break_even = ((total_opex + annual_debt) / pgi) * 100 if pgi else 0

    # Pro-forma projection
    cash_flows = []
    current_rent = pgi
    current_value = p['purchase_price']
    total_returned = 0

    for year in range(1, p['hold_years'] + 1):
        vac = current_rent * p['vacancy_rate'] / 100
        egi_y = current_rent - vac
        mgmt_y = egi_y * p['mgmt_pct'] / 100
        maint_y = egi_y * p['maintenance_pct'] / 100
        capex_y = egi_y * p['capex_pct'] / 100
        opex_y = (mgmt_y + p['property_taxes'] + p['insurance'] +
                  p['hoa_fees'] + maint_y + p['utilities'] +
                  capex_y + p['other_opex'])
        noi_y = egi_y - opex_y
        cf_y = noi_y - annual_debt
        cash_flows.append(cf_y)
        total_returned += cf_y
        current_rent *= (1 + p['rent_growth_rate'] / 100)
        current_value *= (1 + p['appreciation_rate'] / 100)

    # Loan balance at exit
    remaining_balance = loan_amount
    for _ in range(p['hold_years'] * 12):
        interest = remaining_balance * (p['interest_rate'] / 100 / 12)
        principal = monthly_pmt - interest
        remaining_balance -= principal
    remaining_balance = max(0, remaining_balance)

    selling_costs = current_value * p['selling_costs_pct'] / 100
    net_sale_proceeds = current_value - selling_costs - remaining_balance
    total_returned += net_sale_proceeds

    # IRR via Newton-Raphson
    irr_guess = 0.10
    for _ in range(1000):
        npv = -total_cash
        cf_all = cash_flows + [net_sale_proceeds]
        for t, cf in enumerate(cf_all):
            npv += cf / (1 + irr_guess)**(t + 1)
        if abs(npv) < 0.001:
            break
        dnpv = 0
        for t, cf in enumerate(cf_all):
            dnpv += -cf * (t + 1) / (1 + irr_guess)**(t + 2)
        if dnpv == 0:
            break
        irr_guess -= npv / dnpv
        if irr_guess < -1:
            irr_guess = 0.5
            break

    irr = irr_guess * 100
    equity_multiple = total_returned / total_cash if total_cash else 0

    return {
        'pgi': round(pgi, 2),
        'egi': round(egi, 2),
        'total_opex': round(total_opex, 2),
        'noi': round(noi, 2),
        'annual_debt': round(annual_debt, 2),
        'pre_tax_cf': round(pre_tax_cf, 2),
        'total_cash_invested': round(total_cash, 2),
        'cap_rate': round(cap_rate, 2),
        'cash_on_cash': round(coc_return, 2),
        'grm': round(grm, 2),
        'dscr': round(dscr, 2),
        'break_even_occupancy': round(break_even, 2),
        'monthly_payment': round(monthly_pmt, 2),
        'loan_amount': round(loan_amount, 2),
        'projected_sale_value': round(current_value, 2),
        'net_sale_proceeds': round(net_sale_proceeds, 2),
        'irr': round(irr, 2),
        'equity_multiple': round(equity_multiple, 2),
        'cash_flows': [round(cf, 2) for cf in cash_flows],
        'remaining_balance': round(remaining_balance, 2),
    }

def print_report(r):
    print("=" * 60)
    print("INVESTMENT PROPERTY ANALYSIS REPORT")
    print("=" * 60)
    print(f"\nINCOME — PGI: ${r['pgi']:>10,.2f}  EGI: ${r['egi']:>10,.2f}")
    print(f"EXPENSES — Total OpEx: ${r['total_opex']:>10,.2f}")
    print(f"NOI: ${r['noi']:>10,.2f}")
    print(f"DEBT — Monthly: ${r['monthly_payment']:>10,.2f}  Annual: ${r['annual_debt']:>10,.2f}")
    print(f"CASH FLOW: ${r['pre_tax_cf']:>10,.2f}  Cash Invested: ${r['total_cash_invested']:>10,.2f}")
    print(f"\nMETRICS")
    print(f"  Cap Rate:          {r['cap_rate']:>8.2f}%")
    print(f"  Cash-on-Cash:      {r['cash_on_cash']:>8.2f}%")
    print(f"  GRM:               {r['grm']:>8.2f}")
    print(f"  DSCR:              {r['dscr']:>8.2f}")
    print(f"  Break-Even Occ:    {r['break_even_occupancy']:>8.2f}%")
    print(f"  IRR:               {r['irr']:>8.2f}%")
    print(f"  Equity Multiple:   {r['equity_multiple']:>8.2f}x")
    print(f"\nPROJECTION ({len(r['cash_flows'])} yrs)")
    for i, cf in enumerate(r['cash_flows']):
        print(f"  Year {i+1} CF: ${cf:>9,.2f}")
    print(f"  Sale Value:    ${r['projected_sale_value']:>10,.2f}")
    print(f"  Net Proceeds:  ${r['net_sale_proceeds']:>10,.2f}")

if __name__ == '__main__':
    import json
    result = analyze_property()
    print_report(result)
    print(f"\nJSON:\n{json.dumps(result, indent=2)}")
```

### 4. Sensitivity Analysis

```python
def sensitivity_table(base_params, param_ranges):
    from itertools import product
    results = []
    keys, values = zip(*param_ranges.items())
    for combo in product(*values):
        params = dict(base_params)
        for k, v in zip(keys, combo):
            params[k] = v
        r = analyze_property(**params)
        combo_str = ', '.join(f'{k}={v}' for k, v in zip(keys, combo))
        results.append({
            'params': combo_str,
            'cap_rate': r['cap_rate'],
            'coc_return': r['cash_on_cash'],
            'dscr': r['dscr'],
            'irr': r['irr'],
        })
    return results

# Vary price, vacancy, and rate simultaneously
table = sensitivity_table(
    {'purchase_price': 500000, 'gross_annual_rent': 60000, 'vacancy_rate': 5, 'interest_rate': 6.5},
    {'purchase_price': [450000, 500000, 550000], 'vacancy_rate': [3, 5, 8], 'interest_rate': [5.5, 6.5, 7.5]}
)
for row in table:
    print(f"{row['params']:60s} | Cap: {row['cap_rate']:5.2f}% | CoC: {row['coc_return']:5.2f}% | DSCR: {row['dscr']:.2f} | IRR: {row['irr']:5.2f}%")
```

### 5. Generate PDF Report

```bash
pip install fpdf2
```

```python
from fpdf import FPDF

class InvestmentReport(FPDF):
    def header(self):
        self.set_font('Helvetica', 'B', 16)
        self.cell(0, 10, 'Property Investment Analysis', ln=True, align='C')
        self.ln(5)

    def section(self, title):
        self.set_font('Helvetica', 'B', 12)
        self.set_fill_color(230, 230, 240)
        self.cell(0, 8, title, ln=True, fill=True)
        self.ln(2)

    def metric_line(self, label, value):
        self.set_font('Helvetica', '', 10)
        self.cell(120, 7, label)
        self.cell(0, 7, str(value), ln=True)

def generate_summary_pdf(result, filename='investment_report.pdf'):
    pdf = InvestmentReport()
    pdf.add_page()
    pdf.section('Income & Expenses')
    pdf.metric_line('Potential Gross Income:', f"${result['pgi']:,.2f}")
    pdf.metric_line('Effective Gross Income:', f"${result['egi']:,.2f}")
    pdf.metric_line('Total OpEx:', f"${result['total_opex']:,.2f}")
    pdf.metric_line('Net Operating Income:', f"${result['noi']:,.2f}")
    pdf.section('Financing')
    pdf.metric_line('Loan Amount:', f"${result['loan_amount']:,.2f}")
    pdf.metric_line('Monthly P&I:', f"${result['monthly_payment']:,.2f}")
    pdf.section('Returns')
    pdf.metric_line('Cap Rate:', f"{result['cap_rate']:.2f}%")
    pdf.metric_line('Cash-on-Cash:', f"{result['cash_on_cash']:.2f}%")
    pdf.metric_line('DSCR:', f"{result['dscr']:.2f}")
    pdf.metric_line('IRR:', f"{result['irr']:.2f}%")
    pdf.metric_line('Equity Multiple:', f"{result['equity_multiple']:.2f}x")
    pdf.section('Projection')
    for i, cf in enumerate(result['cash_flows']):
        pdf.metric_line(f'Year {i+1} CF:', f"${cf:,.2f}")
    pdf.metric_line('Sale Value:', f"${result['projected_sale_value']:,.2f}")
    pdf.metric_line('Net Proceeds:', f"${result['net_sale_proceeds']:,.2f}")
    pdf.output(filename)
    print(f"Report: {filename}")
```

## Common Pitfalls

- **Ignoring CapEx reserves**: Always budget 5-15% of EGI for long-term replacements (roof, HVAC, parking lot).
- **Using pro forma NOI instead of trailing NOI**: Sellers' pro formas are optimistic. Verify actual T12 operating statements.
- **Overlooking management costs**: Include 8-10% management fee even if self-managing to reflect market cost.
- **Forgetting vacancy between tenants**: Use market vacancy (5-10%), not current occupancy. Account for lease-up time.
- **Skipping sensitivity analysis**: A single-point estimate is dangerous. Stress-test across vacancy, rate, and rent growth.
- **Misapplying cap rate**: Cap rate does not account for leverage, financing, or appreciation. Never use as sole decision metric.
- **Underestimating insurance**: Property insurance has risen 15-30% annually in many markets. Get actual quotes.
- **Assuming linear rent growth**: Check local rent control ordinances before projecting growth.
- **Using tax assessment instead of market value**: Tax-assessed value often lags market. Use appraised/purchase price.
- **Ignoring loan prepayment penalties**: Confirm whether the loan has yield maintenance, defeasance, or prepayment lockout.

## Verification Checklist

- [ ] GRM calculated and cross-checked against market comps
- [ ] Cap rate computed and compared to market average for property type/class
- [ ] Cash-on-cash with correct total cash invested (down payment + closing costs + immediate repairs)
- [ ] DSCR confirmed ≥ 1.25 for conventional financing
- [ ] Break-even occupancy confirmed below 85%
- [ ] Sensitivity table run with at least 3 variables (price, vacancy, interest rate)
- [ ] Pro-forma projections checked against trailing 12-month actuals
- [ ] CapEx reserve included (minimum 5% of EGI)
- [ ] Property management fee included (even if self-managing)
- [ ] All income/expense figures in annual amounts
- [ ] Loan amortization verified (correct term, rate, payment)
- [ ] IRR and equity multiple computed for full hold period
- [ ] Selling costs included in exit proceeds
- [ ] At least one downside scenario modeled
- [ ] Report exported and reviewed for numerical consistency
