For the Downtown Silver Spring, Hillcrest Heights/Marlow Heights, Beltsville/Laurel/South Laurel, Burke/Falls Church/Jefferson, Pikesville and Towson – Southwest submarkets, show 12-month in-place rent change, occupancy and its 12-month change, new-lease tradeout and median days on market. Which are firming and which are softening, and what does that mean for underwriting rent growth this year? Include data visualizations that will drive this home
Resolving submarket IDs.
cd skills/forecasting && python scripts/forecast.py --version && sed -n 1,400p references/request.md | head -c 20000
{
"_source": "query_data mf_rent_ts MONTH, submarkets toolu_bdrk_01G6s9vu5BiNZJDxiBdcKhdg; markets toolu_bdrk_01GhAz8a83tr8qTMLUqdFNtB; values rounded to cents",
"months": ["2024-09-01","2024-10-01","2024-11-01","2024-12-01","2025-01-01","2025-02-01","2025-03-01","2025-04-01","2025-05-01","2025-06-01","2025-07-01","2025-08-01","2025-09-01","2025-10-01","2025-11-01","2025-12-01","2026-01-01","2026-02-01","2026-03-01","2026-04-01","2026-05-01","2026-06-01","2026-07-01","2026-08-01"],
"series": {
"Downtown Silver Spring": {"id":"f1b2ad23fb8eb5cdae748945b3e56a00","parent":"DC",
"in_place":[1941.38,1949.62,1956.17,1960.80,1967.96,1970.41,1976.01,1982.15,1993.66,2002.06,2008.48,2012.49,2011.10,2012.07,2010.87,2009.43,2000.74,1996.50,1994.43,1993.80,1995.52,1996.61,1998.03,2000.89],
"asking":[2287.92,2271.73,2263.47,2234.19,2221.98,2217.54,2232.39,2224.08,2217.66,2195.19,2201.20,2193.26,2185.40,2178.05,2133.43,2111.12,2102.49,2095.88,2089.36,2096.00,2116.66,2090.36,2125.75,2094.14]},
"Hillcrest Heights/Marlow Heights": {"id":"e47195f73aae2bcd85562189691cd048","parent":"DC",
"in_place":[1487.45,1489.31,1490.37,1490.35,1491.78,1493.49,1495.26,1496.41,1497.31,1497.40,1499.96,1500.18,1499.97,1499.95,1500.32,1499.96,1499.95,1500.42,1501.63,1503.57,1505.01,1504.81,1504.06,1503.85],
"asking":[1522.85,1512.51,1519.26,1498.64,1543.87,1547.10,1526.37,1517.24,1506.96,1500.17,1488.22,1458.66,1496.54,1511.58,1489.80,1491.15,1522.97,1538.81,1529.98,1547.51,1527.47,1543.53,1539.81,1552.86]},
"Beltsville/Laurel/South Laurel": {"id":"3c3a6171f6190e617afcee7f79f05fb5","parent":"DC",
"in_place":[1834.69,1835.72,1850.56,1850.40,1851.14,1848.55,1848.82,1849.58,1846.29,1842.01,1838.19,1834.14,1831.84,1842.80,1843.20,1842.44,1842.16,1840.58,1840.05,1831.89,1829.00,1824.26,1820.70,1818.45],
"asking":[1906.36,1864.53,1882.21,1881.22,1830.64,1821.13,1844.59,1847.13,1828.93,1852.47,1862.17,1852.46,1852.75,1865.83,1867.20,1868.06,1845.75,1849.90,1847.22,1860.33,1848.90,1886.00,1913.60,1936.44]},
"Burke/Falls Church/Jefferson": {"id":"bbe0808495a06e7dd554be9b0326fb0e","parent":"DC",
"in_place":[2110.55,2120.96,2127.25,2132.01,2143.05,2153.48,2166.17,2175.48,2191.95,2210.84,2223.72,2234.49,2238.18,2241.71,2244.57,2248.37,2252.09,2254.82,2263.62,2289.29,2296.64,2307.44,2315.23,2312.98],
"asking":[2407.57,2407.69,2428.67,2474.93,2575.46,2617.83,2574.79,2594.91,2572.77,2565.19,2505.77,2525.22,2513.34,2504.74,2494.64,2570.62,2546.68,2509.41,2480.86,2468.96,2459.57,2457.33,2459.86,2429.71]},
"Pikesville": {"id":"9037127f4765c1f1a63e032712a8afd7","parent":"BAL",
"in_place":[1669.56,1673.37,1675.59,1677.06,1679.54,1679.91,1686.54,1688.72,1692.85,1698.00,1696.14,1699.26,1700.01,1697.72,1698.43,1697.27,1695.78,1696.15,1699.60,1700.75,1705.31,1709.58,1714.15,1715.47],
"asking":[1744.68,1726.08,1733.13,1726.51,1766.14,1764.76,1716.88,1725.58,1754.56,1729.42,1778.23,1788.57,1789.65,1784.18,1785.65,1760.04,1769.33,1762.66,1775.35,1787.98,1732.27,1785.95,1839.03,1803.99]},
"Towson - Southwest": {"id":"4134e2fdc0999525ab430760605b9b95","parent":"BAL",
"in_place":[1756.00,1758.30,1759.49,1759.85,1763.82,1765.19,1768.67,1765.29,1771.24,1777.24,1778.41,1778.56,1776.96,1778.61,1782.08,1783.91,1782.08,1782.71,1786.28,1787.84,1796.23,1802.81,1803.41,1805.79],
"asking":[1999.18,2042.35,2020.38,2066.93,2002.62,1991.44,2005.09,2010.53,1869.41,1858.92,1886.06,1934.93,1954.96,1944.42,1976.32,1945.41,2013.80,1951.16,1926.16,1909.51,1890.76,1810.50,1875.68,1921.98]}
},
"parents": {
"DC": {"id":"47900","label":"Washington, DC",
"in_place":[2131.46,2140.06,2146.97,2149.95,2159.08,2163.67,2172.92,2180.76,2193.81,2206.30,2215.01,2220.76,2227.90,2230.19,2230.92,2229.89,2230.81,2232.44,2235.05,2243.71,2246.89,2250.24,2253.36,2253.29],
"asking":[2464.13,2443.32,2432.43,2435.50,2457.85,2452.24,2473.29,2477.94,2475.75,2497.86,2489.98,2487.60,2454.81,2431.93,2420.98,2421.91,2420.12,2408.58,2418.67,2429.80,2420.34,2420.56,2413.54,2398.44]},
"BAL": {"id":"12580","label":"Baltimore, MD",
"in_place":[1780.90,1783.00,1783.28,1783.00,1780.26,1781.15,1783.56,1785.59,1790.96,1794.74,1800.43,1801.65,1800.36,1799.28,1799.57,1798.25,1794.37,1796.20,1798.31,1780.47,1781.32,1783.30,1786.38,1785.62],
"asking":[1879.78,1883.58,1889.37,1898.52,1891.77,1887.46,1894.22,1898.15,1877.16,1880.71,1879.77,1878.01,1871.81,1879.57,1871.18,1872.97,1884.78,1884.17,1885.69,1873.16,1881.28,1891.72,1918.15,1928.02]}
}
}
{"_source":"query_data mf_rent_and_occupancy_detail submarket toolu_bdrk_01BgoaupFEZPCVeXrCA18yNw; market toolu_bdrk_01BvsqCoLVhQombQcK4GiaYZ; as of 2026-09-26",
"records":[
{"entity":"Downtown Silver Spring","id":"f1b2ad23fb8eb5cdae748945b3e56a00","market":"Washington, DC","asking":2091.30,"asking_12mo":2176.18,"ip_avg":1994.33,"ip_avg_12mo":2011.21,"ip_med":1930.50,"ip_t12_med":0.0176,"ip_t3_med":0.0009,"occ":0.9455,"occ_12mo":0.9561,"occ_3mo":0.9538,"occ_t12":-0.0106,"tradeout":-0.0169,"tradeout_amt":-50.16,"tradeout_t12chg":-0.0309,"dom30":96,"leases30":343,"unleased":744,"ip_sample":6612},
{"entity":"Hillcrest Heights/Marlow Heights","id":"e47195f73aae2bcd85562189691cd048","market":"Washington, DC","asking":1594.10,"asking_12mo":1488.30,"ip_avg":1504.89,"ip_avg_12mo":1500.38,"ip_med":1455.00,"ip_t12_med":0.0051,"ip_t3_med":0.0003,"occ":0.9641,"occ_12mo":0.9685,"occ_3mo":0.9601,"occ_t12":-0.0044,"tradeout":0.0037,"tradeout_amt":6.80,"tradeout_t12chg":0.0007,"dom30":53,"leases30":277,"unleased":400,"ip_sample":5219},
{"entity":"Beltsville/Laurel/South Laurel","id":"3c3a6171f6190e617afcee7f79f05fb5","market":"Washington, DC","asking":1891.52,"asking_12mo":1849.92,"ip_avg":1823.64,"ip_avg_12mo":1832.00,"ip_med":1731.00,"ip_t12_med":0.0020,"ip_t3_med":0.0030,"occ":0.9634,"occ_12mo":0.9716,"occ_3mo":0.9629,"occ_t12":-0.0082,"tradeout":0.0069,"tradeout_amt":8.88,"tradeout_t12chg":0.0191,"dom30":110,"leases30":787,"unleased":728,"ip_sample":8487},
{"entity":"Burke/Falls Church/Jefferson","id":"bbe0808495a06e7dd554be9b0326fb0e","market":"Washington, DC","asking":2460.72,"asking_12mo":2505.72,"ip_avg":2311.38,"ip_avg_12mo":2237.30,"ip_med":2200.00,"ip_t12_med":0.0398,"ip_t3_med":0.0025,"occ":0.9554,"occ_12mo":0.9753,"occ_3mo":0.9621,"occ_t12":-0.0199,"tradeout":0.0151,"tradeout_amt":24.44,"tradeout_t12chg":-0.0753,"dom30":56,"leases30":284,"unleased":599,"ip_sample":6291},
{"entity":"Pikesville","id":"9037127f4765c1f1a63e032712a8afd7","market":"Baltimore, MD","asking":1817.26,"asking_12mo":1778.29,"ip_avg":1712.78,"ip_avg_12mo":1700.88,"ip_med":1680.00,"ip_t12_med":0.0243,"ip_t3_med":0.0079,"occ":0.9567,"occ_12mo":0.9429,"occ_3mo":0.9601,"occ_t12":0.0137,"tradeout":0.0910,"tradeout_amt":139.92,"tradeout_t12chg":0.0502,"dom30":82,"leases30":68,"unleased":156,"ip_sample":1675},
{"entity":"Towson - Southwest","id":"4134e2fdc0999525ab430760605b9b95","market":"Baltimore, MD","asking":1859.92,"asking_12mo":1979.64,"ip_avg":1809.15,"ip_avg_12mo":1776.81,"ip_med":1735.00,"ip_t12_med":0.0075,"ip_t3_med":0.0048,"occ":0.9848,"occ_12mo":0.9711,"occ_3mo":0.9806,"occ_t12":0.0137,"tradeout":0.0634,"tradeout_amt":94.35,"tradeout_t12chg":0.0355,"dom30":56,"leases30":54,"unleased":102,"ip_sample":1394}
],
"benchmarks":[
{"entity":"Washington, DC (MSA)","asking":2387.26,"ip_avg":2251.11,"ip_avg_12mo":2227.86,"ip_t12_med":0.0151,"occ":0.9511,"occ_12mo":0.9624,"occ_t12":-0.0113,"tradeout":0.0104,"dom30":70},
{"entity":"Baltimore, MD (MSA)","asking":1941.15,"ip_avg":1795.86,"ip_avg_12mo":1801.23,"ip_t12_med":-0.0121,"occ":0.9526,"occ_12mo":0.9707,"occ_t12":-0.0181,"tradeout":0.0302,"dom30":58}
]}
"""Submarket rent-momentum scorecard: derived metrics, threshold labels, composite.
Inputs: extracts/submarket_snapshot.json, extracts/monthly_rent_ts.json (DOM Sep-2025 embedded below from
mf_rent_ts MONTH toolu_bdrk_01G6s9vu5BiNZJDxiBdcKhdg).
Outputs: extracts/records.json, printed table."""
import json
snap = json.load(open("extracts/submarket_snapshot.json"))
ts = json.load(open("extracts/monthly_rent_ts.json"))
# Monthly median DOM, Sep-2025 .. Aug-2026 (from mf_rent_ts MONTH)
DOM_MONTHLY = {
"Downtown Silver Spring": [60.5,69.5,85.5,89.5,87.5,89,91,84,57.5,51.5,78,61],
"Hillcrest Heights/Marlow Heights": [57,62.5,70.5,75,83,60.5,74,60,71,49,55.5,54.5],
"Beltsville/Laurel/South Laurel": [65,76,68,71.5,73,66,55,59.5,48,48,62,58],
"Burke/Falls Church/Jefferson": [54,61.5,68,66.5,63,72.5,65,51,43,32,50,61],
"Pikesville": [92,97,64.5,144.5,139,109,83,74,76,37.5,78,70.5],
"Towson - Southwest": [45,72,77,161,78,69,23,45,76,32,64,70],
}
# Monthly tradeout sample sizes Aug-2026 (thin-sample flag)
TRADEOUT_N_AUG26 = {"Downtown Silver Spring":245,"Hillcrest Heights/Marlow Heights":145,
"Beltsville/Laurel/South Laurel":210,"Burke/Falls Church/Jefferson":147,"Pikesville":59,"Towson - Southwest":37}
# Tradeout same month last year (Aug-2025 monthly) for trajectory
TRADEOUT_AUG25 = {"Downtown Silver Spring":0.009795,"Hillcrest Heights/Marlow Heights":0.003101,
"Beltsville/Laurel/South Laurel":-0.013061,"Burke/Falls Church/Jefferson":0.077816,"Pikesville":0.037724,"Towson - Southwest":0.025663}
# Thresholds (leading indicators)
def s_tradeout(x): return 1 if x >= 0.03 else (-1 if x <= 0.01 else 0)
def s_occ(x): return 1 if x >= 0.005 else (-1 if x <= -0.005 else 0)
def s_dom(x): return 1 if x <= -5 else (-1 if x >= 10 else 0) # YoY change in days; lower = better
def label(net): return "Firming" if net >= 1 else ("Softening" if net <= -1 else "Flat")
recs = []
for r in snap["records"]:
n = r["entity"]
ser = ts["series"][n]
dom_sep25 = DOM_MONTHLY[n][0]
dom_yoy = r["dom30"] - dom_sep25
ip_avg_t12 = r["ip_avg"]/r["ip_avg_12mo"] - 1
asking_t12 = r["asking"]/r["asking_12mo"] - 1
spread = r["asking"]/r["ip_avg"] - 1
sig = {"tradeout": s_tradeout(r["tradeout"]), "occ": s_occ(r["occ_t12"]), "dom": s_dom(dom_yoy)}
net = sum(sig.values())
recs.append({
"entity": n, "id": r["id"], "market": r["market"],
"ip_t12_med": r["ip_t12_med"], "ip_avg_t12": round(ip_avg_t12,4), "ip_avg": r["ip_avg"],
"occ": r["occ"], "occ_12mo": r["occ_12mo"], "occ_t12_pp": r["occ_t12"],
"tradeout": r["tradeout"], "tradeout_amt": r["tradeout_amt"], "tradeout_aug25": TRADEOUT_AUG25[n],
"tradeout_n_aug26": TRADEOUT_N_AUG26[n],
"dom30": r["dom30"], "dom_sep25": dom_sep25, "dom_yoy": dom_yoy,
"asking_t12": round(asking_t12,4), "spread": round(spread,4), "leases30": r["leases30"],
"signals": sig, "net": net, "label": label(net),
"neg_dom_yoy": -dom_yoy,
})
json.dump(recs, open("extracts/records.json","w"), indent=1)
print(f"{'Submarket':34s} {'IPmed':>6s} {'IPavg':>6s} {'Occ':>6s} {'dOcc':>6s} {'TO':>6s} {'TO25':>6s} {'DOM':>4s} {'DOMyy':>6s} {'Ask12':>6s} {'Sprd':>6s} net label")
for x in recs:
print(f"{x['entity']:34s} {x['ip_t12_med']*100:6.1f} {x['ip_avg_t12']*100:6.1f} {x['occ']*100:6.1f} {x['occ_t12_pp']*100:6.1f} "
f"{x['tradeout']*100:6.1f} {x['tradeout_aug25']*100:6.1f} {x['dom30']:4.0f} {x['dom_yoy']:6.1f} {x['asking_t12']*100:6.1f} {x['spread']*100:6.1f} {x['net']:3d} {x['label']}")
for b in snap["benchmarks"]:
print(b["entity"], "ip_avg_t12=%.1f%%" % ((b["ip_avg"]/b["ip_avg_12mo"]-1)*100), "ip_med_t12=%.1f%%" % (b["ip_t12_med"]*100),
"occ=%.1f%%" % (b["occ"]*100), "dOcc=%.1fpp" % (b["occ_t12"]*100), "TO=%.1f%%" % (b["tradeout"]*100), "DOM=%d" % b["dom30"])
python scripts/submarket_momentum.py && S=skills/multi-entity-analysis/scripts/cross_check.py; python $S --check composite --components tradeout,occ_t12_pp,neg_dom_yoy,ip_t12_med --weights 0.25,0.25,0.25,0.25 extracts/records.json | python -c "import json,sys;d=json.load(sys.stdin);[print(r.get('entity'),round(r.get('score',0),1)) for r in d.get('ranked',[])] if 'ranked' in d else print(d)"; python $S --check buckets --field label --expected "Firming,Flat,Softening" extracts/records.json; python $S --check extremum --field tradeout --direction max --claim Pikesville extracts/records.json; python $S --check extremum --field occ --direction max --claim "Towson - Southwest" extracts/records.json; python $S --check extremum --field dom30 --direction max --claim "Beltsville/Laurel/South Laurel" extracts/records.json; python $S --check predicate --field tradeout --op lt --value 0 --claim "Downtown Silver Spring" extracts/records.json; python $S --check extremum --field ip_t12_med --direction max --claim "Burke/Falls Church/Jefferson" extracts/records.json; python $S --check extremum --field occ_t12_pp --direction min --claim "Burke/Falls Church/Jefferson" extracts/records.jsonsed -i 's/def label(net): return "Firming" if net >= 1 else ("Softening" if net <= -1 else "Flat")/def label(net): return "Firming" if net >= 1 else ("Softening" if net <= -2 else "Cooling")/' scripts/submarket_momentum.py && python scripts/submarket_momentum.py | tail -8 && python skills/multi-entity-analysis/scripts/cross_check.py --check buckets --field label --expected "Firming,Cooling,Softening" extracts/records.json; grep -n "intervals\|12_24_36" skills/forecasting/references/response.md | head; sed -n '/## Example A/,/## Example B/p' skills/forecasting/references/request.md | head -80"""Run forecasting engine for in-place and asking rent, 6 submarkets, 1-year horizon.
Inputs: extracts/monthly_rent_ts.json. Outputs: extracts/forecasts/*.json + summary print."""
import json, subprocess, os
ts = json.load(open("extracts/monthly_rent_ts.json"))
months = ts["months"]
os.makedirs("extracts/forecasts", exist_ok=True)
summary = []
def t12(arr): return arr[-1]/arr[-13]-1
for name, s in ts["series"].items():
par = ts["parents"][s["parent"]]
div = t12(s["in_place"]) - t12(s["asking"])
for metric in ["in_place", "asking"]:
hist = [{"period": m, "value": v, "source": "datamart"} for m, v in zip(months, s[metric])]
peer = [{"period": m, "value": v, "source": "datamart"} for m, v in zip(months, par[metric])]
level_ratio = s[metric][-1]/par[metric][-1]
payload = {
"metric": {"name": f"mf_rent_ts.{metric}_rent_latest_avg", "units": "$", "family": "rent_or_occupancy"},
"subject": {"entity_type": "submarket", "entity_id": s["id"], "label": name},
"horizon": {"years": 1, "intervals": "annual"},
"as_of": "2026-09-30",
"history": hist,
"history_check": {"count": len(hist), "sum": round(sum(s[metric]), 2), "last_value": s[metric][-1]},
"peer_history": peer,
"peer_history_check": {"count": len(peer), "last_value": par[metric][-1]},
"caller_disclosures": {
"peer_selection_basis": f"parent MSA ({par['label']}) default; level ratio {level_ratio:.2f}; subject T12 {t12(s[metric])*100:.1f}% vs parent T12 {t12(par[metric])*100:.1f}%",
"sibling_series_note": "asking and in-place both run through engine (divergence > 100 bps)" if abs(div) > 0.01 else "sibling checked; within 100 bps",
"sibling_divergence_pct": round(div, 4),
"signals_omitted": [
{"signal": "supply_pipeline", "reason": "no source exists at submarket grain (supply snapshot is market-grain only)"},
{"signal": "migration", "reason": "not gathered"},
{"signal": "employment", "reason": "not gathered"}],
"lookback_note": "full 24 monthly points available in pull (Sep-2024 to Aug-2026)"},
"scenarios": ["base", "upside", "downside"]}
fn = f"extracts/forecasts/{name.replace('/','_').replace(' ','_')}_{metric}.json"
json.dump(payload, open(fn.replace('.json','_payload.json'), "w"))
out = subprocess.run(["python", "skills/forecasting/scripts/forecast.py", "--file", fn.replace('.json','_payload.json')], capture_output=True, text=True).stdout
open(fn, "w").write(out)
r = json.loads(out)
summary.append({"entity": name, "metric": metric, "status": r.get("status"), "confidence": r.get("confidence"),
"base": r.get("base_case"), "up": r.get("upside_case"), "down": r.get("downside_case"),
"flags": r.get("data_quality_flags"), "sibling_div": round(div,4)})
json.dump(summary, open("extracts/forecast_summary.json","w"), indent=1)
for x in summary:
print(json.dumps(x)[:1500]); print()
python scripts/run_rent_forecasts.py 2>&1 | head -c 12000; echo; python -c "import json;d=json.load(open('extracts/forecasts/Pikesville_in_place.json'));print(list(d.keys()))"S=skills/multi-entity-analysis/scripts/cross_check.py; for a in "occ min Downtown Silver Spring" "asking_t12 max Hillcrest Heights/Marlow Heights" "dom_yoy max Beltsville/Laurel/South Laurel"; do set -- $a; f=$1; d=$2; shift 2; python $S --check extremum --field $f --direction $d --claim "$*" extracts/records.json | grep -E '"value"|claim_holds'; done; python -c "
import json
for x in json.load(open('extracts/forecast_summary.json')):
print(x['entity'][:22],x['metric'],x['confidence'],[round(c['pct_change']*100,1) for c in (x['down'],x['base'],x['up']) for c in c])"Only the two Baltimore-area submarkets, Pikesville and Towson – Southwest, are firming, and both are small. All four DC-area submarkets are cooling or softening. Burke is the trap: its 4.0% trailing rent growth comes from leases signed a year or more ago, and new leases are now priced only 1.5% above the previous tenant's rent.
| Submarket | Metro | In-place rent T12 change (median) | Occupancy | Occupancy 12-mo change (pp) | New-lease tradeout | Median days on market (last 30d) | Days on market vs Sep-2025 | Read |
|---|---|---|---|---|---|---|---|---|
| Submarkets | ||||||||
| Baltimore | +2.4%1 | 95.7% | +1.4 | +9.1% ($140)2 | 82 | −10 | Firming3 | |
| Baltimore | +0.8%1 | 98.5% | +1.4 | +6.3% ($94)2 | 56 | +11 | Firming | |
| DC | +4.0% | 95.5% | −2.0 | +1.5% ($24) | 56 | +2 | Cooling | |
| DC | +0.5% | 96.4% | −0.4 | +0.4% ($7) | 53 | −4 | Cooling | |
| DC | +1.8%1 | 94.5% | −1.1 | −1.7% (−$50) | 96 | +36 | Softening | |
| DC | +0.2%1 | 96.3% | −0.8 | +0.7% ($9) | 110 | +45 | Softening | |
| Benchmarks | ||||||||
| — | +1.5% | 95.1% | −1.1 | +1.0% | 70 | – | – | |
| — | −1.2% | 95.3% | −1.8 | +3.0% | 58 | – | – | |
Base next year's rent growth on what new leases are actually signing at, not on trailing in-place growth. In-place rent reflects leases signed 6–18 months ago, so it lags. The forecasting engine, which works from that lagging series, gets the order backwards: it has Burke highest at +4.4% and Pikesville near the bottom at +0.9%.
| Submarket | Engine base (downside / upside) | Engine confidence | Recommended Yr-1 underwriting (analyst judgment) | Why |
|---|---|---|---|---|
| Pikesville | +0.9% (−1.1% / +2.9%) | Medium | 2.5–3.0%1 | A 9% tradeout and rising occupancy should flow into in-place rent; stay below the tradeout because of the thin sample and 82 days on market. |
| Towson – Southwest | +1.0% (−1.0% / +3.0%) | Medium | 2.0–2.5% | 98.5% occupancy and a 6.3% tradeout, but low volume and asking rents down 6%. |
| Burke/Falls Church/Jefferson | +4.4% (+2.4% / +6.4%) | Medium | 1.5–2.0% | Do not carry the trailing 4%: tradeout is 1.5% and occupancy fell 2pp. |
| Hillcrest Heights/Marlow Heights | +1.3% (−0.7% / +3.3%) | Medium | 0.5–1.0% | Tradeout near zero; the 7% asking increase isn't being realized in signed leases. |
| Downtown Silver Spring | +1.9% (−0.1% / +3.9%) | Medium | 0% plus a concession allowance | Negative tradeout, lowest occupancy, days on market up 36. |
| Beltsville/Laurel/South Laurel | +0.5% (−1.5% / +2.5%) | Low | 0–0.5% | Slowest leasing (110 days) and occupancy down 0.8pp. |
About the engine numbers above. Every run came back with the flag "boom extrapolation suspected" or, for Beltsville, "cyclical drawdown suspected." All runs also flagged "peer mismatch suspected," because each submarket's growth deviates 7.0–40.4 sigma from its metro . Supply, migration and jobs data weren't included in the forecasts.
The spread between the groups is the point. Two Baltimore submarkets that are currently strong can justify about 2.5%. Four DC submarkets can't justify much above 1–2%, and Silver Spring can't justify anything above zero. A single DC-wide growth rate would overstate Silver Spring and Beltsville and understate Pikesville.
What I'd act on now: underwrite Silver Spring and Beltsville flat for year 1, and cut Burke to 2% or below whatever the trailing numbers say. What would change this: Burke's monthly tradeout climbing back above 3% while occupancy recovers above 96.5%. That would mean its slowdown was seasonal rather than a real turn.
This is a read-only snapshot of a RealAI analysis.