I want to run an acquisition screening on Preserve at Ridgeville using the attached offering memorandum, comparing the broker's stated rents and financials against class-A comps in the surrounding submarket and accepting broker assumptions only where directly corroborated by comp data. My renovation/positioning stance is stabilized acquisition, no value-add program, and I want sensitivity testing on rent growth and supply-shock scenarios plus a goal-seek on offering price at 65% LTV and current market financing terms to solve for a 12% IRR. Flag any OM line items — NOI, expense ratios, rent assumptions, or renovation costs — that appear out of line with comp-supported data, and produce a Go/No-Go/Conditional recommendation with the supporting pro forma.
cd /vercel/sandbox && python3 -c "
import pdfplumber
with pdfplumber.open('OM.pdf') as pdf:
print('NUM PAGES', len(pdf.pages))
for i, page in enumerate(pdf.pages, start=1):
text = page.extract_text() or ''
print(f'=== PAGE {i} ===')
print(text)
" > om_text.txt
wc -l om_text.txt
cd /vercel/sandbox && cat skills/forecasting/references/request.md | head -150
cd /vercel/sandbox && python3 skills/forecasting/scripts/forecast.py --version
{
"metric": {
"name": "in-place rent, Preserve at Ridgeville",
"units": "$",
"family": "rent_or_occupancy"
},
"subject": {
"entity_type": "property",
"entity_id": "5a69c440a0085cd6b55f5a8d5c3a703f",
"label": "Preserve at Ridgeville"
},
"horizon": {
"years": 5,
"intervals": "annual"
},
"as_of": "2026-09-16",
"requested_lookback_years": 2,
"caller_disclosures": {
"peer_selection_basis": "Charleston, SC MSA (parent market) mf_rent_ts in_place_rent_latest_avg; level ratio subject/market = 0.83 (1547.75/1861.57 as of Aug-2026); subject trailing-12mo in-place rent change -4.71% vs market +1.66% over same window - subject is decelerating against a firming parent market, disclosed as a divergence not a data error.",
"sibling_series_note": "Asking-rent sibling checked at property and market grain; subject asking rent (Aug-26 avg $1653) trades above subject in-place rent ($1548), consistent with post-concession-burn lease trade-outs; not run as a separate series since it shares the same underlying leasing data.",
"lookback_note": "Full 24-month history available (Sep-2024 through Aug-2026) at MONTH period_type; used in full.",
"signals_omitted": [
{"signal": "migration", "reason": "not gathered"},
{"signal": "employment.job_growth_1_year_pct", "reason": "not gathered"},
{"signal": "rate_environment", "reason": "not applicable to rent_or_occupancy family"},
{"signal": "structural_ceiling", "reason": "not gathered - USDA 538 AMI rent cap is a property-specific regulatory ceiling, not a rent-to-income structural ceiling; not representable in this signal"}
]
},
"history": [
{"period": "2024-09-01", "value": 1525.488889, "source": "datamart"},
{"period": "2024-10-01", "value": 1647.177083, "source": "datamart"},
{"period": "2024-11-01", "value": 1645.782828, "source": "datamart"},
{"period": "2024-12-01", "value": 1642.551724, "source": "datamart"},
{"period": "2025-01-01", "value": 1636.502392, "source": "datamart"},
{"period": "2025-02-01", "value": 1633.595238, "source": "datamart"},
{"period": "2025-03-01", "value": 1627.946078, "source": "datamart"},
{"period": "2025-04-01", "value": 1632.389744, "source": "datamart"},
{"period": "2025-05-01", "value": 1603.415842, "source": "datamart"},
{"period": "2025-06-01", "value": 1583.169082, "source": "datamart"},
{"period": "2025-07-01", "value": 1568.580645, "source": "datamart"},
{"period": "2025-08-01", "value": 1560.024876, "source": "datamart"},
{"period": "2025-09-01", "value": 1557.430052, "source": "datamart"},
{"period": "2025-10-01", "value": 1551.432432, "source": "datamart"},
{"period": "2025-11-01", "value": 1549.672222, "source": "datamart"},
{"period": "2025-12-01", "value": 1550.759358, "source": "datamart"},
{"period": "2026-01-01", "value": 1549.446237, "source": "datamart"},
{"period": "2026-02-01", "value": 1550.095745, "source": "datamart"},
{"period": "2026-03-01", "value": 1548.491979, "source": "datamart"},
{"period": "2026-04-01", "value": 1547.277174, "source": "datamart"},
{"period": "2026-05-01", "value": 1555.156682, "source": "datamart"},
{"period": "2026-06-01", "value": 1556.844340, "source": "datamart"},
{"period": "2026-07-01", "value": 1556.750000, "source": "datamart"},
{"period": "2026-08-01", "value": 1547.747664, "source": "datamart"}
],
"history_check": {"count": 24, "first_value": 1525.488889, "last_value": 1547.747664},
"peer_history": [
{"period": "2024-09-01", "value": 1804.604892, "source": "datamart"},
{"period": "2024-10-01", "value": 1809.799282, "source": "datamart"},
{"period": "2024-11-01", "value": 1812.174131, "source": "datamart"},
{"period": "2024-12-01", "value": 1812.178320, "source": "datamart"},
{"period": "2025-01-01", "value": 1810.164676, "source": "datamart"},
{"period": "2025-02-01", "value": 1821.066895, "source": "datamart"},
{"period": "2025-03-01", "value": 1824.462105, "source": "datamart"},
{"period": "2025-04-01", "value": 1828.487736, "source": "datamart"},
{"period": "2025-05-01", "value": 1837.431265, "source": "datamart"},
{"period": "2025-06-01", "value": 1839.630658, "source": "datamart"},
{"period": "2025-07-01", "value": 1837.266375, "source": "datamart"},
{"period": "2025-08-01", "value": 1834.983333, "source": "datamart"},
{"period": "2025-09-01", "value": 1834.953105, "source": "datamart"},
{"period": "2025-10-01", "value": 1833.291299, "source": "datamart"},
{"period": "2025-11-01", "value": 1830.317662, "source": "datamart"},
{"period": "2025-12-01", "value": 1830.842864, "source": "datamart"},
{"period": "2026-01-01", "value": 1839.895733, "source": "datamart"},
{"period": "2026-02-01", "value": 1839.892982, "source": "datamart"},
{"period": "2026-03-01", "value": 1848.595285, "source": "datamart"},
{"period": "2026-04-01", "value": 1850.189238, "source": "datamart"},
{"period": "2026-05-01", "value": 1854.086069, "source": "datamart"},
{"period": "2026-06-01", "value": 1861.011883, "source": "datamart"},
{"period": "2026-07-01", "value": 1863.590811, "source": "datamart"},
{"period": "2026-08-01", "value": 1861.569304, "source": "datamart"}
],
"peer_history_check": {"count": 24, "last_value": 1861.569304},
"context_signals": {
"supply_pipeline": {
"existing_stock": 67673,
"under_construction_t12": 3327,
"permitted_units_t13_t24": 1000
}
},
"scenarios": ["base", "upside", "downside"]
}
cd /vercel/sandbox && python3 skills/forecasting/scripts/forecast.py --file rent_growth_payload.json > rent_growth_result.json 2>&1; cat rent_growth_result.json
cd /vercel/sandbox && cp "agents/019eb33d-d26e-739e-91bf-0c2b1fe4a447/RealAI_Pro_Forma_Template_v4.xlsx" PreserveAtRidgeville_underwriting_2026-09-16.xlsx python skills/xlsx/scripts/workbook_profile.py PreserveAtRidgeville_underwriting_2026-09-16.xlsx
cd /vercel/sandbox && python3 skills/xlsx/scripts/workbook_extract.py PreserveAtRidgeville_underwriting_2026-09-16.xlsx --sheet Assumptions --columns B,C,D,H,I,J --rows 1-85 2>/dev/null | python3 -c "
import json,sys
d=json.load(sys.stdin)
for r in d['rows']:
print(r)
" 2>/dev/null || python3 skills/xlsx/scripts/workbook_extract.py PreserveAtRidgeville_underwriting_2026-09-16.xlsx --sheet Assumptions --columns B,C,D,H,I,J --rows 1-85cd /vercel/sandbox && python3 skills/xlsx/scripts/workbook_extract.py PreserveAtRidgeville_underwriting_2026-09-16.xlsx --sheet Assumptions --columns B,C,D,H,I,J --start-row 1 --end-row 85 --mode both --output assump_dump.json cat assump_dump.json | python3 -m json.tool 2>/dev/null | head -300 || cat assump_dump.json
cd /vercel/sandbox && python3 -c "
import csv
with open('assump_dump.values.json') as f:
import json
print(f.read()[:200])
"
head -c 2000 assump_dump.values.json
echo ---
python3 -c "
import json
d=json.load(open('assump_dump.values.json'))
print(type(d))
print(list(d.keys())[:5] if isinstance(d,dict) else d[:5])
"cd /vercel/sandbox && python3 skills/xlsx/scripts/workbook_extract.py PreserveAtRidgeville_underwriting_2026-09-16.xlsx --sheet Sensitivity --columns A,B,C,D,E,F,G,H,I,J,K --start-row 1 --end-row 79 --mode both --output sens_dump.json cat sens_dump.values.json
import openpyxl, math
from datetime import date
FN = "PreserveAtRidgeville_underwriting_2026-09-16.xlsx"
wb = openpyxl.load_workbook(FN)
a = wb["Assumptions"]
rc = wb["Rent Comps"]
sc = wb["Sales Comps"]
def hav(lat1, lon1, lat2, lon2):
R = 3958.8
p1, p2 = math.radians(lat1), math.radians(lat2)
dphi = math.radians(lat2-lat1)
dl = math.radians(lon2-lon1)
hav = math.sin(dphi/2)**2 + math.cos(p1)*math.cos(p2)*math.sin(dl/2)**2
return 2*R*math.asin(math.sqrt(hav))
SUBJ_LAT, SUBJ_LON = 33.13977867364892, -80.30525743961334
# ---------------- Assumptions: Property Overview ----------------
a["C5"] = "Preserve at Ridgeville"
a["C6"] = "1050 Old Gilliard Rd"
a["C7"] = "Ridgeville, SC 29472"
a["C8"] = "Multifamily - Garden"
a["C9"] = 240
a["C10"] = 240624
a["C11"] = 2023
a["C12"] = None
# ---------------- Acquisition ----------------
a["C15"] = 42000000 # initial placeholder price - will be goal-sought below
a["C18"] = 0.025
# ---------------- Disposition ----------------
a["C22"] = 0.0525
a["C23"] = 0.02
# ---------------- CapEx (none - stabilized, no value-add program) ----------------
a["C26"] = None
a["C28"] = None
# ---------------- Reserves ----------------
a["C32"] = 200
# ---------------- Growth & Hold Period ----------------
a["H15"] = 5
a["H16"] = 0.02 # base case rent growth - structural judgment override, see memo
a["H17"] = 0.03
a["H18"] = 0.03
a["H19"] = 0.92 # stabilized occupancy - see memo re: current 89.6% trailing
# ---------------- T12 Operating Statement (Year 0) ----------------
a["H33"] = 4490384.16 # GPR - datamart T12 actual
a["H34"] = -499143.67 # Vacancy & credit loss - datamart T12 actual (negative)
a["H37"] = 777957 # Other income - broker Year-1 pro forma (no T12 verified figure exists)
a["H41"] = 639392.14 # RE taxes - datamart T12 actual (assessor)
a["H42"] = 120000 # Insurance - broker pro forma ($500/unit)
a["H43"] = 230553 # Utilities - broker pro forma ($961/unit)
a["H44"] = 48000 # R&M - broker pro forma ($200/unit)
a["H45"] = 122961 # Management fees - broker pro forma (2.5% of their EGI)
a["H46"] = 420000 # Payroll & benefits - broker pro forma ($1,750/unit)
a["H47"] = 108000 # G&A - broker pro forma ($450/unit)
a["H48"] = 120000 # Advertising & marketing - broker pro forma
a["H49"] = 200769 # Other expenses - broker turnover ($48k) + contract services ($152,769)
# ---------------- Senior Debt Financing ----------------
a["C38"] = 0.65
a["C40"] = 0.0593 # Freddie Mac CME Fixed Rate, 65% LTV, 10yr - current market avg
a["C41"] = 10 # Interest Only for full loan term (protected default structure)
a["C42"] = None
a["C43"] = 10
wb.save(FN)
print("Assumptions written")
# ---------------- Rent Comps ----------------
comps = [
{
"name": "The Palms at Edgewater", "address": "1005 Sonoran Cir", "city": "Summerville, SC 29485",
"lat": 32.97628730535516, "lon": -80.24402797222137,
"unit_count": 288, "rentable_sf": 289152, "year_built": 2023, "year_renovated": None,
"in_place_rent_unit": 1581.05,
"units_0": None, "units_1": 96, "units_2": 144, "units_3": 48, "units_4": None,
"rent_0": None, "rent_1": 1398.25, "rent_2": 1620.91, "rent_3": 1842.17, "rent_4": None,
"occ": 0.9479,
},
{
"name": "The Overlook at Cane Bay", "address": "900 Owl Wood Ln", "city": "Summerville, SC 29486",
"lat": 33.108353912830445, "lon": -80.12778103351593,
"unit_count": 300, "rentable_sf": None, "year_built": 2020, "year_renovated": None,
"in_place_rent_unit": None,
"units_0": None, "units_1": None, "units_2": None, "units_3": None, "units_4": None,
"rent_0": None, "rent_1": None, "rent_2": None, "rent_3": None, "rent_4": None,
"occ": None,
},
{
"name": "The Isley at Windsor Hill", "address": "8251 Windsor Hill Blvd", "city": "North Charleston, SC 29420",
"lat": 32.92680591344842, "lon": -80.09619534015656,
"unit_count": 332, "rentable_sf": 316396, "year_built": 2022, "year_renovated": None,
"in_place_rent_unit": 1646.56,
"units_0": None, "units_1": None, "units_2": None, "units_3": None, "units_4": None,
"rent_0": None, "rent_1": 1467.71, "rent_2": 1751.35, "rent_3": 2047.77, "rent_4": None,
"occ": 0.9639,
},
{
"name": "Hawthorne at Summerville", "address": "9000 Palm Passage Loop", "city": "Summerville, SC 29483",
"lat": 32.979023158550355, "lon": -80.23995101451874,
"unit_count": 168, "rentable_sf": 169848, "year_built": 2024, "year_renovated": None,
"in_place_rent_unit": 1648.28,
"units_0": None, "units_1": None, "units_2": None, "units_3": None, "units_4": None,
"rent_0": None, "rent_1": 1505.52, "rent_2": 1710.76, "rent_3": 1998.42, "rent_4": None,
"occ": 0.9583,
},
{
"name": "Azure Carnes Crossroads", "address": "900 Conway Cir", "city": "Summerville, SC 29486",
"lat": 33.05895298719415, "lon": -80.09166777133942,
"unit_count": 295, "rentable_sf": 320370, "year_built": 2021, "year_renovated": None,
"in_place_rent_unit": 1674.12,
"units_0": None, "units_1": None, "units_2": None, "units_3": None, "units_4": None,
"rent_0": None, "rent_1": 1433.15, "rent_2": 1759.98, "rent_3": 2157.77, "rent_4": None,
"occ": 0.9797,
},
]
cols = ["D", "E", "F", "G", "H"] # 5 comps -> comp1..comp5
for i, comp in enumerate(comps):
col = cols[i]
dist = hav(SUBJ_LAT, SUBJ_LON, comp["lat"], comp["lon"])
rc[f"{col}6"] = comp["name"]
rc[f"{col}7"] = comp["address"]
rc[f"{col}8"] = comp["city"]
rc[f"{col}9"] = round(dist, 1)
rc[f"{col}10"] = comp["unit_count"]
rc[f"{col}11"] = comp["rentable_sf"]
rc[f"{col}12"] = comp["year_built"]
rc[f"{col}13"] = comp["year_renovated"]
rc[f"{col}14"] = comp["in_place_rent_unit"]
rc[f"{col}17"] = comp["units_0"]
rc[f"{col}18"] = comp["units_1"]
rc[f"{col}19"] = comp["units_2"]
rc[f"{col}20"] = comp["units_3"]
rc[f"{col}21"] = comp["units_4"]
rc[f"{col}25"] = comp["rent_0"]
rc[f"{col}26"] = comp["rent_1"]
rc[f"{col}27"] = comp["rent_2"]
rc[f"{col}28"] = comp["rent_3"]
rc[f"{col}29"] = comp["rent_4"]
rc[f"{col}34"] = comp["occ"]
wb.save(FN)
print("Rent Comps written")
# ---------------- Sales Comps (recent Class-A garden trades in same submarket) ----------------
sales = [
{"name": "The Palms at Edgewater", "address": "1005 Sonoran Cir", "city": "Summerville, SC 29485",
"lat": 32.97628730535516, "lon": -80.24402797222137,
"unit_count": 288, "year_built": 2023, "year_renovated": None,
"sale_date": date(2025, 9, 29), "sale_price": 58500000, "in_place_rent": 1581.05,
"buyer": None, "seller": None, "source": "RealAI datamart mf_sales_history"},
{"name": "The Overlook at Cane Bay", "address": "900 Owl Wood Ln", "city": "Summerville, SC 29486",
"lat": 33.108353912830445, "lon": -80.12778103351593,
"unit_count": 300, "year_built": 2020, "year_renovated": None,
"sale_date": date(2025, 12, 5), "sale_price": 68500000, "in_place_rent": None,
"buyer": None, "seller": None, "source": "RealAI datamart mf_sales_history"},
{"name": "The Isley at Windsor Hill", "address": "8251 Windsor Hill Blvd", "city": "North Charleston, SC 29420",
"lat": 32.92680591344842, "lon": -80.09619534015656,
"unit_count": 332, "year_built": 2022, "year_renovated": None,
"sale_date": date(2024, 6, 6), "sale_price": 65300000, "in_place_rent": 1646.56,
"buyer": None, "seller": None, "source": "RealAI datamart mf_sales_history"},
{"name": "Azure Carnes Crossroads", "address": "900 Conway Cir", "city": "Summerville, SC 29486",
"lat": 33.05895298719415, "lon": -80.09166777133942,
"unit_count": 295, "year_built": 2021, "year_renovated": None,
"sale_date": date(2026, 6, 8), "sale_price": 70100000, "in_place_rent": 1674.12,
"buyer": None, "seller": None, "source": "RealAI datamart mf_sales_history"},
]
for i, s in enumerate(sales):
col = cols[i]
dist = hav(SUBJ_LAT, SUBJ_LON, s["lat"], s["lon"])
sc[f"{col}6"] = s["name"]
sc[f"{col}7"] = s["address"]
sc[f"{col}8"] = s["city"]
sc[f"{col}9"] = round(dist, 1)
sc[f"{col}10"] = s["unit_count"]
sc[f"{col}11"] = s["year_built"]
sc[f"{col}12"] = s["year_renovated"]
sc[f"{col}15"] = s["sale_date"]
sc[f"{col}16"] = s["sale_price"]
sc[f"{col}18"] = s["in_place_rent"]
sc[f"{col}19"] = s["buyer"]
sc[f"{col}20"] = s["seller"]
sc[f"{col}21"] = s["source"]
# Adjustments: explicit 0 for all - unrestricted market-rate comps, no adjustment applied
# (adjustment withheld deliberately since subject carries an AMI rent-restriction the comps do not - see memo)
for r in range(24, 29):
sc[f"{col}{r}"] = 0
sc[f"{col}30"] = 0.25 # equal weight across 4 comps
wb.save(FN)
print("Sales Comps written")
cd /vercel/sandbox && python3 populate.py
Now let's recalc and check outputs, then run the goal-seek for purchase price to hit 12% levered IRR at 65% LTV.
cd /vercel/sandbox && python3 skills/xlsx/scripts/recalc.py PreserveAtRidgeville_underwriting_2026-09-16.xlsx 2>&1 | tail -30
cd /vercel/sandbox && python3 -c "
import openpyxl
wb = openpyxl.load_workbook('PreserveAtRidgeville_underwriting_2026-09-16.xlsx', data_only=True)
a = wb['Assumptions']
cells = ['H5','H6','H7','H8','H9','H10','H11','C50','C51','C52','H52','M52','C15','C19','C64']
for c in cells:
print(c, a[c].value)
"
import subprocess, openpyxl, sys
FN = "PreserveAtRidgeville_underwriting_2026-09-16.xlsx"
TARGET = 0.12
TOL = 0.001 # 0.10%
def set_price_and_recalc(price):
wb = openpyxl.load_workbook(FN)
wb["Assumptions"]["C15"] = price
wb.save(FN)
subprocess.run(["python3", "skills/xlsx/scripts/recalc.py", FN], check=True, capture_output=True)
wb2 = openpyxl.load_workbook(FN, data_only=True)
irr = wb2["Assumptions"]["H9"].value
return irr
lo, hi = 30000000.0, 80000000.0
history = []
for i in range(10):
mid = (lo + hi) / 2
irr = set_price_and_recalc(mid)
history.append((mid, irr))
print(f"iter {i}: price={mid:,.0f} irr={irr:.4%}")
if irr is None:
break
if abs(irr - TARGET) <= TOL:
break
# IRR decreases as price increases
if irr > TARGET:
lo = mid
else:
hi = mid
print("FINAL", history[-1])
cd /vercel/sandbox && python3 goalseek.py
cd /vercel/sandbox && python3 -c "
import openpyxl
wb = openpyxl.load_workbook('PreserveAtRidgeville_underwriting_2026-09-16.xlsx', data_only=True)
a = wb['Assumptions']
cells = {'H5':'T12 cap rate','H6':'Yr1 cap rate','H7':'Yield on cost','H8':'Unlevered IRR','H9':'Levered IRR','H10':'Avg CoC','H11':'Equity multiple',
'C50':'Going-in DSCR','C51':'Debt yield','C52':'Breakeven occ','H52':'T12 NOI','M52':'Yr1 NOI','C15':'Purchase price','C19':'Total basis','C64':'Total equity required',
'C9':'Unit count','C38':'LTV','C40':'Rate','H33':'GPR','H34':'Vacancy','H37':'Other inc','H38':'EGI' if False else 'EGI?','H36':'Implied occ'}
for c,l in cells.items():
print(l, c, a[c].value)
print('---SU---')
su = wb['Sources & Uses']
for r in range(1,12):
print(r, [su.cell(row=r,column=c).value for c in range(1,9)])
"
cd /vercel/sandbox && python3 -c "
import openpyxl
wb = openpyxl.load_workbook('PreserveAtRidgeville_underwriting_2026-09-16.xlsx', data_only=True)
a = wb['Assumptions']
for c in ['H35','H38','H50','H51','H52','H53','H54','H55','H56','H57','H58','H59','H60','H61']:
print(c, a[c].value)
print('other income share', a['H37'].value / a['H38'].value)
"
cd /vercel/sandbox && python3 -c "
import openpyxl
wb = openpyxl.load_workbook('PreserveAtRidgeville_underwriting_2026-09-16.xlsx', data_only=True)
s = wb['Sensitivity']
for r in range(4,26):
print(r, [s.cell(row=r,column=c).value for c in range(2,9)])
"
cd /vercel/sandbox && python3 -c "
import openpyxl
wb = openpyxl.load_workbook('PreserveAtRidgeville_underwriting_2026-09-16.xlsx', data_only=True)
s = wb['Sensitivity']
for r in range(22,30):
print(r, [s.cell(row=r,column=c).value for c in range(2,9)])
"
cd /vercel/sandbox && python3 skills/xlsx/scripts/workbook_integrity_scan.py PreserveAtRidgeville_underwriting_2026-09-16.xlsx --json integrity.json; echo EXIT:$?
python3 -c "
import json
d=json.load(open('integrity.json'))
print(d.get('status'))
sev = {}
for f in d.get('findings',[]):
sev[f['severity']] = sev.get(f['severity'],0)+1
print(sev)
for f in d.get('findings',[]):
if f['severity']=='high':
print(f)
"cd /vercel/sandbox && python3 -c "
import openpyxl
wb = openpyxl.load_workbook('PreserveAtRidgeville_underwriting_2026-09-16.xlsx', data_only=True)
rc = wb['Rent Comps']
for r in [38,39,40,41,42,43,44,45,46,47,48,49]:
print(r, rc.cell(row=r,column=2).value, rc.cell(row=r,column=3).value)
print('---sales---')
sc = wb['Sales Comps']
for r in [34,35,36,37]:
print(r, sc.cell(row=r,column=2).value, sc.cell(row=r,column=3).value)
print('price per unit', 49726562.5/240)
"
cd /vercel/sandbox && python3 skills/xlsx/scripts/recalc.py PreserveAtRidgeville_underwriting_2026-09-16.xlsx 2>&1 | tail -5
CONDITIONAL — Preserve at Ridgeville, 240 units in Ridgeville, SC, needs to trade around $49.73M ($207,194/unit) — roughly 4.3% below the $216,443/unit average of four recent Class-A garden trades in the same submarket — to clear a 12% levered IRR at 65% LTV and current Freddie Mac 10-year pricing . At that basis the deal is comfortably financeable (1.54x DSCR, 8.5% debt yield) , but the discount to comps exists for a reason the OM's comp table never mentions: this is a USDA 538 income-restricted asset with a deed restriction running through 2061, benchmarked against five unrestricted, market-rate Class-A comps .
The thesis — new (2023), well-located, priced below replacement cost, with genuine mark-to-market room versus its own AMI ceiling — is real. But three OM inputs run hotter than the data supports: rent growth (4.25% vs. a data-supported 2%), occupancy (94% snapshot vs. 89.6% trailing and falling) , and Year-1 real estate taxes, which the OM's own pro forma shows stepping up ~23% on point-of-sale reassessment — a jump this template's flat escalator won't capture . Go firm at or below $49.7M with underwriting reset to the comp-supported numbers below; don't go at the OM's own assumptions.
| Property | Distance (mi) | Units | Year Built | In-Place Rent/Unit | Occupancy | Rent Type |
|---|---|---|---|---|---|---|
| Preserve at Ridgeville (subject) | 0.0 | 240 | 2023 | $1,5591 | 89.6% | Affordable/AMI-restricted |
| The Palms at Edgewater | 8.9 | 288 | 2023 | $1,581 | 94.8% | Market |
| The Isley at Windsor Hill | 14.6 | 332 | 2022 | $1,647 | 96.4% | Market |
| Hawthorne at Summerville | 11.1 | 168 | 2024 | $1,648 | 95.8% | Market |
| Azure Carnes Crossroads | 8.4 | 295 | 2021 | $1,674 | 98.0% | Market |
| Comp average (unrestricted) | — | — | — | $1,6381 | 96.2% | — |
The OM's own five comps — Palms at Edgewater, Overlook at Cane Bay, Isley at Windsor Hill, Hawthorne at Summerville, Azure Carnes Crossroads — are all conventional, unrestricted, 2020–2024-vintage Class-A garden product within 15 miles . Independently pulling four of them from the datamart confirms the OM's rent figures are directionally right: subject in-place rent of $1,559/unit sits 4.8% below the comp average of $1,638/unit . But none of these comps carry the household restriction subject does — every one is flagged rent_type: MARKET with no restriction, against subject's AFFORDABLE / INCOME_RESTRICTED classification . The OM's own debt page discloses the mechanism — total rent capped at 30% of 115% AMI, a restriction that survives even if the USDA loan is repaid, running to 2061 — but the "mark-to-market opportunity" and comp-set framing never reconcile the two .
Sale comps tell the same story on price. Four Class-A garden trades within the same submarket over the last 26 months — Palms ($203,125/unit, Sep-25), Overlook ($228,333/unit, Dec-25), Isley ($196,687/unit, Jun-24), Azure Carnes Crossroads ($237,627/unit, Jun-26) — average $216,443/unit . The 65% LTV / 12% IRR breakeven price of $207,194/unit is a 4.3% discount to that basis, which is the right direction given the rent ceiling, but not a large one — there's limited room to overpay without an unrestricted-comp basis that doesn't apply here.
| Property | Sale Date | Distance (mi) | Units | Year Built | Sale Price | $/Unit |
|---|---|---|---|---|---|---|
| The Palms at Edgewater1 | Sep 2025 | 8.9 | 288 | 2023 | $58,500,000 | $203,125 |
| The Overlook at Cane Bay1 | Dec 2025 | 5.8 | 300 | 2020 | $68,500,000 | $228,333 |
| The Isley at Windsor Hill1 | Jun 2024 | 14.6 | 332 | 2022 | $65,300,000 | $196,687 |
| Azure Carnes Crossroads1 | Jun 2026 | 8.4 | 295 | 2021 | $70,100,000 | $237,627 |
| Comp average (unadjusted) | — | — | — | — | — | $216,443 |
| Preserve at Ridgeville — 12% IRR breakeven price | — | — | 240 | 2023 | $49,726,563 | $207,194 |
Ridgeville sits inside a genuinely strong demand corridor — Volvo's $1.3B plant, Redwood Materials' $3.5B battery campus, and Google's $9B data center expansion are all within an 8-minute drive, and the OM's claim of zero competing Class-A supply within 6 miles is a real structural advantage . Charleston MSA multifamily cap rates for Class A assets run roughly 4.75–5.20% and have been compressing ; the underwritten entry at $49.73M implies a T12 cap rate of 5.55% — about 35–55 bps above that band, i.e. priced favorably relative to unrestricted Class-A . Submarket-wide, Summerville's multifamily pipeline is thinning fast (2026/2027 deliveries down 35%/50% YoY), supporting the supply-shielded thesis for the broader corridor even if it doesn't resolve the AMI ceiling specific to this asset .
| Line | Stabilized |
|---|---|
| Effective gross income | $4,769,197 |
| Operating expenses | $2,009,675 |
| NOI (T12) | $2,759,522 |
| Going-in cap rate | 5.55% |
| Levered IRR (5-yr hold) | 12.04% |
| Equity multiple | 1.69x |
| Going-in DSCR | 1.54x |
| Debt yield | 8.5% |
Footnote: GPR and vacancy are datamart T12 actuals; taxes are the T12 assessed amount; all other expense lines and other income are sourced from the broker's Year-1 pro forma — no independent T12 detail exists for insurance, utilities, R&M, payroll, G&A, marketing, or other income, so those lines carry broker-only confidence.
Two things to flag before trusting this table at face value. First, real estate taxes are understated for Year 1. The OM's own pro forma shows Year-1 taxes of $785,683 (South Carolina reassesses at 100% of purchase price on sale) versus $639,392 T12 — a 23% step-up the template's flat 3% escalator doesn't replicate . Second, other income (16–20% of net rent) is entirely broker-Year-1, not T12-verified — no independent source exists to corroborate it, and it sits above the typical 2–12% benchmark band . Both push actual Year-1 NOI below what a first glance at the table suggests.
The deal is far more sensitive to rent growth than to exit cap. Holding exit cap at 5.25%, levered IRR runs from 6.0% at 0.5% rent growth to 12.0% at 2.0% growth — precisely the range between the forecasting engine's drawdown-flagged base case and the judgment-based assumption used here, and well below the OM's 4.25% (which would produce ~15%+ IRR, explaining why the OM's own numbers look so much better) . On the supply-shock side (near-zero rent growth, the scenario a slower Summerville lease-up implies), IRR falls to the 6–8% range — below the 12% hurdle. On cap rate, widening 50 bps to 5.75% (plausible for a restricted-income asset with a thinner buyer pool) cuts IRR to 7.9% at 2% rent growth. The deal clears the hurdle in the base case; it doesn't have much room for error on either lever.
This is a real asset in a real demand corridor, priced right around fair value once the deed restriction is priced correctly into the comp basis — but the OM's own operating assumptions (rent growth, occupancy, taxes) are stacked to make the deal look better than the data supports. Go at or below the $49.7M ($207K/unit) breakeven, insist the seller's Year-1 tax and rent-growth assumptions get replaced with the comp-supported figures above, and get clarity on how the AMI deed restriction is treated at a future exit before this moves past screening.
This is a read-only snapshot of a RealAI analysis.