Files
workspace/tools/build-re-excel.py

283 lines
11 KiB
Python

#!/usr/bin/env python3
"""Build Excel spreadsheet from ARI's real estate cost seg model"""
import sys
sys.path.insert(0, '/home/wdjones/.openclaw/workspace-ari/data/real-estate-model')
from detailed_calculations import *
import openpyxl
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side, numbers
from openpyxl.utils import get_column_letter
# Check if openpyxl available
try:
import openpyxl
except ImportError:
import subprocess
subprocess.run([sys.executable, '-m', 'pip', 'install', 'openpyxl'], check=True)
import openpyxl
results = run_portfolio_model()
wb = openpyxl.Workbook()
# Styles
header_font = Font(bold=True, color="FFFFFF", size=11)
header_fill = PatternFill(start_color="1a1a2e", end_color="1a1a2e", fill_type="solid")
red_font = Font(color="FF4444", bold=True)
green_font = Font(color="44FF44", bold=True)
money_fmt = '#,##0'
money_neg_fmt = '#,##0;[Red]-#,##0'
pct_fmt = '0.0%'
thin_border = Border(
left=Side(style='thin'), right=Side(style='thin'),
top=Side(style='thin'), bottom=Side(style='thin')
)
section_fill = PatternFill(start_color="2d2d44", end_color="2d2d44", fill_type="solid")
section_font = Font(bold=True, color="00BFFF", size=11)
warn_fill = PatternFill(start_color="4a1a1a", end_color="4a1a1a", fill_type="solid")
warn_font = Font(bold=True, color="FF6666", size=12)
def style_header(ws, row, max_col):
for col in range(1, max_col + 1):
cell = ws.cell(row=row, column=col)
cell.font = header_font
cell.fill = header_fill
cell.alignment = Alignment(horizontal='center', wrap_text=True)
cell.border = thin_border
def style_row(ws, row, max_col):
for col in range(1, max_col + 1):
cell = ws.cell(row=row, column=col)
cell.border = thin_border
cell.alignment = Alignment(horizontal='right')
if col == 1:
cell.alignment = Alignment(horizontal='left')
def auto_width(ws):
for col in ws.columns:
max_len = 0
col_letter = get_column_letter(col[0].column)
for cell in col:
if cell.value:
max_len = max(max_len, len(str(cell.value)))
ws.column_dimensions[col_letter].width = min(max_len + 4, 22)
# ============ SHEET 1: Portfolio Overview ============
ws1 = wb.active
ws1.title = "Portfolio Overview"
ws1.sheet_properties.tabColor = "1a1a2e"
# Title
ws1.merge_cells('A1:K1')
ws1['A1'] = "REAL ESTATE COST SEGREGATION MODEL — 5-YEAR PORTFOLIO"
ws1['A1'].font = Font(bold=True, color="00BFFF", size=14)
# Warning banner
ws1.merge_cells('A3:K3')
ws1['A3'] = "⚠️ VERDICT: NO-GO — Negative cash flow all 5 years. $0 tax benefit at >$150K AGI. $337K passive losses stranded."
ws1['A3'].font = warn_font
ws1['A3'].fill = warn_fill
# Headers
row = 5
headers = ['Year', 'Properties', 'Gross Rent', 'Effective Rent', 'NOI', 'Cash Flow',
'Depreciation', 'Taxable Income', 'Cash Invested', 'Cumulative Invested', 'Cumulative Cash Flow']
for col, h in enumerate(headers, 1):
ws1.cell(row=row, column=col, value=h)
style_header(ws1, row, len(headers))
cumulative_invested = 0
cumulative_cf = 0
for i, r in enumerate(results):
row = 6 + i
cumulative_invested += r['total_cash_invested']
cumulative_cf += r['total_cash_flow']
values = [
f"Year {r['year']}", r['properties_owned'], r['total_gross_rent'],
r['total_effective_rent'], r['total_noi'], r['total_cash_flow'],
r['total_depreciation'], r['total_taxable_income'], r['total_cash_invested'],
cumulative_invested, cumulative_cf
]
for col, v in enumerate(values, 1):
cell = ws1.cell(row=row, column=col, value=v)
if col >= 3:
cell.number_format = money_neg_fmt
if col == 6 and isinstance(v, (int, float)) and v < 0:
cell.font = red_font
if col == 8 and isinstance(v, (int, float)) and v < 0:
cell.font = red_font
style_row(ws1, row, len(headers))
# Totals row
row = 11
ws1.cell(row=row, column=1, value="5-YEAR TOTALS").font = Font(bold=True)
ws1.cell(row=row, column=3, value=sum(r['total_gross_rent'] for r in results)).number_format = money_fmt
ws1.cell(row=row, column=5, value=sum(r['total_noi'] for r in results)).number_format = money_fmt
ws1.cell(row=row, column=6, value=sum(r['total_cash_flow'] for r in results))
ws1.cell(row=row, column=6).number_format = money_neg_fmt
ws1.cell(row=row, column=6).font = red_font
ws1.cell(row=row, column=7, value=sum(r['total_depreciation'] for r in results)).number_format = money_fmt
ws1.cell(row=row, column=9, value=395000).number_format = money_fmt
style_row(ws1, row, len(headers))
auto_width(ws1)
# ============ SHEET 2: Tax Impact by AGI ============
ws2 = wb.create_sheet("Tax Impact by AGI")
ws2.sheet_properties.tabColor = "4a1a1a"
ws2.merge_cells('A1:H1')
ws2['A1'] = "PASSIVE LOSS & TAX SAVINGS BY AGI BRACKET"
ws2['A1'].font = Font(bold=True, color="00BFFF", size=14)
# Critical note
ws2.merge_cells('A3:H4')
ws2['A3'] = ("CRITICAL: At AGI >$150K (D J + partners likely scenario), passive losses CANNOT offset W-2 income. "
"Losses carry forward indefinitely but only offset future passive rental income or are released upon sale. "
"This means $0 annual tax benefit from depreciation — the entire cost seg strategy provides NO cash flow benefit.")
ws2['A3'].font = Font(color="FF6666", size=10, italic=True)
ws2['A3'].alignment = Alignment(wrap_text=True)
row = 6
headers = ['Year', 'Passive Loss Generated',
'Tax Savings\n(AGI <$100K)', 'Losses Carried\n(AGI <$100K)',
'Tax Savings\n(AGI $100-150K)', 'Losses Carried\n(AGI $100-150K)',
'Tax Savings\n(AGI >$150K)', 'Losses Carried\n(AGI >$150K)']
for col, h in enumerate(headers, 1):
ws2.cell(row=row, column=col, value=h)
style_header(ws2, row, len(headers))
for i, r in enumerate(results):
row = 7 + i
values = [
f"Year {r['year']}", abs(r['total_taxable_income']),
r['tax_savings_scenario_a'], r['passive_losses_scenario_a'],
r['tax_savings_scenario_b'], r['passive_losses_scenario_b'],
r['tax_savings_scenario_c'], r['passive_losses_scenario_c']
]
for col, v in enumerate(values, 1):
cell = ws2.cell(row=row, column=col, value=v)
if col >= 2:
cell.number_format = money_fmt
# Highlight the >$150K columns
if col in (7, 8) and isinstance(v, (int, float)):
cell.font = red_font
style_row(ws2, row, len(headers))
# Summary
row = 13
ws2.cell(row=row, column=1, value="5-YEAR TOTALS").font = Font(bold=True)
ws2.cell(row=row, column=3, value=30000).number_format = money_fmt
ws2.cell(row=row, column=5, value=15000).number_format = money_fmt
ws2.cell(row=row, column=7, value=0).font = red_font
ws2.cell(row=row, column=7).number_format = money_fmt
ws2.cell(row=row, column=8, value=337877).font = red_font
ws2.cell(row=row, column=8).number_format = money_fmt
auto_width(ws2)
# ============ SHEET 3: S&P 500 Comparison ============
ws3 = wb.create_sheet("vs S&P 500")
ws3.sheet_properties.tabColor = "1a4a1a"
ws3.merge_cells('A1:F1')
ws3['A1'] = "SAME CAPITAL IN S&P 500 vs REAL ESTATE PORTFOLIO"
ws3['A1'].font = Font(bold=True, color="00BFFF", size=14)
row = 3
headers = ['Year', 'Capital Deployed', 'S&P 500 Value\n(9% avg)', 'RE Portfolio Equity', 'RE Cash Flow\n(Cumulative)', 'S&P 500 Advantage']
for col, h in enumerate(headers, 1):
ws3.cell(row=row, column=col, value=h)
style_header(ws3, row, len(headers))
sp500_values = []
sp500_total = 0
cum_cf = 0
for i, r in enumerate(results):
row = 4 + i
year = r['year']
# Each year invest $79K, compounds at 9%
sp500_total = (sp500_total * 1.09) + r['total_cash_invested']
sp500_values.append(sp500_total)
# RE equity = appreciation + principal paydown (rough)
props = r['properties_owned']
re_equity = props * (DOWN_PAYMENT + (PURCHASE_PRICE * ((1.03 ** year) - 1))) # appreciation gain
# Add principal paydown estimate (~$3K/yr per property in early years)
re_equity += props * 3000 * ((year + 1) / 2)
cum_cf += r['total_cash_flow']
advantage = sp500_total - (re_equity + cum_cf)
values = [f"Year {year}", r['total_cash_invested'], sp500_total, re_equity, cum_cf, advantage]
for col, v in enumerate(values, 1):
cell = ws3.cell(row=row, column=col, value=v)
if col >= 2:
cell.number_format = money_neg_fmt
if col == 6 and isinstance(v, (int, float)) and v > 0:
cell.font = green_font
style_row(ws3, row, len(headers))
auto_width(ws3)
# ============ SHEET 4: Property Assumptions ============
ws4 = wb.create_sheet("Assumptions")
ws4.sheet_properties.tabColor = "444444"
ws4.merge_cells('A1:C1')
ws4['A1'] = "MODEL ASSUMPTIONS"
ws4['A1'].font = Font(bold=True, color="00BFFF", size=14)
assumptions = [
("Purchase", "", ""),
("Purchase Price", "$345,000", ""),
("Down Payment", "20%", "$69,000"),
("Closing Costs", "$6,000", ""),
("Cost Seg Study", "$4,000", "per property"),
("Total Cash Per Property", "$79,000", ""),
("", "", ""),
("Financing", "", ""),
("Loan Amount", "$276,000", ""),
("Interest Rate", "6.25%", "30-year fixed"),
("Monthly P&I", "$1,699.38", ""),
("Monthly PITI", "$2,016.05", ""),
("", "", ""),
("Income", "", ""),
("Monthly Rent", "$2,000", "0.58% rent-to-price ratio"),
("Annual Increase", "4%", "Aggressive — 2-3% more realistic"),
("Vacancy", "5%", ""),
("Management", "0%", "Self-managed"),
("", "", ""),
("Expenses", "", ""),
("Property Tax", "$2,400/yr", ""),
("Insurance", "$1,400/yr", ""),
("Maintenance", "$2,000/yr", "LOW — should be 1% of value = $3,450"),
("Appreciation", "3%/yr", ""),
("", "", ""),
("Cost Segregation", "", ""),
("5-Year Property", "15%", "$41,250 of $275K basis"),
("15-Year Property", "12%", "$33,000 of $275K basis"),
("27.5-Year Property", "73%", "$200,750 of $275K basis"),
("Bonus Depreciation (2026)", "40%", "Declining 20%/yr from 100% in 2022"),
("", "", ""),
("Tax Rules", "", ""),
("AGI >$150K", "$0 passive deduction", "Losses carry forward ONLY"),
("AGI $100-150K", "Partial $25K deduction", "Phased out"),
("AGI <$100K", "Full $25K deduction", "Against W-2 income"),
]
for i, (label, val, note) in enumerate(assumptions):
row = 3 + i
ws4.cell(row=row, column=1, value=label)
ws4.cell(row=row, column=2, value=val)
ws4.cell(row=row, column=3, value=note).font = Font(italic=True, color="888888")
if val == "" and note == "" and label:
ws4.cell(row=row, column=1).font = Font(bold=True, color="00BFFF")
auto_width(ws4)
# Save
outpath = "/home/wdjones/.openclaw/workspace/data/real-estate-cost-seg-model.xlsx"
wb.save(outpath)
print(f"Saved to {outpath}")