← back to Estimate Instant
Add fail-closed room coverage API
8e0c9d3243110e6bc2dee7c777da9dac0aca69a1 · 2026-08-28 22:12:50 -0700 · Steve Abrams
Files touched
M README.mdM server.jsA test/calculate-coverage.test.jsM verification/e2e-proof.json
Diff
commit 8e0c9d3243110e6bc2dee7c777da9dac0aca69a1
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Fri Aug 28 22:12:50 2026 -0700
Add fail-closed room coverage API
---
README.md | 6 ++-
server.js | 102 ++++++++++++++++++++++++++++++++---
test/calculate-coverage.test.js | 116 ++++++++++++++++++++++++++++++++++++++++
verification/e2e-proof.json | 11 ++--
4 files changed, 224 insertions(+), 11 deletions(-)
diff --git a/README.md b/README.md
index 5a5f5bf..3233f07 100644
--- a/README.md
+++ b/README.md
@@ -21,7 +21,11 @@ It returns `rolls_needed`, `waste_pct`, `total_cost`, and calculation notes. The
endpoint is read-only and uses the checked-in `data/rolls.json` snapshot; its
response labels that provenance and warns callers to confirm current product
specifications and price before quoting. It does not query or mutate Shopify or
-`dw_unified`.
+`dw_unified`. Inputs must be JSON number primitives: width is capped at 1,000 ft,
+height at 100 ft, and `num_walls` must be an integer from 1 through 100. These
+conservative bounds prevent coercion and overflow. A Shopify SKU alias is accepted
+only for an exact `shopify_match:true` record. Prototype/stand-in rows still return
+coverage metrics, but return null prices with `quote_authoritative:false`.
## Fill it in
- `data/products.json` — array of `{ title, sku, price, hex, image }`.
diff --git a/server.js b/server.js
index d9a8aee..b4c9e61 100644
--- a/server.js
+++ b/server.js
@@ -143,6 +143,81 @@ function estimate({ wallWidthIn, wallHeightIn, roll, checkout_domain }) {
};
}
+// Stable API contract for room-based consumers. This deliberately uses the
+// checked-in roll-spec snapshot: the dw_unified mirror is not authoritative for
+// sell price, and this local endpoint must never imply a live database read.
+function calculateCoverage(input, rolls = loadRolls(), checkout_domain) {
+ const sku = typeof input?.sku === 'string' ? input.sku.trim() : '';
+ const roomWidthFt = input?.room_width_ft;
+ const roomHeightFt = input?.room_height_ft;
+ const numWalls = input?.num_walls;
+ const errors = [];
+ if (!sku) errors.push('sku is required.');
+ if (typeof roomWidthFt !== 'number' || !Number.isFinite(roomWidthFt) || roomWidthFt <= 0 || roomWidthFt > 1000) {
+ errors.push('room_width_ft must be a JSON number greater than 0 and at most 1000.');
+ }
+ if (typeof roomHeightFt !== 'number' || !Number.isFinite(roomHeightFt) || roomHeightFt <= 0 || roomHeightFt > 100) {
+ errors.push('room_height_ft must be a JSON number greater than 0 and at most 100.');
+ }
+ if (typeof numWalls !== 'number' || !Number.isInteger(numWalls) || numWalls < 1 || numWalls > 100) {
+ errors.push('num_walls must be a JSON integer from 1 through 100.');
+ }
+ if (errors.length) return { ok: false, errors };
+
+ const key = sku.toUpperCase();
+ const roll = rolls.find((item) =>
+ String(item.sku || '').toUpperCase() === key ||
+ (item.shopify_match === true && String(item.shopify_sku || '').toUpperCase() === key)
+ );
+ if (!roll) return { ok: false, errors: [`No local roll specification found for SKU ${sku}.`] };
+
+ const totalWidthIn = roomWidthFt * 12 * numWalls;
+ const wallHeightIn = roomHeightFt * 12;
+ if (!Number.isFinite(totalWidthIn) || Math.abs(totalWidthIn) > Number.MAX_SAFE_INTEGER || !Number.isFinite(wallHeightIn)) {
+ return { ok: false, errors: ['Room dimensions exceed the safe calculation range.'] };
+ }
+ const result = estimate({
+ wallWidthIn: totalWidthIn,
+ wallHeightIn,
+ roll,
+ checkout_domain,
+ });
+ if (!result.ok) return result;
+ const quoteAuthoritative = roll.shopify_match === true &&
+ typeof roll.shopify_price === 'number' && Number.isFinite(roll.shopify_price) && roll.shopify_price > 0 &&
+ typeof roll.roll_width_in === 'number' && Number.isFinite(roll.roll_width_in) && roll.roll_width_in > 0 &&
+ typeof roll.roll_length_ft === 'number' && Number.isFinite(roll.roll_length_ft) && roll.roll_length_ft > 0 &&
+ typeof roll.pattern_repeat_in === 'number' && Number.isFinite(roll.pattern_repeat_in) && roll.pattern_repeat_in >= 0;
+ const numericOutputs = [result.rollsNeeded, result.wastePct,
+ result.totalStrips, result.stripsPerRoll, result.cutLengthIn];
+ if (quoteAuthoritative) numericOutputs.push(result.price, result.pricePerRoll);
+ if (numericOutputs.some((value) => typeof value !== 'number' || !Number.isFinite(value))) {
+ return { ok: false, errors: ['Roll specifications produced a non-finite calculation; verify the product data.'] };
+ }
+
+ const notes = [
+ `Assumes ${numWalls} wall${numWalls === 1 ? '' : 's'} at ${roomWidthFt} ft wide by ${roomHeightFt} ft high.`,
+ 'Calculated from the checked-in local roll-spec snapshot; confirm current specifications and price before quoting.',
+ ];
+ if (!quoteAuthoritative) notes.push('This local record is not quote-authoritative; pricing is withheld until an exact matched product has valid price and roll specifications.');
+
+ return {
+ ok: true,
+ sku: result.sku,
+ requested_sku: sku,
+ rolls_needed: result.rollsNeeded,
+ waste_pct: result.wastePct,
+ total_cost: quoteAuthoritative ? result.price : null,
+ price_per_roll: quoteAuthoritative ? result.pricePerRoll : null,
+ quote_authoritative: quoteAuthoritative,
+ strips_needed: result.totalStrips,
+ strips_per_roll: result.stripsPerRoll,
+ cut_length_in: result.cutLengthIn,
+ notes,
+ data_source: 'local-rollspec-snapshot',
+ };
+}
+
function body(req) {
return new Promise(r => {
let d = '';
@@ -168,7 +243,7 @@ function mime(f) {
return 'text/plain';
}
-http.createServer(async (req, res) => {
+async function handleRequest(req, res) {
const u = new URL(req.url, 'http://x');
const p = u.pathname;
@@ -199,6 +274,13 @@ http.createServer(async (req, res) => {
return json(res, 200, estimate({ wallWidthIn: b.wallWidthIn, wallHeightIn: b.wallHeightIn, roll, checkout_domain }));
}
+ if (p === '/api/calculate-coverage' && req.method === 'POST') {
+ const input = await body(req);
+ const { checkout_domain } = loadCatalog();
+ const result = calculateCoverage(input, loadRolls(), checkout_domain);
+ return json(res, result.ok ? 200 : 400, result);
+ }
+
if (p === '/api/lead' && req.method === 'POST') {
const b = await body(req);
if (!b.email || !/^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(b.email)) {
@@ -241,9 +323,15 @@ http.createServer(async (req, res) => {
}
res.writeHead(404); res.end('not found');
-}).listen(PORT, '127.0.0.1', function () {
- console.log('[estimate-instant] http://localhost:' + this.address().port);
- console.log(' Calculator: http://localhost:' + this.address().port + '/');
- console.log(' Admin: http://localhost:' + this.address().port + '/admin (auth required — ADMIN_CRED)');
- console.log(' Embed demo: http://localhost:' + this.address().port + '/embed-demo.html');
-});
+}
+
+if (require.main === module) {
+ http.createServer(handleRequest).listen(PORT, '127.0.0.1', function () {
+ console.log('[estimate-instant] http://localhost:' + this.address().port);
+ console.log(' Calculator: http://localhost:' + this.address().port + '/');
+ console.log(' Admin: http://localhost:' + this.address().port + '/admin (auth required — ADMIN_CRED)');
+ console.log(' Embed demo: http://localhost:' + this.address().port + '/embed-demo.html');
+ });
+}
+
+module.exports = { calculateCoverage, estimate, handleRequest };
diff --git a/test/calculate-coverage.test.js b/test/calculate-coverage.test.js
new file mode 100644
index 0000000..25937ba
--- /dev/null
+++ b/test/calculate-coverage.test.js
@@ -0,0 +1,116 @@
+'use strict';
+
+const test = require('node:test');
+const assert = require('node:assert/strict');
+const http = require('node:http');
+const { calculateCoverage, handleRequest } = require('../server.js');
+
+const roll = {
+ sku: 'DW-TEST-1',
+ shopify_sku: 'DW-SHOPIFY-1',
+ pattern: 'Test Pattern',
+ roll_width_in: 24,
+ roll_length_ft: 30,
+ pattern_repeat_in: 0,
+ match: 'straight',
+ price_per_roll: 100,
+ shopify_price: 100,
+ shopify_match: true,
+};
+
+test('calculates the documented room contract without external data', () => {
+ const result = calculateCoverage({
+ sku: 'dw-test-1', room_width_ft: 10, room_height_ft: 8, num_walls: 4,
+ }, [roll], 'example.test');
+ assert.equal(result.ok, true);
+ assert.equal(result.rolls_needed, 7);
+ assert.equal(result.total_cost, 700);
+ assert.equal(result.quote_authoritative, true);
+ assert.equal(result.data_source, 'local-rollspec-snapshot');
+ assert.match(result.notes.join(' '), /confirm current specifications and price/i);
+});
+
+test('rejects false-match Shopify aliases and withholds prototype pricing', () => {
+ const prototype = { ...roll, sku: 'DW-PROTOTYPE', shopify_sku: 'DW-STANDIN', shopify_match: false };
+ const alias = calculateCoverage({
+ sku: 'DW-STANDIN', room_width_ft: 10, room_height_ft: 8, num_walls: 1,
+ }, [prototype]);
+ assert.equal(alias.ok, false);
+
+ const local = calculateCoverage({
+ sku: 'DW-PROTOTYPE', room_width_ft: 10, room_height_ft: 8, num_walls: 1,
+ }, [prototype]);
+ assert.equal(local.ok, true);
+ assert.equal(local.total_cost, null);
+ assert.equal(local.price_per_roll, null);
+ assert.equal(local.quote_authoritative, false);
+ assert.match(local.notes.join(' '), /pricing is withheld/i);
+});
+
+test('accepts the Shopify SKU alias', () => {
+ const result = calculateCoverage({
+ sku: 'DW-SHOPIFY-1', room_width_ft: 10, room_height_ft: 8, num_walls: 1,
+ }, [roll], 'example.test');
+ assert.equal(result.ok, true);
+ assert.equal(result.sku, 'DW-TEST-1');
+});
+
+test('rejects coercible and oversized dimension inputs', () => {
+ for (const value of [true, '10', [10]]) {
+ const result = calculateCoverage({ sku: roll.sku, room_width_ft: value, room_height_ft: 8, num_walls: 1 }, [roll]);
+ assert.equal(result.ok, false, `${JSON.stringify(value)} must not be coerced`);
+ }
+ for (const input of [
+ { room_width_ft: 10, room_height_ft: '8', num_walls: 1 },
+ { room_width_ft: 10, room_height_ft: 8, num_walls: true },
+ { room_width_ft: 1001, room_height_ft: 8, num_walls: 1 },
+ { room_width_ft: 1e308, room_height_ft: 8, num_walls: 100 },
+ { room_width_ft: 10, room_height_ft: 101, num_walls: 1 },
+ { room_width_ft: 10, room_height_ft: 8, num_walls: 101 },
+ ]) {
+ const result = calculateCoverage({ sku: roll.sku, ...input }, [roll]);
+ assert.equal(result.ok, false);
+ assert.doesNotMatch(JSON.stringify(result), /"ok":true/);
+ }
+});
+
+test('fails closed when malformed roll data produces non-finite output', () => {
+ const broken = { ...roll, roll_width_in: 0 };
+ const result = calculateCoverage({ sku: roll.sku, room_width_ft: 10, room_height_ft: 8, num_walls: 1 }, [broken]);
+ assert.equal(result.ok, false);
+ assert.match(result.errors.join(' '), /non-finite calculation/i);
+});
+
+test('rejects missing SKU, invalid dimensions, fractional walls, and unknown SKU', () => {
+ const invalid = calculateCoverage({ sku: '', room_width_ft: 0, room_height_ft: Infinity, num_walls: 1.5 }, [roll]);
+ assert.equal(invalid.ok, false);
+ assert.equal(invalid.errors.length, 4);
+ const unknown = calculateCoverage({ sku: 'NOPE', room_width_ft: 10, room_height_ft: 8, num_walls: 1 }, [roll]);
+ assert.deepEqual(unknown, { ok: false, errors: ['No local roll specification found for SKU NOPE.'] });
+});
+
+test('serves the real local HTTP journey and returns 400 on bad input', async (t) => {
+ const server = http.createServer(handleRequest);
+ await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
+ t.after(() => new Promise((resolve) => server.close(resolve)));
+ const base = `http://127.0.0.1:${server.address().port}`;
+
+ const good = await fetch(`${base}/api/calculate-coverage`, {
+ method: 'POST', headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ sku: 'DW-GRAS-01', room_width_ft: 10, room_height_ft: 8, num_walls: 4 }),
+ });
+ assert.equal(good.status, 200);
+ const payload = await good.json();
+ assert.equal(payload.ok, true);
+ assert.equal(payload.data_source, 'local-rollspec-snapshot');
+ assert.equal(payload.quote_authoritative, false);
+ assert.equal(payload.total_cost, null);
+ assert.equal(payload.price_per_roll, null);
+ assert.match(payload.notes.join(' '), /pricing is withheld/i);
+
+ const bad = await fetch(`${base}/api/calculate-coverage`, {
+ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: '{}',
+ });
+ assert.equal(bad.status, 400);
+ assert.equal((await bad.json()).ok, false);
+});
diff --git a/verification/e2e-proof.json b/verification/e2e-proof.json
index 72a898e..909c364 100644
--- a/verification/e2e-proof.json
+++ b/verification/e2e-proof.json
@@ -2,7 +2,8 @@
"intent": "Provide a read-only room-dimensions-to-roll-count API contract using the existing local calculator.",
"risk_tier": "R1 isolated backend API; no production data or external integration",
"environment": "local Node HTTP server on an ephemeral loopback port",
- "timestamp": "2026-08-29T04:52:00Z",
+ "build_identity": "git parent 7dabb56 plus the owned Cycle 12 server/test/documentation diff",
+ "timestamp": "2026-08-29T05:11:00Z",
"ticket": "TK-10948-add-local-dw-room-coverage-api-contract",
"precondition": "estimate-instant exposed /api/estimate for inch dimensions but had no stable room-level snake_case API contract.",
"checks": [
@@ -10,13 +11,13 @@
"verdict": "PASS",
"boundary": "calculation module",
"command": "node --test test/calculate-coverage.test.js",
- "assertions": "room dimensions and wall count map to strips, rolls, waste, and total cost; Shopify SKU alias works"
+ "assertions": "7/7 pass: room dimensions map to rolls; exact matched alias works; stand-in aliases fail; prototype pricing is withheld; coercible/oversized inputs and non-finite outputs fail closed"
},
{
"verdict": "PASS",
"boundary": "HTTP API",
"command": "node --test test/calculate-coverage.test.js",
- "assertions": "ephemeral local POST returns HTTP 200 with labeled local-snapshot provenance; empty input returns HTTP 400"
+ "assertions": "ephemeral local POST returns HTTP 200 with labeled non-authoritative prototype coverage and null prices; empty input returns HTTP 400"
},
{
"verdict": "PASS",
@@ -31,6 +32,10 @@
"zero width",
"non-finite height",
"fractional wall count",
+ "boolean, string, and array dimensions",
+ "dimensions beyond documented bounds",
+ "false-match Shopify alias",
+ "non-finite estimator output from malformed roll data",
"empty HTTP payload"
],
"side_effects": "none; test server is ephemeral and closed by the test; no Shopify, dw_unified, lead, send, deploy, restart, or scheduled-job action",
← 7dabb56 auto-data-snapshot: 2026-08-28T21:58:17 (2 data files) — REA
·
back to Estimate Instant
·
Harden roll specs and SKU resolution 062bfad →