← back to Nationalrealestate
Market detail: Climate Risk (FEMA NRI) + Rental Market (HUD FMR + ZORI vs FMR note) panels; favicons across pages; README
db8222e45a460621b45c0aa0af641eea29ad6a94 · 2026-07-21 15:17:13 -0700 · Steve Abrams
Files touched
A README.mdM public/admin.htmlM public/compare.htmlM public/market.htmlM public/markets.htmlM public/watchlist.html
Diff
commit db8222e45a460621b45c0aa0af641eea29ad6a94
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Tue Jul 21 15:17:13 2026 -0700
Market detail: Climate Risk (FEMA NRI) + Rental Market (HUD FMR + ZORI vs FMR note) panels; favicons across pages; README
---
README.md | 37 +++++++++++++++++++++++++++++++++++++
public/admin.html | 1 +
public/compare.html | 1 +
public/market.html | 34 ++++++++++++++++++++++++++++++++++
public/markets.html | 1 +
public/watchlist.html | 1 +
6 files changed, 75 insertions(+)
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..38958e7
--- /dev/null
+++ b/README.md
@@ -0,0 +1,37 @@
+# USRealEstate — national market explorer
+
+Live: https://usrealestate.agentabrams.com (basic auth) · local: `npm run server` → http://127.0.0.1:9913
+
+Free-data national U.S. residential market platform: market metrics + opportunity scoring for every
+county/metro, per-property detail (13 sections) for covered counties, and a national broker registry
+from state licensing rosters. No MLS licenses, no paid data vendors — every source is free/public
+with provenance (`ingest_runs`) and fail-loud URL-drift detection.
+
+## Stack
+TypeScript + tsx + Express + Postgres (`usre`, socket). Frontend: vanilla HTML/JS in `public/`
+(dark/gold house style, no build step). pm2 `usrealestate`, port 9913.
+
+## Data sources ($0)
+| Source | Metrics | Cadence |
+|---|---|---|
+| Redfin Data Center | sale price, inventory, DOM, sale-to-list, supply | monthly |
+| Zillow Research | ZHVI, ZORI | monthly |
+| Census ACS 5-yr | income, rent, population, vacancy (key required) | annual |
+| FHFA HPI | house price index | quarterly |
+| FEMA NRI v1.20 | climate risk scores | release |
+| HUD FMR FY26 | fair market rents 0–4BR | annual |
+| State licensing rosters | brokers/firms (FL TX NY CO CT + wave 2) | on demand |
+| County parcels | LA (CRCP SQLite) + expanding (`parcel_source` registry) | on demand |
+
+## Commands
+`npm run migrate` · `seed:regions` · `ingest:zillow|redfin|acs|fhfa|nri|fmr` · `derive` · `score` ·
+`ingest:brokers -- <state>|all` · `refresh` (full monthly chain, cron day-25 03:30)
+
+## Key invariants
+- Scoring is deterministic (frozen snapshot + `score-config.json` weights); thin markets
+ (pop<50k or <10 sales/mo) are scored but **excluded from rank**.
+- Legacy CT counties (09001–09015) coexist with planning regions — Zillow/Redfin ship legacy,
+ the choropleth GeoJSON has legacy shapes.
+- Every grid page has sort + density controls persisted to localStorage (house rule).
+- Deploy: rsync to Kamatera `/root/public-projects/nationalrealestate`; nginx vhost must
+ `listen 45.61.58.125:443`; `parcel_source` rows must be inserted per environment.
diff --git a/public/admin.html b/public/admin.html
index 6a37386..df28ee2 100644
--- a/public/admin.html
+++ b/public/admin.html
@@ -3,6 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
+<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' rx='3' fill='%230a0d13'/%3E%3Ctext x='8' y='12' font-size='10' text-anchor='middle' fill='%23c8a24b'%3EUS%3C/text%3E%3C/svg%3E">
<title>USRealEstate — Admin · Ingest Provenance</title>
<style>
:root{--bg:#0a0d13;--panel:#11151d;--line:#222936;--fg:#e8ecf2;--muted:#8a93a3;--gold:#c8a24b;--red:#f85149;--green:#3fb950;--amber:#d29922;}
diff --git a/public/compare.html b/public/compare.html
index 500097f..b48ad28 100644
--- a/public/compare.html
+++ b/public/compare.html
@@ -3,6 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
+<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' rx='3' fill='%230a0d13'/%3E%3Ctext x='8' y='12' font-size='10' text-anchor='middle' fill='%23c8a24b'%3EUS%3C/text%3E%3C/svg%3E">
<title>USRealEstate — Compare Markets</title>
<link rel="stylesheet" href="/vendor/uPlot.min.css">
<style>
diff --git a/public/market.html b/public/market.html
index 26cc27e..3fd3beb 100644
--- a/public/market.html
+++ b/public/market.html
@@ -3,6 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
+<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' rx='3' fill='%230a0d13'/%3E%3Ctext x='8' y='12' font-size='10' text-anchor='middle' fill='%23c8a24b'%3EUS%3C/text%3E%3C/svg%3E">
<title>USRealEstate — Market Detail</title>
<link rel="stylesheet" href="/vendor/uPlot.min.css">
<style>
@@ -67,6 +68,16 @@
<h3>Demographics & Derived</h3>
<div id="stats"></div>
</div>
+ <div class="grid2" style="margin-top:16px;">
+ <div class="panel">
+ <h3>Climate Risk <span style="color:var(--muted);font-weight:400;text-transform:none">— FEMA National Risk Index</span></h3>
+ <div id="climate" class="nodata">no climate data for this region</div>
+ </div>
+ <div class="panel">
+ <h3>Rental Market <span style="color:var(--muted);font-weight:400;text-transform:none">— HUD Fair Market Rents FY26</span></h3>
+ <div id="rental" class="nodata">no FMR data for this region</div>
+ </div>
+ </div>
<div class="panel" style="margin-top:16px;">
<h3>Parcels</h3>
<div class="nodata" id="parcels">Parcels — county data not yet loaded</div>
@@ -160,6 +171,29 @@ function chart(elId, entries, fmt){
document.getElementById('stats').innerHTML = STATS.map(([k, v]) =>
`<div class="stat"><div class="k">${k}</div><div class="v">${v}</div></div>`).join('');
+ if (latest.nri_risk_score != null) {
+ const riskWord = v => v >= 90 ? 'Very High' : v >= 70 ? 'High' : v >= 40 ? 'Moderate' : v >= 15 ? 'Low' : 'Very Low';
+ const CR = [
+ ['Overall Risk', latest.nri_risk_score, true],
+ ['Expected Annual Loss', latest.nri_eal_score, true],
+ ['Social Vulnerability', latest.nri_sovi_score, false],
+ ['Community Resilience', latest.nri_resl_score, false],
+ ];
+ document.getElementById('climate').className = '';
+ document.getElementById('climate').innerHTML = CR.map(([k, v, word]) => `
+ <div class="stat"><div class="k">${k}</div>
+ <div class="v">${v.toFixed(0)}<span style="font-size:11px;color:var(--muted)">/100</span>
+ ${word ? `<span style="font-size:11px;color:${v >= 70 ? '#f85149' : v >= 40 ? '#d29922' : '#3fb950'};margin-left:6px">${riskWord(v)}</span>` : ''}</div></div>`).join('');
+ }
+ if (latest.fmr_2br != null) {
+ const FMRS = [['Studio', latest.fmr_0br], ['1 BR', latest.fmr_1br], ['2 BR', latest.fmr_2br], ['3 BR', latest.fmr_3br], ['4 BR', latest.fmr_4br]];
+ const zoriNote = latest.zori != null
+ ? `<div style="font-size:12px;color:var(--muted);margin-top:8px">ZORI observed rent: <b style="color:var(--gold)">${usd0(latest.zori)}</b>/mo · FMR 2BR ${latest.zori > latest.fmr_2br ? 'below' : 'above'} market</div>` : '';
+ document.getElementById('rental').className = '';
+ document.getElementById('rental').innerHTML = FMRS.map(([k, v]) => `
+ <div class="stat"><div class="k">${k}</div><div class="v">${usd0(v)}<span style="font-size:11px;color:var(--muted)">/mo</span></div></div>`).join('') + zoriNote;
+ }
+
// M-P1: counties with parcel coverage get a property-level search entry point
if (region.region_type === 'county' && region.fips === '06037') {
document.getElementById('parcels').innerHTML =
diff --git a/public/markets.html b/public/markets.html
index 4dd923e..206eeb4 100644
--- a/public/markets.html
+++ b/public/markets.html
@@ -3,6 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
+<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' rx='3' fill='%230a0d13'/%3E%3Ctext x='8' y='12' font-size='10' text-anchor='middle' fill='%23c8a24b'%3EUS%3C/text%3E%3C/svg%3E">
<title>USRealEstate — Markets Ranking</title>
<link rel="stylesheet" href="/crcp-grid.css">
<style>
diff --git a/public/watchlist.html b/public/watchlist.html
index 7f8f485..870cbe3 100644
--- a/public/watchlist.html
+++ b/public/watchlist.html
@@ -3,6 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
+<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Crect width='16' height='16' rx='3' fill='%230a0d13'/%3E%3Ctext x='8' y='12' font-size='10' text-anchor='middle' fill='%23c8a24b'%3EUS%3C/text%3E%3C/svg%3E">
<title>USRealEstate — Watchlist</title>
<style>
:root{--bg:#0a0d13;--panel:#11151d;--line:#222936;--fg:#e8ecf2;--muted:#8a93a3;--gold:#c8a24b;--red:#f85149;--green:#3fb950;}
← 502de06 Compare Markets page: up to 4 markets side-by-side (best-val
·
back to Nationalrealestate
·
Perf: 5-min in-process cache on /api/markets (1.8s scan -> m 511984e →