Build me a comprehensive, polished slide deck for presenting The Griffis at Edgemoor in Bethesda to my investment committee, at a purchase price of $120M. I want more depth than a quick overview, so break it out into a full set of slides with comparable properties, key risks and mitigants, and a final recommendation slide. Make it visual and professional, something I could present directly to my IC
Load the "rental-comps" skill and use its methodology to find 8-10 multifamily rent comps for the subject property "Griffis Edgemoor" (property_mfr id: e05e719f035f1e066beacde104a6cfaf), located at 4900 Moorland Ln, Bethesda, MD 20814 (submarket: Downtown Bethesda, submarket_id cf68f58e30e84a8d364e54d66a014319, market: Washington, DC, market_id 47900). The subject is a 229-unit, mid-rise, market-rate property built in 2021 (average unit size ~915 sqft, in-place rent avg ~$3,401, asking rent avg ~$3,430, occupancy ~93.5%). Return a table of comps with: property name, distance from subject, unit count, year built, star/quality rating if available, asking rent, in-place rent, occupancy. Also state where the subject sits relative to the comp set. Return the raw comp data (not just a written summary) so it can be used to build a table artifact.
RENTAL COMP SET — Griffis Edgemoor, 4900 Moorland Ln, Bethesda, MD 20814 (Downtown Bethesda submarket, Washington DC market)
Methodology: Per the rental-comps skill, queried property_mfr at the Census Place level (Bethesda, place_id 2407125) for MARKET-rate properties with non-null in-place rent and unit count. Retrieval returned 20 eligible candidates — above the 20-candidate threshold, so the search stopped at Census Place (no county-wide expansion needed). All candidates carry no household restrictions (unrestricted, matching subject). Distances computed via haversine from subject lat/lon (38.98551, -77.09826). Selected the 10 closest comps balancing distance, building style (mid-rise preferred to match subject), vintage proximity to subject's 2021 build year, and unit-count tier (subject is "Standard" tier, 75-250 units; allowed ±1 tier for a few very-close large properties). Two very-close candidates (Gallery Bethesda I and II, both ~0.35mi) were excluded as outliers — each showed abnormal ~36-38% occupancy, indicating active renovation/repositioning disruption rather than representative stabilized market performance. Quality/improvements_rating field was null for the subject and every candidate in this dataset, so no star rating is available from the datamart for this comp set.
SUBJECT — Griffis Edgemoor: 229 units, Mid-Rise, built 2021, ~915 sqft avg unit, Asking rent avg $3,430, In-place rent avg $3,401, Occupancy 93.5%.
COMP TABLE (raw data, sorted by distance from subject):
| # | Property Name | Distance (mi) | Units | Year Built | Building Style | Quality Rating | Asking Rent (avg) | In-Place Rent (avg) | Occupancy |
|---|---|---|---|---|---|---|---|---|---|
| 1 | The Edge Apartments | 0.04 | 154 | 2021 | Mid-Rise | N/A | $3,465 | $3,326 | 90.9% |
| 2 | The Brody | 0.12 | 112 | 2018 | Mid-Rise | N/A | $5,045 | $3,475 | 98.2% |
| 3 | Solaire 7607 Old Georgetown | 0.14 | 198 | 2024 | High-Rise | N/A | $3,397 | $3,019 | 76.8% |
| 4 | Element 28 | 0.16 | 101 | 2016 | Mid-Rise | N/A | $3,860 | $3,605 | 94.1% |
| 5 | Windsor Bethesda | 0.20 | 200 | 2016 | High-Rise | N/A | $4,052 | $3,392 | 99.0% |
| 6 | 8001 Woodmont | 0.32 | 322 | 2021 | High-Rise | N/A | $3,832 | $3,753 | 93.8% |
| 7 | The Elm | 0.33 | 456 | 2021 | High-Rise | N/A | $4,359 | $3,516 | 87.9% |
| 8 | Palisades of Bethesda | 0.36 | 311 | 2003 (renov. 2008) | High-Rise | N/A | $2,745 | $2,633 | 96.8% |
| 9 | Flats at Bethesda Avenue | 0.39 | 162 | 2015 | Mid-Rise | N/A | $4,030 | $3,879 | 94.4% |
| 10 | Rosedale Park Apartments | 0.49 | 164 | 2003 | Mid-Rise | N/A | $2,510 | $2,259 | 97.0% |
Comp set aggregates (10 comps):
SUBJECT VS. COMP SET POSITIONING:
Load the "sales-comps" skill and use its methodology to find 4-6 multifamily sale comps for the subject property "Griffis Edgemoor" (property_mfr id: e05e719f035f1e066beacde104a6cfaf), located at 4900 Moorland Ln, Bethesda, MD 20814 (submarket: Downtown Bethesda, submarket_id cf68f58e30e84a8d364e54d66a014319, market: Washington, DC, market_id 47900). The subject is a 229-unit, mid-rise, market-rate property built in 2021 (~209,535 total rentable sqft, average unit size ~915 sqft). We are underwriting a purchase at $120,000,000 ($524,017/unit). Return a table with: property name, sale date, distance, unit count, year built, sale price, price per unit, cap rate if available. State where the $120M / $524K per unit basis for the subject sits relative to the comp set. Return raw comp data for use in a table artifact.
METHODOLOGY / DATA AVAILABILITY NOTE: Following the sales-comps skill, I queried multifamily sales progressively wider from the subject's location (zipcode 20814 → Bethesda/Montgomery County MD → the full Washington, DC CBSA). Neither the zipcode, the Downtown Bethesda submarket, the Bethesda census place, nor all of Montgomery County, MD returned a single market-rate MF sale with a recorded price in the datamart over the trailing several years — this appears to be a genuine data-coverage gap for Montgomery County MD sales in the RealAI datamart, not a lack of actual transaction activity. I therefore widened the search to the entire Washington, DC market (CBSA), filtered to MARKET rent type, RENTAL property type, and unit counts in the subject's size tier (roughly 150–350 units), and ranked ~400 candidate sales by great-circle distance to the subject and by vintage/building-style similarity (MID_RISE/HIGH_RISE, built 2014–2023). The closest comps that clear this data gap are concentrated in DC's Upper NW/Petworth corridor and the Rosslyn-Ballston (Arlington) corridor, roughly 4–7 miles from Griffis Edgemoor — not literally "next door" in Bethesda, but the best vintage- and product-matched market-rate trades available in the dataset.
SUBJECT: Griffis Edgemoor, 4900 Moorland Ln, Bethesda MD 20814 — 229 units, mid-rise, built 2021, market-rate, ~209,535 rentable sqft (~915 sf/unit avg). Proposed basis: $120,000,000 total / $524,017 per unit / ~$572.62 per sqft.
COMP SET (6 comps, sorted by distance):
| Property | Sale Date | Distance | Units | Year Built | Style | Sale Price | $/Unit | $/SF |
|---|---|---|---|---|---|---|---|---|
| Griffis Edgemoor (Subject) | pending | 0.0 mi | 229 | 2021 | Mid-Rise | $120,000,000 | $524,017 | $572.62 |
| The Vale at the Parks (DC, 6800 Georgia Ave NW) | 2021-07-28 | 3.9 mi | 301 | 2021 | Mid-Rise | $126,250,000 | $419,435 | $549.00 |
| Verde Pointe (Arlington, 1947 N Uhle St) | 2021-12-01 | 6.2 mi | 198 | 2014 | High-Rise | $106,000,000 | $535,354 | $670.87 |
| The George (DC, 2601 Virginia Ave NW) | 2024-07-05 | 6.3 mi | 250 | 2020 | High-Rise | $67,500,000 | $270,000 | n/a (sqft not reported) |
| The Earl (Arlington, 1122 N Hudson St) | 2021-07-26 | 6.8 mi | 333 | 2021 | High-Rise | $192,000,000 | $576,577 | $699.05 |
| The Lurgan (DC, 915 L St NW) | 2022-11-01 | 6.8 mi | 214 | 2018 | Mid/High-Rise (12-story) | $104,200,000 | $486,916 | $629.02 |
| Fitzroy (Arlington, 3275 Washington Blvd) | 2024-12-30 | 6.9 mi | 267 | 2023 | High-Rise | $158,300,000 | $592,884 | $801.15 |
All six are market-rate, unrestricted, RENTAL-type mid/high-rise assets built 2014–2023 (5 of 6 built 2018–2023, closely bracketing the subject's 2021 vintage), sized 198–333 units (bracketing the subject's 229 units), sold 2021–2024.
WHERE THE $120M / $524,017-PER-UNIT BASIS SITS:
Per-unit basis: Comp per-unit pricing (excluding The George, an outlier likely reflecting a distressed/note or partial-interest trade given its unusually low $270K/unit) ranges from $419,435 (Vale at the Parks) to $592,884 (Fitzroy), averaging ~$522,000/unit across the 5 non-outlier comps. The subject's proposed $524,017/unit sits almost exactly at the average and squarely mid-pack within the range — neither aggressive nor a bargain relative to comparable-vintage DC-metro Class A trades. Including The George pulls the comp average down to ~$480,000/unit, in which case the subject basis sits slightly above average, but that comp's pricing looks anomalous and is not a reliable anchor.
Per-square-foot basis: Comps with reported sqft price at $549–$801/sf (average ~$670/sf). The subject's $572.62/sf sits below this average and near the low end of the range (just above The Vale at the Parks' $549/sf) — a relatively favorable basis on a per-square-foot view, consistent with the subject's slightly larger average unit size (915 sf vs. the comps' 740–825 sf), which naturally produces a lower $/sf for a similar $/unit.
Net read: On a per-unit basis, $524,017/unit is right in line with (essentially at) the market for comparable-vintage, comparable-size mid/high-rise Class A product trading in the DC metro over the past ~3 years. On a per-square-foot basis, the basis looks somewhat more attractive than the comp average, reflecting the subject's larger average unit size. Caveat: the closest true geographic comps (same submarket/county) were unavailable in the datamart, so this comparison leans on the broader DC metro market rather than Bethesda-specific pricing; Bethesda/Chevy Chase Class A assets have historically commanded a premium to the DC/Arlington urban core on a $/unit basis, which would argue the subject basis is reasonable-to-conservative if that premium holds today.
mkdir -p /vercel/sandbox/proforma && cat > /vercel/sandbox/proforma/underwriting.py << 'EOF'
import json
# Inputs
purchase_price = 120_000_000
units = 229
rsf = 209_535
gpr = 9_350_987.55
vacancy_loss = 575_973.77
net_rent = gpr - vacancy_loss # matches reported net_rent
other_income_pct_of_net_rent = 0.0699 # DC market benchmark
other_income = net_rent * other_income_pct_of_net_rent
egi = net_rent + other_income
opex_pct_of_egi = 0.4523 # DC market benchmark
opex = egi * opex_pct_of_egi
noi = egi - opex
going_in_cap = noi / purchase_price
price_per_unit = purchase_price / units
price_per_sf = purchase_price / rsf
market_cap_rate = 0.0573 # Washington DC MF, GreenStreet, 2Q26
implied_value_at_market_cap = noi / market_cap_rate
cap_rate_spread_bps = (going_in_cap - market_cap_rate) * 10000
# Financing - Fannie Mae 65% LTV / 10yr / avg rate 6.01%, 30yr amortization
ltv = 0.65
loan_amount = purchase_price * ltv
equity = purchase_price - loan_amount
rate = 0.0601
amort_years = 30
n = amort_years * 12
r = rate / 12
monthly_payment = loan_amount * (r * (1+r)**n) / ((1+r)**n - 1)
annual_debt_service = monthly_payment * 12
dscr = noi / annual_debt_service
debt_yield = noi / loan_amount
year1_cash_flow = noi - annual_debt_service
cash_on_cash = year1_cash_flow / equity
# Break-even price to clear market cap rate
breakeven_price_at_market_cap = noi / market_cap_rate
# closing costs
closing_costs_pct = 0.025
closing_costs = purchase_price * closing_costs_pct
total_equity_required = equity + closing_costs
results = {
"gpr": gpr,
"vacancy_loss": vacancy_loss,
"vacancy_pct_of_gpr": vacancy_loss/gpr,
"net_rent": net_rent,
"other_income": other_income,
"egi": egi,
"opex": opex,
"opex_pct_of_egi": opex_pct_of_egi,
"noi": noi,
"going_in_cap_pct": going_in_cap*100,
"price_per_unit": price_per_unit,
"price_per_sf": price_per_sf,
"market_cap_rate_pct": market_cap_rate*100,
"cap_rate_spread_bps": cap_rate_spread_bps,
"implied_value_at_market_cap": implied_value_at_market_cap,
"value_gap_vs_ask": purchase_price - implied_value_at_market_cap,
"value_gap_pct": (purchase_price - implied_value_at_market_cap)/purchase_price*100,
"loan_amount": loan_amount,
"equity": equity,
"closing_costs": closing_costs,
"total_equity_required": total_equity_required,
"interest_rate_pct": rate*100,
"annual_debt_service": annual_debt_service,
"dscr": dscr,
"debt_yield_pct": debt_yield*100,
"year1_cash_flow": year1_cash_flow,
"cash_on_cash_pct": cash_on_cash*100,
}
for k,v in results.items():
print(f"{k}: {v:,.4f}" if isinstance(v,float) else f"{k}: {v}")
with open("/vercel/sandbox/proforma/results.json","w") as f:
json.dump(results, f, indent=2)
EOF
python3 /vercel/sandbox/proforma/underwriting.pycd /vercel/sandbox/proforma && python3 - << 'EOF'
import json
d = json.load(open("results.json"))
noi = d["noi"]
market_cap = d["market_cap_rate_pct"]/100
rate = d["interest_rate_pct"]/100
ltv = 0.65
n=30*12
r=rate/12
# debt service factor per $ loan
monthly_payment_per_dollar = (r*(1+r)**n)/((1+r)**n-1)
annual_ds_factor = monthly_payment_per_dollar*12
target_dscr = 1.25
max_annual_ds = noi/target_dscr
max_loan = max_annual_ds/annual_ds_factor
max_price_dscr = max_loan/ltv
print("max_loan", max_loan)
print("max_price_at_1.25x_dscr", max_price_dscr)
# price at 1.20x floor
max_annual_ds_120 = noi/1.20
max_loan_120 = max_annual_ds_120/annual_ds_factor
max_price_120 = max_loan_120/ltv
print("max_price_at_1.20x_dscr", max_price_120)
EOFpip show python-pptx 2>/dev/null | head -3; python3 -c "import pptx; print(pptx.__version__)"
Now building the deck.
import json
from pptx import Presentation
from pptx.util import Inches, Pt, Emu
from pptx.dml.color import RGBColor
from pptx.enum.text import PP_ALIGN, MSO_ANCHOR
from pptx.enum.shapes import MSO_SHAPE
from pptx.chart.data import CategoryChartData
from pptx.enum.chart import XL_CHART_TYPE, XL_LEGEND_POSITION, XL_LABEL_POSITION
from pptx.oxml.ns import qn
import copy
d = json.load(open("/vercel/sandbox/proforma/results.json"))
# ---------- Brand palette ----------
BG = RGBColor(0xFA, 0xF9, 0xF6) # greige
WHITE = RGBColor(0xFF, 0xFF, 0xFF)
INK = RGBColor(0x15, 0x15, 0x13) # near-black
GRAY = RGBColor(0x6B, 0x6A, 0x66)
BORDER = RGBColor(0xE4, 0xE3, 0xDF)
ORANGE = RGBColor(0xF4, 0x63, 0x3A)
SLATE = RGBColor(0x3C, 0x53, 0x6B)
SLATE_L = RGBColor(0x8FA, 0x00, 0x00) # placeholder unused
SLATE_LIGHT = RGBColor(0xA9, 0xB9, 0xC9)
GOLD = RGBColor(0xC7, 0x9A, 0x3D)
GREEN = RGBColor(0x3F, 0x6B, 0x4A)
RED = RGBColor(0xB3, 0x3A, 0x2E)
SERIF = "Georgia"
SANS = "Calibri"
prs = Presentation()
prs.slide_width = Inches(13.333)
prs.slide_height = Inches(7.5)
BLANK = prs.slide_layouts[6]
SW, SH = prs.slide_width, prs.slide_height
def add_slide():
s = prs.slides.add_slide(BLANK)
bg = s.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, 0, SW, SH)
bg.fill.solid()
bg.fill.fore_color.rgb = BG
bg.line.fill.background()
bg.shadow.inherit = False
s.shapes._spTree.remove(bg._element)
s.shapes._spTree.insert(2, bg._element)
return s
def add_text(slide, left, top, width, height, text, size=14, bold=False, italic=False,
color=INK, font=SANS, align=PP_ALIGN.LEFT, anchor=MSO_ANCHOR.TOP, line_spacing=1.0,
space_after=4):
tb = slide.shapes.add_textbox(left, top, width, height)
tf = tb.text_frame
tf.word_wrap = True
tf.vertical_anchor = anchor
lines = text.split("\n")
for i, line in enumerate(lines):
p = tf.paragraphs[0] if i == 0 else tf.add_paragraph()
p.alignment = align
p.line_spacing = line_spacing
p.space_after = Pt(space_after)
r = p.add_run()
r.text = line
r.font.size = Pt(size)
r.font.bold = bold
r.font.italic = italic
r.font.name = font
r.font.color.rgb = color
return tb
def add_bullets(slide, left, top, width, height, items, size=13, color=INK, font=SANS,
bullet_color=ORANGE, space_after=8, bold_lead=False):
tb = slide.shapes.add_textbox(left, top, width, height)
tf = tb.text_frame
tf.word_wrap = True
for i, item in enumerate(items):
p = tf.paragraphs[0] if i == 0 else tf.add_paragraph()
p.space_after = Pt(space_after)
p.line_spacing = 1.08
if isinstance(item, tuple):
lead, rest = item
r1 = p.add_run()
r1.text = "\u25AA " + lead
r1.font.bold = True
r1.font.size = Pt(size)
r1.font.name = font
r1.font.color.rgb = INK
r2 = p.add_run()
r2.text = " " + rest
r2.font.size = Pt(size)
r2.font.name = font
r2.font.color.rgb = GRAY
else:
r = p.add_run()
r.text = "\u25AA " + item
r.font.size = Pt(size)
r.font.name = font
r.font.color.rgb = color
return tb
def header(slide, kicker, title, page_no=None):
add_text(slide, Inches(0.55), Inches(0.32), Inches(8), Inches(0.3), kicker.upper(),
size=11, bold=True, color=ORANGE, font=SANS)
add_text(slide, Inches(0.55), Inches(0.58), Inches(11.5), Inches(0.7), title,
size=26, bold=True, color=INK, font=SERIF)
ln = slide.shapes.add_connector(1, Inches(0.55), Inches(1.28), Inches(12.78), Inches(1.28))
ln.line.color.rgb = BORDER
ln.line.width = Pt(1)
if page_no:
add_text(slide, Inches(12.4), Inches(7.12), Inches(0.7), Inches(0.3), str(page_no),
size=10, color=GRAY, align=PP_ALIGN.RIGHT)
add_text(slide, Inches(0.55), Inches(7.12), Inches(6), Inches(0.3),
"The Griffis at Edgemoor | Bethesda, MD", size=9, color=GRAY)
def stat_card(slide, left, top, width, height, value, label, value_color=INK, sub=None):
card = slide.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, left, top, width, height)
card.adjustments[0] = 0.06
card.fill.solid()
card.fill.fore_color.rgb = WHITE
card.line.color.rgb = BORDER
card.line.width = Pt(1)
card.shadow.inherit = False
tf = card.text_frame
tf.word_wrap = True
tf.margin_top = Pt(10)
tf.margin_bottom = Pt(6)
tf.margin_left = Pt(10)
tf.margin_right = Pt(10)
tf.vertical_anchor = MSO_ANCHOR.MIDDLE
p = tf.paragraphs[0]
p.alignment = PP_ALIGN.CENTER
r = p.add_run()
r.text = value
r.font.size = Pt(26)
r.font.bold = True
r.font.name = SERIF
r.font.color.rgb = value_color
p2 = tf.add_paragraph()
p2.alignment = PP_ALIGN.CENTER
p2.space_before = Pt(2)
r2 = p2.add_run()
r2.text = label.upper()
r2.font.size = Pt(9.5)
r2.font.bold = True
r2.font.name = SANS
r2.font.color.rgb = GRAY
if sub:
p3 = tf.add_paragraph()
p3.alignment = PP_ALIGN.CENTER
p3.space_before = Pt(2)
r3 = p3.add_run()
r3.text = sub
r3.font.size = Pt(9)
r3.font.name = SANS
r3.font.italic = True
r3.font.color.rgb = GRAY
return card
def make_table(slide, left, top, width, height, headers, rows, col_widths=None,
font_size=10.5, header_bg=SLATE, header_color=WHITE, highlight_row=None):
nrows = len(rows) + 1
ncols = len(headers)
gtable = slide.shapes.add_table(nrows, ncols, left, top, width, height).table
if col_widths:
total = sum(col_widths)
for i, w in enumerate(col_widths):
gtable.columns[i].width = Emu(int(width * (w / total)))
for j, htext in enumerate(headers):
cell = gtable.cell(0, j)
cell.text = htext
cell.fill.solid()
cell.fill.fore_color.rgb = header_bg
cell.margin_top = Pt(4)
cell.margin_bottom = Pt(4)
cell.vertical_anchor = MSO_ANCHOR.MIDDLE
for p in cell.text_frame.paragraphs:
p.alignment = PP_ALIGN.CENTER if j > 0 else PP_ALIGN.LEFT
for r in p.runs:
r.font.size = Pt(font_size)
r.font.bold = True
r.font.color.rgb = header_color
r.font.name = SANS
for i, row in enumerate(rows):
for j, val in enumerate(row):
cell = gtable.cell(i + 1, j)
cell.text = str(val)
cell.margin_top = Pt(3)
cell.margin_bottom = Pt(3)
cell.vertical_anchor = MSO_ANCHOR.MIDDLE
is_hl = (highlight_row is not None and i == highlight_row)
cell.fill.solid()
cell.fill.fore_color.rgb = RGBColor(0xFC, 0xEF, 0xEA) if is_hl else (WHITE if i % 2 == 0 else RGBColor(0xF3, 0xF2, 0xEE))
for p in cell.text_frame.paragraphs:
p.alignment = PP_ALIGN.CENTER if j > 0 else PP_ALIGN.LEFT
for r in p.runs:
r.font.size = Pt(font_size)
r.font.name = SANS
r.font.bold = is_hl
r.font.color.rgb = INK
# remove default table style banding by keeping our own fills; set thin borders via XML
tbl = gtable._tbl
return gtable
def style_chart_fonts(chart, size=10, color=INK):
try:
chart.font.size = Pt(size)
chart.font.color.rgb = color
chart.font.name = SANS
except Exception:
pass
# =========================================================
# SLIDE 1 — TITLE
# =========================================================
s = add_slide()
band = s.shapes.add_shape(MSO_SHAPE.RECTANGLE, 0, 0, SW, Inches(0.12))
band.fill.solid(); band.fill.fore_color.rgb = ORANGE; band.line.fill.background(); band.shadow.inherit = False
add_text(s, Inches(0.9), Inches(2.35), Inches(11), Inches(0.4), "INVESTMENT COMMITTEE PRESENTATION",
size=14, bold=True, color=ORANGE, font=SANS)
add_text(s, Inches(0.9), Inches(2.75), Inches(11.5), Inches(1.6), "The Griffis at Edgemoor",
size=54, bold=True, color=INK, font=SERIF)
add_text(s, Inches(0.9), Inches(3.75), Inches(11.5), Inches(0.6), "4900 Moorland Lane, Bethesda, Maryland 20814",
size=20, color=GRAY, font=SERIF, italic=True)
ln = s.shapes.add_connector(1, Inches(0.9), Inches(4.55), Inches(6.5), Inches(4.55))
ln.line.color.rgb = BORDER; ln.line.width = Pt(1)
add_text(s, Inches(0.9), Inches(4.85), Inches(4), Inches(0.4), "PROPOSED PURCHASE PRICE", size=11, bold=True, color=GRAY)
add_text(s, Inches(0.9), Inches(5.15), Inches(4), Inches(0.7), "$120,000,000", size=32, bold=True, color=INK, font=SERIF)
add_text(s, Inches(5.3), Inches(4.85), Inches(3), Inches(0.4), "229 UNITS | BUILT 2021", size=11, bold=True, color=GRAY)
add_text(s, Inches(5.3), Inches(5.15), Inches(4), Inches(0.7), "$524,017 / unit", size=32, bold=True, color=ORANGE, font=SERIF)
add_text(s, Inches(0.9), Inches(6.75), Inches(8), Inches(0.4),
"Prepared for Investment Committee Review | September 2026", size=11, color=GRAY, italic=True)
# =========================================================
# SLIDE 2 — EXECUTIVE SUMMARY
# =========================================================
s = add_slide()
header(s, "Executive Summary", "The ask outruns the in-place cash flow", 2)
add_text(s, Inches(0.55), Inches(1.5), Inches(12.2), Inches(0.9),
"Griffis Edgemoor is a well-located, newly built (2021) 229-unit mid-rise in the heart of Downtown Bethesda. "
"At $120.0M ($524,017/unit), the price is in line with recent DC-metro Class A trades on a per-unit basis \u2014 "
"but in-place NOI supports roughly $88\u2013$92M at today's market cap rate and lender constraints, a ~25% gap.",
size=14, color=INK, font=SANS, line_spacing=1.15)
cards = [
("$5.14M", "In-place NOI", INK, "T12 GPR less vacancy,\n+ market other income/opex"),
("4.29%", "Going-in cap rate", RED, "vs. 5.73% DC metro\nmultifamily benchmark"),
("0.92x", "Stabilized DSCR", RED, "at 65% LTV / 6.01% \u2014\nbelow 1.25x lender floor"),
("93.5%", "Physical occupancy", GOLD, "down 4.8% over 3 months;\nretention 68.1%"),
]
cw = Inches(2.9); gap = Inches(0.2); left0 = Inches(0.55)
for i, (val, lab, col, sub) in enumerate(cards):
stat_card(s, left0 + i * (cw + gap), Inches(2.65), cw, Inches(1.55), val, lab, col, sub)
add_text(s, Inches(0.55), Inches(4.55), Inches(4), Inches(0.35), "INVESTMENT THESIS", size=12, bold=True, color=ORANGE)
add_bullets(s, Inches(0.55), Inches(4.9), Inches(5.9), Inches(2.1), [
("Trophy location, ", "Bethesda Row-adjacent, Metro-accessible, anchored by NIH/Walter Reed demand base."),
("Newest-vintage product ", "in a submarket where 2021\u20132024 deliveries still command the top of the rent stack."),
("Mark-to-market is thin ", "in-place rent ($3,401) already tracks the comp-set median \u2014 limited embedded upside."),
], size=12.5)
add_text(s, Inches(6.85), Inches(4.55), Inches(4), Inches(0.35), "WHAT BREAKS THE DEAL", size=12, bold=True, color=ORANGE)
add_bullets(s, Inches(6.85), Inches(4.9), Inches(5.9), Inches(2.1), [
("Basis vs. NOI: ", "$120M requires a 4.29% cap in a market pricing multifamily at 5.73%."),
("Financing floor: ", "DSCR of 0.92x at typical agency terms is unfundable without a materially lower price or larger equity check."),
("Submarket supply: ", "13,291 units under construction vs. 1,871 delivered YTD keeps near-term rent growth capped."),
], size=12.5)
# =========================================================
# SLIDE 3 — PROPERTY OVERVIEW
# =========================================================
s = add_slide()
header(s, "The Asset", "Property overview", 3)
left_col = Inches(0.55)
add_text(s, left_col, Inches(1.55), Inches(6), Inches(0.35), "PHYSICAL CHARACTERISTICS", size=12, bold=True, color=ORANGE)
facts = [
("Address", "4900 Moorland Lane, Bethesda, MD 20814"),
("Submarket", "Downtown Bethesda, Washington, DC metro"),
("Year built", "2021"),
("Building style", "Mid-rise"),
("Units", "229"),
("Total rentable area", "209,535 SF"),
("Average unit size", "915 SF"),
("Site area", "1.31 acres"),
("Rent type", "Market rate"),
("Former name", "Maizon"),
]
top = Inches(2.0)
for i, (k, v) in enumerate(facts):
row_top = top + Inches(0.36) * i
add_text(s, left_col, row_top, Inches(2.0), Inches(0.34), k, size=11.5, color=GRAY, bold=True)
add_text(s, left_col + Inches(2.0), row_top, Inches(4.0), Inches(0.34), v, size=11.5, color=INK)
add_text(s, Inches(7.1), Inches(1.55), Inches(5.6), Inches(0.35), "RENT & OCCUPANCY SNAPSHOT", size=12, bold=True, color=ORANGE)
cards2 = [
("$3,430", "Asking rent (avg)", "$3.71/SF"),
("$3,401", "In-place rent (avg)", "$3.64/SF"),
("93.5%", "Occupancy", "-4.8% (t3)"),
("68.1%", "Retention rate", "trailing 12mo"),
]
cw2 = Inches(2.65); g2 = Inches(0.15)
for i, (val, lab, sub) in enumerate(cards2):
r = i // 2; c = i % 2
stat_card(s, Inches(7.1) + c * (cw2 + g2), Inches(2.0) + r * Inches(1.35), cw2, Inches(1.2), val, lab, INK, sub)
add_text(s, Inches(7.1), Inches(4.85), Inches(5.6), Inches(0.35), "UNIT-TYPE RENTS (IN-PLACE)", size=12, bold=True, color=ORANGE)
uchart_data = CategoryChartData()
uchart_data.categories = ["Studio", "1 Bed", "2 Bed", "3 Bed"]
uchart_data.add_series("In-place rent", (2146, 2536, 4141, 8477))
gframe = s.shapes.add_chart(XL_CHART_TYPE.COLUMN_CLUSTERED, Inches(7.1), Inches(5.2), Inches(5.6), Inches(1.75), uchart_data)
chart = gframe.chart
chart.has_legend = False
chart.plots[0].series[0].format.fill.solid()
chart.plots[0].series[0].format.fill.fore_color.rgb = SLATE
plot = chart.plots[0]
plot.has_data_labels = True
plot.data_labels.number_format = '"$"#,##0'
plot.data_labels.number_format_is_linked = False
plot.data_labels.font.size = Pt(9)
style_chart_fonts(chart, 9)
chart.category_axis.tick_labels.font.size = Pt(9)
chart.value_axis.visible = False
chart.category_axis.format.line.color.rgb = BORDER
add_text(s, left_col, Inches(5.75), Inches(6.2), Inches(1.2),
"229 total units across studio through 3-bedroom floor plans; average 915 SF. Property tracked with excellent "
"sample confidence (126-property sample, 55% coverage of the submarket).", size=10.5, color=GRAY, italic=True)
# =========================================================
# SLIDE 4 — LOCATION & SUBMARKET
# =========================================================
s = add_slide()
header(s, "Where it sits", "Downtown Bethesda submarket", 4)
add_text(s, Inches(0.55), Inches(1.5), Inches(12.2), Inches(0.75),
"Downtown Bethesda is one of the DC metro's highest-income, most educated submarkets \u2014 anchored by NIH, "
"Walter Reed National Military Medical Center, and Bethesda Row's retail core, with direct Red Line Metro access.",
size=13.5, color=INK, line_spacing=1.15)
cards3 = [
("$131,353", "Median household income", "77th pctile MSA growth"),
("99.0%", "Bachelor's degree+", "far above national avg"),
("69,397", "Submarket population", "+3.0% population growth"),
("86.5%", "Submarket occupancy", "-9.8% YoY \u2014 lease-up drag"),
]
cw3 = Inches(2.9)
for i, (val, lab, sub) in enumerate(cards3):
stat_card(s, left0 + i * (cw3 + gap), Inches(2.5), cw3, Inches(1.4), val, lab, INK, sub)
add_text(s, Inches(0.55), Inches(4.2), Inches(6), Inches(0.35), "WHY THIS LOCATION SUPPORTS DEMAND", size=12, bold=True, color=ORANGE)
add_bullets(s, Inches(0.55), Inches(4.6), Inches(5.9), Inches(2.3), [
"Renter household income of $131K median comfortably supports subject in-place rent (~31% rent-to-income at $3,401/mo).",
"Education and income profiles sit at or near the top of the DC metro \u2014 education score ranks in the 92nd MSA percentile.",
"Walkable to Bethesda Row and the Bethesda Metro station (Red Line), a structural demand anchor independent of new supply.",
], size=12)
add_text(s, Inches(6.85), Inches(4.2), Inches(6), Inches(0.35), "WHAT TO WATCH", size=12, bold=True, color=ORANGE)
add_bullets(s, Inches(6.85), Inches(4.6), Inches(5.9), Inches(2.3), [
"Submarket occupancy has fallen to 86.5%, down 9.8% year-over-year \u2014 a supply-absorption problem, not a demand problem.",
"Several 2021\u20132024-vintage peers (Solaire 7607 at 77% occupied, The Elm at 88%) are still working through lease-up.",
"Subject's own occupancy softness (93.5%, down from prior periods) tracks this same vintage cohort, not idiosyncratic underperformance.",
], size=12)
# =========================================================
# SLIDE 5 — MARKET FUNDAMENTALS
# =========================================================
s = add_slide()
header(s, "Capital markets & supply", "DC metro multifamily fundamentals", 5)
cards4 = [
("5.73%", "MF cap rate (2Q26)", SLATE, "GreenStreet, DC metro"),
("7.9%", "MF vacancy rate", RED, "Cushman & Wakefield"),
("13,291", "Units under construction", RED, "vs. 1,871 delivered YTD"),
("+57%", "Permit acceleration", GOLD, "12,083 units (t12) vs.\n7,692 units (t13-24)"),
]
for i, (val, lab, col, sub) in enumerate(cards4):
stat_card(s, left0 + i * (cw3 + gap), Inches(1.6), cw3, Inches(1.5), val, lab, col, sub)
add_text(s, Inches(0.55), Inches(3.35), Inches(6), Inches(0.35), "SUPPLY PIPELINE VS. ABSORPTION (UNITS)", size=12, bold=True, color=ORANGE)
sc_data = CategoryChartData()
sc_data.categories = ["Delivered YTD", "Net absorption YTD", "Under construction"]
sc_data.add_series("Units", (1871, 3142, 13291))
gframe2 = s.shapes.add_chart(XL_CHART_TYPE.BAR_CLUSTERED, Inches(0.55), Inches(3.75), Inches(5.9), Inches(2.9), sc_data)
chart2 = gframe2.chart
chart2.has_legend = False
ser = chart2.plots[0].series[0]
ser.format.fill.solid(); ser.format.fill.fore_color.rgb = SLATE
plot2 = chart2.plots[0]
plot2.has_data_labels = True
plot2.data_labels.number_format = '#,##0'
plot2.data_labels.number_format_is_linked = False
plot2.data_labels.font.size = Pt(10)
style_chart_fonts(chart2, 10)
chart2.value_axis.visible = False
add_text(s, Inches(6.85), Inches(3.35), Inches(6), Inches(0.35), "WHAT THIS MEANS FOR THE DEAL", size=12, bold=True, color=ORANGE)
add_bullets(s, Inches(6.85), Inches(3.75), Inches(5.9), Inches(2.8), [
("Absorption is outrunning deliveries YTD ", "(3,142 net absorbed vs. 1,871 delivered) \u2014 the metro overall is tightening."),
("But the pipeline is still large and growing: ", "13,291 units under construction, with permitting up 57% versus the prior 12-month period, signaling more supply 12\u201324 months out."),
("Net read: ", "metro-level tightening is a tailwind, but the immediate Downtown Bethesda submarket is absorbing its own recent delivery wave \u2014 the subject's occupancy and rent softness should be read against that backdrop, not as an idiosyncratic flag."),
], size=12)
# =========================================================
# SLIDE 6 — RENTAL COMPS
# =========================================================
s = add_slide()
header(s, "What the comps say", "Rental comparables \u2014 Downtown Bethesda", 6)
rc_headers = ["Property", "Dist.", "Units", "Yr Built", "Asking Rent", "In-Place Rent", "Occupancy"]
rc_rows = [
["Griffis Edgemoor (Subject)", "\u2014", "229", "2021", "$3,430", "$3,401", "93.5%"],
["The Edge Apartments", "0.04 mi", "154", "2021", "$3,465", "$3,326", "90.9%"],
["The Brody", "0.12 mi", "112", "2018", "$5,045", "$3,475", "98.2%"],
["Solaire 7607 Old Georgetown", "0.14 mi", "198", "2024", "$3,397", "$3,019", "76.8%"],
["Element 28", "0.16 mi", "101", "2016", "$3,860", "$3,605", "94.1%"],
["Windsor Bethesda", "0.20 mi", "200", "2016", "$4,052", "$3,392", "99.0%"],
["8001 Woodmont", "0.32 mi", "322", "2021", "$3,832", "$3,753", "93.8%"],
["The Elm", "0.33 mi", "456", "2021", "$4,359", "$3,516", "87.9%"],
["Palisades of Bethesda", "0.36 mi", "311", "2003/08", "$2,745", "$2,633", "96.8%"],
["Flats at Bethesda Avenue", "0.39 mi", "162", "2015", "$4,030", "$3,879", "94.4%"],
["Rosedale Park Apartments", "0.49 mi", "164", "2003", "$2,510", "$2,259", "97.0%"],
]
make_table(s, Inches(0.4), Inches(1.5), Inches(12.5), Inches(3.5), rc_headers, rc_rows,
col_widths=[3.0, 0.9, 0.8, 0.9, 1.3, 1.3, 1.1], font_size=10.5, highlight_row=0)
add_text(s, Inches(0.55), Inches(5.2), Inches(12.2), Inches(1.6),
"Comp-set average asking rent is $3,730 (median $3,846); subject asks $3,430 \u2014 the bottom third of the set. "
"In-place rent tells a different story: subject's $3,401 sits almost exactly at the comp median ($3,433), ahead of "
"value-oriented older stock and just behind top-of-market newer product. Occupancy (93.5%) trails the comp median "
"(94.3%) but tracks its true peer cohort \u2014 other 2021-vintage buildings (The Edge 90.9%, 8001 Woodmont 93.8%, The "
"Elm 87.9%) show the same lease-up drag, not subject-specific underperformance.",
size=12, color=GRAY, line_spacing=1.2)
# =========================================================
# SLIDE 7 — SALES COMPS
# =========================================================
s = add_slide()
header(s, "What the comps say", "Sale comparables \u2014 DC metro Class A", 7)
add_text(s, Inches(0.55), Inches(1.42), Inches(12.2), Inches(0.5),
"Note: no market-rate multifamily sale comps with recorded pricing exist in Bethesda/Montgomery County within the "
"dataset \u2014 a genuine data-coverage gap. Comp set widened to comparable-vintage (2014\u20132023), comparable-size "
"(198\u2013333 units) DC/Arlington trades.", size=10.5, italic=True, color=GRAY)
sc_headers = ["Property", "Sale Date", "Dist.", "Units", "Yr Built", "Sale Price", "$/Unit", "$/SF"]
sc_rows = [
["Griffis Edgemoor (proposed)", "pending", "\u2014", "229", "2021", "$120.0M", "$524,017", "$572.70"],
["The Vale at the Parks (DC)", "Jul 2021", "3.9 mi", "301", "2021", "$126.3M", "$419,435", "$549.00"],
["Verde Pointe (Arlington)", "Dec 2021", "6.2 mi", "198", "2014", "$106.0M", "$535,354", "$670.87"],
["The George (DC)", "Jul 2024", "6.3 mi", "250", "2020", "$67.5M", "$270,000*", "n/a"],
["The Earl (Arlington)", "Jul 2021", "6.8 mi", "333", "2021", "$192.0M", "$576,577", "$699.05"],
["The Lurgan (DC)", "Nov 2022", "6.8 mi", "214", "2018", "$104.2M", "$486,916", "$629.02"],
["Fitzroy (Arlington)", "Dec 2024", "6.9 mi", "267", "2023", "$158.3M", "$592,884", "$801.15"],
]
make_table(s, Inches(0.4), Inches(2.05), Inches(12.5), Inches(2.9), sc_headers, sc_rows,
col_widths=[2.6, 1.0, 0.8, 0.7, 0.8, 1.1, 1.1, 0.9], font_size=10.2, highlight_row=0)
add_text(s, Inches(0.55), Inches(5.15), Inches(12.2), Inches(1.7),
"On a $/unit basis, the proposed $524,017/unit sits almost exactly at the ~$522K average of comparable-vintage "
"trades (excluding The George, an outlier likely reflecting a distressed or partial-interest sale) \u2014 neither "
"aggressive nor a bargain. On a $/SF basis, $572.70 sits below the comp average of ~$670/SF, reflecting the "
"subject's larger average unit size (915 SF vs. 740\u2013825 SF for comps). Basis is defensible against sale comps "
"\u2014 the problem sits with the income approach, not the market approach (see next slide).",
size=12, color=GRAY, line_spacing=1.2)
# =========================================================
# SLIDE 8 — PRO FORMA / FINANCIALS
# =========================================================
s = add_slide()
header(s, "How it underwrites", "Stabilized pro forma & financing", 8)
pf_headers = ["Line item", "Annual ($)", "Per unit"]
def money(x):
return f"${x:,.0f}"
rows = [
["Gross potential rent", money(d["gpr"]), money(d["gpr"]/229)],
["Vacancy loss (6.2%)", f"(${d['vacancy_loss']:,.0f})", f"(${d['vacancy_loss']/229:,.0f})"],
["Net rental income", money(d["net_rent"]), money(d["net_rent"]/229)],
["Other income (mkt. benchmark)", money(d["other_income"]), money(d["other_income"]/229)],
["Effective gross income", money(d["egi"]), money(d["egi"]/229)],
["Operating expenses (45.2% of EGI)", f"(${d['opex']:,.0f})", f"(${d['opex']/229:,.0f})"],
["Net operating income", money(d["noi"]), money(d["noi"]/229)],
]
make_table(s, Inches(0.4), Inches(1.5), Inches(6.1), Inches(3.1), pf_headers, rows,
col_widths=[2.2, 1.1, 0.9], font_size=10.5, highlight_row=6)
add_text(s, Inches(0.55), Inches(4.75), Inches(5.9), Inches(1.6),
"Other income and OpEx are estimated from Washington, DC metro multifamily P&L benchmarks (RealAI Ops "
"Benchmarks) \u2014 the subject's own line-item T12 was not available. Confirm against seller-provided financials "
"before finalizing IC terms.", size=10, italic=True, color=GRAY, line_spacing=1.15)
fin_headers = ["Financing & returns (Fannie Mae, 65% LTV, 10-yr, 6.01%)", "Value"]
fin_rows = [
["Purchase price", "$120,000,000"],
["Loan amount (65% LTV)", f"${d['loan_amount']:,.0f}"],
["Equity + closing costs", f"${d['total_equity_required']:,.0f}"],
["Going-in cap rate", f"{d['going_in_cap_pct']:.2f}%"],
["Annual debt service", f"${d['annual_debt_service']:,.0f}"],
["Year-1 DSCR", f"{d['dscr']:.2f}x"],
["Debt yield", f"{d['debt_yield_pct']:.2f}%"],
["Year-1 cash flow / cash-on-cash", f"(${abs(d['year1_cash_flow']):,.0f}) / {d['cash_on_cash_pct']:.1f}%"],
]
make_table(s, Inches(6.85), Inches(1.5), Inches(5.9), Inches(3.5), fin_headers, fin_rows,
col_widths=[3.2, 1.6], font_size=10.5, highlight_row=4)
add_text(s, Inches(6.85), Inches(5.15), Inches(5.9), Inches(1.6),
"At the proposed $120M basis, Year-1 DSCR of 0.92x falls well short of the 1.25x floor typical of Fannie Mae, "
"Freddie Mac, and life-company multifamily financing \u2014 the loan as structured is not fundable, and the deal "
"produces negative leveraged cash flow from day one.", size=10.5, italic=True, color=RED, line_spacing=1.15)
# =========================================================
# SLIDE 9 — VALUATION GAP
# =========================================================
s = add_slide()
header(s, "How it underwrites", "Basis vs. supportable value", 9)
vg_data = CategoryChartData()
vg_data.categories = ["Proposed price\n($120.0M)", "Value at market\ncap rate (5.73%)", "Max price at\n1.25x DSCR", "Max price at\n1.20x DSCR"]
vg_data.add_series("Value ($M)", (120.0, 89.7, 87.9, 91.5))
gframe3 = s.shapes.add_chart(XL_CHART_TYPE.COLUMN_CLUSTERED, Inches(0.55), Inches(1.55), Inches(7.1), Inches(4.6), vg_data)
chart3 = gframe3.chart
chart3.has_legend = False
plot3 = chart3.plots[0]
ser3 = plot3.series[0]
ser3.format.fill.solid()
ser3.format.fill.fore_color.rgb = ORANGE
# recolor points individually
pts = ser3.points
colors_list = [ORANGE, SLATE, SLATE, SLATE]
for pt, c in zip(pts, colors_list):
pt.format.fill.solid()
pt.format.fill.fore_color.rgb = c
plot3.has_data_labels = True
plot3.data_labels.number_format = '"$"#,##0.0"M"'
plot3.data_labels.number_format_is_linked = False
plot3.data_labels.font.size = Pt(11)
plot3.data_labels.font.bold = True
style_chart_fonts(chart3, 10)
chart3.category_axis.tick_labels.font.size = Pt(9.5)
chart3.value_axis.visible = False
add_text(s, Inches(7.95), Inches(1.6), Inches(4.8), Inches(0.35), "THE GAP, IN ONE NUMBER", size=12, bold=True, color=ORANGE)
add_text(s, Inches(7.95), Inches(2.0), Inches(4.8), Inches(1.3), "$30.3M", size=42, bold=True, color=RED, font=SERIF)
add_text(s, Inches(7.95), Inches(2.85), Inches(4.8), Inches(0.9),
"Gap between the $120.0M ask and the $89.7M implied by today's 5.73% DC metro multifamily cap rate \u2014 a 25% premium to market.",
size=12, color=GRAY, line_spacing=1.15)
add_bullets(s, Inches(7.95), Inches(3.95), Inches(4.8), Inches(2.6), [
("Three independent tests ", "(market cap rate, 1.25x DSCR floor, 1.20x DSCR floor) converge on a $88\u2013$92M supportable range."),
("Sale comps ", "support the $524K/unit basis \u2014 the gap is an income-approach problem, not a market-pricing problem."),
("Closing this gap ", "requires either a lower entry price or NOI growth assumptions well beyond what current rent and occupancy trends support."),
], size=12)
# =========================================================
# SLIDE 10 — RISKS & MITIGANTS
# =========================================================
s = add_slide()
header(s, "Key risks", "Risks & mitigants", 10)
risk_headers = ["Risk", "Why it matters", "Mitigant"]
risk_rows = [
["Basis vs. NOI", "$120M requires a 4.29% cap vs. 5.73% market \u2014 a $30M+ gap", "Renegotiate toward the $88\u2013$92M supportable range or restructure with seller financing"],
["Unfundable at 65% LTV", "0.92x DSCR fails the 1.25x agency floor; loan proceeds capped near $57M", "Lower leverage / larger equity check, or price reduction to restore proceeds"],
["Submarket oversupply", "13,291 units under construction; Downtown Bethesda occupancy down 9.8% YoY", "NIH/Walter Reed anchor demand; metro-wide absorption (3,142 units) is outrunning deliveries"],
["Softening occupancy/retention", "Subject occupancy down 4.8% (3mo); retention only 68.1%", "Softness tracks the 2021-vintage peer cohort, not idiosyncratic \u2014 monitor lease-up completion"],
["Unverified financials", "Other income/OpEx modeled from market benchmarks, not seller T12", "Require full trailing-12 financials and rent roll before firming terms"],
["Negative rent momentum", "Subject asking rent down 4.0% t12; days-on-market at 88 (elevated)", "Track leasing velocity through Q4; re-underwrite if trend does not stabilize"],
]
make_table(s, Inches(0.4), Inches(1.5), Inches(12.5), Inches(5.3), risk_headers, risk_rows,
col_widths=[2.1, 3.9, 4.3], font_size=11)
# =========================================================
# SLIDE 11 — RECOMMENDATION
# =========================================================
s = add_slide()
header(s, "Recommendation", "Conditional \u2014 not at the current basis", 11)
verdict_box = s.shapes.add_shape(MSO_SHAPE.ROUNDED_RECTANGLE, Inches(0.55), Inches(1.5), Inches(4.0), Inches(1.0))
verdict_box.adjustments[0] = 0.15
verdict_box.fill.solid(); verdict_box.fill.fore_color.rgb = GOLD
verdict_box.line.fill.background()
verdict_box.shadow.inherit = False
tf = verdict_box.text_frame
tf.vertical_anchor = MSO_ANCHOR.MIDDLE
p = tf.paragraphs[0]; p.alignment = PP_ALIGN.CENTER
r = p.add_run(); r.text = "CONDITIONAL"
r.font.size = Pt(28); r.font.bold = True; r.font.color.rgb = WHITE; r.font.name = SERIF
add_text(s, Inches(4.8), Inches(1.5), Inches(7.9), Inches(1.0),
"Griffis Edgemoor is a strong, newly built asset in a top-tier location \u2014 but $120.0M ($524,017/unit) is "
"priced roughly 25% above what in-place NOI supports at today's market cap rate and financing terms.",
size=13.5, color=INK, line_spacing=1.2, anchor=MSO_ANCHOR.MIDDLE)
add_text(s, Inches(0.55), Inches(2.75), Inches(6), Inches(0.35), "CONDITIONS TO MOVE TO GO", size=12, bold=True, color=ORANGE)
add_bullets(s, Inches(0.55), Inches(3.15), Inches(6.0), Inches(3.3), [
("Re-trade to $90\u2013$95M ", "(~$393K\u2013$415K/unit) \u2014 aligns with the market-cap-implied value and restores DSCR above 1.20x."),
("Obtain full trailing-12 financials and rent roll ", "to verify other income and OpEx assumed here from market benchmarks."),
("Confirm lease-up trajectory ", "\u2014 occupancy should be tracking toward 95%+ before underwriting further rent growth."),
("Re-underwrite if submarket absorption stalls ", "\u2014 revisit before signing if Downtown Bethesda occupancy has not stabilized."),
], size=12.5)
add_text(s, Inches(6.95), Inches(2.75), Inches(5.9), Inches(0.35), "WHAT MOST AFFECTS CONVICTION", size=12, bold=True, color=ORANGE)
add_bullets(s, Inches(6.95), Inches(3.15), Inches(5.9), Inches(3.3), [
"The location and asset quality are not in question \u2014 this is a basis conversation, not a fundamentals conversation.",
"Sale comps validate the $/unit pricing; the gap is entirely in the income approach, driven by in-place NOI and the current rate environment.",
"If the seller will not move on price, this is a pass at $120M \u2014 the numbers do not support committing capital at a basis the underlying cash flow cannot service.",
], size=12.5)
prs.save("/vercel/sandbox/proforma/Griffis_Edgemoor_IC_Deck.pptx")
print("saved")
cd /vercel/sandbox/proforma && python3 build_deck.py
SLATE_L = RGBColor(0x8FA, 0x00, 0x00) # placeholder unused SLATE_LIGHT = RGBColor(0xA9, 0xB9, 0xC9)
SLATE_LIGHT = RGBColor(0xA9, 0xB9, 0xC9)
cd /vercel/sandbox/proforma && python3 build_deck.py
Deck built. Let's convert to PDF to visually QA a couple of slides before delivering.
cd /vercel/sandbox/proforma && libreoffice --headless --convert-to pdf Griffis_Edgemoor_IC_Deck.pptx --outdir . 2>&1 | tail -5 && pdftoppm -png -r 80 Griffis_Edgemoor_IC_Deck.pdf slide && ls slide*
cd /vercel/sandbox/proforma && montage slide-01.png slide-02.png slide-05.png slide-09.png slide-11.png -tile 3x2 -geometry 500x+5+5 overview.png 2>&1 | tail -5; ls -la overview.png
cd /vercel/sandbox/proforma && pdftotext -layout Griffis_Edgemoor_IC_Deck.pdf - | head -100
This looks clean. Let's check the remaining slides quickly, then save and present the deck.
cd /vercel/sandbox/proforma && pdftotext -layout Griffis_Edgemoor_IC_Deck.pdf - | sed -n '100,230p'
Content looks solid across all slides. Now saving and presenting the deck.
Conditional — not at the $120M basis
An 11-slide IC deck is attached: title, executive summary, property overview, submarket profile, market fundamentals, rental comps, sales comps, pro forma & financing, valuation gap, risks & mitigants, and the recommendation.
The headline the deck delivers: the asset itself is not the problem. Sale comps validate $524,017/unit almost exactly against comparable-vintage DC-metro trades . The income approach is where this breaks — in-place NOI of roughly $5.14M supports only a 4.29% going-in cap at $120M, against a 5.73% DC-metro multifamily benchmark , implying a supportable value near $89.7M. Financing confirms it independently: at 65% LTV / 6.01% (Fannie Mae, 10-yr) , Year-1 DSCR comes in at 0.92x — well under the 1.25x floor lenders will actually underwrite to, and the max price that clears that floor is $87.9M . Three independent tests converge on an $88–92M supportable range, a ~25% gap to the ask.
Layered on top: Downtown Bethesda occupancy has fallen to 86.5% (-9.8% YoY) as the 2021–2024 delivery cohort — which includes the subject — works through lease-up, and 13,291 units remain under construction against only 1,871 delivered YTD . The subject's own occupancy softness (93.5%, down 4.8% over three months) and thin 68.1% retention track that peer cohort rather than signaling an asset-specific problem, but they mean there's no near-term rent-growth story available to close the valuation gap organically.
The recommendation: re-trade toward $90–95M, get the full trailing-12 to replace the market-benchmark other income/OpEx assumptions used here, and confirm lease-up is tracking toward 95%+ before underwriting further upside. If the seller won't move off $120M, this is a pass — a great building at an unfinanceable basis is still an unfinanceable basis.
This is a read-only snapshot of a RealAI analysis.