Find market-rate rental properties in Fairfax County, Arlington County, Alexandria, Loudoun County and Prince William County, VA with 150 or more units, built before 2000, whose latest sale was between January 2021 and December 2022. For each, show units, year built, renovation year, sale date and price per unit, current occupancy and its 12-month change, retention, and 12-month in-place rent change. Split the list into properties that look stabilized enough for agency takeout financing now and ones that don't yet
"""Agency-takeout stabilization screen for NoVA market-rate rentals
(150+ units, built <2000, latest sale 2021-01-01..2022-12-31).
Inputs transcribed from query_data results toolu_bdrk_01CQoFSuwLJjDjzogYMbXpYK (counties)
and toolu_bdrk_019DKrQwFfZtnANEYqzZKFZU (Alexandria city), rent_type == MARKET only.
Fields: name, juris, units, yb, yr_reno, sale_date, ppu, occ, occ_chg(pts, decimal), retention, ipr_chg(median, decimal), conf
"""
import json, csv
rows = [
("Vistas of Annandale","Fairfax",1065,1973,2022,"2022-07-20",258685,0.9465,-0.0376,0.7277,-0.0508,"EXCELLENT"),
("Monticello Falls Church","Fairfax",794,1964,2015,"2022-02-16",None,0.9723,-0.0050,0.8212,0.0317,"GOOD"),
("Amberleigh","Fairfax",752,1968,2009,"2022-09-29",180851,0.9548,0.0120,0.6835,0.0194,"EXCELLENT"),
("Halstead Fair Oaks","Fairfax",491,1987,2017,"2021-01-21",273117,0.9511,-0.0020,0.6538,-0.0059,"EXCELLENT"),
("Quimby on 23rd","Arlington",455,1971,2014,"2021-10-04",None,0.9516,-0.0022,0.4374,0.0323,"EXCELLENT"),
("Ravens Crest","Prince William",444,1989,2019,"2021-04-30",254505,0.9730,-0.0090,0.7703,0.0006,"EXCELLENT"),
("Huntington Gateway","Fairfax",443,1990,None,"2021-10-14",282167,0.9752,-0.0023,0.7991,0.0204,"EXCELLENT"),
("Arbors on Duke","Alexandria",400,1990,2022,"2021-07-19",None,0.9275,-0.0450,0.7475,0.0012,"EXCELLENT"),
("Avana Fieldstone","Loudoun",384,1987,2013,"2021-09-16",286458,0.8750,-0.0911,0.5781,0.0047,"EXCELLENT"),
("Fairway Apartments","Fairfax",346,1969,2018,"2021-05-04",268786,0.9335,-0.0462,0.8324,0.0362,"EXCELLENT"),
("Crystal Woods","Fairfax",344,1966,2014,"2021-09-01",285174,0.9709,0.0116,0.7471,0.0195,"EXCELLENT"),
("Dominion Plaza","Arlington",318,1956,2015,"2022-04-19",287736,0.9119,-0.0472,0.7736,-0.0018,"EXCELLENT"),
("Waterside at Reston","Fairfax",276,1985,2013,"2022-01-06",305616,0.9457,-0.0254,0.6123,0.0139,"EXCELLENT"),
("2410 Little Current Dr (unnamed)","Fairfax",269,1996,None,"2021-12-20",222454,None,None,None,None,None),
("Avana Stoney Ridge","Prince William",264,1985,2021,"2021-04-30",201515,0.8977,-0.0682,0.6250,0.0351,"EXCELLENT"),
("Colvin Woods","Fairfax",259,1979,2007,"2022-09-06",None,None,None,None,None,None),
("Windsor at Fair Lakes","Fairfax",250,1988,2009,"2022-11-21",320000,0.9520,-0.0040,0.7160,-0.0027,"EXCELLENT"),
("Eagle Rock at Columbia Pike","Arlington",247,1991,2011,"2021-12-28",449292,0.9393,-0.0405,0.6802,0.0006,"EXCELLENT"),
("The Knoll at Fair Oaks","Fairfax",246,1989,2015,"2022-08-02",338211,0.9512,-0.0366,0.6260,0.0107,"EXCELLENT"),
("Chase Heritage","Loudoun",236,1986,2010,"2021-01-29",243644,0.9364,-0.0593,0.8644,0.0197,"EXCELLENT"),
("Windsor Park","Prince William",220,1986,2008,"2021-12-27",284823,0.9682,-0.0136,0.7273,0.0055,"EXCELLENT"),
("Barton House","Arlington",214,1966,None,"2021-12-15",350467,None,None,None,None,None),
("J Harbor Park at North Point","Fairfax",190,1996,2007,"2021-09-14",347105,0.9684,-0.0053,0.7211,0.0146,"EXCELLENT"),
("101 North Ripley","Alexandria",189,1963,2022,"2022-06-09",265079,0.9788,0.0741,0.6138,-0.0298,"EXCELLENT"),
("Cortland Arlington","Arlington",184,1986,None,"2022-07-06",545652,0.9620,0.0707,0.7283,-0.0066,"EXCELLENT"),
("Springwoods at Lake Ridge","Prince William",180,1986,2022,"2021-12-27",285216,0.9667,-0.0111,0.7667,0.0232,"EXCELLENT"),
("Trellis Herndon","Fairfax",168,1988,None,"2022-07-05",136161,0.9821,0.0119,0.4881,0.0174,"EXCELLENT"),
("The Glen","Fairfax",152,1978,None,"2022-10-20",240132,0.9671,-0.0132,0.8158,-0.0262,"GOOD"),
]
# Screen (agency floor is ~90% physical occupancy sustained ~90 days; we require a buffer):
# NOT YET if occ < 93%, OR (occ < 95% AND occupancy fell > 3 pts YoY), OR in-place rent fell > 3% YoY
# Flags (still Ready): retention < 55%; rent down 2-3%; occ up > 5 pts YoY (verify 90-day seasoning);
# occ down > 3 pts but >= 95%; sample confidence below EXCELLENT
out = []
for r in rows:
name,j,u,yb,yr,sd,ppu,occ,dch,ret,ipr,conf = r
flags=[]
if occ is None:
status="No current data"
else:
fail = occ<0.93 or (occ<0.95 and dch<-0.03) or ipr<-0.03
status = "Not yet" if fail else "Ready"
if ret<0.55: flags.append("retention <55%")
if -0.03<=ipr<-0.02: flags.append("in-place rent down 2-3%")
if dch>0.05: flags.append("occupancy just recovered - verify 90-day seasoning")
if dch<-0.03 and occ>=0.95: flags.append("occupancy down >3 pts YoY")
if conf!="EXCELLENT": flags.append(f"{conf} sample confidence")
out.append(dict(name=name,juris=j,units=u,year_built=yb,year_renovated=yr,sale_date=sd,price_per_unit=ppu,
occupancy=occ,occ_chg_pts=None if dch is None else round(dch*100,2),retention=ret,
ipr_t12_chg=ipr,status=status,flags="; ".join(flags)))
with open("extracts/nova_agency_screen.csv","w",newline="") as f:
w=csv.DictWriter(f,fieldnames=out[0].keys()); w.writeheader(); w.writerows(out)
from collections import Counter
print(Counter(o["status"] for o in out))
for s in ["Ready","Not yet","No current data"]:
g=[o for o in out if o["status"]==s]
print(f"\n== {s} ({len(g)}) units={sum(o['units'] for o in g)}")
for o in g: print(o["name"],o["occupancy"],o["occ_chg_pts"],o["ipr_t12_chg"],o["flags"])
rd=[o for o in out if o["status"]=="Ready"]; ny=[o for o in out if o["status"]=="Not yet"]
import statistics as st
print("\nmedian occ ready",st.median(o["occupancy"] for o in rd),"not yet",st.median(o["occupancy"] for o in ny))
print("median occ chg ready",st.median(o["occ_chg_pts"] for o in rd),"not yet",st.median(o["occ_chg_pts"] for o in ny))
mkdir -p extracts && python scripts/nova_agency_screen.py
Of the 28 market-rate properties that meet your filters, 17 look stabilized enough for an agency takeout now, 8 don't yet, and 3 have no current operating data. All figures are as of the 9/26/2026 rent-index read.
The two groups split mainly on whether occupancy is holding, not on rents. The median "ready" property is 96.7% occupied and lost 0.4 points over the year. The median "not yet" property is 93.1% occupied and lost 4.7 points . In-place rents are roughly flat across both groups, so rent levels aren't what holds anyone back.
How I split them: Fannie Mae and Freddie Mac generally want at least 90% physical occupancy held for about 90 days. I added a cushion above that. A property is "not yet" if any of these is true:
What I filtered out and missed:
| Property | Jurisdiction | Units | Year built | Renovated | Latest sale | Price/unit | Occupancy | Occ. 12-mo chg (pts) | Retention | In-place rent 12-mo chg (median) | Flags |
|---|---|---|---|---|---|---|---|---|---|---|---|
| Fairfax Co. | 794 | 1964 | 2015 | 2/16/2022 | n/a | 97.2%1 | -0.5 | 82.1%2 | +3.2%3 | Good (not excellent) sample confidence; no sale price4 | |
| Fairfax Co. | 752 | 1968 | 2009 | 9/29/2022 | $180,851 | 95.5% | +1.2 | 68.4% | +1.9% | — | |
| Fairfax Co. | 491 | 1987 | 2017 | 1/21/2021 | $273,117 | 95.1% | -0.2 | 65.4% | -0.6% | — | |
| Arlington | 455 | 1971 | 2014 | 10/4/2021 | n/a | 95.2% | -0.2 | 43.7% | +3.2% | Low retention; no sale price | |
| Prince William | 444 | 1989 | 2019 | 4/30/2021 | $254,505 | 97.3% | -0.9 | 77.0% | +0.1% | — | |
| Fairfax Co. | 443 | 1990 | — | 10/14/2021 | $282,167 | 97.5% | -0.2 | 79.9% | +2.0% | — | |
| Fairfax Co. | 344 | 1966 | 2014 | 9/1/2021 | $285,174 | 97.1% | +1.2 | 74.7% | +2.0% | — | |
| Fairfax Co. | 276 | 1985 | 2013 | 1/6/2022 | $305,616 | 94.6% | -2.5 | 61.2% | +1.4% | Borderline | |
| Fairfax Co. | 250 | 1988 | 2009 | 11/21/2022 | $320,000 | 95.2% | -0.4 | 71.6% | -0.3% | — | |
| Fairfax Co. | 246 | 1989 | 2015 | 8/2/2022 | $338,211 | 95.1% | -3.7 | 62.6% | +1.1% | Occupancy down >3 pts | |
| Prince William | 220 | 1986 | 2008 | 12/27/2021 | $284,823 | 96.8% | -1.4 | 72.7% | +0.6% | — | |
| Fairfax Co. | 190 | 1996 | 2007 | 9/14/2021 | $347,105 | 96.8% | -0.5 | 72.1% | +1.5% | — | |
| Alexandria | 189 | 1963 | 2022 | 6/9/2022 | $265,079 | 97.9% | +7.4 | 61.4% | -3.0% | Just recovered; rents cut | |
| Arlington | 184 | 1986 | — | 7/6/2022 | $545,652 | 96.2% | +7.1 | 72.8% | -0.7% | Just recovered; verify 90 days | |
| Prince William | 180 | 1986 | 2022 | 12/27/2021 | $285,216 | 96.7% | -1.1 | 76.7% | +2.3% | — | |
| Fairfax Co. | 168 | 1988 | — | 7/5/2022 | $136,161 | 98.2% | +1.2 | 48.8% | +1.7% | Low retention; price/unit looks off | |
| Fairfax Co. | 152 | 1978 | — | 10/20/2022 | $240,132 | 96.7% | -1.3 | 81.6% | -2.6% | Rents slipping; Good sample confidence |
Cleanest of the group: Ravens Crest, Huntington Gateway, Crystal Woods, Windsor Park, J Harbor Park and Springwoods. Each is at least 96.7% occupied, lost no more than 1.4 points over the year, and has in-place rents flat to up.
Ready, but check these first:
| Property | Jurisdiction | Units | Year built | Renovated | Latest sale | Price/unit | Occupancy | Occ. 12-mo chg (pts) | Retention | In-place rent 12-mo chg (median) | Why not yet |
|---|---|---|---|---|---|---|---|---|---|---|---|
| Fairfax Co. | 1,065 | 1973 | 2022 | 7/20/2022 | $258,685 | 94.7%1 | -3.8 | 72.8% | -5.1% | Occupancy and rents both falling | |
| Alexandria | 400 | 1990 | 2022 | 7/19/2021 | n/a | 92.8% | -4.5 | 74.8% | +0.1% | Below 93%, falling | |
| Loudoun | 384 | 1987 | 2013 | 9/16/2021 | $286,458 | 87.5% | -9.1 | 57.8% | +0.5% | Below the 90% agency floor | |
| Fairfax Co. | 346 | 1969 | 2018 | 5/4/2021 | $268,786 | 93.4% | -4.6 | 83.2% | +3.6% | Occupancy sliding | |
| Arlington | 318 | 1956 | 2015 | 4/19/2022 | $287,736 | 91.2% | -4.7 | 77.4% | -0.2% | Barely above floor, falling | |
| Prince William | 264 | 1985 | 2021 | 4/30/2021 | $201,515 | 89.8% | -6.8 | 62.5% | +3.5% | Below the 90% agency floor | |
| Arlington | 247 | 1991 | 2011 | 12/28/2021 | $449,292 | 93.9% | -4.1 | 68.0% | +0.1% | Occupancy sliding | |
| Loudoun | 236 | 1986 | 2010 | 1/29/2021 | $243,644 | 93.6% | -5.9 | 86.4% | +2.0% | Occupancy sliding |
Rent cuts would be the wrong fix for most of these. Fairway, Chase Heritage and Avana Stoney Ridge all lost 4.6–6.8 points of occupancy while raising in-place rents 2.0–3.6% . They look like they pushed rents ahead of demand. Easing renewal increases would probably bring them back to about 95% within a couple of quarters.
Avana Fieldstone is furthest away. It's at 87.5%, down 9.1 points , and would need a bridge extension or a lease-up period first.
Vistas of Annandale is the biggest problem. It's the largest asset on the list at 1,065 units, and it's the only one where occupancy and in-place rent are both falling: down 3.8 points and 5.1% .
The data has no rent or occupancy reads for these, so I couldn't screen them. You'd need a rent roll and T-12 (trailing 12 months of operations) from the owner:
One timing point: the 2021–22 buyers mostly used floating-rate bridge loans, so many of these are in their extension window now. That makes the "not yet" group the likeliest place to find owners under refinancing pressure.
The full screen is saved at extracts/nova_agency_screen.csv.
For Crystal Woods (Alexandria, VA), Windsor Park (Woodbridge, VA) and Springwoods at Lake Ridge (Woodbridge, VA), estimate whether an agency takeout covers the acquisition debt. For each: compare in-place rent by bedroom to its submarket's in-place rent by bedroom, show RealAI's modeled NOI, and size a 10-year Fannie Mae fixed loan at today's 80% LTV / 1.25x quote on that NOI, with a 5% vacancy floor. Assume the 2021 purchase was financed with a bridge loan at 70% of the purchase price. Show the estimated bridge balance, supportable agency proceeds, and the gap for each, and say what each owner would need to close it.
wc -c scratch/inputs/agents/01a087be-a40c-70fb-88a9-40a61b0703c2/*; head -c 6000 scratch/inputs/agents/01a087be-a40c-70fb-88a9-40a61b0703c2/RealAI_Credit_Underwriting_Template_manifest_v5.md
{
"_sources": {
"property_financials_rents_sale": "query_data toolu_bdrk_01GMeQYLuJSbc2btYdAyZcQd (property_mfr, mf_property_financials / mf_rent_and_occupancy_detail / mf_property_attributes)",
"submarket_rents": "query_data toolu_bdrk_019VoJvGbAwD2vPUFJqdJJ2h (submarket mf_rent_and_occupancy_detail)",
"loan_quote": "query_data toolu_bdrk_01Nu6Jyffo7WGP3Hm5epoaFW (mortgage_rates, Fannie Mae - Conventional, 10-yr, 80% LTV, 1.25x DSCR, as of 2026-09-23)",
"cap_rate": "query_data toolu_bdrk_011Bq33XqWEHwJ3q5HjLc2D3 (market 47900 caprate_ts multifamily 2Q26)",
"treasury": "query_data toolu_bdrk_01KzgTYYyzUUoq9uKSkaGVWv (ten_year_treasury_pct 2026-09-24)"
},
"loan_quote": {"product": "Fannie Mae - Conventional", "as_of": "2026-09-23", "term_yrs": 10, "rate_avg": 0.0638, "rate_min": 0.0623, "rate_max": 0.0653, "spread_avg": 0.0145, "ltv_max": 0.80, "dscr_min": 1.25},
"ten_year_treasury": {"date": "2026-09-24", "value": 0.0518},
"dc_msa_mf_cap_rate": {"quarter": "2Q26", "value": 0.0573, "q4_2021": 0.0393, "q3_2021": 0.0398},
"vacancy_floor": 0.05,
"bridge_ltc": 0.70,
"properties": {
"Crystal Woods": {"id": "a85b1fc19856f0e626654bdee39594d3", "units": 344, "sale_date": "2021-09-01", "sale_price": 98100000,
"gpr": 9007733.12, "vacancy_loss": 518627.17, "net_rent": 8489105.95, "other_income": 1036097.89, "egi": 9525203.84,
"opex": 3758776.24, "noi": 5766427.59, "capex": 107465.39, "tax": 1011207.07, "occupancy": 0.9709,
"ipr": {"avg": 2180.08, "1": 1820.94, "2": 2175.96, "3": 2832.17},
"submarket": {"id": "e260c22d6d706dc928d0daede02dd488", "name": "Annandale/Franconia/Springfield", "occupancy": 0.9438,
"ipr": {"avg": 2154.56, "0": 1924.42, "1": 1920.39, "2": 2252.51, "3": 2812.37}}},
"Windsor Park": {"id": "b6139062bd44384bbf713729ff3eec72", "units": 220, "sale_date": "2021-12-27", "sale_price": 62661065,
"gpr": 4714386.83, "vacancy_loss": 192582.27, "net_rent": 4521804.57, "other_income": 615865.26, "egi": 5137669.83,
"opex": 1923509.17, "noi": 3214160.66, "capex": 31459.29, "tax": 471734.22, "occupancy": 0.9682,
"ipr": {"avg": 1783.80, "1": 1688.47, "2": 1902.97},
"submarket": {"id": "723b35bde0500c5a7e254bbd69412c61", "name": "Prince George/Manassas", "occupancy": 0.9531,
"ipr": {"avg": 2077.52, "0": 1648.07, "1": 1878.60, "2": 2213.01, "3": 2357.52}}},
"Springwoods at Lake Ridge": {"id": "7f31390129f11c7eba88b44fbaea42e5", "units": 180, "sale_date": "2021-12-27", "sale_price": 51338936,
"gpr": 3675134.92, "vacancy_loss": 185405.83, "net_rent": 3489729.09, "other_income": 471510.15, "egi": 3961239.24,
"opex": 1581446.53, "noi": 2379792.71, "capex": 25743.18, "tax": 354298.76, "occupancy": 0.9667,
"ipr": {"avg": 1695.65, "1": 1616.75, "2": 1784.96},
"submarket": {"id": "f3bbdd2b333142c06c6b65d4492ee82f", "name": "Dale City/Lorton/Woodbridge", "occupancy": 0.9398,
"ipr": {"avg": 2010.41, "0": 1583.46, "1": 1810.18, "2": 2085.19, "3": 2385.04}}}
}
}
import json
d = json.load(open("extracts/takeout_inputs.json"))
for name, p in d["properties"].items():
s = p["submarket"]
print(f"== {name} vs {s['name']} (occ {p['occupancy']:.1%} vs {s['occupancy']:.1%})")
for k in ["1", "2", "3", "avg"]:
if k in p["ipr"] and k in s["ipr"]:
a, b = p["ipr"][k], s["ipr"][k]
print(f" {k:>3}: ${a:,.0f} vs ${b:,.0f} diff ${a-b:+,.0f} ({a/b-1:+.1%})")
vac = p["vacancy_loss"] / p["gpr"]
print(f" modeled vacancy {vac:.2%}; implied GPR/unit/mo ${p['gpr']/p['units']/12:,.0f}; "
f"NOI/unit ${p['noi']/p['units']:,.0f}; NOI yield on purchase {p['noi']/p['sale_price']:.2%}; "
f"OpEx ratio {p['opex']/p['egi']:.1%}")
python scripts/rent_vs_submarket.py; grep -n "^#\|^##" scratch/inputs/agents/01a087be-a40c-70fb-88a9-40a61b0703c2/RealAI_Credit_Underwriting_Template_manifest_v5.md | head -60
M=scratch/inputs/agents/01a087be-a40c-70fb-88a9-40a61b0703c2/RealAI_Credit_Underwriting_Template_manifest_v5.md; sed -n '115,127p;147,216p;469,514p;659,682p' $M
M=scratch/inputs/agents/01a087be-a40c-70fb-88a9-40a61b0703c2/RealAI_Credit_Underwriting_Template_manifest_v5.md; sed -n '296,330p;447,468p;514,560p' $M; cd /vercel/sandbox && python3 -c "
import openpyxl
wb=openpyxl.load_workbook('scratch/inputs/agents/01a087be-a40c-70fb-88a9-40a61b0703c2/RealAI_Credit_Underwriting_Template_v5_clean.xlsx')
ws=wb['Loan Sizing']
for r in range(22,30):
print([ (c.coordinate,c.value) for c in ws[r][1:6]])
"cd /vercel/sandbox && python3 - <<'EOF'
import json
d=json.load(open("extracts/takeout_inputs.json"))
li={"Crystal Woods":dict(payroll=631317.38,rm=493650.44,util=644176.35,ga=258993.40,mkt=63600.78,mgmt=328192.06,ins=327638.77,tax=1011207.07,yb=1966,address="4905 Southland Ave",csz="Alexandria, VA 22312",county_bench=0.4054),
"Windsor Park":dict(payroll=370116.69,rm=232189.61,util=252125.27,ga=284542.65,mkt=44839.98,mgmt=180599.23,ins=87361.51,tax=471734.22,yb=1986,address="3937 Penshurst Ln",csz="Woodbridge, VA 22192",county_bench=0.3992),
"Springwoods at Lake Ridge":dict(payroll=323688.05,rm=237947.06,util=181907.97,ga=229096.17,mkt=45577.82,mgmt=138753.83,ins=70176.86,tax=354298.76,yb=1986,address="12395 Midsummer Ln",csz="Woodbridge, VA 22192",county_bench=0.3992)}
for k,v in li.items():
p=d["properties"][k]; p.update(v)
tot=v['payroll']+v['rm']+v['util']+v['ga']+v['mkt']+v['mgmt']+v['ins']+v['tax']
print(k, round(tot,2), p['opex'], "mgmt%EGI",round(v['mgmt']/p['egi'],4),"opex%EGI",round(p['opex']/p['egi'],4),"bridge",round(0.7*p['sale_price']), "subvac", round(1-p['submarket']['occupancy'],4))
d["_sources"]["opex_lines"]="query_data toolu_bdrk_014QkzJ7qDFcryh5N3wNK1yz"
d["_sources"]["county_opex_bench"]="query_data toolu_bdrk_01JZ37Xd8APVaXXL1DrCh7k3"
d["treasury_curve_2026_09_24_web_anchored"]={"6m":0.0405,"1y":0.0440,"2y":0.0480,"3y":0.0488,"5y":0.0498,"7y":0.0508,"10y":0.0518,"20y":0.0555,"30y":0.0551}
d["ten_year_oct1_reuters"]=0.0532
json.dump(d,open("extracts/takeout_inputs.json","w"),indent=1)
EOF# Agency takeout sizing — common brief (read fully before writing any cell) ## Goal Populate the RealAI Credit Underwriting template (LoanType 1) for ONE property to answer: does a 10-year Fannie Mae fixed-rate takeout (80% LTV / 1.25x DSCR quote) sized on RealAI's modeled NOI with a 5% vacancy floor cover the 2021 bridge loan (assumed interest-only, balance = 70% of 2021 purchase price)? The parent analyst writes the memo; you build, recalc, verify and read back the workbooks. ## Files - Template (copy, never edit in place): `scratch/inputs/agents/01a087be-a40c-70fb-88a9-40a61b0703c2/RealAI_Credit_Underwriting_Template_v5_clean.xlsx` - Manifest (the address map — authoritative; read §5, §6, §7, §7b, §7c, §4, §9, §10, §11, §13): `scratch/inputs/agents/01a087be-a40c-70fb-88a9-40a61b0703c2/RealAI_Credit_Underwriting_Template_manifest_v5.md` - All deal inputs: `extracts/takeout_inputs.json` (property block keyed by property name; loan_quote; dc_msa_mf_cap_rate; treasury curve; ten_year_oct1_reuters). - Load the `xlsx` skill (sandbox_skills) and use its recalc script (`skills/xlsx/scripts/recalc.py` or whatever it documents) for recalculation + error scan. ## Hard write rules (from the template owner — MUST follow) - Write ONLY to manifest-listed input cells. Never overwrite a formula or cross-sheet link. Never change number formats. Never add/remove/reorder rows, columns or sheets. - Respect enum lists exactly as the manifest lists them (e.g. Operating Basis `T12 Actuals`; Per-Metric Basis `Units`; Property Type `Multifamily`; Loan Flavor / Mode / Recourse / Guarantee enums — read exact strings from the manifest or the cell's data validation). - A value you cannot source is NOT written (no `N/A`, `TBD`, `0` stand-ins in numeric inputs) — leave the template default and list it as a gap with its sheet!cell. Never blank a scalar the deal doesn't use. - Populate the Treasury curve (`Prepayment!D27:D35`, date `C36`) on every workbook. - After recalc: zero Excel errors required. Integrity panel `Assumptions!H23:I47`: any `CHECK` = data failure → fix your inputs (never the formulas) and recalc. `FLAG` = credit finding → report it, don't "fix" it. - Never use Python to compute the loan figures you report — every reported figure must be read back from a recalculated cell. ## BASE CASE inputs (the user's specification) — file 1 Assumptions: - C5 property name only; C6 address; C7 "City, ST ZIP"; C8 Multifamily; C9 units; C11 Units; C13 year built; C14 occupancy (property `occupancy`); C15 `T12 Actuals`. C10 (NRA) not available → leave unwritten, list as gap. - F5 Origination; F6 Refinance-Term flavor (exact enum); F7 2026-10-01; **F8 Requested Proceeds = round(0.70 × sale_price)** (this is the bridge balance being taken out — so `Loan Sizing!C35` surplus/(gap) is the answer); F9 0.0638; F10 10; F11 0 (no IO); F12 30; F15 non-recourse enum. F14 origination fee: leave template default. - Credit box: F18 0.80; F19 1.25; F20 leave default; F21 leave default; **F22 0** (no other-income haircut — user sizes on modeled NOI); F23 `user-provided`; F24/F25 leave default 50/50. - `Loan Sizing!D25` = `No` (no IO period, IO DSCR test not applicable) and `Loan Sizing!D26` = `No` (agency execution does not size on debt yield). Keep D24 and D27 = `Yes`. - Benchmarks: F29 market vacancy = (1 − submarket occupancy) **except Springwoods at Lake Ridge, where F29 = 0.05** (user's 5% floor; its submarket physical vacancy 6.02% goes in the downside case); F30 0.05; F32 = property's own modeled OpEx ratio (opex/egi) and **F33 = same property OpEx ratio** — this deliberately neutralizes the template's benchmark OpEx floor because the user asked to size on RealAI's modeled NOI; F35 Yes; F36 0.0573 (Green Street DC MSA multifamily cap, 2Q26 — this is the lender cap); F37 leave blank (no appraisal); F38 submarket in-place avg rent (submarket.ipr.avg); F39–F41 leave unwritten unless a check requires them (not required on T12 basis); F44 `Modest`. - Sponsor: C18 "2021 buyer (unconfirmed)"; C19 guarantee enum `None`; C20 `No`. - Growth: C28 0.03 and C32 0.03 (state as assumption); C29/C30/C31 leave defaults. - Takeout at maturity: C34 0.0638, C35 1.25, C36 0.80, C37 leave default, C38 30, C41 leave default. - Bridge block C47:C51 — leave defaults (flavor is not Bridge). Pro Forma column E (T12) — map from the JSON property block: E5 gpr; E6 vacancy_loss; E7 0; E8 0; E9 other_income; E13 payroll; E14 rm; E15 util; E16 0; E17 0; E18 ga + mkt; E19 mgmt; E20 tax; E21 ins; E22 0. C39 = mgmt / egi (property rate, 4 dp); **C40 = 0** (base case sizes on modeled NOI, which carries no replacement reserve). Leave columns C/D (Year −2/−1) unwritten. Prepayment (Fannie standard yield maintenance): C4 exit month 60; C10 `Yield Maintenance`; lockout 0 months; YM through month 114 then open (open period 6 months); fee floor 1% if the template has a minimum-fee input; follow §7 for exact cells C4:C10 and step-down inputs (write sensible defaults the manifest requires; never leave §7 mandatory scalars blank). Curve D27:D35 from `treasury_curve_2026_09_24_web_anchored` (6m,1y,2y,3y,5y,7y,10y,20y,30y); C36 = 2026-09-24. Defeasance: C53 `No`; C11:C16 per §7b defaults. Check after recalc that `Pro Forma!G28` (lender NOI) ≈ property NOI from the JSON, less only the vacancy-floor effect (Windsor Park's 4.08% modeled vacancy rises to 5%; others should tie within a small mgmt-fee re-strike). Report the tie-out. ## DOWNSIDE CASE — file 2 (copy of the populated base file, then change ONLY these inputs) "What a Fannie underwriter would more likely size today": - F9 = 0.0677 (10Y moved to ~5.32% on 2026-10-01 per Reuters + the quote's 145 bp spread); C34 = 0.0677. - F22 = 0.10 (other-income haircut); F29 = true submarket vacancy (1 − submarket occupancy) for every property; F32 = county benchmark OpEx ratio (`county_bench` in JSON); F33 = 0.42 (template asset-class norm); Pro Forma C40 = 250 ($/unit reserves). ## Output - Directory: the one named in your task. Base file: `<Property Name> - Agency Takeout Screen 2026-10-01.xlsx`; downside: `<Property Name> - Agency Takeout Downside 2026-10-01.xlsx`. - Save a JSON readback `<dir>/readback.json` with, for EACH file: cell address → recalculated value for: Pro Forma E11, E28, G5, G6, G9, G11, G25, G28, G32 (vacancy rate), C43, C44, C45; Assumptions F31, F34; Loan Sizing C14, C17, C18, C19, E24, E27, C29, C30, C31, C35, C36, J5:J13 (label+value); Stress & Break-Even C17, C22, C23, C24; Refi Analysis C20, C28, C30; integrity panel I23:I47 counts of PASS/FLAG/CHECK/- and the names of any FLAG. - Return: the two file paths, the readback table (cell → value) for both files, the Pro Forma G28 vs JSON NOI tie-out, every gap (sheet!cell not written and why), every FLAG, and confirmation of zero Excel errors and zero CHECK. Flag any manifest/template conflict instead of working around it silently.
Build the agency-takeout sizing workbooks for Crystal Woods (344 units, 4905 Southland Ave, Alexandria, VA 22312; 2021-09-01 purchase $98,100,000; bridge balance assumed $68,670,000).
Read subagents/takeout_brief.md in full first — it contains the goal, file paths (template, manifest, extracts/takeout_inputs.json), the template owner's hard write rules, the exact base-case and downside-case inputs cell by cell, and the required readback/output contract. Follow it exactly. Use the "Crystal Woods" block of extracts/takeout_inputs.json for all property figures (county benchmark OpEx 0.4054 = Fairfax County; submarket Annandale/Franconia/Springfield occupancy 0.9438).
Output directory: subagents/crystal_woods/. Load the xlsx skill for recalculation. Return the two workbook paths, the full readback (cell → value) for both files, the G28 NOI tie-out, gaps, FLAGs, and error/CHECK status.
Short answer: the 10-year Fannie takeout does not cover the $68,670,000 bridge in either case.
Files (both recalculated, 0 Excel errors, 0 CHECK):
subagents/crystal_woods/Crystal Woods - Agency Takeout Screen 2026-10-01.xlsxsubagents/crystal_woods/Crystal Woods - Agency Takeout Downside 2026-10-01.xlsxsubagents/crystal_woods/readback.jsonsubagents/crystal_woods/build.py (builds both files from the template and extracts/takeout_inputs.json), then subagents/crystal_woods/readback.py (re-reads the values after skills/xlsx/scripts/recalc.py).Readback (cell, base, downside). All values come from recalculated cells.
| Cell | Base | Downside |
|---|---|---|
| Pro Forma!E11 (T12 EGI) | 9,525,203.84 | 9,525,203.84 |
| Pro Forma!E28 (T12 NOI) | 5,766,427.59 | 5,766,427.59 |
| Pro Forma!G5 (GPR) | 9,007,733.12 | 9,007,733.12 |
| Pro Forma!G6 (vacancy) | 518,627.17 | 518,627.17 |
| Pro Forma!G9 (other income) | 1,036,097.89 | 932,488.10 |
| Pro Forma!G11 (lender EGI) | 9,525,203.84 | 9,421,594.05 |
| Pro Forma!G25 (lender OpEx) | 3,759,203.72 | 3,957,069.50 |
| Pro Forma!G28 (lender NOI) | 5,766,000.12 | 5,464,524.55 |
| Pro Forma!G32 (vacancy rate) | 5.7576% | 5.7576% |
| Pro Forma!C43 (lender cap) | 5.73% | 5.73% |
| Pro Forma!C44 (lender value) | 100,628,274.30 | 95,366,920.59 |
| Pro Forma!C45 (value per unit) | 292,524.05 | 277,229.42 |
| Assumptions!F31 (vacancy floor applied) | 5.7576% | 5.7576% |
| Assumptions!F34 (OpEx ratio applied) | 39.4614% | 42.00% |
| Loan Sizing!C14 (lender NOI) | 5,766,000.12 | 5,464,524.55 |
| Loan Sizing!C17 (DSCR at request) | 1.1210x | 1.0203x |
| Loan Sizing!C18 (debt yield at request) | 8.397% | 7.958% |
| Loan Sizing!C19 (LTV at request) | 68.24% | 72.01% |
| Loan Sizing!E24 (max loan, DSCR test) | 61,583,127.62 | 56,052,606.03 |
| Loan Sizing!E27 (max loan, LTV test) | 80,502,619.44 | 76,293,536.47 |
| Loan Sizing!C29 (supportable proceeds) | 61,583,127.62 | 56,052,606.03 |
| Loan Sizing!C30 (binding constraint) | Debt Service Coverage (Amortizing) Test | same |
| Loan Sizing!C31 (supportable LTV) | 61.20% | 58.78% |
| Loan Sizing!C35 (surplus/(gap)) | (7,086,872.38) | (12,617,393.97) |
| Loan Sizing!C36 (counter-offer) | 61,583,127.62 | 56,052,606.03 |
| Loan Sizing!J5 Term (Months) | 120 | 120 |
| Loan Sizing!J6 Interest-Only (Months) | 0 | 0 |
| Loan Sizing!J7 Amortization (Months) | 360 | 360 |
| Loan Sizing!J8 Amortizing Payment (Monthly) | 428,636.05 | 446,305.63 |
| Loan Sizing!J9 Interest-Only Payment (Monthly) | 365,095.50 | 387,413.25 |
| Loan Sizing!J10 Annual Debt Service - Amortizing | 5,143,632.59 | 5,355,667.50 |
| Loan Sizing!J11 Annual Debt Service - Interest-Only | 4,381,146 | 4,648,959 |
| Loan Sizing!J12 Governing DSCR Basis | Amortizing | Amortizing |
| Loan Sizing!J13 Annual Debt Service - Governing | 5,143,632.59 | 5,355,667.50 |
| Stress & Break-Even!C17 | 5,143,632.59 | 5,355,667.50 |
| Stress & Break-Even!C22 | 6,429,540.74 | 6,694,584.38 |
| Stress & Break-Even!C23 | 0.8733 | 0.9303 |
| Stress & Break-Even!C24 | 0.9267 | 0.9872 |
| Refi Analysis!C20 (balance at maturity) | 58,039,458.45 | 58,604,629.74 |
| Refi Analysis!C28 (stressed refi proceeds) | 0 (artifact, see Gaps) | 0 (artifact) |
| Refi Analysis!C30 (refi gap) | (58,039,458.45) (artifact) | (58,604,629.74) (artifact) |
Integrity panel (Assumptions!I23:I47).
Pro Forma!G28 tie-out (base).
Downside notes.
Gaps and template/manifest conflicts.
Flags to report. Request Within Supportable (the request exceeds the DSCR-bound supportable loan by $7.09M base and $12.62M downside) and DSCR at Request below 1.25x (1.12x base, 1.02x downside) are the real credit findings. The Refi Gap FLAG comes from the blank C37 (gap 3). Sponsor & Guarantee is driven by Guarantee = None and Financials = No. Debt Yield vs Floor comes from the written 9.5% default and does not affect sizing.
Build the agency-takeout sizing workbooks for Windsor Park (220 units, 3937 Penshurst Ln, Woodbridge, VA 22192; 2021-12-27 purchase $62,661,065; bridge balance assumed $43,862,746).
Read subagents/takeout_brief.md in full first — it contains the goal, file paths (template, manifest, extracts/takeout_inputs.json), the template owner's hard write rules, the exact base-case and downside-case inputs cell by cell, and the required readback/output contract. Follow it exactly. Use the "Windsor Park" block of extracts/takeout_inputs.json for all property figures (county benchmark OpEx 0.3992 = Prince William County; submarket Prince George/Manassas occupancy 0.9531).
Output directory: subagents/windsor_park/. Load the xlsx skill for recalculation. Return the two workbook paths, the full readback (cell → value) for both files, the G28 NOI tie-out, gaps, FLAGs, and error/CHECK status.
Files
subagents/windsor_park/Windsor Park - Agency Takeout Screen 2026-10-01.xlsxsubagents/windsor_park/Windsor Park - Agency Takeout Downside 2026-10-01.xlsxsubagents/windsor_park/readback.jsonbuild.py (writes the inputs), then recalc, then readback.py. The recalc needs python3 skills/xlsx/scripts/recalc.py <file> 240. The default 30s timeout exits before saving cached values.Status
Readback (cell → value, base / downside)
Pro Forma:
| Cell | Base | Downside |
|---|---|---|
| E11 | 5,137,669.82 | 5,137,669.82 |
| E28 | 3,214,160.66 | 3,214,160.66 |
| G5 | 4,714,386.83 | 4,714,386.83 |
| G6 | 235,719.34 | 235,719.34 |
| G9 | 615,865.26 | 554,278.73 |
| G11 | 5,094,532.75 | 5,032,946.22 |
| G25 | 1,922,237.48 | 2,113,837.41 |
| G28 (lender NOI) | 3,172,295.27 | 2,919,108.81 |
| G32 | 0.05 | 0.05 |
| C43 | 0.0573 | 0.0573 |
| C44 | 55,362,919.12 | 50,944,307.31 |
| C45 | 251,649.63 | 231,565.03 |
Assumptions:
| Cell | Base | Downside |
|---|---|---|
| F31 | 0.05 | 0.05 |
| F34 | 0.3744 | 0.42 |
Loan Sizing:
| Cell | Base | Downside |
|---|---|---|
| C14 | 3,172,295.27 | 2,919,108.81 |
| C17 (DSCR at request) | 0.9656 | 0.8533 |
| C18 (debt yield at request) | 0.07232 | 0.06655 |
| C19 (LTV at request) | 0.7923 | 0.8610 |
| E24 | 33,881,349.33 | 29,942,889.74 |
| E27 | 44,290,335.30 | 40,755,445.85 |
| C29 (supportable proceeds) | 33,881,349.33 | 29,942,889.74 |
| C30 (binding constraint) | Debt Service Coverage (Amortizing) Test | same |
| C31 | 0.6120 | 0.5878 |
| C35 (surplus/(gap)) | (9,981,396.67) | (13,919,856.26) |
| C36 (counter-offer) | 33,881,349.33 | 29,942,889.74 |
Stress & Break-Even:
| Cell | Base | Downside |
|---|---|---|
| C17 | 3,285,479.10 | 3,420,915.73 |
| C22 | 4,106,848.88 | 4,276,144.66 |
| C23 | 0.9740 | 1.0564 |
| C24 | 1.0253 | 1.1120 |
Refi Analysis:
| Cell | Base | Downside |
|---|---|---|
| C20 (balance at maturity) | 37,072,521.10 | 37,433,522.48 |
| C28 | 0 | 0 |
| C30 | (37,072,521.10) | (37,433,522.48) |
G28 NOI tie-out (base)
FLAGs (credit findings, not fixed)
Gaps and template or manifest conflicts
workbook_integrity_scan returns exit 1 (high findings). These are label-link and blank-reference findings from the template's own structure and the blank C29/C37/C41 cells (see gap 2). They are not caused by any formula edit.Data sources: all property, cap rate, quote and Treasury inputs come from extracts/takeout_inputs.json (Windsor Park block).
Build the agency-takeout sizing workbooks for Springwoods at Lake Ridge (180 units, 12395 Midsummer Ln, Woodbridge, VA 22192; 2021-12-27 purchase $51,338,936; bridge balance assumed $35,937,255).
Read subagents/takeout_brief.md in full first — it contains the goal, file paths (template, manifest, extracts/takeout_inputs.json), the template owner's hard write rules, the exact base-case and downside-case inputs cell by cell, and the required readback/output contract. Follow it exactly (note the Springwoods-specific F29 = 0.05 rule in the base case). Use the "Springwoods at Lake Ridge" block of extracts/takeout_inputs.json for all property figures (county benchmark OpEx 0.3992 = Prince William County; submarket Dale City/Lorton/Woodbridge occupancy 0.9398).
Output directory: subagents/springwoods/. Load the xlsx skill for recalculation. Return the two workbook paths, the full readback (cell → value) for both files, the G28 NOI tie-out, gaps, FLAGs, and error/CHECK status.
Files (recalculated by LibreOffice; 5,209 formulas, 0 Excel errors, 0 CHECK in both):
subagents/springwoods/Springwoods at Lake Ridge - Agency Takeout Screen 2026-10-01.xlsxsubagents/springwoods/Springwoods at Lake Ridge - Agency Takeout Downside 2026-10-01.xlsxsubagents/springwoods/readback.json (both files; I5:J13 label and value included).subagents/springwoods/build.py (writes both files) and subagents/springwoods/readback.py. Rerun order: build.py, then skills/xlsx/scripts/recalc.py on each file, then readback.py.Result: the takeout does not cover the bridge in either case. The base case is short by $10.52M (Loan Sizing!C35) and the downside by $12.86M. The binding constraint is amortizing DSCR in both.
Readback (cell → value)
| Cell | Base | Downside |
|---|---|---|
| Pro Forma!E11 (T12 EGI) | 3,961,239.24 | 3,961,239.24 |
| Pro Forma!E28 (T12 NOI) | 2,379,792.72 | 2,379,792.72 |
| Pro Forma!G5 | 3,675,134.92 | 3,675,134.92 |
| Pro Forma!G6 | 185,405.83 | 221,243.12 |
| Pro Forma!G9 | 471,510.15 | 424,359.14 |
| Pro Forma!G11 | 3,961,239.24 | 3,878,250.93 |
| Pro Forma!G25 | 1,581,336.06 | 1,628,865.39 |
| Pro Forma!G28 | 2,379,903.18 | 2,249,385.54 |
| Pro Forma!G32 (vacancy rate) | 0.05045 | 0.0602 |
| Pro Forma!C43 (cap) | 0.0573 | 0.0573 |
| Pro Forma!C44 (lender value) | 41,534,086.85 | 39,256,292.16 |
| Pro Forma!C45 | 230,744.93 | 218,090.51 |
| Assumptions!F31 | 0.05045 | 0.0602 |
| Assumptions!F34 | 0.3992 | 0.42 |
| Loan Sizing!C14 | 2,379,903.18 | 2,249,385.54 |
| Loan Sizing!C17 (DSCR at request) | 0.8841x | 0.8026x |
| Loan Sizing!C18 (debt yield) | 6.622% | 6.259% |
| Loan Sizing!C19 (LTV) | 86.52% | 91.55% |
| Loan Sizing!E24 | 25,418,293.11 | 23,073,173.23 |
| Loan Sizing!E27 | 33,227,269.48 | 31,405,033.74 |
| Loan Sizing!C29 (supportable proceeds) | 25,418,293.11 | 23,073,173.23 |
| Loan Sizing!C30 (binding test) | Debt Service Coverage (Amortizing) Test | Debt Service Coverage (Amortizing) Test |
| Loan Sizing!C31 | 0.6120 | 0.5878 |
| Loan Sizing!C35 (surplus/(gap)) | (10,518,961.89) | (12,864,081.77) |
| Loan Sizing!C36 (counter-offer) | 25,418,293.11 | 23,073,173.23 |
| Stress & Break-Even!C17 | 2,691,831.02 | 2,802,795.82 |
| Stress & Break-Even!C22 | 3,364,788.78 | 3,503,494.77 |
| Stress & Break-Even!C23 | 1.0344 | 1.0904 |
| Stress & Break-Even!C24 | 1.0894 | 1.1602 |
| Refi Analysis!C20 (balance at maturity) | 30,373,945.22 | 30,669,717.82 |
| Refi Analysis!C28 (stressed refi proceeds) | 0 (artifact, see Flags) | 0 (artifact) |
| Refi Analysis!C30 (refi gap) | (30,373,945.22) | (30,669,717.82) |
Loan Sizing J5:J13:
| Item | Base | Downside |
|---|---|---|
| Term (months) | 120 | 120 |
| Interest-only (months) | 0 | 0 |
| Amortization (months) | 360 | 360 |
| Amortizing payment (monthly) | 224,319.25 | 233,566.32 |
| Interest-only payment (monthly) | 191,066.41 | 202,746.01 |
| Annual debt service, amortizing | 2,691,831.02 | 2,802,795.82 |
| Annual debt service, interest-only | 2,292,796.87 | 2,432,952.16 |
| Governing DSCR basis | Amortizing | Amortizing |
| Annual debt service, governing | 2,691,831.02 | 2,802,795.82 |
Integrity panel (Assumptions!I23:I47)
G28 NOI tie-out
extracts/takeout_inputs.json, Springwoods at Lake Ridge block). Pro Forma!E28 ties to it at 2,379,792.72.Inputs written (per brief)
Gaps (not written)
Judgment calls and template conflicts
Flags
cd /vercel/sandbox; M=scratch/inputs/agents/01a087be-a40c-70fb-88a9-40a61b0703c2/RealAI_Credit_Underwriting_Template_manifest_v5.md; grep -n "C41\|C31\|C30\|C29\|C37" $M | head -30; python3 -c "
import openpyxl
wb=openpyxl.load_workbook('subagents/windsor_park/Windsor Park - Agency Takeout Screen 2026-10-01.xlsx')
ws=wb['Assumptions']
for c in ['B29','C29','B30','C30','B31','C31','B37','C37','B41','C41']: print(c, ws[c].value)
for dv in ws.data_validations.dataValidation:
if any(x in str(dv.sqref) for x in ['C31','C41']): print(dv.sqref, dv.formula1)
"; ls subagents/*/"""Fill the maturity/exit inputs the clean template ships blank (stated assumptions, not sourced data):
C29 cap drift 0 bps/yr, C30 selling costs 2%, C31 sale-exit liquidity Deep, C37 takeout debt-yield floor 8.0%
(agency MF proxy), C41 refinance market depth Deep. These touch only Refi Analysis / exit blocks; Loan Sizing unaffected."""
import glob, openpyxl
for f in sorted(glob.glob("subagents/*/* - Agency Takeout *.xlsx")):
wb = openpyxl.load_workbook(f)
ws = wb["Assumptions"]
ws["C29"] = 0; ws["C30"] = 0.02; ws["C31"] = "Deep"; ws["C37"] = 0.08; ws["C41"] = "Deep"
wb.save(f); print("filled", f)
cd /vercel/sandbox && python3 subagents/fill_exit_gaps.py && for f in subagents/*/*" - Agency Takeout "*.xlsx; do python3 skills/xlsx/scripts/recalc.py "$f" 240 | tail -c 300; echo; done
Now a consolidated read-back of the cells I'll cite, plus the integrity panel.
import glob, openpyxl, json
cells = {"Pro Forma": ["E28", "G6", "G28", "G32", "C44"],
"Loan Sizing": ["F8" , "C17", "C19", "E24", "E27", "C29", "C30", "C35", "J13"],
"Stress & Break-Even": ["C22", "C24"],
"Refi Analysis": ["C20", "C28", "C30"],
"Assumptions": ["F8", "F9"]}
out = {}
for f in sorted(glob.glob("subagents/*/* - Agency Takeout *.xlsx")):
wb = openpyxl.load_workbook(f, data_only=True)
r = {}
for sh, cs in cells.items():
for c in cs:
if sh == "Loan Sizing" and c == "F8": continue
r[f"{sh}!{c}"] = wb[sh][c].value
panel = [wb["Assumptions"][f"I{i}"].value for i in range(23, 48)]
names = [wb["Assumptions"][f"H{i}"].value for i in range(23, 48)]
r["flags"] = [n for n, v in zip(names, panel) if v == "FLAG"]
r["checks"] = [n for n, v in zip(names, panel) if v == "CHECK"]
out[f.split("/")[-1]] = r
print(f.split("/")[-1]); [print(" ", k, v) for k, v in r.items()]
json.dump(out, open("extracts/takeout_readback.json", "w"), indent=1, default=str)
cd /vercel/sandbox && python3 subagents/consolidated_readback.py
cd /vercel/sandbox && ls subagents && python3 subagents/consolidated_readback.py
"""Path-to-close what-ifs layered on the recalculated base-case workbooks (extracts/takeout_readback.json).
Annual loan constant is taken from the workbook (Loan Sizing!J13 / Assumptions!F8), so no loan math is re-derived.
Mark-to-submarket: lift in-place rent to the submarket average; NOI gain = uplift x units x 12 x (1 - vacancy used in
lender case, Pro Forma!G32) x (1 - property mgmt-fee rate)."""
import json
rb = json.load(open("extracts/takeout_readback.json"))
d = json.load(open("extracts/takeout_inputs.json"))
dale_city = d["properties"]["Springwoods at Lake Ridge"]["submarket"]["ipr"]
for name in ["Crystal Woods", "Windsor Park", "Springwoods at Lake Ridge"]:
b = rb[f"{name} - Agency Takeout Screen 2026-10-01.xlsx"]; p = d["properties"][name]
bridge = b["Assumptions!F8"]; noi = b["Pro Forma!G28"]; const = b["Loan Sizing!J13"] / bridge
need = b["Stress & Break-Even!C22"]; dnoi = need - noi
vac = b["Pro Forma!G32"]; mg = p["mgmt"] / p["egi"]; u = p["units"]
rent_lift_mo = dnoi / (u * 12 * (1 - vac) * (1 - mg))
print(f"== {name}: bridge ${bridge:,.0f} lender NOI ${noi:,.0f} NOI needed @1.25x ${need:,.0f} shortfall ${dnoi:,.0f} (+{dnoi/noi:.1%})")
print(f" annual constant {const:.4%}; implied debt yield needed {1.25*const:.2%}; current DY on bridge {noi/bridge:.2%}")
print(f" rent lift to close with NOI alone: ${rent_lift_mo:,.0f}/unit/mo = +{rent_lift_mo/p['ipr']['avg']:.1%} -> ${p['ipr']['avg']+rent_lift_mo:,.0f} vs submarket avg ${p['submarket']['ipr']['avg']:,.0f}")
up = max(p["submarket"]["ipr"]["avg"] - p["ipr"]["avg"], 0)
gain = up * u * 12 * (1 - vac) * (1 - mg)
new_loan = (noi + gain) / 1.25 / const
print(f" mark-to-submarket uplift ${up:,.0f}/mo -> NOI +${gain:,.0f} -> supportable ${new_loan:,.0f}; residual cash-in ${max(bridge-new_loan,0):,.0f}")
print(f" value at 5.73% vs price: ${b['Pro Forma!C44']:,.0f} vs ${p['sale_price']:,.0f} ({b['Pro Forma!C44']/p['sale_price']-1:+.1%}); NOI yield on price {p['noi']/p['sale_price']:.2%}")
print(f" cash-in per unit to close base gap ${-b['Loan Sizing!C35']/u:,.0f}")
wp = d["properties"]["Windsor Park"]["ipr"]
print("Windsor vs Dale City/Lorton/Woodbridge:", {k: f"{wp[k]/dale_city[k]-1:+.1%}" for k in ["1", "2", "avg"]})
cd /vercel/sandbox && python3 scripts/takeout_path_to_close.py
None of the three properties gets covered by an agency takeout at today's quote. The 1.25x debt-coverage test binds long before 80% LTV. At 6.38% with 30-year amortization, covering the bridge balance at 1.25x needs NOI equal to about 9.4% of that balance. Crystal Woods produces 8.4%, Windsor Park 7.2% and Springwoods 6.6%. The shortfalls are about $7.1M, $10.0M and $10.5M.
Throughout, "bridge balance" means 70% of the 2021 price, assumed interest-only with nothing paid down. "Lender NOI" is RealAI's modeled NOI with the 5% vacancy floor applied.
| Crystal Woods | Windsor Park | Springwoods | |
|---|---|---|---|
| Bridge balance (70% of price) | $68.7M | $43.9M | $35.9M |
| RealAI modeled NOI | $5.77M | $3.21M | $2.38M |
| Lender NOI | $5.77M | $3.17M | $2.38M |
| Value at 5.73% cap vs. price paid | $100.6M (+3%) | $55.4M (−12%) | $41.5M (−19%) |
| Supportable Fannie loan | $61.6M | $33.9M | $25.4M |
| Gap | ($7.1M) | ($10.0M) | ($10.5M) |
| Gap per unit | $20.6k | $45.4k | $58.4k |
| Gap in the downside case | ($12.6M) | ($13.9M) | ($12.9M) |
| 1BR | 2BR | 3BR | Average | |
|---|---|---|---|---|
| Crystal Woods | $1,821 | $2,176 | $2,832 | $2,180 |
| Annandale/Franconia/Springfield | $1,920 (−5%) | $2,253 (−3%) | $2,812 (+1%) | $2,155 (+1%) |
| Windsor Park | $1,688 | $1,903 | — | $1,784 |
| Prince George/Manassas | $1,879 (−10%) | $2,213 (−14%) | $2,078 (−14%) | |
| Springwoods | $1,617 | $1,785 | — | $1,696 |
| Dale City/Lorton/Woodbridge | $1,810 (−11%) | $2,085 (−14%) | $2,010 (−16%) |
The data places Windsor Park in a different submarket from Springwoods, even though they share a ZIP code. Against Springwoods' submarket, Windsor still runs 7–9% below on 1BR and 2BR .
Crystal Woods: closeable with cash. Its rents are already at the submarket average, so there's no below-market rent to capture. Closing the gap through NOI alone takes about +$664k, roughly +$177/unit/month . That would put rents about $200 above the submarket average, which isn't realistic. The value has held up, though: the bridge is 68% of today's value . A cash-in refinance of about $7M closes it, and the owner keeps meaningful equity. Expect that to grow toward about $12.6M if rates hold at today's level.
Windsor Park: rent growth plus time plus some cash. Rents are 14% below the submarket average. Bringing them up to that average would add about $711k of NOI and lift proceeds to about $41.5M, leaving about $2.4M to cover with cash. That means about 16% growth in in-place rents, which takes two lease cycles. The owner would need a bridge extension of 18–24 months with an interest-rate cap, probably with a partial paydown as the price of the extension. The bridge is already 79% of today's value , so there's little equity cushion left.
Springwoods: not closeable by operations alone. Even with rents at the submarket average, proceeds only reach about $32.1M, still about $3.9M short. Today the shortfall is $10.5M, or $58k a unit, and the bridge is 87% of value . Realistic options are:
Windsor Park and Springwoods traded the same day, 12/27/2021, so they were probably one portfolio deal, possibly on one cross-collateralized bridge. If so, the combined gap is about $20.5M, and the two would have to be refinanced or recapitalized together.
Files are in your library:
This is a read-only snapshot of a RealAI analysis.