[object Object]

← 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 →