[object Object]

← back to Filemaker Mcp

Backfill blank Name/Color of Pattern on WallQuest GRS grasscloth WALLPAPER masters (joins specialty_grasscloths_stage; idempotent)

dd37d309d4f99a100f45a4660200130a4825407d · 2026-08-10 11:04:01 -0700 · Steve

Files touched

Diff

commit dd37d309d4f99a100f45a4660200130a4825407d
Author: Steve <steve@designerwallcoverings.com>
Date:   Mon Aug 10 11:04:01 2026 -0700

    Backfill blank Name/Color of Pattern on WallQuest GRS grasscloth WALLPAPER masters (joins specialty_grasscloths_stage; idempotent)
---
 scripts/backfill-grs-name-color.mjs | 65 +++++++++++++++++++++++++++++++++++++
 1 file changed, 65 insertions(+)

diff --git a/scripts/backfill-grs-name-color.mjs b/scripts/backfill-grs-name-color.mjs
new file mode 100644
index 0000000..4016cd0
--- /dev/null
+++ b/scripts/backfill-grs-name-color.mjs
@@ -0,0 +1,65 @@
+// Backfill blank "Name of Pattern" / "Color of Pattern" on the recent WallQuest
+// specialty-grasscloth WALLPAPER masters (Series=GRS, vid=WQ). Today's import created
+// ~73 records with Width + Internal Description populated but Name/Color left blank
+// (the pusher never mapped those two fields). This joins each FM record to the
+// authoritative dw_unified.specialty_grasscloths_stage by mfr code (FM lowercase
+// "Mfr Pattern" == stage UPPER "mfr_sku") and writes clean pattern/color.
+//
+// Idempotent + safe: only fills fields that are currently BLANK, only via the
+// "*List Wallpapers - Full View" layout (the one that actually exposes Name/Color),
+// reports any FM record with no stage match instead of guessing.
+//
+//   node backfill-grs-name-color.mjs            # DRY-RUN (prints plan)
+//   node backfill-grs-name-color.mjs --apply    # commit
+import { execFileSync } from 'node:child_process';
+import * as fm from '../src/fm-client.js';
+
+const APPLY = process.argv.includes('--apply');
+const DB = 'WALLPAPER';
+const LAYOUT = '*List Wallpapers - Full View';
+const PSQL = process.env.PSQL_BIN || '/opt/homebrew/opt/postgresql@14/bin/psql';
+const PG = 'postgresql:///dw_unified?host=/tmp&user=stevestudio2';
+const sleep = ms => new Promise(r => setTimeout(r, ms));
+
+// authoritative pattern/color keyed by UPPER(mfr_sku)
+const stageRows = JSON.parse(execFileSync(PSQL, [PG, '-tAc',
+  `select coalesce(json_agg(json_build_object('mfr',upper(mfr_sku),'pattern',pattern,'color',color))::text,'[]')
+   from specialty_grasscloths_stage`], { encoding: 'utf8', maxBuffer: 64e6 }).trim());
+const stage = new Map(stageRows.map(r => [r.mfr, r]));
+
+async function main() {
+  // the recent broken batch: GRS / WQ, Name blank, Internal Description present
+  const res = await fm.findRecords(DB, LAYOUT,
+    { Series: 'GRS', vid: 'WQ', 'Name of Pattern': '=', 'Internal Description': '*' },
+    { limit: 500 });
+  const recs = res.records || [];
+  console.log(`Found ${recs.length} GRS/WQ records with blank Name of Pattern · ${APPLY ? 'APPLY' : 'DRY-RUN'}\n`);
+
+  let ok = 0, skip = 0, nomatch = 0, fail = 0;
+  const misses = [];
+  for (const rec of recs) {
+    const fd = rec.fieldData;
+    const sku = fd['combo sku'];
+    const mfr = String(fd['Mfr Pattern'] || '').trim();
+    const s = stage.get(mfr.toUpperCase());
+    if (!s || !s.pattern) { nomatch++; misses.push(`${sku} (mfr ${mfr || '—'})`); continue; }
+
+    const patch = {};
+    if (!String(fd['Name of Pattern'] || '').trim()) patch['Name of Pattern'] = s.pattern;
+    if (!String(fd['Color of Pattern'] || '').trim() && s.color) patch['Color of Pattern'] = s.color;
+    if (!Object.keys(patch).length) { skip++; continue; }
+
+    if (!APPLY) {
+      if (ok < 80) console.log(`  ${sku}  ${mfr}  Name="${patch['Name of Pattern'] ?? '(kept)'}"  Color="${patch['Color of Pattern'] ?? '(kept)'}"`);
+      ok++; continue;
+    }
+    try {
+      await fm.updateRecord(DB, LAYOUT, rec.recordId, patch, { dryRun: false });
+      ok++; if (ok % 20 === 0) console.log(`  ...updated ${ok}`);
+      await sleep(120);
+    } catch (e) { fail++; console.error(`  FAIL ${sku}: ${e.fmCode || e.message}`); await sleep(400); }
+  }
+  console.log(`\n${APPLY ? 'Updated' : 'Planned'}=${ok}  alreadyFilled=${skip}  noStageMatch=${nomatch}  fail=${fail}`);
+  if (misses.length) console.log(`No stage match (left untouched):\n  ${misses.join('\n  ')}`);
+}
+main().catch(e => { console.error(e); process.exit(1); });

← d32c199 wallpaper: prefix-agnostic UNIQUE-number mfr fallback so Sho  ·  back to Filemaker Mcp  ·  Add GRS/WQ 20% price-bump script + canonical grasscloth impo ef0b961 →