Run a rent comp and operational analysis for Brix on Belleview to benchmark its current performance against the competitive set. The property's current condition is recently renovated and my primary objective is identifying rent growth opportunities. Compare rents, concessions, and occupancy against nearby comps and flag any operational gaps or upside relative to the market.
Now let's pull submarket and market benchmarks, run the rental comps retrieval, and check supply pipeline.
cat << 'EOF' > /tmp/comps.py
import json, math
subject = {"lat":39.62547808885584,"lon":-104.99015271663666,"unit_count":183,"style":"GARDEN","year":1962,"reno":2012}
candidates = [
{"name":"Winchester & Browning","lat":39.661709368229,"lon":-104.98969137668608,"units":80,"style":"MID_RISE","yr":1973,"reno":None,"in_place_avg":1194.50,"asking_avg":1179.00,"occ":0.9625,"restr":None,"sqft":458,"in1":1194.50,"in2":None,"ask1":1179.00,"ask2":None,"in0":None,"ask0":None,"in3":None,"ask3":None},
{"name":"Noble Old Hampden","lat":39.65343207120904,"lon":-104.97172057628632,"units":119,"style":"MID_RISE","yr":2024,"reno":None,"in_place_avg":2139.27,"asking_avg":2079.90,"occ":0.9076,"restr":None,"sqft":770,"in0":1609.30,"in1":1891.47,"in2":2542.94,"ask0":1724.50,"ask1":2039.87,"ask2":2498.11},
{"name":"ArtWalk at CityCenter","lat":39.65580850839624,"lon":-104.99679386615755,"units":438,"style":"GARDEN","yr":2001,"reno":2018,"in_place_avg":1731.53,"asking_avg":1713.84,"occ":0.9521,"restr":"[]","sqft":919,"in0":1421.50,"in1":1519.90,"in2":1906.55,"in3":2526.52,"ask0":None,"ask1":1559.44,"ask2":1934.30,"ask3":2541.50},
{"name":"Creekside Apartments","lat":39.64987546205529,"lon":-104.98020708560944,"units":17,"style":"LOW_RISE","yr":1973,"reno":None,"in_place_avg":1072.00,"asking_avg":1082.14,"occ":0.6471,"restr":None,"sqft":591},
{"name":"201Lofts","lat":39.655540287494745,"lon":-104.99034583568574,"units":30,"style":"MID_RISE","yr":2016,"reno":None,"in_place_avg":1539.44,"asking_avg":1560.00,"occ":0.9000,"restr":None,"sqft":829},
{"name":"Penn Row","lat":39.652380645275215,"lon":-104.98222410678864,"units":12,"style":"TOWNHOUSE","yr":2019,"reno":None,"in_place_avg":2766.67,"asking_avg":None,"occ":1.0,"restr":None,"sqft":1315},
{"name":"Arden Englewood","lat":39.65224117040644,"lon":-105.0001734495163,"units":302,"style":"LOW_RISE","yr":2024,"reno":None,"in_place_avg":2127.52,"asking_avg":1909.39,"occ":0.9371,"restr":None,"sqft":862},
{"name":"Kent Place Residences","lat":39.65464442968377,"lon":-104.96117413043976,"units":300,"style":"MID_RISE","yr":2014,"reno":None,"in_place_avg":2559.02,"asking_avg":2758.13,"occ":0.9500,"restr":None,"sqft":1249},
{"name":"Golden Nugget Apartments","lat":39.62497383356103,"lon":-104.99101102352144,"units":206,"style":"LOW_RISE","yr":1976,"reno":None,"in_place_avg":1117.41,"asking_avg":1197.30,"occ":0.9757,"restr":None,"sqft":601,"in1":1082.80,"in2":1506.75,"ask1":1197.30},
{"name":"The Marks","lat":39.65489655733117,"lon":-104.96742904186249,"units":616,"style":"GARDEN","yr":1986,"reno":2008,"in_place_avg":1787.59,"asking_avg":1989.48,"occ":0.9367,"restr":None,"sqft":846},
{"name":"Lynwood Apartments","lat":39.65158134698877,"lon":-104.99033510684966,"units":19,"style":"LOW_RISE","yr":1958,"reno":2024,"in_place_avg":945.83,"asking_avg":995.00,"occ":0.9474,"restr":None,"sqft":478},
{"name":"Debbie J II","lat":39.65739101171503,"lon":-104.98376905918121,"units":17,"style":"LOW_RISE","yr":1972,"reno":None,"in_place_avg":1245.21,"asking_avg":1265.00,"occ":0.8235,"restr":None,"sqft":566},
{"name":"The Normandy Apartments","lat":39.65208560228357,"lon":-104.98139798641205,"units":42,"style":"MID_RISE","yr":1972,"reno":2021,"in_place_avg":1139.00,"asking_avg":952.33,"occ":0.9286,"restr":None,"sqft":588},
{"name":"Iron Works Village Apartments","lat":39.66595798730859,"lon":-104.99437987804413,"units":36,"style":"TOWNHOUSE","yr":2020,"reno":None,"in_place_avg":2587.75,"asking_avg":2550.00,"occ":0.9722,"restr":None,"sqft":1420},
{"name":"The Emerson","lat":39.65449959039697,"lon":-104.97663438320161,"units":264,"style":"HIGH_RISE","yr":2024,"reno":None,"in_place_avg":2344.77,"asking_avg":2097.57,"occ":0.7083,"restr":None,"sqft":730},
{"name":"Alvista Trailside","lat":39.62344497442254,"lon":-104.99211609363556,"units":312,"style":"GARDEN","yr":1990,"reno":2016,"in_place_avg":1536.12,"asking_avg":1630.93,"occ":0.9519,"restr":None,"sqft":612,"in1":1405.69,"in2":1691.63,"ask1":1511.20,"ask2":1697.44},
{"name":"Rustic Arms Apartments","lat":39.65410262346276,"lon":-104.97386634349823,"units":48,"style":"GARDEN","yr":1968,"reno":None,"in_place_avg":1383.80,"asking_avg":1499.00,"occ":0.8958,"restr":None,"sqft":577},
{"name":"Oxford Station Apartments","lat":39.641179740429024,"lon":-105.00482976436615,"units":238,"style":"LOW_RISE","yr":2016,"reno":None,"in_place_avg":1664.06,"asking_avg":1747.29,"occ":0.9160,"restr":None,"sqft":769},
{"name":"Fox Street Apartments","lat":39.65005248785029,"lon":-104.99441206455231,"units":45,"style":"LOW_RISE","yr":1972,"reno":1997,"in_place_avg":1188.08,"asking_avg":1124.00,"occ":0.8889,"restr":None,"sqft":513},
{"name":"Shady Brook Apartments","lat":39.650363624096,"lon":-104.98206317424774,"units":38,"style":"MID_RISE","yr":1973,"reno":None,"in_place_avg":815.67,"asking_avg":849.00,"occ":0.9737,"restr":None,"sqft":580},
{"name":"Greenwood Point","lat":39.61849898099908,"lon":-104.98399436473846,"units":312,"style":"GARDEN","yr":1985,"reno":2010,"in_place_avg":1633.73,"asking_avg":1670.00,"occ":0.9263,"restr":"[]","sqft":940},
{"name":"Lorinda Apartments","lat":39.656468331813905,"lon":-104.97908055782318,"units":39,"style":"LOW_RISE","yr":1970,"reno":None,"in_place_avg":1333.85,"asking_avg":1289.29,"occ":0.8205,"restr":"[]","sqft":528},
{"name":"Avalon Cherry Hills","lat":39.650218784809205,"lon":-104.9870091676712,"units":306,"style":"LOW_RISE","yr":2014,"reno":None,"in_place_avg":1710.38,"asking_avg":1764.78,"occ":0.9052,"restr":None,"sqft":906},
{"name":"Parkview Towers","lat":39.65012222528466,"lon":-104.98103320598602,"units":92,"style":"HIGH_RISE","yr":1972,"reno":None,"in_place_avg":1393.01,"asking_avg":1356.25,"occ":0.7826,"restr":None,"sqft":610},
{"name":"Kingsbrook Arms","lat":39.65406507253656,"lon":-104.9727076292038,"units":34,"style":"LOW_RISE","yr":1970,"reno":None,"in_place_avg":1016.07,"asking_avg":1154.75,"occ":0.9412,"restr":None,"sqft":519},
{"name":"Sequel Apartments","lat":39.66220825910577,"lon":-104.99647200107576,"units":203,"style":"GARDEN","yr":2023,"reno":None,"in_place_avg":1846.07,"asking_avg":1917.33,"occ":0.9458,"restr":None,"sqft":812},
{"name":"Windsong Apartments","lat":39.652520120143976,"lon":-104.97860848903656,"units":47,"style":"GARDEN","yr":1971,"reno":2023,"in_place_avg":1244.52,"asking_avg":1375.00,"occ":0.9149,"restr":None,"sqft":536},
{"name":"Off Broadway Flats","lat":39.65074449777612,"lon":-104.97902691364288,"units":91,"style":"LOW_RISE","yr":1961,"reno":2013,"in_place_avg":1034.86,"asking_avg":1109.00,"occ":0.9780,"restr":None,"sqft":501},
{"name":"The Girard at Cherry Hills","lat":39.65577095747003,"lon":-104.96572315692902,"units":152,"style":"GARDEN","yr":1974,"reno":2018,"in_place_avg":2050.47,"asking_avg":2212.64,"occ":0.9605,"restr":None,"sqft":1082},
{"name":"Canterbury East Apartments","lat":39.652208983898255,"lon":-104.99204099178316,"units":21,"style":"LOW_RISE","yr":1973,"reno":None,"in_place_avg":1181.00,"asking_avg":1222.00,"occ":0.8571,"restr":None,"sqft":530},
{"name":"Aspen Creek Apartments","lat":39.652450382709596,"lon":-104.97906982898712,"units":36,"style":"MID_RISE","yr":1971,"reno":2017,"in_place_avg":1114.29,"asking_avg":1162.50,"occ":0.9167,"restr":None,"sqft":593},
]
def haversine(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)
a=math.sin(dphi/2)**2+math.cos(p1)*math.cos(p2)*math.sin(dl/2)**2
return 2*R*math.asin(math.sqrt(a))
for c in candidates:
c["dist"]=haversine(subject["lat"],subject["lon"],c["lat"],c["lon"])
candidates.sort(key=lambda c: c["dist"])
for c in candidates:
print(f"{c['dist']:.2f}mi {c['name']:32s} units={c['units']:4d} style={c['style']:10s} yr={c['yr']} reno={c['reno']} occ={c['occ']*100:.1f}% in_place=${c['in_place_avg']:.0f} asking={c['asking_avg']}")
EOF
python3 /tmp/comps.pycat << 'EOF' > /tmp/rentgap.py
# Rent gap analysis: Brix on Belleview vs comp sets
subject = {
"asking_avg": 1321.25, "inplace_avg": 1281.22,
"asking_0": 995.00, "inplace_0": 1091.46,
"asking_1": 1208.33, "inplace_1": 1162.15,
"asking_2": 1404.38, "inplace_2": 1441.68,
"asking_persf": 1.93, "inplace_persf": 2.01,
"occ_latest": 0.9290, "occ_30d": 0.9358, "occ_6mo_ago": 0.9672, "occ_12mo_ago": 0.9290,
"asking_t12": -0.0966, "inplace_t12": -0.0372,
"tradeout_pct": -0.0227, "dom": 45, "retention": 0.6284,
"units": 183,
}
# Renovated garden/low-density comp set (recently or moderately renovated, similar submarket)
renovated_comps = {
"Alvista Trailside (renov 2016)": {"1bd_ask":1511.20,"1bd_in":1405.69,"2bd_ask":1697.44,"2bd_in":1691.63,"avg_in":1536.12,"avg_ask":1630.93,"persf_in":1.93},
"Greenwood Point (renov 2010)": {"1bd_ask":1492.31,"1bd_in":1487.99,"2bd_ask":1791.58,"2bd_in":1771.54,"avg_in":1633.73,"avg_ask":1670.00,"persf_in":1.77},
"Windsong Apartments (renov 2023)": {"1bd_ask":1195.00,"1bd_in":1192.58,"2bd_ask":1465.00,"2bd_in":1491.25,"avg_in":1244.52,"avg_ask":1375.00,"persf_in":2.34},
"ArtWalk at CityCenter (renov 2018)": {"0bd_in":1421.50,"1bd_ask":1559.44,"1bd_in":1519.90,"2bd_ask":1934.30,"2bd_in":1906.55,"avg_in":1731.53,"avg_ask":1713.84,"persf_in":1.91},
"The Marks (renov 2008)": {"1bd_ask":1811.03,"1bd_in":1606.34,"2bd_ask":2214.19,"2bd_in":2085.49,"avg_in":1787.59,"avg_ask":1989.48,"persf_in":2.13},
}
unrenovated_comps = {
"Golden Nugget Apartments (no reno, built 1976)": {"1bd_ask":1197.30,"1bd_in":1082.80,"2bd_in":1506.75,"avg_in":1117.41,"avg_ask":1197.30,"persf_in":1.87},
"Fox Street Apartments (renov 1997)": {"1bd_ask":1124.00,"1bd_in":1154.23,"2bd_in":1425.00,"avg_in":1188.08,"avg_ask":1124.00,"persf_in":2.32},
"Rustic Arms Apartments (no reno, built 1968)": {"2bd_ask":1499.00,"1bd_in":1368.75,"2bd_in":1444.00,"avg_in":1383.80,"avg_ask":1499.00,"persf_in":2.38},
}
import statistics
def avg(vals): return sum(vals)/len(vals)
reno_1bd_in = [v["1bd_in"] for v in renovated_comps.values() if "1bd_in" in v]
reno_2bd_in = [v["2bd_in"] for v in renovated_comps.values() if "2bd_in" in v]
reno_avg_in = [v["avg_in"] for v in renovated_comps.values()]
reno_persf = [v["persf_in"] for v in renovated_comps.values()]
unreno_1bd_in = [v["1bd_in"] for v in unrenovated_comps.values() if "1bd_in" in v]
unreno_2bd_in = [v["2bd_in"] for v in unrenovated_comps.values() if "2bd_in" in v]
unreno_avg_in = [v["avg_in"] for v in unrenovated_comps.values()]
print("=== In-place rent comparison ===")
print(f"Subject 1bd in-place: ${subject['inplace_1']:.0f} | Renovated comp avg 1bd: ${avg(reno_1bd_in):.0f} (n={len(reno_1bd_in)}) | Unrenovated comp avg 1bd: ${avg(unreno_1bd_in):.0f} (n={len(unreno_1bd_in)})")
print(f"Subject 2bd in-place: ${subject['inplace_2']:.0f} | Renovated comp avg 2bd: ${avg(reno_2bd_in):.0f} (n={len(reno_2bd_in)}) | Unrenovated comp avg 2bd: ${avg(unreno_2bd_in):.0f} (n={len(unreno_2bd_in)})")
print(f"Subject property avg in-place: ${subject['inplace_avg']:.0f} | Renovated comp set avg: ${avg(reno_avg_in):.0f} | Unrenovated comp set avg: ${avg(unreno_avg_in):.0f}")
print(f"Subject in-place $/sf: ${subject['inplace_persf']:.2f} | Renovated comp set avg $/sf: ${avg(reno_persf):.2f}")
gap_1bd_pct = (avg(reno_1bd_in) - subject['inplace_1'])/subject['inplace_1']
gap_2bd_pct = (avg(reno_2bd_in) - subject['inplace_2'])/subject['inplace_2']
gap_1bd_vs_unreno_pct = (subject['inplace_1'] - avg(unreno_1bd_in))/avg(unreno_1bd_in)
gap_2bd_vs_unreno_pct = (subject['inplace_2'] - avg(unreno_2bd_in))/avg(unreno_2bd_in)
print()
print("=== Gap sizing ===")
print(f"1BR: subject trails renovated-comp avg by {gap_1bd_pct*100:.1f}% (${avg(reno_1bd_in)-subject['inplace_1']:.0f}/mo); leads unrenovated-comp avg by only {gap_1bd_vs_unreno_pct*100:.1f}% (${subject['inplace_1']-avg(unreno_1bd_in):.0f}/mo)")
print(f"2BR: subject trails renovated-comp avg by {gap_2bd_pct*100:.1f}% (${avg(reno_2bd_in)-subject['inplace_2']:.0f}/mo); vs unrenovated-comp avg subject is {gap_2bd_vs_unreno_pct*100:.1f}% ({'below' if gap_2bd_vs_unreno_pct<0 else 'above'}, ${subject['inplace_2']-avg(unreno_2bd_in):.0f}/mo)")
# Loss to lease (asking vs in-place)
ltl = (subject['asking_avg']-subject['inplace_avg'])/subject['asking_avg']
print()
print(f"Subject loss-to-lease (asking vs in-place): {ltl*100:.1f}%")
# Annualized upside if 2BR and 1BR closed 50% of gap to renovated comp avg (illustrative, not full unit mix known)
print()
print("=== Illustrative annualized upside (per unit, if unit mix were 100% of that type) ===")
print(f"1BR full capture to renovated-comp avg: ${(avg(reno_1bd_in)-subject['inplace_1'])*12:.0f}/unit/yr")
print(f"2BR full capture to renovated-comp avg: ${(avg(reno_2bd_in)-subject['inplace_2'])*12:.0f}/unit/yr")
# Occupancy & leasing velocity vs market
print()
print("=== Occupancy / leasing velocity vs benchmarks ===")
print(f"Subject occupancy latest: {subject['occ_latest']*100:.1f}% | 30d avg: {subject['occ_30d']*100:.1f}%")
print(f"Submarket (Englewood) occupancy latest: 92.74% | Market (Denver) occupancy latest: 92.62%")
print(f"Subject DOM (leases signed past 30d, median): {subject['dom']} days")
print(f"Submarket DOM: 82 days | Market DOM: 62 days -> subject leases {82-subject['dom']} days faster than submarket, {62-subject['dom']} days faster than market")
print()
print("=== Asking rent trend vs market (red flag check) ===")
print(f"Subject asking rent 12mo change: {subject['asking_t12']*100:.1f}% (small sample size n=12 -- low confidence)")
print(f"Submarket asking rent 12mo change: -6.44% | Market asking rent 12mo change: -2.76%")
print(f"Subject in-place rent 12mo change: {subject['inplace_t12']*100:.1f}% | Submarket: -2.21% | Market: -1.56%")
print()
print("=== Tradeout (leading indicator) ===")
print(f"Subject tradeout on new leases: {subject['tradeout_pct']*100:.1f}% | Submarket: -2.65% | Market: -3.21%")
print("Subject tradeout has improved materially over trailing periods (t3 chg +53%, t6 +48%, t12 +86%) -- narrowing least negative among the three.")
EOF
python3 /tmp/rentgap.pyBrix on Belleview is priced at parity with its renovated comp set on a $/SF basis — the real opportunity isn't a hidden rent gap, it's underused leasing velocity and a widening expense gap
| Property | Distance from subject (mi) | Units | Building style | Year built/renovated | Occupancy (latest) | Asking rent avg | In-place rent avg | In-place rent $/SF |
|---|---|---|---|---|---|---|---|---|
| Brix on Belleview (Subject) | 0.00 mi | 183 | Garden | 1962 / 2012 | 92.9% | $1,321 | $1,281 | $2.01 |
| 0.06 mi | 206 | Low-Rise | 1976 / — | 97.6% | $1,1971 | $1,117 | $1.87 | |
| 0.17 mi | 312 | Garden | 1990 / 2016 | 95.2% | $1,6311 | $1,536 | $1.93 | |
| 0.58 mi | 312 | Garden | 1985 / 2010 | 92.6% | $1,670 | $1,634 | $1.77 | |
| 1.34 mi | 238 | Low-Rise | 2016 / — | 91.6% | $1,747 | $1,664 | $2.24 | |
| 1.71 mi | 45 | Low-Rise | 1972 / 1997 | 88.9% | $1,1241 | $1,188 | $2.32 | |
| 1.72 mi | 306 | Low-Rise | 2014 / — | 90.5% | $1,765 | $1,710 | $1.93 | |
| 1.96 mi | 47 | Garden | 1971 / 2023 | 91.5% | $1,3751 | $1,245 | $2.34 | |
| 2.12 mi | 438 | Garden | 2001 / 2018 | 95.2% | $1,714 | $1,732 | $1.91 | |
| 2.16 mi | 48 | Garden | 1968 / — | 89.6% | $1,4991 | $1,384 | $2.38 | |
| 2.36 mi | 616 | Garden | 1986 / 2008 | 93.7% | $1,989 | $1,788 | $2.13 |
Run the $/unit numbers cold and Brix looks like it's leaving $280–350/month per 1BR and 2BR on the table against renovated peers (Alvista, Greenwood, ArtWalk, The Marks, Windsong) . But that comparison is contaminated by size — those comps average 770–940 SF against Brix's 651 SF. Normalize to $/SF, the metric that actually isolates pricing power from square footage, and the story flips: Brix's 1BR runs $2.14/SF and 2BR $1.82/SF, both landing right at or fractionally above the renovated-comp average ($2.11 and $1.86, respectively) . The "recently renovated, capture upside" thesis doesn't hold on a like-for-like basis — the property is already pricing its renovation into the rent, just on a smaller floorplate.
Where the real signal sits: leasing velocity, not rent gap. Brix is leasing new units in a median 45 days, versus 82 days for the Englewood submarket and 62 days for Denver metro — while holding occupancy (92.9% latest, 93.6% 30-day avg) essentially at parity with both the submarket (92.7%) and metro (92.6%) . That combination — materially faster absorption at the same occupancy — is the classic underpriced signal. The cleanest comp for testing it is Windsong Apartments: same small-unit profile (536 SF), renovated in 2023 (closest vintage match to Brix's reno story), and getting $2.34/SF — 16% above Brix's $2.01/SF . That's the ceiling worth testing on next vacancies, not the broader comp set's larger units.
The trend line is the flag, not the level. Brix's in-place rent fell 3.7% over the past 12 months versus 2.2% for the submarket and 1.6% for the metro — the property is discounting faster than the market it competes in, despite the renovation story . Asking rent shows a steeper -9.7% drop, though that figure rests on a sample of just 12 listings and should be treated as directional, not precise . Tradeout on new leases is still negative (-2.3%, meaning new tenants are signing below what the outgoing tenant paid) but improving — it's tightened roughly 53% over the last quarter, ahead of the submarket's pace . Retention sits at 62.8%, about 7 points below the Englewood submarket's 70.1% and roughly in line with the metro's 63.2% — with 68 turnovers on 183 units (a ~37% annualized turn rate), Brix is re-leasing more of its roll into a concession-heavy market than its immediate neighbors, which is likely a bigger drag on effective rent than any listed asking price.
Operational gap: expense ratio, not occupancy. NOI runs at 48% of EGI at Brix versus a 56% Denver-metro benchmark — an 8-point gap that's larger than anything visible in the rent comps . Before chasing rent upside, that expense-ratio gap is worth a dedicated operating-efficiency read, since closing even half of it moves NOI more than a modest rent push would.
Market backdrop. Denver multifamily concessions are running at a 21-year record — averaging 9.5% of gross rent, with roughly two-thirds of metro listings offering some incentive, concentrated in 2022–2024 vintage buildings . Several of the renovated/newer comps here (Windsong, Sequel, Oxford Station, Avalon Cherry Hills) fall in or near that vintage band, so their advertised asking rents likely carry unlisted incentives that aren't netted out in the $/SF comparison above — the true achievable premium at those properties is probably a little lower than face rents suggest. Brix's own fast DOM suggests it isn't needing to compete on concessions as heavily, which is a competitive advantage worth protecting rather than discounting away.
Bottom line: don't underwrite a renovation-driven rent-growth story here — Brix is already priced in line with its renovated peer set on a normalized basis. The actionable upside is (1) testing a modest asking increase on turning units, leaning on the DOM cushion and the Windsong benchmark, while watching absorption closely; (2) tightening renewal pricing to arrest the retention gap and above-market turnover, which is costing more in re-leasing and concession exposure than it's earning in mark-to-market; and (3) running an operating-efficiency review on the expense ratio, which is the larger and more controllable value lever right now. A unit-level rent roll would sharpen all three — happy to run the Mark-to-Market or Rent Revenue Optimizer workflow against it if you can pull one.
This is a read-only snapshot of a RealAI analysis.