[object Object]

← back to Rentv 2026

harden(llm-extract): Cody gate — anchor num() at the LEADING number + a >=100k 'already a full dollar value' guard, fixing a CATASTROPHE my first pass introduced ('51800000 (51.8M)' concatenated digit runs then scaled → $5e15). Also covers mln/bn abbrevs; anchoring rejects 'lot 5b'. 17/17 verified

b2b4b8c88d39b306b2ee7a250efeb55865625ce3 · 2026-08-06 12:28:05 -0700 · Steve

Files touched

Diff

commit b2b4b8c88d39b306b2ee7a250efeb55865625ce3
Author: Steve <steve@designerwallcoverings.com>
Date:   Thu Aug 6 12:28:05 2026 -0700

    harden(llm-extract): Cody gate — anchor num() at the LEADING number + a >=100k 'already a full dollar value' guard, fixing a CATASTROPHE my first pass introduced ('51800000 (51.8M)' concatenated digit runs then scaled → $5e15). Also covers mln/bn abbrevs; anchoring rejects 'lot 5b'. 17/17 verified
---
 scripts/lib/llm-extract.mjs | 24 ++++++++++++++----------
 1 file changed, 14 insertions(+), 10 deletions(-)

diff --git a/scripts/lib/llm-extract.mjs b/scripts/lib/llm-extract.mjs
index 847ce8c2..97b2c044 100644
--- a/scripts/lib/llm-extract.mjs
+++ b/scripts/lib/llm-extract.mjs
@@ -50,18 +50,22 @@ export async function llmExtract(title, story, { endpoint = OLLAMA_ENDPOINTS[0],
     try { obj = JSON.parse(j.response); } catch { return null; }
     if (!obj || typeof obj !== 'object') return null;
     // normalize
-    // Magnitude-aware: the prompt asks for a plain integer, but if the model returns "51.8 million" /
-    // "$1.2 billion" / "$51.8M" / "985k" the old digits-only strip signed those as 52 / 1 / 52 / 985.
-    // A compliant integer (no magnitude word/suffix) is unchanged; only a magnitude string gets scaled.
+    // Magnitude-aware, anchored at the LEADING number so a chatty value can't concatenate digit runs.
+    // The prompt asks for a plain integer, but a model may return "51.8 million" / "$1.2 billion" / "$51.8M"
+    // / "985k" / "1.2 bn" (scale those) OR annotate a real integer like "51800000 (51.8M)" (do NOT scale —
+    // the base is already a full dollar value, so the >=100k guard returns it as-is; scaling would give $5e15).
     const num = (v) => {
       if (v == null) return null;
-      const s = String(v).toLowerCase();
-      let mult = 1;
-      if (/billion|[\d.]\s*b\b/.test(s)) mult = 1e9;
-      else if (/million|[\d.]\s*m\b/.test(s)) mult = 1e6;
-      else if (/thousand|[\d.]\s*k\b/.test(s)) mult = 1e3;
-      const n = parseFloat(s.replace(/[^0-9.]/g, '')) * mult;
-      return isFinite(n) && n > 0 ? Math.round(n) : null;
+      const s = String(v).toLowerCase().trim();
+      if (!s || /^(null|n\/a|none|unknown)$/i.test(s)) return null;
+      const m = s.match(/^\$?\s*([\d,]+(?:\.\d+)?)\s*(billion|bil\b|bn\b|b\b|million|mln\b|m\b|thousand|k\b)?/);
+      if (!m) return null;
+      const base = parseFloat(m[1].replace(/,/g, ''));
+      if (!isFinite(base) || base <= 0) return null;
+      if (base >= 100000) return Math.round(base); // already a full dollar value → never scale
+      const sfx = (m[2] || '').trim();
+      const mult = /^(billion|bil|bn|b)$/.test(sfx) ? 1e9 : /^(million|mln|m)$/.test(sfx) ? 1e6 : /^(thousand|k)$/.test(sfx) ? 1e3 : 1;
+      return Math.round(base * mult);
     };
     const amount = num(obj.amount);
     const clean = (v) => { const s = v == null ? '' : String(v).trim(); return s && !/^(null|n\/a|none|unknown)$/i.test(s) ? s : null; };

← 869dd436 auto-data-snapshot: 2026-08-06T12:25:51 (1 data files) — pac  ·  back to Rentv 2026  ·  feat(services): rank big multi-location firms first (solicit 427a4cf2 →