← back to Commercialrealestate
warrantability: extract fhaEffectiveSignal helper, wire all 3 FHA consumers (Cody FIX-FIRST)
ebb3d7b155189ba93c3d5ca81e0f6a9f62dac6ff · 2026-07-31 12:20:00 -0700 · Steve
Cody gate on 8ba940e found the frozen-signal bug in 3 more consumers I left
unfixed. Now a single fhaEffectiveSignal(c, today) helper is called from:
- /api/warrantability (counts + expiring)
- /api/fha-condos (the browsable directory condos.html falls back to on prod —
filters lapsed out of ?status=fha_approved AND overwrites the returned
warrant_signal so a card badge never shows a lapsed cert as approved)
- /api/crcp/stats (the dashboard headline tile)
Also fixes the timezone boundary bug: zeroes both dates to local midnight
(matching /api/fha-expiring), so 'expires today' correctly stays approved.
Verified live (throwaway): all 3 agree fha_approved 350 / fha_expired 2311,
0 lapsed still surfacing in the directory.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Files touched
Diff
commit ebb3d7b155189ba93c3d5ca81e0f6a9f62dac6ff
Author: Steve <steve@designerwallcoverings.com>
Date: Fri Jul 31 12:20:00 2026 -0700
warrantability: extract fhaEffectiveSignal helper, wire all 3 FHA consumers (Cody FIX-FIRST)
Cody gate on 8ba940e found the frozen-signal bug in 3 more consumers I left
unfixed. Now a single fhaEffectiveSignal(c, today) helper is called from:
- /api/warrantability (counts + expiring)
- /api/fha-condos (the browsable directory condos.html falls back to on prod —
filters lapsed out of ?status=fha_approved AND overwrites the returned
warrant_signal so a card badge never shows a lapsed cert as approved)
- /api/crcp/stats (the dashboard headline tile)
Also fixes the timezone boundary bug: zeroes both dates to local midnight
(matching /api/fha-expiring), so 'expires today' correctly stays approved.
Verified live (throwaway): all 3 agree fha_approved 350 / fha_expired 2311,
0 lapsed still surfacing in the directory.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---
scripts/serve.js | 44 +++++++++++++++++++++++++++++---------------
1 file changed, 29 insertions(+), 15 deletions(-)
diff --git a/scripts/serve.js b/scripts/serve.js
index 10ca8a3..05719fd 100644
--- a/scripts/serve.js
+++ b/scripts/serve.js
@@ -595,6 +595,19 @@ app.get('/api/firms/share', async (req, res) => {
} catch (e) { res.status(502).json({ error: String(e.message).split('\n')[0] }); }
});
+// An FHA cert's stored warrant_signal is frozen at fetch-time; re-derive at request time so a
+// project whose expiration_date has already passed reads as expired, not approved (else the
+// warrantable proxy over-reports). Dates are zeroed to local midnight to match /api/fha-expiring
+// and avoid a timezone boundary flip on a "expires today" project. `today` must be pre-zeroed.
+// Single source of truth for /api/warrantability, /api/fha-condos, and /api/crcp/stats.
+function fhaEffectiveSignal(c, today) {
+ if (c.warrant_signal !== 'fha_approved') return c.warrant_signal;
+ const m = (c.expiration_date || '').match(/(\d{2})\/(\d{2})\/(\d{4})/);
+ if (!m) return c.warrant_signal;
+ const exp = new Date(+m[3], +m[1] - 1, +m[2]); exp.setHours(0, 0, 0, 0);
+ return (exp < today) ? 'fha_expired' : 'fha_approved';
+}
+
// Warrantability breakdown from the FHA-approved list (the authoritative warrantability source).
// status counts + per-city top approved + expiring-soon list. All from data/fha-approved-condos.json.
app.get('/api/warrantability', (req, res) => {
@@ -604,21 +617,18 @@ app.get('/api/warrantability', (req, res) => {
const byStatus = {};
const byCity = {};
const expiring = [];
- const now = new Date();
+ const now = new Date(); now.setHours(0, 0, 0, 0);
for (const c of condos) {
- // Re-derive at request time: warrant_signal is frozen at fetch-time and silently goes
- // stale as certs lapse. An 'fha_approved' project whose expiration_date is already in the
- // past is NOT FHA-eligible today — count it as expired, not approved (else the warrantable
- // proxy over-reports). Only the approved bucket is date-gated; others pass through.
- const m = (c.expiration_date || '').match(/(\d{2})\/(\d{2})\/(\d{4})/);
- let days = null;
- if (m) { const exp = new Date(+m[3], +m[1] - 1, +m[2]); days = Math.round((exp - now) / 86400000); }
- let sig = c.warrant_signal;
- if (sig === 'fha_approved' && days !== null && days < 0) sig = 'fha_expired';
+ const sig = fhaEffectiveSignal(c, now);
byStatus[sig] = (byStatus[sig] || 0) + 1;
if (sig === 'fha_approved') {
if (c.city) byCity[c.city] = (byCity[c.city] || 0) + 1;
- if (days !== null && days >= 0 && days <= 365) expiring.push({ project: c.project_name, city: c.city, zip: c.zip, expiration: c.expiration_date, days });
+ const m = (c.expiration_date || '').match(/(\d{2})\/(\d{2})\/(\d{4})/);
+ if (m) {
+ const exp = new Date(+m[3], +m[1] - 1, +m[2]); exp.setHours(0, 0, 0, 0);
+ const days = Math.round((exp - now) / 86400000);
+ if (days >= 0 && days <= 365) expiring.push({ project: c.project_name, city: c.city, zip: c.zip, expiration: c.expiration_date, days });
+ }
}
}
const cities = Object.entries(byCity).sort((a, b) => b[1] - a[1]).slice(0, 12).map(([city, n]) => ({ city, n }));
@@ -729,11 +739,14 @@ app.get('/api/fha-condos', (req, res) => {
const { meta, condos } = JSON.parse(fs.readFileSync(file, 'utf8'));
const q = String(req.query.q || '').toLowerCase();
const status = req.query.status || 'fha_approved';
- let rows = condos.filter(c => status === 'all' || c.warrant_signal === status);
+ // re-derive the signal at request time so lapsed certs don't surface as fha_approved here
+ // (this browsable directory is condos.html's fallback on prod, so it must not over-report)
+ const today = new Date(); today.setHours(0, 0, 0, 0);
+ let rows = condos.filter(c => status === 'all' || fhaEffectiveSignal(c, today) === status);
if (q) rows = rows.filter(c => (c.project_name + ' ' + c.city + ' ' + c.zip + ' ' + c.address).toLowerCase().includes(q));
// Normalize the ALL-CAPS HUD city names + repair the "CANADA"/91011 truncation for display,
// consistent with Panel 7 (fixCity, defined below). Non-mutating — the source JSON is untouched.
- const out = rows.slice(0, 400).map(c => ({ ...c, city: fixCity(c.city, c.zip) }));
+ const out = rows.slice(0, 400).map(c => ({ ...c, city: fixCity(c.city, c.zip), warrant_signal: fhaEffectiveSignal(c, today) }));
res.json({ label: meta.label, count: rows.length, condos: out });
} catch (e) { res.status(502).json({ error: String(e.message).split('\n')[0], condos: [] }); }
});
@@ -811,8 +824,9 @@ app.get('/api/crcp/stats', async (req, res) => {
// FHA warrantability reference
try {
const fc = JSON.parse(fs.readFileSync(path.join(ROOT, 'data', 'fha-approved-condos.json'), 'utf8')).condos;
- out.fhaApproved = fc.filter(c => c.warrant_signal === 'fha_approved').length;
- out.fhaExpired = fc.filter(c => c.warrant_signal === 'fha_expired').length;
+ const fhaToday = new Date(); fhaToday.setHours(0, 0, 0, 0); // re-derive lapsed certs (shared helper)
+ out.fhaApproved = fc.filter(c => fhaEffectiveSignal(c, fhaToday) === 'fha_approved').length;
+ out.fhaExpired = fc.filter(c => fhaEffectiveSignal(c, fhaToday) === 'fha_expired').length;
out.fhaTotal = fc.length; // full HUD reference-list size (Panel 4 breakdown)
out.fhaMethods = fc.reduce((m, c) => { const k = c.approval_method || '—'; m[k] = (m[k] || 0) + 1; return m; }, {});
} catch (_) {}
← 8ba940e warrantability: reclassify lapsed FHA certs as expired at re
·
back to Commercialrealestate
·
index.html: fix list-view column resize not persisting (CSS- a6365d0 →