ACRE buys value-add workforce multifamily. In the Atlanta market, find market-rate properties of 150 to 400 units built 1985 to 2008 with no renovation since 2015, occupancy at or above 93%, and average resident FICO of 640 or higher, excluding Carrington Park. For each, show in-place rent per square foot, 12-month in-place rent change, retention, resident median household income, rent-to-income ratio, and the most recent sale date. Rank by in-place rent per square foot, lowest first.
mkdir -p work && cat > work/atl_screen.py << 'EOF'
import csv, io
# name|units|yb|reno|ptype|sale|psf|t12|occ|rti|ret|fico|hhi (from query_data toolu_bdrk_01QH9Zp2widKjEcwypv12tnc)
raw = """The Atlantic BridgeMill|236|1999|2016|RENTAL|2026-04-20|1.29|-0.0186|0.9449|0.1011|0.6695|650.72|166902
Signature at Sugarloaf|268|2000||RENTAL|2019-09-12|1.36|-0.0156|0.9664|0.1716|0.8022|642.58|97814
Park at Towne Lake Apartments|243|1997|2018|RENTAL|2014-08-20|1.38|0.0005|0.9712|0.1797|0.7243|715.47|105819
Camden Shiloh|232|2000||RENTAL|2009-04-20|1.40|-0.0020|0.9698|0.606|0.6853|643.37|30871
Halston Shiloh Valley|300|2001|2016|RENTAL|2022-06-27|1.40|-0.0285|0.9633|0.3012|0.7067|645.57|62189
Hawthorne at Kennesaw|322|2004|2014|RENTAL|2014-11-12|1.43|-0.0167|0.9689|0.5395|0.6211|660.88|36189
Walton Vinings|216|1999||RENTAL|2004-10-13|1.47|-0.0232|0.9583|0.2268|0.7546|657.10|102883
The Eva|300|1998|2011|RENTAL|2026-06-18|1.47|-0.0200|0.9467|0.1573|0.7567|643.30|128208
Atler at Brookhaven|240|1986|2001|RENTAL||1.48|-0.0229|0.9583||0.7333|647.78|
Walden Ridge Apartment Homes|210|2001||RENTAL|2024-11-21|1.49|-0.0417|0.9333|0.1602|0.7762|648.07|119678
The Pointe at Suwanee Station|336|2006||RENTAL|2019-05-14|1.49|-0.0202|0.9583|0.2069|0.6429|663.64|91759
Magnolia Commons|194|2002|2022|RENTAL|2021-12-29|1.51|-0.0059|0.9536|0.2992|0.8196|655.12|66907
Avana Ridenour|255|2001|2014|RENTAL|2020-09-01|1.53|0.0426|0.9412|0.3137|0.7608|672.03|60173
Preserve at Mill Creek|400|2001|2015|RENTAL|2026-05-26|1.53|-0.0091|0.9950|0.2774|0.83|655.67|65656
Wood Pointe Apartment Homes|178|1986|2017|RENTAL|2000-12-05|1.53|0.0005|0.9607|0.2169|0.6348|680.23|81778
The Boro|266|2000|2023|RENTAL|2021-12-14|1.54|0.0556|0.9662|0.1765|0.7895|667.12|116523
The Boro - Kings|152|2008||RENTAL|2021-12-14|1.57|0.0769|0.9671|0.2231|0.7961|648.74|94193
Retreat at River Park|222|1998|2010|RENTAL|2022-02-23|1.57|0.0164|0.9550|0.2766|0.6532|647.40|66823
Retreat at Johns Creek|352|1996|2007|RENTAL|2014-11-10|1.60|-0.0268|0.9489|0.1971|0.7102|710.33|105326
Avana TownPark|300|1995|2020|RENTAL|2025-02-04|1.61|0.0277|0.9500|0.2772|0.7|640.68|65965
Flats at North Springs|396|1999|2014|RENTAL|2020-05-29|1.62|-0.0020|0.9369|0.2239|0.7601|725.05|95414
Avonlea on The River|294|1996|2016|RENTAL|1999-11-23|1.62|-0.0010|0.9592|0.2125|0.8095|712.23|106555
Barrett Walk|290|2002||RENTAL|2013-05-17|1.63||0.9414|0.2719|0.731|662.27|64005
Camden Creekstone|223|2002||RENTAL|2012-07-12|1.64|-0.0393|0.9731|0.1833|0.6861|640.44|102689
Willowest in Collier Hills|396|1997|2016|RENTAL|2021-08-19|1.65|-0.0288|0.9419|0.2176|0.6591|672.42|78589
Amberley Senior Living|168|2001||RENTAL|2017-03-01|1.67||1.0000|0.1946||676.50|92500
The Greens at Peachtree City|198|1986|2016|RENTAL|2021-10-28|1.68|0.0389|0.9495|0.2594|0.7273|684.70|81415
Belmont Place|326|2004|2020|RENTAL|2020-12-28|1.68|0.0049|0.9969|0.2075|0.5951|655.03|106045
The Atlantic Loring Heights|278|1990|2010|RENTAL|2014-01-21|1.70|-0.0144|0.9388|0.1448|0.7302|659.69|102510
Monroe Place Apartments|241|2000|2011|RENTAL|2001-08-22|1.71|-0.0026|0.9627|0.2078|0.8838|662.38|88063
Lumin East Cobb|323|1990|2018|RENTAL|2018-08-08|1.71|-0.0196|0.9690|0.2129|0.1796|642.44|93153
Park Trace Apartments|260|1988||RENTAL|2005-08-09|1.71|-0.0053|0.9577|0.2312|0.75|666.57|68058
Tuscany at Lindbergh|324|2001||RENTAL|2013-06-11|1.72|-0.0016|0.9815|0.2226|0.6451|666.41|91699
MAA Stratford|250|2000|2023|RENTAL|1999-03-25|1.72|-0.0088|0.9640|0.1823|0.596|680.13|105767
Camden Brookwood|359|2002||RENTAL|2003-06-24|1.73|0.0140|0.9359|0.1937|0.7019|666.45|92872
Aven Chastain|212|1997|2010|RENTAL|2020-06-18|1.73|-0.0142|0.9434|0.209|0.7264|687.89|83206
The Peninsula at Buckhead|311|2008|2017|RENTAL|2017-07-13|1.73|0.0252|0.9743|0.2226|0.6559|653.90|86217
Willowest in Lindbergh|396|1998|2016|RENTAL|2025-04-30|1.73|-0.0076|0.9444|0.2443|0.6212|684.18|73741
Perimeter 5550|165|1995|2015|RENTAL|2014-05-19|1.76|-0.0032|0.9515|0.1858|0.5394|653.54|98151
The Waterford on Piedmont|153|2004||RENTAL|2014-10-22|1.77|0.0090|0.9477|0.313|0.7582|668.31|72835
1660 Peachtree Midtown|355|1999|2016|RENTAL|2026-01-12|1.78||0.9408|0.1859|0.6873|653.73|99727
MAA Buckhead|230|2002|2013|RENTAL|2012-05-10|1.80|0.0073|0.9391|0.1538|0.5609|673.51|129749
Bell Perimeter Center|380|2008|2013|RENTAL|2005-04-13|1.82|-0.0247|0.9526|0.195|0.6553|678.75|106723
Townhouse Atlanta|254|1997||RENTAL||1.85|0.0015|0.9685|0.3649|0.7953|647.80|60116
Camden Midtown Atlanta|296|2002||RENTAL|2008-09-15|1.86|0.0256|0.9628|0.2019|0.6993|671.46|95015
Azalea Springs|232|1995|2007|RENTAL|2020-01-15|1.86||0.9569|0.2524|0.8491|662.04|67031
Bower Westside|336|2008||RENTAL|2022-10-13|1.87|-0.0106|0.9315|0.2578|0.1399|709.15|72612
West Inman Lofts|205|2007||RENTAL|2013-07-18|1.88|-0.0034|0.9415|0.1353|0.6585|715.39|141705
Cortland 3131|268|1992|2014|RENTAL|2013-03-28|1.89|-0.0052|0.9776|0.1575|0.7351|722.58|112749
Buckhead 960|305|2005||RENTAL|2017-07-07|1.91|0.0088|0.9377|0.2177|0.7213|704.51|91262
The Quinn at Perimeter|312|2006||RENTAL|2025-08-19|1.91|-0.1088|0.9551|0.2017|0.7404|697.78|101134
Morningside Courts|172|1990|2010|RENTAL|2013-08-28|1.97|-0.0128|0.9477|0.1861|0.6686|679.51|89162
Meridian Buckhead|232|1997||CONDO||1.98|0.1864|0.9957|0.1765||747.85|180177
Avana on Main|364|2007|2022|RENTAL||1.99|0.0180|0.9396|0.2596|0.4368|684.41|75315
Park at Johns Creek 55+ Active Adult Living|245|2004||RENTAL|2014-07-28|2.01|-0.0081|0.9918|0.2559|0.8367|771.14|90724
Westmount at Ashwood|160|2008|2023|RENTAL|2021-09-30|2.02|-0.0126|0.9625|0.1802|0.7313|712.21|120374
The Dakota|227|2000||CONDO||2.04|0.0164|1.0000|0.1639||803.40|256309
Flats at Perimeter Place|323|2006||RENTAL|2022-11-29|2.08||0.9443|0.1981|0.5759|673.24|117803
Lenox Green Condominiums|221|1991||CONDO||2.09|-0.1905|0.9910|0.2397||697.91|85107
The Paramount at Buckhead|300|2004||CONDO||2.13|-0.0206|1.0000|0.1653||740.17|192409
Biltmore at Midtown|276|2001||RENTAL|2018-05-11|2.17|-0.0082|0.9674|0.1632|0.5145|718.51|110685
The Grandview|225|1990||CONDO||2.18|0.0000|1.0000|0.1372||737.90|144366
Habersham of Buckhead|182|1989|2024|CONDO||2.21|0.0000|0.9945|0.167||745.74|118197
Mariposa Lofts|253|2004||RENTAL|2014-08-28|2.21|0.0136|0.9842|0.1301|0.7194|743.58|187398
Tuscany|230|1996||CONDO||2.23|0.0279|0.9957|0.1333||783.58|166490
MAA Piedmont Park|188|1999||RENTAL|2008-10-01|2.26|-0.0009|0.9628|0.1655|0.6064|729.79|130539
Windsor at Midtown|188|2001||RENTAL|1999-08-30|2.38|-0.0190|0.9681|0.1141|0.6702|762.20|215802
Union Eleven|339|2007|2025|RENTAL|2021-12-28|2.39|-0.0013|0.9617|0.2933|0.8171|671.41|83378
Mayfair Tower|327|1990|2002|CONDO||2.47|-0.0444|1.0000|0.1738||752.34|138107
AMLI Old 4th Ward|337|2008||RENTAL|2012-02-29|2.56|0.0202|0.9703|0.1816|0.5549|761.58|163313
Block Lofts|244|2005||RENTAL||2.63|0.0019|0.9713|0.1513|0.6393|756.95|167932"""
cols="name units yb reno ptype sale psf t12 occ rti ret fico hhi".split()
rows=[dict(zip(cols,l.split('|'))) for l in raw.splitlines()]
print("pulled",len(rows))
f=lambda x: float(x) if x else None
keep=[];drop={'renovated_2015+':[], 'condo':[], 'age_restricted':[], 'carrington':[]}
for r in rows:
if 'carrington' in r['name'].lower(): drop['carrington'].append(r['name']); continue
if r['ptype']!='RENTAL': drop['condo'].append(r['name']); continue
if r['reno'] and int(r['reno'])>=2015: drop['renovated_2015+'].append(r['name']+f" ({r['reno']})"); continue
if 'senior' in r['name'].lower() or '55+' in r['name']: drop['age_restricted'].append(r['name']); continue
keep.append(r)
keep.sort(key=lambda r:(f(r['psf']), r['name']))
for k,v in drop.items(): print(k,len(v),v)
print("final",len(keep))
out=io.StringIO(); w=csv.writer(out)
w.writerow(["rank","name","units","year_built","year_renovated","inplace_psf","inplace_t12_chg","occupancy","retention","hhi_median","rent_to_income","fico","latest_sale"])
for i,r in enumerate(keep,1):
w.writerow([i,r['name'],r['units'],r['yb'],r['reno'] or '-',r['psf'],
f"{f(r['t12'])*100:.1f}%" if r['t12'] else 'n/a', f"{f(r['occ'])*100:.1f}%",
f"{f(r['ret'])*100:.0f}%" if r['ret'] else 'n/a', r['hhi'] or 'n/a',
f"{f(r['rti'])*100:.1f}%" if r['rti'] else 'n/a', r['fico'], r['sale'] or 'none recorded'])
open('work/atl_shortlist.csv','w').write(out.getvalue()); print(out.getvalue())
import statistics as s
print("median psf", s.median(f(r['psf']) for r in keep))
print("rti>30%", [r['name'] for r in keep if r['rti'] and f(r['rti'])>0.30])
print("ret<50%", [r['name'] for r in keep if r['ret'] and f(r['ret'])<0.5])
print("sold<=2015 or none", sum(1 for r in keep if not r['sale'] or r['sale']<'2016'))
EOF
python3 work/atl_screen.pyI found 40 properties that meet every criterion. Carrington Park isn't among them, so leaving it out changed nothing. The cheapest rents, which is where a value-add play would start, are in suburban Gwinnett and Cobb. Rents rise toward Midtown and Buckhead, and the Buckhead/Midtown names at the bottom are core-plus properties rather than workforce deals.
How I read the filters:
| Rank | Property | Units | Year Built | Last Renovation | In-Place Rent/SF ($, avg) | 12-Mo In-Place Rent Change (median) | Occupancy | Retention (T12) | Resident Median HH Income ($) | Rent-to-Income | Avg FICO | Most Recent Sale Date |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | 268 | 2000 | - | $1.36 | -1.6% | 96.6% | 80% | $97,814 | 17.2% | 642.58 | 2019-09-12 | |
| 2 | 232 | 2000 | - | $1.40 | -0.2% | 97.0% | 69% | $30,871 | 60.6%1 | 643.37 | 2009-04-20 | |
| 3 | 322 | 2004 | 2014 | $1.43 | -1.7% | 96.9% | 62% | $36,189 | 53.9%1 | 660.88 | 2014-11-12 | |
| 4 | 300 | 1998 | 2011 | $1.47 | -2.0% | 94.7% | 76% | $128,208 | 15.7% | 643.30 | 2026-06-18 | |
| 5 | 216 | 1999 | - | $1.47 | -2.3% | 95.8% | 75% | $102,883 | 22.7% | 657.10 | 2004-10-13 | |
| 6 | 240 | 1986 | 2001 | $1.48 | -2.3% | 95.8% | 73% | n/a2 | n/a2 | 647.78 | none recorded2 | |
| 7 | 336 | 2006 | - | $1.49 | -2.0% | 95.8% | 64% | $91,759 | 20.7% | 663.64 | 2019-05-14 | |
| 8 | 210 | 2001 | - | $1.49 | -4.2% | 93.3% | 78% | $119,678 | 16.0% | 648.07 | 2024-11-21 | |
| 9 | 255 | 2001 | 2014 | $1.53 | 4.3% | 94.1% | 76% | $60,173 | 31.4% | 672.03 | 2020-09-01 | |
| 10 | 222 | 1998 | 2010 | $1.57 | 1.6% | 95.5% | 65% | $66,823 | 27.7% | 647.40 | 2022-02-23 | |
| 11 | 152 | 2008 | - | $1.57 | 7.7% | 96.7% | 80% | $94,193 | 22.3% | 648.74 | 2021-12-14 | |
| 12 | 352 | 1996 | 2007 | $1.60 | -2.7% | 94.9% | 71% | $105,326 | 19.7% | 710.33 | 2014-11-10 | |
| 13 | 396 | 1999 | 2014 | $1.62 | -0.2% | 93.7% | 76% | $95,414 | 22.4% | 725.05 | 2020-05-29 | |
| 14 | 290 | 2002 | - | $1.63 | n/a2 | 94.1% | 73% | $64,005 | 27.2% | 662.27 | 2013-05-17 | |
| 15 | 223 | 2002 | - | $1.64 | -3.9% | 97.3% | 69% | $102,689 | 18.3% | 640.44 | 2012-07-12 | |
| 16 | 278 | 1990 | 2010 | $1.70 | -1.4% | 93.9% | 73% | $102,510 | 14.5% | 659.69 | 2014-01-21 | |
| 17 | 241 | 2000 | 2011 | $1.71 | -0.3% | 96.3% | 88% | $88,063 | 20.8% | 662.38 | 2001-08-22 | |
| 18 | 260 | 1988 | - | $1.71 | -0.5% | 95.8% | 75% | $68,058 | 23.1% | 666.57 | 2005-08-09 | |
| 19 | 324 | 2001 | - | $1.72 | -0.2% | 98.2% | 65% | $91,699 | 22.3% | 666.41 | 2013-06-11 | |
| 20 | 212 | 1997 | 2010 | $1.73 | -1.4% | 94.3% | 73% | $83,206 | 20.9% | 687.89 | 2020-06-18 | |
| 21 | 359 | 2002 | - | $1.73 | 1.4% | 93.6% | 70% | $92,872 | 19.4% | 666.45 | 2003-06-24 | |
| 22 | 153 | 2004 | - | $1.77 | 0.9% | 94.8% | 76% | $72,835 | 31.3% | 668.31 | 2014-10-22 | |
| 23 | 230 | 2002 | 2013 | $1.80 | 0.7% | 93.9% | 56% | $129,749 | 15.4% | 673.51 | 2012-05-10 | |
| 24 | 380 | 2008 | 2013 | $1.82 | -2.5% | 95.3% | 66% | $106,723 | 19.5% | 678.75 | 2005-04-13 | |
| 25 | 254 | 1997 | - | $1.85 | 0.1% | 96.9% | 80% | $60,116 | 36.5% | 647.80 | none recorded2 | |
| 26 | 232 | 1995 | 2007 | $1.86 | n/a2 | 95.7% | 85% | $67,031 | 25.2% | 662.04 | 2020-01-15 | |
| 27 | 296 | 2002 | - | $1.86 | 2.6% | 96.3% | 70% | $95,015 | 20.2% | 671.46 | 2008-09-15 | |
| 28 | 336 | 2008 | - | $1.87 | -1.1% | 93.2% | 14%3 | $72,612 | 25.8% | 709.15 | 2022-10-13 | |
| 29 | 205 | 2007 | - | $1.88 | -0.3% | 94.2% | 66% | $141,705 | 13.5% | 715.39 | 2013-07-18 | |
| 30 | 268 | 1992 | 2014 | $1.89 | -0.5% | 97.8% | 74% | $112,749 | 15.8% | 722.58 | 2013-03-28 | |
| 31 | 305 | 2005 | - | $1.91 | 0.9% | 93.8% | 72% | $91,262 | 21.8% | 704.51 | 2017-07-07 | |
| 32 | 312 | 2006 | - | $1.91 | -10.9% | 95.5% | 74% | $101,134 | 20.2% | 697.78 | 2025-08-19 | |
| 33 | 172 | 1990 | 2010 | $1.97 | -1.3% | 94.8% | 67% | $89,162 | 18.6% | 679.51 | 2013-08-28 | |
| 34 | 323 | 2006 | - | $2.08 | n/a2 | 94.4% | 58% | $117,803 | 19.8% | 673.24 | 2022-11-29 | |
| 35 | 276 | 2001 | - | $2.17 | -0.8% | 96.7% | 51% | $110,685 | 16.3% | 718.51 | 2018-05-11 | |
| 36 | 253 | 2004 | - | $2.21 | 1.4% | 98.4% | 72% | $187,398 | 13.0% | 743.58 | 2014-08-28 | |
| 37 | 188 | 1999 | - | $2.26 | -0.1% | 96.3% | 61% | $130,539 | 16.6% | 729.79 | 2008-10-01 | |
| 38 | 188 | 2001 | - | $2.38 | -1.9% | 96.8% | 67% | $215,802 | 11.4% | 762.20 | 1999-08-30 | |
| 39 | 337 | 2008 | - | $2.56 | 2.0% | 97.0% | 55% | $163,313 | 18.2% | 761.58 | 2012-02-29 | |
| 40 | 244 | 2005 | - | $2.63 | 0.2% | 97.1% | 64% | $167,932 | 15.1% | 756.95 | none recorded2 |
Best fits for the value-add workforce strategy:
Things to check before relying on the numbers:
For Signature at Sugarloaf in Duluth, GA: who lives there — income, income distribution, credit, rent to income, liquidity, etc. Then compute the comp-supported market gap by bedroom type against renovated peers in the same submarket built 1990 to 2008 with 150 to 400 units, keeping renovation premium separate. Can the current residents absorb the renovated rent?
mkdir -p work && cat > work/sugarloaf_mtm.py << 'EOF'
"""Signature at Sugarloaf: tenant capacity + comp-supported mark-to-market by bedroom.
Sources: subject rent roll (toolu_bdrk_01K8N7swzk1FzVtCBVXV7uBd p1, toolu_bdrk_01WkwNdTr3QuFCV4KkJmDUDu p2),
subject profile (toolu_bdrk_01UFWgSso8D4ftJ8epmf4NJ5), peers (toolu_bdrk_013MwHXhpKy8R8r8y89sXXws),
submarket (toolu_bdrk_0146Z8wQvyrsj3G12f1PvjHz)."""
import statistics as st
# ---- rent roll: bed|status|in_place|leased(YYYY-MM)
rr = """4 O 1604 2025-08|3 O 2029 2026-05|3 O 1335 2025-10|1 O 1403 2025-01|4 O 1789 2026-01|1 O 1160 2025-01|1 O 1186 2024-10|2 O 1161 2025-10|1 O 1223 2024-08|2 O 1611 2026-05|2 O 1108 2025-06|3 O 1382 2026-03|1 O 1295 2026-07|1 O 955 2025-10|2 O 1471 2024-12|2 O - 2024-07|2 O 1355 2024-12|4 O 1978 2025-10|3 O 1861 2025-03|3 V - 2024-07|3 O 1594 2026-04|2 O 1208 2025-09|2 O 1345 2025-10|1 O 1196 2026-06|2 O 1346 2026-03|2 O 1287 2024-12|1 O 1160 2025-01|3 O 1373 2026-08|2 O 1230 2025-06|2 O 1501 2025-02|2 O 1278 2025-11|3 O 1765 2025-07|3 O 1895 2026-02|1 O 1140 2024-11|2 O 1486 2025-01|2 O 1429 2024-09|1 O 1288 2024-08|3 O 1902 2026-07|2 O 1222 2025-12|2 V - 2026-01|2 O 1457 2026-05|3 O 1488 2025-07|2 O 1240 2025-10|1 O 1270 2026-06|3 O 1397 2025-02|2 O 1670 2024-07|2 O 1402 2025-11|1 O 1413 2025-02|3 O - 2026-08|2 O 1312 2025-05|3 V - 2024-07|3 O 1196 2024-11|2 O - 2024-07|3 O 1818 2025-02|1 O 1001 2025-10|3 O 1680 2024-08|3 O 1633 2025-03|1 O 1125 2026-05|2 O 1403 2025-10|2 O 1440 2026-02|2 O 1376 2024-07|1 O 1295 2025-05|3 O 1720 2024-07|3 O 1417 2024-11|1 O 1322 2026-03|2 O 1594 2026-06|3 O 1591 2026-04|3 O - 2024-07|2 O 1731 2024-07|2 O 1520 2024-10|2 O 1183 2025-11|1 O 1113 2024-12|3 O 2097 2025-03|2 V - 2025-05|2 O 1342 2026-03|3 O 2156 2026-09|3 O 1681 2025-07|3 O 1331 2025-08|2 O 1412 2024-09|1 O 1248 2024-12|2 O 1821 2024-07|1 O - -|1 V - 2026-02|1 O 1096 2024-10|1 V - 2025-07|3 O 1384 2025-11|3 O 1294 2024-11|4 V - 2026-08|1 O 1388 2024-10|2 O 1417 2025-11|3 V - 2026-09|4 O 1850 2026-02|4 O 1916 2026-06|3 O 1616 2025-07|3 O 1640 2024-07|1 O 1090 2025-06|3 O 1577 2025-06|4 O 1905 2026-05|2 O 1543 2024-12|2 O 1463 2025-10|2 O 1203 2025-12|3 O 1400 2024-12|2 O 1433 2026-03|1 O 1273 2025-04|3 O 1807 2026-07|3 O 1752 2026-08|3 O 1561 2025-08|1 O 1195 2026-05|1 O 1172 2026-01|2 O 1563 2025-07|1 O 1008 2025-01|2 O 1282 2025-06|2 O 1391 2025-08|2 O 1267 2024-11|1 O 1404 2026-04|3 O 1783 2024-08|2 O 1529 2024-08|1 O 958 2026-03|1 O 1356 2025-01|1 O 1151 2025-08|3 O 1358 2025-08|3 O 1549 2025-11|2 O 1193 2024-11|1 O 1102 2026-02|4 O 1610 2025-07|3 O 1890 2026-02|2 O 1346 2025-08|1 O 1311 2025-11|2 O 1342 2026-08|3 O 1700 2026-02|1 O 1247 2024-08|1 V - 2025-10|3 O 1537 2026-06|2 O 1531 2026-09|1 O 1340 2024-08|1 O 1163 2026-04|2 O 1444 2024-09|2 O 1237 2024-11|2 O 1441 2026-08|2 O 1791 2024-07|2 O 1215 2025-09|3 O 1856 2024-07|3 O 1782 2025-03|1 O 1140 2025-12"""
units=[u.split() for u in rr.split('|')]
print("rent-roll rows:",len(units))
TOTAL=268
mix={b:sum(1 for u in units if u[0]==b) for b in '1234'}
print("sample mix:",mix)
est={b:round(mix[b]/len(units)*TOTAL) for b in mix}
print("est. unit mix scaled to 268:",est)
inplace={b:[int(u[2]) for u in units if u[0]==b and u[2]!='-'] for b in '1234'}
for b in '1234': print(f"{b}BR in-place rent-roll mean ${st.mean(inplace[b]):.0f} (n={len(inplace[b])})")
# lease age: leased > 12 mo before 2026-09 (signed on/before 2025-09)
dated=[u for u in units if u[1]=='O' and u[3]!='-']
old=sum(1 for u in dated if u[3]<='2025-09'); print(f"occupied units whose last observed lease is >12mo old (renewed/rolling): {old}/{len(dated)} = {old/len(dated):.1%}")
# ---- subject by-bed (property snapshot, in-place avg by bed)
subj={'1':1205.34,'2':1399.43,'3':1636.59}
subj_ask={'1':1268.33,'2':1538.00,'3':1907.00}
# ---- peers: name, yr_reno, unit_sf, ip1,ip2,ip3, ask1,ask2,ask3 (market-rate, unrestricted, rent data present)
P=[
("ARIUM Johns Creek",None,1241,1426.67,1865.00,2318.05,1685.86,1855.93,2507.57),
("Astor Place",2014,1036,1337.05,1578.82,1822.07,1420.55,1642.23,1904.50),
("MAA River Oaks",None,1277,1463.23,1684.13,1938.40,1327.00,1694.00,1938.00),
("The Berkeley",None,1093,1326.59,1584.59,1876.68,1247.00,1584.48,2315.00),
("The Columns at Club Drive",2008,1133,1276.77,1559.59,1884.28,1203.00,1531.93,1789.14),
("Andover at Johns Creek",2013,1098,1546.48,1957.78,2375.41,1588.00,1766.00,2264.67),
("MAA Berkeley Lake",None,1356,1496.15,1744.32,2023.76,None,1765.00,1900.67),
("St. Andrews Apartment Homes",2008,1265,1656.62,1860.98,2193.51,1938.50,1966.33,2193.50),
("MAA Prescott",None,1069,1371.08,1655.43,2077.74,1417.29,1669.78,None),
("MAA River Place",None,1334,1449.68,1698.95,2042.02,None,1687.25,1861.33),
("Retreat at Johns Creek",2007,1101,1511.94,1758.20,2086.74,1594.44,1802.69,2258.00),
("The Reserve at Johns Creek Walk",None,1162,1566.22,1964.53,2567.71,1580.18,1893.00,2777.50),
("Reflections on Sweetwater",2008,936,1291.78,1598.77,1961.88,1323.46,1670.25,1986.00),
("The Veranda",2011,1316,1400.74,1747.51,2076.95,1404.33,1737.94,2066.57),
("Avonlea on The River",2016,1197,1577.66,1980.41,2359.97,1502.00,2282.40,3336.00),
("The Quinn Sugarloaf",2016,991,1296.73,1627.07,2039.76,1447.13,1786.62,2201.00),
("The Reserve at Sugarloaf",2021,1225,1462.46,1863.51,2148.66,1496.33,1936.70,None),
("Lealand Place",2021,1026,1269.13,1473.58,1820.63,1235.56,1539.13,1816.00),
("Grande Club",2020,954,1317.33,1600.78,1982.47,1309.77,1604.73,None),
("The Hartley at Sweetwater Creek",2016,1070,1255.49,1540.70,1791.96,1261.75,1621.30,1840.00),
("Retreat at Sweetwater",2022,1042,1307.03,1565.88,1856.12,1687.70,1846.36,2487.00),
("Parc 85",2020,983,1323.71,1519.39,1884.79,1413.00,1646.00,None),
("The Maddox",2019,1196,1443.33,1725.05,1995.47,1406.50,1763.38,1962.50),
]
ren=[p for p in P if p[1] and p[1]>=2015]; cls=[p for p in P if not(p[1] and p[1]>=2015)]
print(f"\nrenovated peers (reno>=2015): {len(ren)}; classic peers: {len(cls)}")
print("renovated:",[p[0] for p in ren])
def med(g,i): v=[p[i] for p in g if p[i] is not None]; return st.median(v),len(v)
print("median avg unit SF renovated %.0f classic %.0f subject 1065"%(med(ren,2)[0],med(cls,2)[0]))
occ_rate=0.9664
rows=[];tot_mtm=tot_reno=0
print("\nBed | Subject IP | Classic IP med | Renov IP med | MTM gap $ (%) | Reno prem $ | Total gap $ (%) | Ask: classic / renov")
for k,b in enumerate('123'):
c,nc=med(cls,3+k); r,nr=med(ren,3+k); ca,_=med(cls,6+k); ra,nra=med(ren,6+k)
s=subj[b]; mtm=max(c-s,0); prem=r-c; tot=r-s
u=est[b]*occ_rate
tot_mtm+=mtm*12*u; tot_reno+=prem*12*u
rows.append((b,s,c,r,mtm,prem,tot))
print(f"{b}BR | {s:.0f} | {c:.0f} (n={nc}) | {r:.0f} (n={nr}) | {c-s:+.0f} ({(c-s)/c:+.1%}) | {prem:+.0f} ({prem/c:+.1%}) | {tot:+.0f} ({tot/r:+.1%}) | {ca:.0f} / {ra:.0f} (n={nra})")
print(f"4BR: subject in-place $1807; no peer 4BR data -> excluded from gap")
print(f"\nAnnualized MTM upside (1-3BR, occupied): ${tot_mtm:,.0f}; renovation premium: ${tot_reno:,.0f}; combined ${tot_mtm+tot_reno:,.0f}")
# ---- affordability of renovated rent for CURRENT residents
hhi_med=97814
bins=[(50000,75000,0.126984),(75000,100000,0.761905),(100000,150000,0.111111)]
def share_at_least(x):
s=0
for lo,hi,p in bins:
if x<=lo: s+=p
elif x<hi: s+=p*(hi-x)/(hi-lo)
return s
wavg_ren=sum(est[b]*[r for (bb,_,_,r,_,_,_) in rows if bb==b][0] for b in '123')/sum(est[b] for b in '123')
wavg_ip=sum(est[b]*subj[b] for b in '123')/sum(est[b] for b in '123')
print(f"\nmix-weighted 1-3BR in-place ${wavg_ip:.0f} -> renovated-peer rent ${wavg_ren:.0f} ({wavg_ren/wavg_ip-1:+.1%})")
for lab,rent in [("current in-place",wavg_ip),("renovated peer",wavg_ren)]+[(f"{b}BR renovated",[r for (bb,_,_,r,_,_,_) in rows if bb==b][0]) for b in '123']:
rti=rent*12/hhi_med; q3=rent*36
print(f"{lab:18s} rent ${rent:,.0f}: RTI at median income {rti:.1%}; income needed at 3x ${q3:,.0f}; share of households qualifying {share_at_least(q3):.0%}")
# break-even rent at 30% RTI for median, and for bottom-quintile proxy ($50-75k bin midpoint 62.5k)
print(f"rent at 30% of median income: ${hhi_med*0.30/12:,.0f}/mo; at 30% of $62.5K (lower band): ${62500*0.30/12:,.0f}/mo")
print(f"\nsubmarket: median HHI $160,501 vs subject $97,814 ({97814/160501-1:+.1%}); FICO 721 vs 643; past-due 6.9% vs 13.7%")
EOF
python3 work/sugarloaf_mtm.pycat > work/sugarloaf_gap_psf.py << 'EOF'
"""Size-normalized comp gap by bedroom for Signature at Sugarloaf (source: toolu_bdrk_01WCC1LirwQhkb9GpmbASZEK;
subject bed SF from rent roll toolu_bdrk_01K8N7swzk1FzVtCBVXV7uBd). Peer $/SF x subject SF -> comp rent in subject-sized units.
Renovated = year_renovated >= 2015. Excludes affordable/income-restricted/senior (Magnolia Pointe, Flats at Riverview, Sweetwater Terraces)."""
import statistics as st
SF={'1':814,'2':1079,'3':1201}; subj_psf={'1':1.48,'2':1.30,'3':1.36}; subj_ip={'1':1205.34,'2':1399.43,'3':1636.59}
units={'1':73,'2':99,'3':82}; occ=0.9664
# name, county, reno, psf1, psf2, psf3, psf_avg
P=[("ARIUM Johns Creek","F",None,1.70,1.46,1.45,1.51),("Avonlea on The River","F",2016,1.80,1.52,1.49,1.62),
("Astor Place","G",2014,1.73,1.43,1.38,1.52),("The Quinn Sugarloaf","G",2016,1.79,1.48,1.46,1.61),
("MAA River Oaks","G",None,1.60,1.30,1.33,1.37),("The Berkeley","G",None,1.57,1.34,1.31,1.42),
("The Columns at Club Drive","G",2008,1.48,1.32,1.33,1.36),("Andover at Johns Creek","F",2013,1.85,1.58,1.70,1.70),
("The Reserve at Sugarloaf","G",2021,1.81,1.41,1.32,1.53),("Lealand Place","G",2021,1.53,1.27,1.26,1.40),
("Grande Club","G",2020,1.78,1.41,1.30,1.57),("MAA Berkeley Lake","G",None,1.51,1.31,1.28,1.34),
("The Hartley at Sweetwater Creek","G",2016,1.73,1.32,1.36,1.44),("St. Andrews","F",2008,1.60,1.50,1.49,1.51),
("MAA Prescott","G",None,1.55,1.36,1.40,1.46),("Retreat at Sweetwater","G",2022,1.71,1.37,1.33,1.52),
("MAA River Place","G",None,1.60,1.30,1.31,1.34),("Retreat at Johns Creek","F",2007,1.72,1.58,1.47,1.60),
("The Reserve at Johns Creek Walk","F",None,1.81,1.68,1.61,1.71),("Parc 85","G",2020,1.64,1.42,1.39,1.51),
("The Maddox","G",2019,1.65,1.30,1.40,1.43),("Reflections on Sweetwater","G",2008,1.75,1.46,1.49,1.61),
("The Veranda","G",2011,1.44,1.29,1.28,1.33)]
isren=lambda p: p[2] is not None and p[2]>=2015
def run(label, pool):
ren=[p for p in pool if isren(p)]; cls=[p for p in pool if not isren(p)]
print(f"\n=== {label}: classic n={len(cls)}, renovated n={len(ren)} ===")
print("classic:",", ".join(p[0] for p in cls)); print("renovated:",", ".join(p[0] for p in ren))
ca=st.median(p[6] for p in cls); ra=st.median(p[6] for p in ren)
print(f"property avg in-place $/SF: subject 1.36 | classic median {ca:.2f} | renovated median {ra:.2f} | reno premium {ra-ca:+.2f} ({(ra-ca)/ca:+.1%})")
tm=tr=0; out={}
for k,b in enumerate('123'):
c=st.median(p[3+k] for p in cls); r=st.median(p[3+k] for p in ren)
cr=c*SF[b]; rr=r*SF[b]; s=subj_ip[b]
mtm=cr-s; prem=rr-cr; tot=rr-s
tm+=max(mtm,0)*12*units[b]*occ; tr+=max(prem,0)*12*units[b]*occ
out[b]=rr
print(f"{b}BR ({SF[b]} SF): subject ${s:.0f} (${subj_psf[b]:.2f}) | classic ${c:.2f}->${cr:.0f} | renov ${r:.2f}->${rr:.0f} | MTM {mtm:+.0f} ({mtm/s:+.1%}) | reno prem {prem:+.0f} ({prem/cr:+.1%}) | total {tot:+.0f} ({tot/s:+.1%})")
print(f"annualized (occupied 1-3BR): MTM ${tm:,.0f} | renovation premium ${tr:,.0f} | total ${tm+tr:,.0f}")
return out
g=run("Gwinnett-side peers (primary)", [p for p in P if p[1]=="G"])
a=run("All submarket peers incl. Johns Creek (sensitivity)", P)
# affordability at Gwinnett renovated rents
hhi=97814; bins=[(50000,75000,0.126984),(75000,100000,0.761905),(100000,150000,0.111111)]
def qual(x): return sum(p if x<=lo else (p*(hi-x)/(hi-lo) if x<hi else 0) for lo,hi,p in bins)
w=sum(units[b]*g[b] for b in g)/sum(units.values()); wi=sum(units[b]*subj_ip[b] for b in g)/sum(units.values())
print(f"\nGwinnett renovated, mix-weighted: ${w:.0f} vs in-place ${wi:.0f} ({w/wi-1:+.1%}); RTI {w*12/hhi:.1%} vs {wi*12/hhi:.1%}")
for b in '123': print(f" {b}BR renovated ${g[b]:.0f}: RTI {g[b]*12/hhi:.1%}; 3x income ${g[b]*36:,.0f}; hh qualifying {qual(g[b]*36):.0%}; monthly increase vs in-place ${g[b]-subj_ip[b]:+.0f}")
EOF
python3 work/sugarloaf_gap_psf.pySignature at Sugarloaf's residents can afford the renovated rent easily, but the market doesn't support much of a renovated rent. After adjusting for unit size, in-place rents already match unrenovated peers. The renovation premium the comps support is small: about $120 on 1BRs, $75 on 2BRs and essentially nothing on 3BRs, or roughly $0.3M a year combined. Income isn't what limits the deal. Two things do: how little the comps support, and how little cash these households have.
This is a middle-income renter base with very little cushion. It sits well below the Duluth submarket average, which is skewed up by Johns Creek owner households. The data has no renter-only benchmark for the submarket, so the income gap overstates how different these renters are from other local renters.
| Metric | Signature at Sugarloaf | Duluth Submarket | Read |
|---|---|---|---|
| Median household income | $97,8141 | $160,501 | -39%, submarket includes owners |
| Income spread | 76% of households earn $75–100K, 13% $50–75K, 11% $100–150K; Gini 0.071 | Broad; Gini 0.43 | Very narrow band |
| Avg FICO | 643, 97% CI 622–6632 | 721 | Weaker, near-prime |
| Accounts past due | 13.7%2 | 6.9% | About 2x |
| Accounts ≥75% utilized | 33%2 | 27% | More stretched |
| Credit cards ≥75% utilized | 33%2 | 21% | More stretched |
| Avg non-mortgage debt | $16,5142 | $22,322 | Lower balances |
| Personal-finance credit inquiry index | 19.12 | 7.9 | Heavy credit-seeking |
| Median liquid savings | Under $5001 | Above MSA average | Very thin |
| Share with net worth under $25K | 83%1 | n/a | Very thin |
| Rent-to-income | 17.2%, 11th percentile in MSA1 | n/a | Low burden |
Income: the median is $97,814, and 76% of households fall in the $75–100K band . That band is unusually narrow, and it comes from a 68-household sample rated "acceptable" . Median income rose 4.9% over the last six months .
Credit: average FICO is 643, against 721 for the submarket . Past-due accounts run 13.7%, twice the submarket's 6.9% , and are up 5.1 points over 12 months . FICO is up 5.5 points over the same period , so credit is mixed rather than simply deteriorating.
Liquidity is the weak point:
These households can pay monthly rent out of income but have little room to absorb a shock.
Operations back up the income picture:
The peer set is market-rate properties in the same submarket, built 1990–2008, with 150–400 units. I dropped affordable, income-restricted and senior properties. "Renovated" means a renovation in 2015 or later.
Comparing raw dollars is misleading here. The unrenovated peers include larger, pricier Johns Creek properties, so on raw rents the renovated peers actually look cheaper than the unrenovated ones . So I converted each peer's rent per square foot by bedroom type to Signature's unit sizes (814, 1,079 and 1,201 SF). I also used the Gwinnett-side peers as the main comp set, because they share Signature's location.
| Bedroom | Est. Units | Signature In-Place | Unrenovated Peer Rent | Market Gap (no renovation) | Renovated Peer Rent | Renovation Premium | Total Gap |
|---|---|---|---|---|---|---|---|
| 1BR | 732 | $1,205 ($1.48/SF) | $1,2781 | +$73 (+6.0%) | $1,4001 | +$122 (+9.6%) | +$195 (+16.2%) |
| 2BR | 992 | $1,399 ($1.30/SF) | $1,4241 | +$25 (+1.8%) | $1,5001 | +$76 (+5.3%) | +$100 (+7.2%) |
| 3BR | 822 | $1,637 ($1.36/SF) | $1,5971 | -$39 (-2.4%) | $1,6151 | +$18 (+1.1%) | -$21 (-1.3%) |
| 1–3BR Total (Annualized) | 254 | — | — | ~$90K3 | — | ~$207K3 | ~$297K3 |
On income, yes, with room to spare:
The real risk is at renewal. With under $500 in median liquid savings and past-due rates rising, a $100–195 jump at one renewal (2BR and 1BR) will push some households out rather than get paid. Phasing the increases in keeps the 80% renewal rate that makes this asset work.
Bottom line: underwrite about $0.3M a year of upside tied to renovations, focused on the 1BR and 2BR units, and leave the 3BRs light. There's essentially no rent gap to capture without renovating. What matters most is whether a $75–120 premium covers the interior budget. The unit count and cost inputs to check that aren't here yet.
Two follow-ups would close the main gaps:
This is a read-only snapshot of a RealAI analysis.